# Adapty Documentation (Full Content) > Complete documentation content across all platforms. Locale: ru Generated on: 2026-07-24T13:01:12.988Z --- # ANDROID - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: ru Generated on: 2026-07-24T13:01:12.673Z Total files: 41 --- # File: sdk-installation-android --- --- title: "Установка и настройка Android SDK" description: "Пошаговое руководство по установке Adapty SDK на Android для приложений с подписками." --- SDK Adapty включает два ключевых модуля для бесшовной интеграции в ваше мобильное приложение: - **Core Adapty**: основной SDK, без которого Adapty не будет работать. - **AdaptyUI**: модуль, необходимый при использовании [Adapty Paywall Builder](adapty-paywall-builder) — удобного no-code инструмента для создания кросс-платформенных пейволов. AdaptyUI активируется автоматически вместе с основным модулем. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наше [демо-приложение](https://github.com/adaptyteam/AdaptySDK-Android/tree/master/app), которое демонстрирует полную настройку: отображение пейволов, совершение покупок и другой базовый функционал. ::: ## Требования \{#requirements\} Минимальная версия SDK: `minSdkVersion 21` :::info Adapty совместим с Google Play Billing Library версий до 8.x включительно. По умолчанию Adapty работает с Google Play Billing Library v.7.0.0, но если вы хотите принудительно использовать более позднюю версию, добавьте [зависимость](https://developer.android.com/google/play/billing/integrate#dependency) вручную. ::: :::info Установка SDK — это шаг 5 настройки Adapty. Прежде чем покупки заработают в вашем приложении, вам также нужно подключить приложение к сторам, а затем создать продукты, пейвол и плейсмент в дашборде Adapty. [Гайд по быстрому старту](quickstart) описывает все необходимые шаги. ::: ## Установите Adapty SDK \{#install-adapty-sdk\} Выберите способ настройки зависимостей: - Стандартный Gradle: добавьте зависимости в `build.gradle` **на уровне модуля** - Если в проекте используются файлы `.gradle.kts`, добавьте зависимости в `build.gradle.kts` на уровне модуля - Если вы используете каталоги версий, добавьте зависимости в файл `libs.versions.toml`, а затем сошлитесь на него в `build.gradle.kts` [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Android.svg?style=flat&logo=android)](https://github.com/adaptyteam/AdaptySDK-Android/releases) ```groovy showLineNumbers dependencies { ... implementation platform('io.adapty:adapty-bom:') implementation 'io.adapty:android-sdk' // Only add this line if you plan to use Paywall Builder implementation 'io.adapty:android-ui' } ``` ```kotlin showLineNumbers dependencies { ... implementation(platform("io.adapty:adapty-bom:")) implementation("io.adapty:android-sdk") // Добавьте эту строку только если планируете использовать Paywall Builder: implementation("io.adapty:android-ui") } ``` ```toml showLineNumbers //libs.versions.toml [versions] .. adaptyBom = "" [libraries] .. adapty-bom = { module = "io.adapty:adapty-bom", version.ref = "adaptyBom" } adapty = { module = "io.adapty:android-sdk" } // Only add this line if you plan to use Paywall Builder: adapty-ui = { module = "io.adapty:android-ui" } //module-level build.gradle.kts dependencies { ... implementation(platform(libs.adapty.bom)) implementation(libs.adapty) // Only add this line if you plan to use Paywall Builder: implementation(libs.adapty.ui) } ``` Если зависимость не разрешается, убедитесь, что в ваших Gradle-скриптах есть `mavenCentral()`.
Инструкция по добавлению Если в вашем `settings.gradle` нет `dependencyResolutionManagement`, добавьте следующее в корневой `build.gradle` в конец блока repositories: ```groovy showLineNumbers title="top-level build.gradle" allprojects { repositories { ... mavenCentral() } } ``` В противном случае добавьте следующее в `settings.gradle` в раздел `repositories` секции `dependencyResolutionManagement`: ```groovy showLineNumbers title="settings.gradle" dependencyResolutionManagement { ... repositories { ... mavenCentral() } } ```
:::important Adapty Android SDK 4.0 находится в стадии пре-релиза. Gradle не выбирает пре-релизные версии через динамические диапазоны версий (например, `+` или `latest.release`), поэтому необходимо указать точную версию. Укажите версию `adapty-bom` для пре-релиза 4.0 — например `io.adapty:adapty-bom:4.0.0-beta.2` или `adaptyBom = "4.0.0-beta.2"` в `libs.versions.toml`. BOM автоматически подбирает совместимые версии `android-sdk` и `android-ui`. См. [Миграция на Adapty Android SDK v4](migration-to-android-sdk-v4). ::: ## Активация модуля Adapty SDK \{#activate-adapty-module-of-adapty-sdk\} ### Базовая настройка \{#basic-setup\} Активируйте Adapty SDK в коде вашего приложения. :::note Adapty SDK нужно активировать в приложении только один раз. ::: Чтобы получить **Public SDK Key**: 1. Откройте дашборд Adapty и перейдите в [**App settings → General**](https://app.adapty.io/settings/general). 2. В разделе **Api keys** скопируйте **Public SDK Key** (НЕ Secret Key). 3. Замените `"YOUR_PUBLIC_SDK_KEY"` в коде. Или получите его программно с помощью [Adapty CLI](developer-cli): ``` npm install -g adapty adapty auth login adapty apps list ``` Или напрямую: ``` npx adapty auth login adapty apps list ``` - Убедитесь, что для инициализации Adapty вы используете **Public SDK key** — **Secret key** предназначен только для [серверного API](getting-started-with-server-side-api). - **SDK-ключи** уникальны для каждого приложения, поэтому если у вас несколько приложений, выберите нужный ключ. ```kotlin showLineNumbers // In your Application class class MyApplication : Application() { override fun onCreate() { super.onCreate() Adapty.activate( applicationContext, AdaptyConfig.Builder("PUBLIC_SDK_KEY") .build() ) } } ``` ```java showLineNumbers // In your Application class public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Adapty.activate( getApplicationContext(), new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .build() ); } } ``` :::important Дождитесь завершения `Adapty.activate` перед вызовом любых других методов SDK. Полная последовательность описана в разделе [Порядок вызовов в Android SDK](android-sdk-call-order). ::: Теперь настройте пейволы в вашем приложении: - Если вы используете [Adapty Paywall Builder](adapty-paywall-builder), следуйте [быстрому старту с Paywall Builder](android-quickstart-paywalls). - Если вы создаёте собственный UI пейвола, смотрите [быстрый старт для кастомных пейволов](android-quickstart-manual). ## Активация модуля AdaptyUI Adapty SDK \{#activate-adaptyui-module-of-adapty-sdk\} Если вы планируете использовать [Paywall Builder](adapty-paywall-builder), вам нужен модуль AdaptyUI. Он активируется автоматически при активации основного модуля — никаких дополнительных действий не требуется. ## Настройка Proguard \{#configure-proguard\} Перед выпуском приложения в продакшн добавьте `-keep class com.adapty.** { *; }` в вашу конфигурацию Proguard. ## Дополнительная настройка \{#optional-setup\} ### Логирование \{#logging\} #### Настройка системы логирования \{#set-up-the-logging-system\} Adapty записывает ошибки и другую важную информацию, чтобы помочь вам разобраться в происходящем. Доступны следующие уровни: | Уровень | Описание | | :----------------------- | :------------------------------------------------------------------------------------------------------------------------------------ | | `AdaptyLogLevel.NONE` | Ничего не будет записано в лог. Значение по умолчанию | | `AdaptyLogLevel.ERROR` | Будут записываться только ошибки | | `AdaptyLogLevel.WARN` | Будут записываться ошибки и сообщения от SDK, которые не вызывают критических ошибок, но заслуживают внимания. | | `AdaptyLogLevel.INFO` | Будут записываться ошибки, предупреждения и различные информационные сообщения. | | `AdaptyLogLevel.VERBOSE` | Будет записываться любая дополнительная информация, которая может быть полезна при отладке: вызовы функций, API-запросы и т. д. | Вы можете задать уровень логирования в приложении до настройки Adapty. ```kotlin showLineNumbers Adapty.logLevel = AdaptyLogLevel.VERBOSE //recommended for development and the first production release ``` ```java showLineNumbers Adapty.setLogLevel(AdaptyLogLevel.VERBOSE); //recommended for development and the first production release ``` #### Перенаправление сообщений системы логирования \{#redirect-the-logging-system-messages\} Если по какой-то причине вам нужно отправлять сообщения от Adapty в вашу систему или сохранять их в файл, вы можете переопределить поведение по умолчанию: ```kotlin showLineNumbers Adapty.setLogHandler { level, message -> //handle the log } ``` ```java showLineNumbers Adapty.setLogHandler((level, message) -> { //handle the log }); ``` ### Политики работы с данными \{#data-policies\} Adapty не хранит персональные данные пользователей, если только вы не передаёте их явно. При этом вы можете настроить дополнительные политики безопасности данных для соответствия требованиям стора или законодательства отдельных стран. #### Отключение сбора и передачи IP-адресов \{#disable-ip-address-collection-and-sharing\} При активации модуля Adapty установите `ipAddressCollectionDisabled` в значение `true`, чтобы отключить сбор и передачу IP-адресов пользователей. Значение по умолчанию — `false`. Используйте этот параметр для защиты конфиденциальности пользователей, соблюдения региональных требований по защите данных (например, GDPR или CCPA) или сокращения лишнего сбора данных, если функции на основе IP-адреса не нужны вашему приложению. ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withIpAddressCollectionDisabled(true) .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withIpAddressCollectionDisabled(true) .build(); ``` #### Отключение сбора и передачи рекламного идентификатора (Ad ID) \{#disable-advertising-id-ad-id-collection-and-sharing\} При активации модуля Adapty установите `adIdCollectionDisabled` в значение `true`, чтобы отключить сбор [рекламного идентификатора](https://support.google.com/googleplay/android-developer/answer/6048248) пользователя. Значение по умолчанию — `false`. Используйте этот параметр для соблюдения требований Play Store, чтобы избежать показа запроса разрешения на доступ к идентификатору рекламы, или если ваше приложение не требует атрибуции рекламы или аналитики на основе Ad ID. ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withAdIdCollectionDisabled(true) .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withAdIdCollectionDisabled(true) .build(); ``` #### Настройка конфигурации кэша медиа для AdaptyUI \{#set-up-media-cache-configuration-for-adaptyui\} По умолчанию AdaptyUI кэширует медиафайлы (изображения и видео) для повышения производительности и снижения сетевой нагрузки. Вы можете настроить параметры кэша, передав пользовательскую конфигурацию. Используйте `AdaptyUI.configureMediaCache`, чтобы переопределить размер кэша и срок его действия по умолчанию. Это необязательно — если вы не вызываете этот метод, будут использоваться значения по умолчанию (100 МБ на диске, 7 дней). ```kotlin showLineNumbers val cacheConfig = MediaCacheConfiguration.Builder() .overrideDiskStorageSizeLimit(200L * 1024 * 1024) // 200 MB .overrideDiskCacheValidityTime(3.days) .build() AdaptyUI.configureMediaCache(cacheConfig) ``` ```java showLineNumbers MediaCacheConfiguration cacheConfig = new MediaCacheConfiguration.Builder() .overrideDiskStorageSizeLimit(200L * 1024 * 1024) // 200 MB .overrideDiskCacheValidityTime(TimeInterval.days(3)) .build(); AdaptyUI.configureMediaCache(cacheConfig); ``` **Параметры:** | Параметр | Наличие | Описание | |------------------------|----------|-----------------------------------------------------------------------------| | diskStorageSizeLimit | optional | Общий размер кэша на диске в байтах. По умолчанию — 100 МБ. | | diskCacheValidityTime | optional | Как долго кэшированные файлы считаются актуальными. По умолчанию — 7 дней. | :::tip Вы можете очистить медиакэш во время выполнения с помощью `AdaptyUI.clearMediaCache(strategy)`, где `strategy` может принимать значение `CLEAR_ALL` или `CLEAR_EXPIRED_ONLY`. ::: ### Задайте обфусцированные идентификаторы аккаунта \{#set-obfuscated-account-ids\} Google Play требует обфусцированные идентификаторы аккаунта в ряде сценариев — для защиты конфиденциальности и безопасности пользователей. Эти идентификаторы помогают Google Play отслеживать покупки, не раскрывая личные данные пользователей, что особенно важно для предотвращения мошенничества и аналитики. Задавать такие идентификаторы может потребоваться, если приложение работает с чувствительными данными пользователей или если вы обязаны соблюдать определённые нормы конфиденциальности. Обфусцированные идентификаторы позволяют Google Play отслеживать покупки, не раскрывая реальные пользовательские идентификаторы. ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObfuscatedAccountId("YOUR_OBFUSCATED_ACCOUNT_ID") .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObfuscatedAccountId("YOUR_OBFUSCATED_ACCOUNT_ID") .build(); ``` ### Запуск Adapty в другом процессе \{#run-adapty-in-a-custom-process\} По умолчанию Adapty может работать только в главном процессе вашего приложения. Если ваше приложение использует несколько процессов, инициализируйте Adapty только один раз — иначе возможно непредсказуемое поведение. Если вам нужно запустить Adapty в другом процессе, укажите его в конфигурации: ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withProcessName(":custom") .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withProcessName(":custom") .build(); ``` Если вы попытаетесь активировать Adapty в другом процессе, не задав это значение, SDK выведет предупреждение и пропустит активацию. ### Включение локальных уровней доступа \{#enable-local-access-levels\} По умолчанию [локальные уровни доступа](local-access-levels) на Android отключены. Чтобы включить их, установите `withLocalAccessLevelAllowed` в `true`: ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withLocalAccessLevelAllowed(true) .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withLocalAccessLevelAllowed(true) .build(); ``` ## Устранение неполадок \{#troubleshooting\} #### Правила резервного копирования Android (настройка Auto Backup) \{#android-backup-rules-auto-backup-configuration\} Некоторые SDK (включая Adapty) поставляются со собственной конфигурацией Android Auto Backup. Если вы используете несколько SDK, в которых определены правила резервного копирования, объединение Android-манифеста может завершиться ошибкой, связанной с `android:fullBackupContent`, `android:dataExtractionRules` или `android:allowBackup`. Типичные симптомы ошибки: `Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/sample_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` Чтобы решить эту проблему, вам нужно: - Указать merge-механизму манифеста использовать значения атрибутов резервного копирования из вашего приложения. - Объединить правила резервного копирования из Adapty и других SDK в один XML-файл (или пару файлов для Android 12+). #### 1. Добавьте пространство имён `tools` в манифест \{#1-add-the-tools-namespace-to-your-manifest\} Если оно ещё не добавлено, добавьте пространство имён `tools` в корневой тег ``: ```xml ... ``` #### 2. Переопределите атрибуты резервного копирования в `` \{#2-override-backup-attributes-in-application\} В файле `AndroidManifest.xml` вашего приложения обновите тег ``, чтобы приложение предоставляло финальные значения и сообщало инструменту слияния манифестов о необходимости заменить значения из библиотек: ```xml ... ``` Если какой-либо SDK также устанавливает `android:allowBackup`, добавьте его в `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. Создайте объединённые файлы правил резервного копирования \{#3-create-merged-backup-rules-files\} Создайте XML-файлы в директории `app/src/main/res/xml/`, которые объединяют правила Adapty с правилами других SDK. Android использует разные форматы правил резервного копирования в зависимости от версии ОС, поэтому создание обоих файлов обеспечивает совместимость со всеми версиями Android, которые поддерживает ваше приложение. :::note В примерах ниже в качестве стороннего SDK используется AppsFlyer. Замените или добавьте правила для любых других SDK, которые вы используете в своём приложении. ::: **Для Android 12 и выше** (используется новый формат правил извлечения данных): ```xml title="sample_data_extraction_rules.xml" ``` **Для Android 11 и ниже** (использует устаревший формат содержимого полного резервного копирования): ```xml title="sample_backup_rules.xml" ``` С такой настройкой: - Исключения резервного копирования Adapty (`AdaptySDKPrefs.xml`) сохраняются. - Исключения других SDK (например, `appsflyer-data`) также применяются. - Инструмент слияния манифестов использует конфигурацию вашего приложения и больше не завершается ошибкой из-за конфликтующих атрибутов резервного копирования. #### Ошибки покупок после возврата из другого приложения \{#purchases-fail-after-returning-from-another-app\} Если Activity, запускающий флоу покупки, использует нестандартный `launchMode`, Android может некорректно пересоздать или переиспользовать его при возврате пользователя из Google Play, банковского приложения или браузера. Это может привести к потере результата покупки или её обработке как отменённой. Чтобы покупки работали корректно, используйте только режимы `standard` или `singleTop` для Activity, запускающего флоу покупки, и избегайте любых других режимов. В файле `AndroidManifest.xml` убедитесь, что Activity, запускающий флоу покупки, имеет режим `standard` или `singleTop`: ```xml ``` --- # File: android-quickstart-paywalls --- --- title: "Включение покупок с помощью Flow Builder в Android SDK" description: "Быстрый старт по включению встроенных покупок с помощью Adapty Flow Builder." --- Чтобы включить встроенные покупки, нужно понять три ключевых понятия: - [**Продукты**](product) – всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ) - [**Флоу**](adapty-flow-builder) – последовательности экранов, которые показывают продукты пользователям; создаются в no-code Flow Builder. SDK получает их через `getFlow`. Если вы предпочитаете строить UI в собственном коде, используйте пейвол — см. [Реализация пейволов вручную](android-quickstart-manual). - [**Плейсменты**](placements) – где и когда показывать флоу в приложении (например, `main`, `onboarding`, `settings`). Вы привязываете флоу к плейсментам в дашборде, а затем запрашиваете их по ID плейсмента в коде. Это упрощает проведение A/B-тестов и показ разных флоу разным пользователям. Adapty предлагает три способа подключить покупки в вашем приложении. Выберите подходящий в зависимости от требований: | Реализация | Сложность | Когда использовать | |---|---|---| | Adapty Flow Builder | ✅ Просто | Вы [создаёте готовый к покупке флоу в no-code конструкторе](quickstart-paywalls). Adapty автоматически отображает его и берёт на себя весь сложный процесс покупки, валидацию чеков и управление подписками. | | Пейволы, созданные вручную | 🟡 Средне | Вы реализуете UI пейвола в коде приложения, но всё равно получаете объект флоу от Adapty, сохраняя гибкость в настройке продуктов. См. [гайд](android-quickstart-manual). | | Режим наблюдателя | 🔴 Сложно | У вас уже есть собственная инфраструктура обработки покупок, и вы хотите продолжать её использовать. Учтите, что режим наблюдателя имеет ограничения в Adapty. См. [статью](observer-vs-full-mode). | :::important **Шаги ниже показывают, как реализовать флоу, созданный в Adapty Flow Builder.** Если вы предпочитаете строить UI пейвола самостоятельно, см. [Реализация пейволов вручную](android-quickstart-manual). ::: Чтобы отобразить флоу, созданный в Adapty Flow Builder, в коде вашего приложения нужно всего лишь: 1. **Получить флоу**: Запросить его из Adapty. 2. **Отобразить его — покупки Adapty обработает за вас**: Показать представление в приложении. 3. **Обработать действия кнопок**: Связать взаимодействия пользователя с реакцией приложения на них. Например, открывать ссылки или закрывать флоу при нажатии на кнопки. ## Перед началом работы \{#before-you-start\} Перед началом выполните следующие шаги: 1. [Подключите приложение к Google Play](initial-android) в дашборде Adapty. 2. [Создайте продукты](create-product) в Adapty. 3. [Создайте флоу и добавьте в него продукты](create-paywall). 4. [Создайте плейсмент и добавьте в него флоу](create-placement). 5. [Установите и активируйте SDK](sdk-installation-android) в коде приложения. В этом гайде используются API Adapty Android SDK v4. :::tip Самый быстрый способ выполнить эти шаги — следовать [руководству по быстрому старту](quickstart) или создать флоу и плейсменты с помощью [Developer CLI](developer-cli-quickstart). ::: ## 1. Получите флоу \{#1-get-the-flow\} Ваши флоу привязаны к плейсментам, настроенным в дашборде. Плейсменты позволяют показывать разные флоу разным аудиториям или запускать [A/B-тесты](ab-tests). Чтобы получить флоу, созданный в Adapty Flow Builder, нужно: 1. Получить объект `flow` по ID [плейсмента](placements) с помощью метода `getFlow` и проверить, содержит ли он конфигурацию представления. 2. Получить конфигурацию представления с помощью метода `getFlowConfiguration`. Конфигурация представления содержит элементы интерфейса и стили, необходимые для отображения флоу. :::important Чтобы получить конфигурацию представления, необходимо включить переключатель **Show on device** во Flow Builder. В противном случае вы получите пустую конфигурацию представления, и флоу не будет отображён. ::: ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> if (result is AdaptyResult.Success) { val flow = result.value if (!flow.hasViewConfiguration) { return@getFlow } AdaptyUI.getFlowConfiguration(flow) { configResult -> if (configResult is AdaptyResult.Success) { val flowConfiguration = configResult.value } } } } ``` ```java showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); if (!flow.hasViewConfiguration()) { return; } AdaptyUI.getFlowConfiguration(flow, configResult -> { if (configResult instanceof AdaptyResult.Success) { AdaptyUI.FlowConfiguration flowConfiguration = ((AdaptyResult.Success) configResult).getValue(); // use loaded configuration } }); } }); ``` ## 2. Отображение флоу \{#display-the-flow\} Теперь, когда у вас есть конфигурация флоу, достаточно добавить несколько строк, чтобы отобразить его. Чтобы отобразить визуальный флоу на экране устройства, необходимо сначала его настроить. Для этого вызовите метод `AdaptyUI.getFlowView()` или создайте `AdaptyFlowView` напрямую: ```kotlin showLineNumbers val flowView = AdaptyUI.getFlowView( activity, flowConfiguration, null, // products = null means auto-fetch eventListener, ) ``` ```kotlin showLineNumbers val flowView = AdaptyFlowView(activity) // or retrieve it from xml ... with(flowView) { showFlow( flowConfiguration, null, // products = null means auto-fetch eventListener, ) } ``` ```java showLineNumbers AdaptyFlowView flowView = AdaptyUI.getFlowView( activity, flowConfiguration, null, // products = null означает автозагрузку eventListener ); ``` ```java showLineNumbers AdaptyFlowView flowView = new AdaptyFlowView(activity); //добавьте в иерархию представлений, если нужно, или получите из xml ... flowView.showFlow(flowConfiguration, products, eventListener); ``` ```xml showLineNumbers ``` После успешного создания view вы можете добавить его в иерархию отображения и показать на экране устройства. :::tip Подробнее о том, как отображать флоу, читайте в нашем [гайде](android-present-paywalls). ::: ## 3. Обработка действий кнопок \{#handle-button-actions\} Когда пользователи нажимают кнопки во флоу, Android SDK автоматически обрабатывает покупки, восстановление, закрытие флоу и открытие ссылок. Однако у других кнопок есть пользовательские или предустановленные ID, и обработку их действий нужно реализовывать в коде. Также вы можете переопределить их поведение по умолчанию. Например, вот поведение по умолчанию для кнопки закрытия. Добавлять его в код не обязательно, но ниже показано, как это делается при необходимости. :::tip Ознакомьтесь с нашими гайдами по обработке [действий](android-handle-paywall-actions) и [событий](android-handling-events) кнопок. ::: ```kotlin showLineNumbers title="Kotlin" override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { AdaptyUI.Action.Close -> (context as? Activity)?.onBackPressed() // default behavior } } ``` ```java showLineNumbers @Override public void onActionPerformed(@NonNull AdaptyUI.Action action, @NonNull Context context) { if (action instanceof AdaptyUI.Action.Close) { if (context instanceof Activity) { ((Activity) context).onBackPressed(); } } } ``` ## Следующие шаги \{#next-steps\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Ваш пейвол готов к отображению в приложении. [Протестируйте покупки в Google Play Store](testing-on-android), чтобы убедиться, что тестовая покупка через пейвол проходит успешно. Теперь нужно [проверить уровень доступа пользователей](android-check-subscription-status), чтобы показывать пейвол или предоставлять доступ к платным функциям только нужным пользователям. ## Полный пример \{#full-example\} Вот как все эти шаги можно объединить в вашем приложении. ```kotlin showLineNumbers title="Kotlin" class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Adapty.getFlow("YOUR_PLACEMENT_ID") { flowResult -> if (flowResult is AdaptyResult.Success) { val flow = flowResult.value if (!flow.hasViewConfiguration) { // Use custom logic return@getFlow } AdaptyUI.getFlowConfiguration(flow) { configResult -> if (configResult is AdaptyResult.Success) { val flowConfiguration = configResult.value val flowView = AdaptyUI.getFlowView( this, flowConfiguration, null, // products = null means auto-fetch object : AdaptyFlowDefaultEventListener() { override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.Close -> { (context as? Activity)?.onBackPressed() } } } } ) setContentView(flowView) } } } } } } ``` ```java showLineNumbers public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Adapty.getFlow("YOUR_PLACEMENT_ID", flowResult -> { if (flowResult instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) flowResult).getValue(); if (!flow.hasViewConfiguration()) { // Use custom logic return; } AdaptyUI.getFlowConfiguration(flow, configResult -> { if (configResult instanceof AdaptyResult.Success) { AdaptyUI.FlowConfiguration flowConfiguration = ((AdaptyResult.Success) configResult).getValue(); AdaptyFlowView flowView = AdaptyUI.getFlowView( this, flowConfiguration, null, // products = null means auto-fetch new AdaptyFlowDefaultEventListener() { @Override public void onActionPerformed(@NonNull AdaptyUI.Action action, @NonNull Context context) { if (action instanceof AdaptyUI.Action.Close) { if (context instanceof Activity) { ((Activity) context).onBackPressed(); } } } } ); setContentView(flowView); } }); } }); } } ``` --- # File: android-check-subscription-status --- --- title: "Проверка статуса подписки в Android SDK" description: "Узнайте, как проверить статус подписки в приложении Android с помощью Adapty." --- Чтобы решить, предоставить ли пользователю доступ к платному контенту или показать пейвол, нужно проверить его [уровень доступа](access-level) в профиле. В этой статье рассказывается, как обращаться к состоянию профиля, чтобы решить, что показывать пользователю — пейвол или платные функции. ## Получение статуса подписки \{#get-subscription-status\} Когда нужно решить, показать пользователю пейвол или платный контент, вы проверяете его [уровень доступа](access-level) в профиле. Для этого есть два варианта: - Вызовите `getProfile`, если нужны актуальные данные прямо сейчас (например, при запуске приложения) или хотите принудительно обновить профиль. - Настройте **автоматические обновления профиля**, чтобы хранить локальную копию, которая обновляется автоматически при изменении статуса подписки. ### Получение профиля \{#get-profile\} Самый простой способ узнать статус подписки — вызвать метод `getProfile`: ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // check the access } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); // check the access } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` ### Отслеживание обновлений подписки \{#listen-to-subscription-updates\} Чтобы автоматически получать обновления профиля в приложении: 1. Используйте `Adapty.setOnProfileUpdatedListener()` для отслеживания изменений профиля — Adapty будет автоматически вызывать этот метод при каждом изменении статуса подписки пользователя. 2. Сохраняйте обновлённые данные профиля при вызове этого метода, чтобы использовать их в приложении без лишних сетевых запросов. ```kotlin class SubscriptionManager { private var currentProfile: AdaptyProfile? = null init { // Listen for profile updates Adapty.setOnProfileUpdatedListener { profile -> currentProfile = profile // Update UI, unlock content, etc. } } // Use stored profile instead of calling getProfile() fun hasAccess(): Boolean { return currentProfile?.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true } } ``` ```java public class SubscriptionManager { private AdaptyProfile currentProfile; public SubscriptionManager() { // Listen for profile updates Adapty.setOnProfileUpdatedListener(profile -> { this.currentProfile = profile; // Update UI, unlock content, etc. }); } // Use stored profile instead of calling getProfile() public boolean hasAccess() { if (currentProfile == null) { return false; } AdaptyAccessLevel premiumAccess = currentProfile.getAccessLevels().get("YOUR_ACCESS_LEVEL"); return premiumAccess != null && premiumAccess.isActive(); } } ``` :::note Adapty автоматически вызывает слушатель обновлений профиля при запуске приложения, предоставляя кешированные данные о подписке даже при отсутствии интернета. ::: ## Связь профиля с логикой пейвола \{#connect-profile-with-paywall-logic\} Когда нужно сразу принять решение о показе пейвола или предоставлении доступа к платным функциям, можно напрямую проверить профиль пользователя. Это удобно при запуске приложения, входе в премиум-разделы или перед показом определённого контента. ```kotlin private fun initializePaywall() { loadPaywall { paywallView -> checkAccessLevel { result -> when (result) { is AdaptyResult.Success -> { if (!result.value && paywallView != null) { setContentView(paywallView) // Show paywall if no access } } is AdaptyResult.Error -> { if (paywallView != null) { setContentView(paywallView) // Show paywall if access check fails } } } } } } private fun checkAccessLevel(callback: ResultCallback) { Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val hasAccess = result.value.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true callback.onResult(AdaptyResult.Success(hasAccess)) } is AdaptyResult.Error -> { callback.onResult(AdaptyResult.Error(result.error)) } } } } ``` ```java private void initializePaywall() { loadPaywall(paywallView -> { checkAccessLevel(result -> { if (result instanceof AdaptyResult.Success) { boolean hasAccess = ((AdaptyResult.Success) result).getValue(); if (!hasAccess && paywallView != null) { setContentView(paywallView); // Show paywall if no access } } else if (result instanceof AdaptyResult.Error) { if (paywallView != null) { setContentView(paywallView); // Show paywall if access check fails } } }); }); } private void checkAccessLevel(ResultCallback callback) { Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); AdaptyAccessLevel premiumAccess = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL"); boolean hasAccess = premiumAccess != null && premiumAccess.isActive(); callback.onResult(AdaptyResult.success(hasAccess)); } else if (result instanceof AdaptyResult.Error) { callback.onResult(AdaptyResult.error(((AdaptyResult.Error) result).getError())); } }); } ``` ## Дальнейшие шаги \{#next-steps\} Теперь, когда вы знаете, как отслеживать статус подписки, узнайте, как [работать с профилями пользователей](android-quickstart-identify), чтобы они получали доступ к тому, за что заплатили. --- # File: android-quickstart-identify --- --- title: "Идентификация пользователей в Android SDK" description: "Краткое руководство по настройке Adapty для управления встроенными покупками в Android." --- :::important Этот гайд для вас, если у вас есть собственная система аутентификации. Здесь вы узнаете, как работать с профилями пользователей в Adapty, чтобы они соответствовали вашей существующей системе аутентификации. ::: То, как вы управляете покупками пользователей, зависит от модели аутентификации вашего приложения: - Если ваше приложение не использует бэкенд-аутентификацию и не хранит данные пользователей, смотрите [раздел об анонимных пользователях](#anonymous-users). - Если ваше приложение использует (или будет использовать) бэкенд-аутентификацию, смотрите [раздел об идентифицированных пользователях](#identified-users). **Ключевые понятия**: - **Профили** — сущности, необходимые для работы SDK. Adapty создаёт их автоматически. - Они могут быть анонимными **(без customer user ID)** или идентифицированными **(с customer user ID)**. - Вы передаёте **customer user ID**, чтобы связать профили в Adapty с вашей внутренней системой авторизации. Вот чем различаются анонимные и идентифицированные пользователи: | | Анонимные пользователи | Идентифицированные пользователи | |-------------------------|---------------------------------------------------------------|-------------------------------------------------------------------------------------------------------| | **Purchase management** | Восстановление покупок на уровне стора | История покупок сохраняется на всех устройствах благодаря customer user ID | | **Profile management** | Новый профиль при каждой переустановке | Один и тот же профиль в разных сессиях и на разных устройствах | | **Data persistence** | Данные анонимных пользователей привязаны к установке приложения | Данные идентифицированных пользователей сохраняются между установками приложения | ## Анонимные пользователи \{#anonymous-users\} Если у вас нет серверной аутентификации, **вам не нужно обрабатывать аутентификацию в коде приложения**: 1. Когда SDK активируется при первом запуске приложения, Adapty **создаёт новый профиль для пользователя**. 2. Когда пользователь совершает покупку в приложении, она **привязывается к его профилю в Adapty и его аккаунту в сторе**. 3. Когда пользователь **переустанавливает** приложение или устанавливает его на **новое устройство**, Adapty **создаёт новый анонимный профиль при активации**. 4. Если пользователь ранее совершал покупки в вашем приложении, по умолчанию они автоматически синхронизируются из App Store при активации SDK. Таким образом, для анонимных пользователей при каждой установке будет создаваться новый профиль — но это не проблема, поскольку в аналитике Adapty можно [настроить, что считается новой установкой](general#4-installs-definition-for-analytics). Для анонимных пользователей установки нужно считать по **идентификаторам устройств**. В этом случае каждая установка приложения на устройство считается отдельной установкой, включая повторные. ## Идентифицированные пользователи \{#identified-users\} У вас есть два способа идентифицировать пользователей в приложении: - [**При входе/регистрации:**](#during-loginsignup) Если пользователи входят в систему после запуска приложения, вызовите `identify()` с customer user ID в момент аутентификации. - [**При активации SDK:**](#during-the-sdk-activation) Если customer user ID уже сохранён на момент запуска приложения, передайте его при вызове `activate()`. :::important По умолчанию, когда Adapty получает покупку от Customer User ID, который уже связан с другим Customer User ID, уровень доступа становится общим — оба профиля получают платный доступ. Вы можете настроить это поведение так, чтобы платный доступ переносился с одного профиля на другой, или полностью отключить совместное использование. Подробнее — в [статье](general#6-sharing-paid-access-between-user-accounts). ::: ### В момент входа или регистрации \{#during-loginsignup\} Если вы идентифицируете пользователей после запуска приложения (например, после входа в аккаунт или регистрации), используйте метод `identify`, чтобы задать их customer user ID. - Если вы **ещё не использовали этот customer user ID**, Adapty автоматически привяжет его к текущему профилю. - Если вы **уже использовали этот customer user ID для идентификации пользователя**, Adapty переключится на работу с профилем, связанным с этим customer user ID. :::important Идентификаторы пользователей (customer user ID) должны быть уникальными для каждого пользователя. Если задать значение параметра жёстко в коде, все пользователи будут считаться одним. ::: Дождитесь срабатывания колбэка завершения `identify`, прежде чем вызывать другие методы SDK. Параллельные вызовы могут попасть в анонимный профиль вместо идентифицированного. См. [Порядок вызовов в Android SDK](android-sdk-call-order). ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") { error -> // Уникальный для каждого пользователя if (error == null) { // успешная идентификация } } ``` ```java showLineNumbers // ID пользователей должны быть уникальными для каждого пользователя Adapty.identify("YOUR_USER_ID", error -> { if (error == null) { // успешная идентификация } }); ``` ### Во время активации SDK \{#during-the-sdk-activation\} Если вы знаете customer user ID уже в момент активации SDK, его можно передать прямо в методе `activate` — вызывать `identify` отдельно не нужно. Если customer user ID известен, но вы задаёте его только после активации, то при активации Adapty создаст новый анонимный профиль и переключится на существующий лишь после вызова `identify`. Вы можете передать как существующий customer user ID (который уже использовался ранее), так и новый. Если вы передадите новый, профиль, созданный при активации, будет автоматически привязан к этому customer user ID. :::note По умолчанию создание анонимных профилей не влияет на аналитические дашборды, поскольку установки учитываются на основе идентификаторов устройств. Идентификатор устройства соответствует одной установке приложения из стора на устройстве и обновляется только при переустановке приложения. Он не зависит от того, первая это установка или повторная, и от того, используется ли существующий customer user ID. Создание профиля (при активации SDK или выходе из системы), вход в систему или обновление приложения без переустановки не генерируют дополнительные события установки. Если вы хотите считать установки по уникальным пользователям, а не устройствам, перейдите в **App settings** и настройте [**Installs definition for analytics**](general#4-installs-definition-for-analytics). ::: ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId("user123") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId("user123") // Идентификаторы пользователей должны быть уникальными для каждого пользователя. Если вы укажете значение параметра в коде явно, все пользователи будут считаться одним. .build(); ``` ### Выход пользователей из системы \{#log-users-out\} Если в приложении есть кнопка выхода, используйте метод `logout`. :::important При выходе из системы для пользователя создаётся новый анонимный профиль. ::: ```kotlin showLineNumbers Adapty.logout { error -> if (error == null) { // successful logout } } ``` ```java showLineNumbers Adapty.logout(error -> { if (error == null) { // successful logout } }); ``` :::info Чтобы снова авторизовать пользователей в приложении, используйте метод `identify`. ::: ### Разрешите покупки без входа в систему \{#allow-purchases-without-login\} Если пользователи могут совершать покупки как до, так и после входа в приложение, нужно убедиться, что после авторизации они сохранят доступ к своим покупкам: 1. Когда неавторизованный пользователь совершает покупку, Adapty привязывает её к анонимному идентификатору профиля. 2. Когда пользователь входит в аккаунт, Adapty переключается на работу с его идентифицированным профилем. - Если это новый customer user ID (например, покупка была совершена до регистрации), Adapty присваивает customer user ID текущему профилю, сохраняя всю историю покупок. - Если это существующий customer user ID (customer user ID уже привязан к профилю), нужно получить актуальный уровень доступа после переключения профиля. Можно вызвать [`getProfile`](android-check-subscription-status) сразу после идентификации или [подписаться на обновления профиля](android-check-subscription-status), чтобы данные синхронизировались автоматически. ## Следующие шаги \{#next-steps\} Поздравляем! Вы реализовали логику встроенных покупок в своём приложении. Желаем вам успехов в монетизации! Чтобы получить от Adapty ещё больше, изучите эти темы: - [**Тестирование**](troubleshooting-test-purchases): Убедитесь, что всё работает как ожидается - [**Онбординги**](android-onboardings): Вовлекайте пользователей с помощью онбордингов и повышайте удержание - [**Интеграции**](configuration): Подключите сервисы маркетинговой атрибуции и аналитики буквально в одну строку кода - [**Установка пользовательских атрибутов профиля**](android-setting-user-attributes): Добавляйте пользовательские атрибуты к профилям, создавайте сегменты и запускайте A/B-тесты или показывайте разные пейволы разным пользователям --- # File: adapty-sdk-integration-skill-android --- --- title: "Интеграция Adapty в ваше Android-приложение с помощью навыка интеграции SDK" description: "Используйте навык adapty-sdk-integration для сквозной интеграции Adapty SDK в ваше Android-приложение с помощью AI-инструмента для написания кода." --- [Скилл adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) автоматизирует интеграцию Adapty от начала до конца: настройку дашборда, установку SDK, пейвол и проверку на каждом этапе. Он автоматически определяет вашу платформу и подгружает нужную документацию Adapty на каждом шаге. **Поддерживаемые инструменты**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Для установки выберите команду для своего инструмента. Полный список — в [README скилла](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex или любой другой инструмент** — используйте [skills CLI](https://skills.sh) (обратите внимание, что скиллы, установленные таким способом, не обновляются автоматически): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` Либо клонируйте репозиторий и скопируйте `skills/adapty-sdk-integration/` в директорию скиллов вашего инструмента. После установки запустите скилл в вашем проекте: ``` /adapty-sdk-integration ``` Скилл задаст несколько вопросов по настройке, а затем проведёт через настройку дашборда, установку SDK, пейвол и проверку. :::important Навык находится в бета-версии. Если он зависнет или будет работать некорректно, воспользуйтесь [пошаговым руководством по интеграции](adapty-cursor-android) — оно проведёт ваш AI-инструмент через каждый этап с нужной документацией. ::: [Скилл adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) автоматизирует интеграцию Adapty от начала до конца: настройку дашборда, установку SDK, пейвол и проверку на каждом этапе. Он автоматически определяет вашу платформу и подгружает нужную документацию Adapty на каждом шаге. **Поддерживаемые инструменты**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Для установки выберите команду для своего инструмента. Полный список — в [README скилла](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex или любой другой инструмент** — используйте [skills CLI](https://skills.sh) (обратите внимание, что скиллы, установленные таким способом, не обновляются автоматически): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` Либо клонируйте репозиторий и скопируйте `skills/adapty-sdk-integration/` в директорию скиллов вашего инструмента. После установки запустите скилл в вашем проекте: ``` /adapty-sdk-integration ``` Скилл задаст несколько вопросов по настройке, а затем проведёт через настройку дашборда, установку SDK, пейвол и проверку. --- # File: adapty-cursor-android --- --- title: "Интеграция Adapty в Android-приложение с помощью ИИ" description: "Пошаговое руководство по интеграции Adapty в Android-приложение с использованием Cursor, Context7, ChatGPT, Claude и других ИИ-инструментов." --- Этот гайд проведёт вас через интеграцию Adapty в ваше Android-приложение шаг за шагом с помощью AI-инструмента для программирования — вы подаёте ему нужную документацию Adapty в правильном порядке. For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## Прежде чем начать: настройка дашборда \{#before-you-start-dashboard-setup\} Adapty требует некоторой настройки в дашборде до написания кода SDK. Вы можете сделать это с помощью интерактивного LLM-скилла или вручную через дашборд. ### Подход через skill (рекомендуется) \{#skill-approach-recommended\} Skill Adapty CLI позволяет вашему LLM настраивать приложение, продукты, уровни доступа, пейволы и плейсменты напрямую — без необходимости открывать дашборд на каждом шаге. Вам нужно лишь [подключить стор](integrate-payments) в дашборде. ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` После добавления skill запустите `/adapty-cli` в вашем агенте. Он проведёт вас через каждый шаг — в том числе укажет, когда нужно открыть дашборд для подключения стора. ### Подход через дашборд Если вы предпочитаете настраивать всё вручную, вот что нужно сделать до написания кода. LLM не может самостоятельно получить значения из дашборда — их нужно предоставить. 1. **Подключите стор**: В дашборде Adapty перейдите в **App settings → General**. Это обязательно для работы покупок. [Подключить Google Play](integrate-payments) 2. **Скопируйте публичный SDK-ключ**: В дашборде Adapty перейдите в **App settings → General** и найдите раздел **API keys**. В коде это строка, которую вы передаёте в конфигуратор Adapty. 3. **Создайте хотя бы один продукт**: В дашборде Adapty перейдите на страницу **Products**. Вы не ссылаетесь на продукты напрямую в коде — Adapty доставляет их через пейволы. [Добавить продукты](quickstart-products) 4. **Создайте пейвол и плейсмент**: В дашборде Adapty создайте пейвол на странице **Paywalls**, затем назначьте его на плейсмент на странице **Placements**. В коде ID плейсмента — это строка, которую вы передаёте в `Adapty.getPaywall("YOUR_PLACEMENT_ID")`. [Создать пейвол](quickstart-paywalls) 5. **Настройте уровни доступа**: В дашборде Adapty настройте каждый продукт на странице **Products**. В коде проверяйте строку `profile.accessLevels["premium"]?.isActive`. Уровень доступа `premium` по умолчанию подходит для большинства приложений. Если платящие пользователи получают доступ к разным функциям в зависимости от продукта (например, план `basic` и план `pro`), [создайте дополнительные уровни доступа](assigning-access-level-to-a-product) до начала разработки. :::tip Как только у вас есть все пять, можно писать код. Скажите своему LLM: «Мой публичный SDK-ключ — X, мой ID плейсмента — Y», чтобы он сгенерировал корректный код инициализации и получения пейвола. ::: ### Настройте, когда будете готовы \{#set-up-when-ready\} Это не нужно для начала разработки, но пригодится по мере развития интеграции: - **A/B-тесты**: Настраиваются на странице **Placements**. Изменения в коде не нужны. [A/B-тесты](ab-tests) - **Дополнительные пейволы и плейсменты**: Добавьте больше вызовов `getPaywall` с разными идентификаторами плейсментов. - **Интеграции аналитики**: Настраиваются на странице **Integrations**. Процесс настройки зависит от интеграции. См. [интеграции аналитики](analytics-integration) и [интеграции атрибуции](attribution-integration). ## Загрузите документацию Adapty в свой LLM \{#feed-adapty-docs-to-your-llm\} ### Используйте Context7 (рекомендуется) \{#use-context7-recommended\} [Context7](https://context7.com) — это MCP-сервер, который даёт вашей LLM прямой доступ к актуальной документации Adapty. LLM автоматически подтягивает нужные docs на основе вашего запроса — никакого ручного копирования ссылок. Context7 работает с **Cursor**, **Claude Code**, **Windsurf** и другими MCP-совместимыми инструментами. Чтобы настроить его, выполните: ``` npx ctx7 setup ``` Команда определит ваш редактор и настроит сервер Context7. Для ручной настройки смотрите [репозиторий Context7 на GitHub](https://github.com/upstash/context7). После настройки укажите библиотеку Adapty в своих промптах: ``` Use the adaptyteam/adapty-docs library to look up how to install the Android SDK ``` :::warning Несмотря на то что Context7 избавляет от необходимости вручную вставлять ссылки на документацию, порядок реализации имеет значение. Следуйте [пошаговому руководству по реализации](#implementation-walkthrough) ниже строго по шагам, чтобы всё работало корректно. ::: ### Используйте документацию в виде обычного текста \{#use-plain-text-docs\} Любой документ Adapty доступен в виде обычного текстового Markdown. Добавьте `.md` в конец URL или нажмите **Copy for LLM** под заголовком статьи. Например: [adapty-cursor-android.md](https://adapty.io/docs/ru/adapty-cursor-android.md). Каждый шаг в [пошаговом руководстве по интеграции](#implementation-walkthrough) ниже содержит блок «Отправьте это в ваш LLM» со ссылками `.md` для вставки. Чтобы получить сразу несколько документов, см. [индексные файлы и платформенные подборки](#plain-text-doc-index-files) ниже. ## Пошаговая реализация \{#implementation-walkthrough\} Далее мы разберём интеграцию Adapty по порядку реализации. Каждый этап включает документы для передачи вашему LLM, то, что вы должны увидеть после завершения, и типичные проблемы. ### Спланируйте интеграцию \{#plan-your-integration\} Прежде чем переходить к коду, попросите LLM проанализировать ваш проект и составить план реализации. Если ваш AI-инструмент поддерживает режим планирования (например, режим плана в Cursor или Claude Code), используйте его — так LLM сможет изучить и структуру вашего проекта, и документацию Adapty перед написанием кода. Сообщите LLM, какой подход к покупкам вы используете — от этого зависит, какими гайдами она должна руководствоваться: - [**Adapty Paywall Builder**](adapty-paywall-builder): Вы создаёте пейволы в визуальном редакторе Adapty, а SDK отображает их автоматически. - [**Пейволы, созданные вручную**](android-making-purchases): Вы строите собственный UI пейвола в коде, но используете Adapty для получения продуктов и обработки покупок. - [**Режим Observer**](observer-vs-full-mode): Вы сохраняете существующую инфраструктуру покупок и используете Adapty только для аналитики и интеграций. Не знаете, что выбрать? Прочитайте [сравнительную таблицу в быстром старте](android-quickstart-paywalls). ### Установка и настройка SDK \{#install-and-configure-the-sdk\} Добавьте зависимость Adapty SDK через Gradle в Android Studio и активируйте её с помощью вашего публичного ключа SDK. Это основа — без неё ничего не работает. **Гайд:** [Установка и настройка Adapty SDK](sdk-installation-android) Отправьте это в ваш LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/sdk-installation-android.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** Приложение собирается и запускается. В Logcat отображается лог активации Adapty. - **Возможная проблема:** «Public API key is missing» → убедитесь, что заменили placeholder на реальный ключ из **App settings**. ::: ### Показывайте пейволы и обрабатывайте покупки \{#show-paywalls-and-handle-purchases\} Получите пейвол по ID плейсмента, отобразите его и обработайте события покупок. Нужные гайды зависят от того, как вы обрабатываете покупки. Тестируйте каждую покупку в песочнице по мере работы — не откладывайте на конец. Инструкции по настройке см. в разделе [Тестирование покупок в песочнице](test-purchases-in-sandbox). **Гайды:** - [Включить покупки с помощью пейволов (быстрый старт)](android-quickstart-paywalls) - [Получить пейволы Paywall Builder и их конфигурацию](android-get-pb-paywalls) - [Отобразить пейволы](android-present-paywalls) - [Обработать события пейвола](android-handling-events) - [Реагировать на действия кнопок](android-handle-paywall-actions) Отправьте это своему LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/android-quickstart-paywalls.md - https://adapty.io/docs/ru/android-get-pb-paywalls.md - https://adapty.io/docs/ru/android-present-paywalls.md - https://adapty.io/docs/ru/android-handling-events.md - https://adapty.io/docs/ru/android-handle-paywall-actions.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** Пейвол отображается с настроенными продуктами. При нажатии на продукт появляется диалог покупки в песочнице. - **Возможная проблема:** Пустой пейвол или ошибка `getPaywall` → убедитесь, что ID плейсмента точно совпадает с тем, что указано в дашборде, и что плейсменту назначена аудитория. ::: **Гайды:** - [Включить покупки в своём кастомном пейволе (быстрый старт)](android-quickstart-manual) - [Получить пейволы и продукты](fetch-paywalls-and-products-android) - [Отобразить пейвол, созданный через Remote Config](present-remote-config-paywalls-android) - [Совершить покупку](android-making-purchases) - [Восстановить покупки](android-restore-purchase) Read these Adapty docs before writing code: - https://adapty.io/docs/ru/android-quickstart-manual.md - https://adapty.io/docs/ru/fetch-paywalls-and-products-android.md - https://adapty.io/docs/ru/present-remote-config-paywalls-android.md - https://adapty.io/docs/ru/android-making-purchases.md - https://adapty.io/docs/ru/android-restore-purchase.md :::tip[Контрольная точка] - **Ожидаемый результат:** Ваш пейвол отображает продукты, полученные из Adapty. Нажатие на продукт вызывает диалог покупки в песочнице. - **Возможная проблема:** Пустой массив продуктов → убедитесь, что к пейволу в дашборде привязаны продукты и у плейсмента есть аудитория. ::: **Гайды:** - [Обзор Observer mode](observer-vs-full-mode) - [Реализация Observer mode](implement-observer-mode-android) - [Отчёт о транзакциях в Observer mode](report-transactions-observer-mode-android) Отправьте это своему LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/observer-vs-full-mode.md - https://adapty.io/docs/ru/implement-observer-mode-android.md - https://adapty.io/docs/ru/report-transactions-observer-mode-android.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После покупки в песочнице через ваш существующий флоу покупки транзакция появляется в **Event Feed** дашборда Adapty. - **Частая ошибка:** Нет событий → убедитесь, что вы передаёте транзакции в Adapty и настроены Google Play Real-Time Developer Notifications. ::: ### Проверка статуса подписки \{#check-subscription-status\} После покупки проверьте профиль пользователя на наличие активного уровня доступа, чтобы закрыть премиум-контент. **Гайд:** [Проверка статуса подписки](android-check-subscription-status) Отправьте это в свой LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/android-check-subscription-status.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После покупки в песочнице `profile.accessLevels["premium"]?.isActive` возвращает `true`. - **Частая ошибка:** Пустой `accessLevels` после покупки → убедитесь, что продукту назначен уровень доступа в дашборде. ::: ### Идентификация пользователей \{#identify-users\} Свяжите аккаунты пользователей вашего приложения с профилями Adapty, чтобы покупки сохранялись на всех устройствах. :::important Пропустите этот шаг, если в вашем приложении нет авторизации. ::: **Гайд:** [Идентификация пользователей](android-quickstart-identify) Отправьте это своему LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/android-quickstart-identify.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После вызова `Adapty.identify("your-user-id")` в разделе **Profiles** дашборда Adapty появляется ваш пользовательский ID. - **Важно:** Вызывайте `identify` после активации, но до получения пейволов, чтобы избежать атрибуции профиля анонимному пользователю. ::: ### Подготовка к релизу \{#prepare-for-release\} Когда интеграция заработает в песочнице, пройдитесь по чеклисту релиза и убедитесь, что всё готово к продакшену. **Гайд:** [Чеклист релиза](release-checklist) Отправьте это в свой LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/ru/release-checklist.md ``` :::tip[Checkpoint] - **Ожидается:** Все пункты чеклиста подтверждены: подключение стора, серверные уведомления, флоу покупки, проверки уровня доступа и требования к конфиденциальности. - **Важно:** Если не настроены Google Play Real-Time Developer Notifications → настройте их в **App settings → Android SDK**, иначе события не будут отображаться в дашборде. ::: ## Индексные файлы документации в виде обычного текста \{#plain-text-doc-index-files\} Если вы хотите дать вашему LLM более широкий контекст, выходящий за рамки отдельных страниц, мы предоставляем индексные файлы, которые перечисляют или объединяют всю документацию Adapty: - [`llms.txt`](https://adapty.io/docs/ru/llms.txt): Список всех страниц со ссылками `.md`. [Формирующийся стандарт](https://llmstxt.org/) для обеспечения доступности сайтов для LLM. Обратите внимание, что для некоторых ИИ-агентов (например, ChatGPT) потребуется скачать `llms.txt` и загрузить его в чат как файл. - [`llms-full.txt`](https://adapty.io/docs/ru/llms-full.txt): Вся документация Adapty, объединённая в один файл. Очень большой объём — используйте только тогда, когда нужна полная картина. - Android-специфичные [`android-llms.txt`](https://adapty.io/docs/ru/android-llms.txt) и [`android-llms-full.txt`](https://adapty.io/docs/ru/android-llms-full.txt): Подборки для конкретной платформы, позволяющие сэкономить токены по сравнению с полным сайтом. --- # File: android-get-pb-paywalls --- --- title: "Получение флоу и пейволов — Android" description: "Загружайте флоу и пейволы из Adapty в своём Android-приложении." --- После того как вы [создали флоу или пейвол в Paywall Builder](adapty-paywall-builder), его можно отобразить в мобильном приложении. Первый шаг — получить флоу или пейвол, связанный с плейсментом, и его конфигурацию представления, как описано ниже. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. :::
Перед тем как начать отображать флоу в мобильном приложении (нажмите, чтобы развернуть) 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте флоу/пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них флоу/пейвол](create-placement) в дашборде Adapty. 4. Установите [SDK Adapty](sdk-installation-android) в своём мобильном приложении.
## Получение флоу/пейвола \{#fetch-flowpaywall\} Если вы создали флоу или пейвол с помощью Flow Builder или Paywall Builder, вам не нужно беспокоиться о его отрисовке в коде мобильного приложения для показа пользователю. Такой флоу или пейвол уже содержит и то, что должно отображаться, и то, как именно это должно выглядеть. Тем не менее вам нужно получить его ID через плейсмент, настроить конфигурацию представления и затем показать его в мобильном приложении. Чтобы обеспечить оптимальную производительность, важно как можно раньше получить флоу или пейвол и его [конфигурацию отображения](android-get-pb-paywalls#fetch-the-view-configuration), чтобы изображения успели загрузиться до того, как пользователь их увидит. Для получения флоу или пейвола используйте метод `getFlow`: ```kotlin showLineNumbers ... Adapty.getFlow("YOUR_PLACEMENT_ID", loadTimeout = 10.seconds) { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value // the requested flow/paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers ... Adapty.getFlow("YOUR_PLACEMENT_ID", TimeInterval.seconds(10), result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); // the requested flow/paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указывали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Рекомендуем именно этот вариант — так пользователи всегда получают актуальные данные.

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

Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.

Adapty SDK хранит флоу и пейволы локально в двух слоях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](fallback-paywalls). Для ускорения загрузки используется CDN, а в случае его недоступности — отдельный резервный сервер. Такая архитектура обеспечивает получение актуальной версии данных и надёжность даже при слабом интернет-соединении.

| | **loadTimeout** | по умолчанию: 5 сек |

Ограничивает время ожидания для этого метода. По истечении таймаута возвращаются кешированные данные или локальный резервный пейвол.

Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения `loadTimeout`, поскольку операция может включать несколько запросов под капотом.

Для Android: объект `TimeInterval` можно создать с помощью функций-расширений (например, `5.seconds`, где `.seconds` из `import com.adapty.utils.seconds`) или через `TimeInterval.seconds(5)`. Чтобы не ограничивать время ожидания, используйте `TimeInterval.INFINITE`.

| Параметры ответа: | Параметр | Описание | | :-------- | :---------- | | Flow | Объект `AdaptyFlow`, содержащий плейсмент, идентификаторы (`id`, `variationId`), название, Remote Config и флаг `hasViewConfiguration`, указывающий, включает ли флоу конфигурацию отображения. Чтобы получить продукты для предзагрузки, кастомного UI или программных проверок, вызовите `getPaywallProducts(flow)`. | ## Получение конфигурации отображения \{#fetch-the-view-configuration\} После получения флоу или пейвола проверьте, содержит ли он конфигурацию отображения, с помощью `flow.hasViewConfiguration`. Этот флаг показывает, каким способом плейсмент был создан в дашборде Adapty: - **`true`** — плейсмент создан в **Flow Builder** (флоу) или **Paywall Builder** (пейвол). Adapty отрисовывает интерфейс за вас. Продолжайте выполнять шаги ниже, чтобы получить конфигурацию представления и [показать флоу или пейвол](android-present-paywalls). - **`false`** — плейсмент является пользовательским пейволом без интерфейса Builder. [Обработайте его как пейвол с Remote Config](present-remote-config-paywalls-android). :::important Убедитесь, что в Flow Builder включён переключатель **Show on device**. Если эта опция не активирована, конфигурация представления не будет доступна для получения. ::: Используйте метод `getFlowConfiguration`, чтобы загрузить конфигурацию представления. ```kotlin showLineNumbers if (!flow.hasViewConfiguration) { // use your custom logic return } AdaptyUI.getFlowConfiguration(flow, loadTimeout = 10.seconds) { result -> when(result) { is AdaptyResult.Success -> { val flowConfiguration = result.value // use loaded configuration } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` | Параметр | Обязательность | Описание | | :-------------- | :------------- | :----------------------------------------------------------- | | **flow** | обязательный | Объект `AdaptyFlow`, полученный через `Adapty.getFlow`. | | **locale** |

необязательный

по умолчанию: локаль устройства

| Идентификатор [локализации](add-paywall-locale-in-adapty-paywall-builder) в виде языкового кода с одним или двумя подтегами, разделёнными дефисом (например, `en`, `pt-br`). См. [Локализации и коды локалей](android-localizations-and-locale-codes). | | **loadTimeout** | по умолчанию: 5 сек | Ограничивает время ожидания для этого метода. По истечении таймаута возвращаются кешированные данные или локальный резервный вариант. Обратите внимание, что в редких случаях метод может завершиться чуть позже указанного в `loadTimeout` значения, так как операция может включать несколько внутренних запросов. |
Используйте метод `getFlowConfiguration` для загрузки конфигурации представления. ```java showLineNumbers if (!flow.hasViewConfiguration()) { // use your custom logic return; } AdaptyUI.getFlowConfiguration(flow, TimeInterval.seconds(10), result -> { if (result instanceof AdaptyResult.Success) { AdaptyUI.FlowConfiguration flowConfiguration = ((AdaptyResult.Success) result).getValue(); // use loaded configuration } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | Параметр | Обязательность | Описание | | :-------------- | :------------- | :----------------------------------------------------------- | | **flow** | обязательный | Объект `AdaptyFlow`, полученный через `Adapty.getFlow`. | | **locale** |

необязательный

по умолчанию: локаль устройства

| Идентификатор [локализации](add-paywall-locale-in-adapty-paywall-builder) в виде языкового кода с одним или двумя подтегами, разделёнными `-` (например, `en`, `pt-br`). См. [Локализации и коды языков](android-localizations-and-locale-codes). | | **loadTimeout** | по умолчанию: 5 сек | Ограничивает время ожидания для этого метода. Если таймаут истёк, будут возвращены кэшированные данные или локальный резервный вариант. Обратите внимание: в редких случаях метод может завершиться чуть позже указанного в `loadTimeout` значения, поскольку операция может состоять из нескольких запросов под капотом. |
:::note Если вы используете несколько языков, узнайте, как добавить [локализацию в Builder](add-paywall-locale-in-adapty-paywall-builder) и как правильно использовать коды локалей [здесь](android-localizations-and-locale-codes). ::: После загрузки [отобразите флоу или пейвол](android-present-paywalls). ## Получите флоу или пейвол для аудитории по умолчанию, чтобы ускорить загрузку \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} Как правило, флоу и пейволы загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и плейсментов, а пользователи работают при слабом интернет-соединении, загрузка флоу или пейвола может занять больше времени, чем хотелось бы. В таких случаях имеет смысл показывать флоу или пейвол для аудитории по умолчанию — это обеспечит плавный пользовательский опыт вместо пустого экрана. Чтобы решить эту проблему, используйте метод `getFlowForDefaultAudience`, который получает флоу или пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать флоу или пейвол через метод `getFlow`, как описано в разделе [Получение флоу/пейвола](#fetch-flowpaywall) выше. :::warning Почему мы рекомендуем использовать `getFlow` Метод `getFlowForDefaultAudience` имеет ряд существенных недостатков: - **Возможные проблемы с обратной совместимостью**: если вам нужно показывать разные флоу для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать флоу с поддержкой текущей (устаревшей) версии, либо мириться с тем, что пользователи на текущей (устаревшей) версии могут столкнуться с проблемами при отображении флоу. - **Потеря таргетинга**: все пользователи будут видеть один и тот же флоу, настроенный для аудитории **All Users**, — это означает отказ от персонализированного таргетинга (в том числе по странам, маркетинговой атрибуции или вашим собственным кастомным атрибутам). Если вы готовы принять эти недостатки ради более быстрой загрузки флоу или пейвола, используйте метод `getFlowForDefaultAudience` следующим образом. В противном случае используйте `getFlow`, описанный [выше](#fetch-flowpaywall). ::: ```kotlin showLineNumbers Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value // the requested flow } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); // the requested flow } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кэшированные данные в случае сбоя. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают самые актуальные данные.

Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `.returnCacheDataElseLoad` — оно возвращает кэшированные данные, если они есть. В таком случае пользователи могут получать не самые последние данные, зато загрузка будет быстрее вне зависимости от качества соединения. Кэш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы сократить количество сетевых запросов.

Обратите внимание: кэш сохраняется после перезапуска приложения и очищается только при переустановке или вручную.

| ## Настройка ассетов \{#customize-assets\} Чтобы настроить изображения и видео в своём флоу или пейволе, используйте пользовательские ассеты. Главные изображения и видео имеют предопределённые идентификаторы: `hero_image` и `hero_video`. В бандле пользовательских ассетов вы обращаетесь к этим элементам по их идентификаторам и настраиваете их поведение. Для остальных изображений и видео нужно [задать пользовательский идентификатор](custom-media) в дашборде Adapty. Например, вы можете: - Показывать разное изображение или видео отдельным пользователям. - Показывать локальное превью-изображение, пока загружается основное удалённое изображение. - Показывать превью-изображение перед запуском видео. Вот пример того, как передавать пользовательские ресурсы через простой словарь: ```kotlin showLineNumbers val customAssets = AdaptyCustomAssets.of( "hero_image" to AdaptyCustomImageAsset.remote( url = "https://example.com/image.jpg", preview = AdaptyCustomImageAsset.file( FileLocation.fromAsset("images/hero_image_preview.png"), ) ), "hero_video" to AdaptyCustomVideoAsset.file( FileLocation.fromResId(requireContext(), R.raw.custom_video), preview = AdaptyCustomImageAsset.file( FileLocation.fromResId(requireContext(), R.drawable.video_preview), ), ), ) val flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, ) ``` :::note Если ресурс не найден, флоу вернётся к внешнему виду по умолчанию. ::: Для видео можно дополнительно передать `resolution`, чтобы заранее зарезервировать место в макете и задать соотношение сторон (`width / height`) до загрузки видео: ```kotlin showLineNumbers AdaptyCustomVideoAsset.file( FileLocation.fromResId(requireContext(), R.raw.custom_video), preview = AdaptyCustomImageAsset.file( FileLocation.fromResId(requireContext(), R.drawable.video_preview), ), resolution = AdaptyCustomVideoAsset.Resolution(width = 1080, height = 1920), ) ```
После того как вы [разработали визуальную часть пейвола](adapty-paywall-builder) в новом Paywall Builder на дашборде Adapty, вы можете отобразить его в своём мобильном приложении. Первый шаг — получить пейвол, связанный с плейсментом, и его конфигурацию отображения, как описано ниже. :::warning Новый Paywall Builder работает с Android SDK версии 3.0 и выше. ::: Пожалуйста, обратите внимание, что эта тема посвящена пейволам, настроенным с помощью Paywall Builder. Если вы реализуете пейволы вручную, обратитесь к теме [Получение пейволов и продуктов для Remote Config пейволов в вашем мобильном приложении](fetch-paywalls-and-products-android). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. :::
Прежде чем начать отображать пейволы в вашем мобильном приложении (нажмите, чтобы раскрыть) 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них пейвол](create-placement) в дашборде Adapty. 4. Установите [Adapty SDK](sdk-installation-android) в своём мобильном приложении.
## Получение пейвола, созданного в Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\} Если вы [создали пейвол с помощью Paywall Builder](adapty-paywall-builder), вам не нужно беспокоиться о его отрисовке в коде мобильного приложения — пейвол уже содержит всю информацию о том, что и как должно отображаться. Тем не менее, вам нужно получить его ID через плейсмент, конфигурацию отображения, а затем показать пейвол в мобильном приложении. Для оптимальной производительности важно получать пейвол и его [конфигурацию вида](android-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) как можно раньше, чтобы изображения успели загрузиться до того, как пейвол будет показан пользователю. Для получения пейвола используйте метод `getPaywall`: ```kotlin showLineNumbers ... Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en", loadTimeout = 10.seconds) { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // the requested paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers ... Adapty.getPaywall("YOUR_PLACEMENT_ID", "en", TimeInterval.seconds(10), result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); // the requested paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** |

необязательный

по умолчанию: `en`

|

Идентификатор [локализации пейвола](add-paywall-locale-in-adapty-paywall-builder). Ожидается языковой код, состоящий из одного или двух подтегов, разделённых символом минус (**-**). Первый подтег — язык, второй — регион.

Пример: `en` означает английский, `pt-br` — португальский (Бразилия).

Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).

| | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем именно этот вариант — он гарантирует, что пользователи всегда получают актуальные данные.

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

Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или ручной очистке.

Adapty SDK хранит пейволы локально в двух слоях: описанный выше регулярно обновляемый кеш и [резервные пейволы](fallback-paywalls). Для ускорения загрузки пейволов также используется CDN, а на случай недоступности CDN — отдельный резервный сервер. Эта система гарантирует, что вы всегда получаете актуальную версию пейволов, сохраняя надёжность даже при нестабильном интернете.

| | **loadTimeout** | по умолчанию: 5 сек |

Ограничивает время ожидания для этого метода. По истечении таймаута будут возвращены кешированные данные или локальный резервный вариант.

Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения `loadTimeout`, поскольку операция может включать несколько внутренних запросов.

Для Android: создать `TimeInterval` можно с помощью функций-расширений (например, `5.seconds`, где `.seconds` из `import com.adapty.utils.seconds`) или через `TimeInterval.seconds(5)`. Чтобы отключить ограничение, используйте `TimeInterval.INFINITE`.

| Параметры ответа: | Параметр | Описание | | :-------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------| | Paywall | Объект [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) со списком идентификаторов продуктов, идентификатором пейвола, Remote Config и рядом других свойств. | ## Получение конфигурации отображения пейвола, созданного в Paywall Builder \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important Убедитесь, что в Paywall Builder включён переключатель **Show on device**. Если эта опция отключена, конфигурация отображения будет недоступна для получения. ::: После получения пейвола проверьте, содержит ли он `ViewConfiguration` — это означает, что он был создан с помощью Paywall Builder. Это подскажет вам, как отображать пейвол. Если `ViewConfiguration` присутствует, обработайте его как пейвол Paywall Builder; если нет — [обработайте его как пейвол с Remote Config](present-remote-config-paywalls). Используйте метод `getViewConfiguration` для загрузки конфигурации отображения. ```kotlin showLineNumbers if (!paywall.hasViewConfiguration) { // use your custom logic return } AdaptyUI.getViewConfiguration(paywall, loadTimeout = 10.seconds) { result -> when(result) { is AdaptyResult.Success -> { val viewConfiguration = result.value // use loaded configuration } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` | Параметр | Наличие | Описание | | :-------------- | :------------------ | :----------------------------------------------------------- | | **paywall** | обязательный | Объект `AdaptyPaywall` для получения контроллера нужного пейвола. | | **loadTimeout** | по умолчанию: 5 сек | Ограничивает время ожидания для этого метода. Если таймаут истёк, будут возвращены кешированные данные или локальный резервный вариант. Обратите внимание, что в редких случаях метод может завершиться чуть позже указанного в `loadTimeout` значения, так как операция может состоять из нескольких запросов под капотом. | Используйте метод `getViewConfiguration` для загрузки конфигурации отображения. ```java showLineNumbers if (!paywall.hasViewConfiguration()) { // use your custom logic return; } AdaptyUI.getViewConfiguration(paywall, TimeInterval.seconds(10), result -> { if (result instanceof AdaptyResult.Success) { AdaptyUI.LocalizedViewConfiguration viewConfiguration = ((AdaptyResult.Success) result).getValue(); // use loaded configuration } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | Параметр | Наличие | Описание | | :----------------------- | :------------- | :----------------------------------------------------------- | | **paywall** | обязательный | Объект `AdaptyPaywall` для получения контроллера нужного пейвола. | | **loadTimeout** | по умолчанию: 5 сек | Ограничивает таймаут для этого метода. Если таймаут истекает, возвращаются кешированные данные или локальный резервный вариант. Обратите внимание, что в редких случаях метод может завершиться с задержкой чуть больше указанного в `loadTimeout`, поскольку операция может включать несколько разных запросов под капотом. | :::note Если вы используете несколько языков, узнайте, как добавить [локализацию в Paywall Builder](add-paywall-locale-in-adapty-paywall-builder) и как правильно использовать коды локалей [здесь](android-localizations-and-locale-codes). ::: После загрузки [покажите пейвол](android-present-paywalls). ## Получите пейвол для аудитории по умолчанию, чтобы ускорить загрузку \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} Как правило, пейволы загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и пейволов, а пользователи работают при слабом интернет-соединении, загрузка пейвола может занять больше времени, чем хотелось бы. В таких случаях имеет смысл показывать пейвол по умолчанию — это обеспечит плавный пользовательский опыт вместо того, чтобы не показывать пейвол вовсе. Чтобы решить эту проблему, можно использовать метод `getPaywallForDefaultAudience`, который получает пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать пейвол с помощью метода `getPaywall`, как описано в разделе [Получение информации о пейволе](#fetch-paywall-designed-with-paywall-builder) выше. :::warning Почему мы рекомендуем использовать `getPaywall` Метод `getPaywallForDefaultAudience` имеет ряд существенных недостатков: - **Потенциальные проблемы с обратной совместимостью**: если вам нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать пейволы с учётом текущей (устаревшей) версии, либо смириться с тем, что пользователи на ней могут столкнуться с проблемами неотображаемых пейволов. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, настроенный для аудитории **All Users**, — это означает потерю персонализированного таргетинга (в том числе по странам, маркетинговой атрибуции или вашим собственным пользовательским атрибутам). Если вы готовы принять эти недостатки ради более быстрого получения пейволов, используйте метод `getPaywallForDefaultAudience` следующим образом. В противном случае используйте `getPaywall`, описанный [выше](#fetch-paywall-designed-with-paywall-builder). ::: ```kotlin showLineNumbers Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", locale = "en") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // the requested paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); // the requested paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` :::note Метод `getPaywallForDefaultAudience` доступен начиная с Android SDK 2.11.3 ::: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы задаёте при создании плейсмента в дашборде Adapty. | | **locale** |

опциональный

по умолчанию: `en`

|

Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается код языка, состоящий из одного или нескольких подтегов, разделённых символом минус (**-**). Первый подтег обозначает язык, второй — регион.

Пример: `en` означает английский, `pt-br` — бразильский португальский.

Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).

| | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.

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

Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.

| ## Настройка ассетов \{#customize-assets\} Чтобы настроить изображения и видео в пейволе, используйте кастомные ассеты. У hero-изображений и видео есть предустановленные ID: `hero_image` и `hero_video`. В пакете кастомных ассетов вы обращаетесь к этим элементам по их ID и настраиваете их поведение. Для остальных изображений и видео нужно [задать кастомный ID](custom-media) в дашборде Adapty. Например, вы можете: - Показывать разные изображения или видео разным пользователям. - Показывать локальное превью, пока загружается основное удалённое изображение. - Показывать превью перед воспроизведением видео. :::important Чтобы использовать эту функцию, обновите Adapty Android SDK до версии 3.7.0 или выше. ::: Вот пример того, как можно передавать пользовательские ресурсы через простой словарь: ```kotlin showLineNumbers val customAssets = AdaptyCustomAssets.of( "hero_image" to AdaptyCustomImageAsset.remote( url = "https://example.com/image.jpg", preview = AdaptyCustomImageAsset.file( FileLocation.fromAsset("images/hero_image_preview.png"), ) ), "hero_video" to AdaptyCustomVideoAsset.file( FileLocation.fromResId(requireContext(), R.raw.custom_video), preview = AdaptyCustomImageAsset.file( FileLocation.fromResId(requireContext(), R.drawable.video_preview), ), ), ) val paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, insets, customAssets, ) ``` :::note Если ассет не найден, пейвол вернётся к внешнему виду по умолчанию. :::
--- # File: android-present-paywalls --- --- title: "Отображение флоу и пейволов - Android" description: "Показывайте флоу и пейволы пользователям в вашем Android-приложении." --- Если вы создали флоу или пейвол, вам не нужно беспокоиться о его рендеринге в коде мобильного приложения для отображения пользователю. Такой флоу или пейвол содержит как то, что должно отображаться внутри него, так и то, как это должно отображаться. :::warning Этот гайд охватывает флоу и **пейволы, построенные в новом Paywall Builder** и рендеримые Adapty. Для Remote Config пейволов и [режима Observer](observer-vs-full-mode) процесс отличается. - Для отображения **пейволов Remote Config** смотрите [Отображение пейвола на основе Remote Config](present-remote-config-paywalls). - Для отображения **пейволов в режиме Observer mode** смотрите [Android — отображение пейволов Paywall Builder в режиме Observer mode](android-present-paywall-builder-paywalls-in-observer-mode) ::: Чтобы получить объект `flowConfiguration`, используемый ниже, смотрите [Получение флоу и пейволов](android-get-pb-paywalls). Чтобы отобразить визуальный флоу на экране устройства, его необходимо предварительно настроить. Для этого вызовите метод `AdaptyUI.getFlowView()` или создайте `AdaptyFlowView` напрямую: ```kotlin showLineNumbers val flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, ) ``` ```kotlin showLineNumbers val flowView = AdaptyFlowView(activity) // or retrieve it from xml ... with(flowView) { showFlow( flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, ) } ``` ```java showLineNumbers AdaptyFlowView flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver ); ``` ```java showLineNumbers AdaptyFlowView flowView = new AdaptyFlowView(activity); //add to the view hierarchy if needed, or you receive it from xml ... flowView.showFlow(flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver); ``` ```xml showLineNumbers ``` После успешного создания вью вы можете добавить его в иерархию вью и отобразить на экране устройства. Если вы получаете `AdaptyFlowView` _не_ с помощью вызова `AdaptyUI.getFlowView()`, вам также потребуется вызвать метод `.showFlow()`. Чтобы отобразить визуальное флоу на экране устройства, его нужно предварительно настроить. Для этого используйте следующую composable-функцию: ```kotlin showLineNumbers AdaptyFlowScreen( flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, ) ``` Параметры запроса: | Параметр | Наличие | Описание | | :---------------------------- | :------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **flowConfiguration** | обязательный | Передайте объект `AdaptyUI.FlowConfiguration` с визуальными настройками флоу. Используйте метод `AdaptyUI.getFlowConfiguration(flow)` для его загрузки. Подробнее см. в разделе [Получение конфигурации представления](android-get-pb-paywalls#fetch-the-view-configuration). | | **products** | необязательный | Передайте массив `AdaptyPaywallProduct`, чтобы оптимизировать время отображения продуктов на экране. Если передать `null`, AdaptyUI автоматически загрузит нужные продукты. | | **eventListener** | необязательный | Передайте `AdaptyFlowEventListener` для отслеживания событий флоу. Для удобства рекомендуется использовать `AdaptyFlowDefaultEventListener`. Подробнее см. в разделе [Обработка событий флоу и пейвола](android-handling-events). | | **insets** | необязательный |

Insets — это отступы вокруг флоу, которые не дают интерактивным элементам скрываться за системными панелями.

По умолчанию: `Unspecified` — Adapty автоматически подберёт отступы, что отлично работает для edge-to-edge флоу.

Если ваш флоу не является edge-to-edge, возможно, потребуется задать пользовательские отступы. Как это сделать — читайте в разделе [Изменение отступов флоу](android-present-paywalls#change-flow-insets) ниже.

| | **customAssets** | необязательный | Передайте объект `AdaptyCustomAssets`, чтобы заменить изображения и видео во флоу или пейволе во время выполнения. Подробнее см. в разделе [Настройка ресурсов](android-get-pb-paywalls#customize-assets). | | **tagResolver** | необязательный | Используйте `AdaptyUiTagResolver` для обработки пользовательских тегов в тексте флоу. Резолвер принимает тег и возвращает соответствующую строку. Подробнее см. в разделе «Пользовательские теги в Paywall Builder». | | **timerResolver** | необязательный | Передайте резолвер сюда, если планируете использовать пользовательскую функциональность таймера. | :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ## Изменение отступов флоу \{#change-flow-insets\} Отступы — это пространство вокруг флоу, которое не даёт кликабельным элементам скрываться за системными панелями. По умолчанию Adapty автоматически подстраивает отступы, что отлично работает для полноэкранных флоу. Если ваш флоу не занимает весь экран, возможно, вам потребуются собственные отступы: - Если ни строка состояния, ни панель навигации не перекрывают `AdaptyFlowView`, используйте `AdaptyFlowInsets.None`. - Для более тонкой настройки — например, если флоу перекрывается с верхней строкой состояния, но не с нижней панелью — можно задать только `bottomInset` равным `0`, как показано в примере ниже: ```kotlin showLineNumbers //create extension function fun View.onReceiveSystemBarsInsets(action: (insets: Insets) -> Unit) { ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets -> val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) ViewCompat.setOnApplyWindowInsetsListener(this, null) action(systemBarInsets) insets } } //and then use it with the view flowView.onReceiveSystemBarsInsets { insets -> val flowInsets = AdaptyFlowInsets.vertical(insets.top, 0) flowView.showFlow( flowConfiguration, products, eventListener, flowInsets, customAssets, tagResolver, timerResolver, ) } ``` ```java showLineNumbers ... ViewCompat.setOnApplyWindowInsetsListener(flowView, (view, insets) -> { Insets systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()); ViewCompat.setOnApplyWindowInsetsListener(flowView, null); AdaptyFlowInsets flowInsets = AdaptyFlowInsets.vertical(systemBarInsets.top, 0); flowView.showFlow(flowConfiguration, products, eventListener, flowInsets); return insets; }); ``` ## Использование таймеров, заданных разработчиком \{#use-developer-defined-timer\} Чтобы использовать таймеры, заданные разработчиком, в мобильном приложении, создайте объект `timerResolver` — словарь или карту, которая сопоставляет пользовательские таймеры со строковыми значениями, подставляемыми при рендеринге флоу. Пример: ```kotlin showLineNumbers ... val customTimers = mapOf( "CUSTOM_TIMER_NY" to Calendar.getInstance(TimeZone.getDefault()).apply { set(2025, 0, 1) }.time, // New Year 2025 ) val timerResolver = AdaptyUiTimerResolver { timerId -> customTimers.getOrElse(timerId, { Date(System.currentTimeMillis() + 3600 * 1000L) /* in 1 hour */ } ) } ``` ```java showLineNumbers ... Map customTimers = new HashMap<>(); customTimers.put( "CUSTOM_TIMER_NY", new Calendar.Builder().setTimeZone(TimeZone.getDefault()).setDate(2025, 0, 1).build().getTime() ); AdaptyUiTimerResolver timerResolver = new AdaptyUiTimerResolver() { @NonNull @Override public Date timerEndAtDate(@NonNull String timerId) { Date date = customTimers.get(timerId); return date != null ? date : new Date(System.currentTimeMillis() + 3600 * 1000L); /* через 1 час */ } }; ``` В этом примере `CUSTOM_TIMER_NY` — это **Timer ID** таймера, заданного разработчиком в дашборде Adapty. `timerResolver` гарантирует, что приложение динамически обновляет таймер с правильным значением — например, `13d 09h 03m 34s` (вычисляется как время окончания таймера, например Новый год, минус текущее время). ## Используйте пользовательские теги \{#use-custom-tags\} Чтобы использовать пользовательские теги в мобильном приложении, создайте объект `tagResolver` — словарь или карту, которая сопоставляет пользовательские теги со строковыми значениями, заменяющими их при отрисовке флоу. Пример: ```kotlin showLineNumbers val customTags = mapOf("USERNAME" to "John") val tagResolver = AdaptyUiTagResolver { tag -> customTags[tag] } ``` ```java showLineNumbers Map customTags = new HashMap<>(); customTags.put("USERNAME", "John"); AdaptyUiTagResolver tagResolver = customTags::get; ``` В этом примере `USERNAME` — это пользовательский тег, который вы задали в дашборде Adapty в виде ``. `tagResolver` обеспечивает динамическую замену этого тега на указанное значение — например, `John`. Рекомендуем создавать и заполнять `tagResolver` непосредственно перед показом флоу. Когда он будет готов, передайте его в метод AdaptyUI, который вы используете для отображения флоу. ## Изменение цвета индикатора загрузки флоу \{#change-flow-loading-indicator-color\} Вы можете переопределить цвет индикатора загрузки по умолчанию следующим образом: ```xml showLineNumbers title = "XML" ```
Если вы создали пейвол с помощью Paywall Builder, вам не нужно беспокоиться о его рендеринге в коде мобильного приложения для отображения пользователю. Такой пейвол содержит как то, что должно отображаться внутри него, так и то, как именно это должно показываться. :::warning Этот гайд предназначен **только для пейволов нового Paywall Builder**, которые требуют SDK v3.0. Процесс отображения пейволов отличается для пейволов, созданных с разными версиями Paywall Builder, пейволов на Remote Config и [режима Observer](observer-vs-full-mode). - Для отображения **пейволов на Remote Config** см. [Отрисовка пейвола на основе Remote Config](present-remote-config-paywalls). - Для отображения **пейволов в режиме Observer** см. [Android — отображение пейволов Paywall Builder в режиме Observer](android-present-paywall-builder-paywalls-in-observer-mode) ::: Чтобы получить объект `viewConfiguration`, используемый ниже, смотрите раздел [Получение пейволов Paywall Builder и их конфигурации](android-get-pb-paywalls). Чтобы отобразить визуальный пейвол на экране устройства, сначала необходимо его сконфигурировать. Для этого вызовите метод `AdaptyUI.getPaywallView()` или создайте `AdaptyPaywallView` напрямую: ```kotlin showLineNumbers val paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver, ) ``` ```kotlin showLineNumbers val paywallView = AdaptyPaywallView(activity) // or retrieve it from xml ... with(paywallView) { showPaywall( viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver, ) } ``` ```java showLineNumbers AdaptyPaywallView paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver ); ``` ```java showLineNumbers AdaptyPaywallView paywallView = new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml ... paywallView.showPaywall(viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver); ``` ```xml showLineNumbers ``` После успешного создания представления вы можете добавить его в иерархию представлений и отобразить на экране устройства. Если вы получаете `AdaptyPaywallView` _не_ через вызов `AdaptyUI.getPaywallView()`, вам также потребуется вызвать метод `.showPaywall()`. Чтобы отобразить визуальный пейвол на экране устройства, сначала необходимо его настроить. Для этого используйте следующую composable-функцию: ```kotlin showLineNumbers AdaptyPaywallScreen( viewConfiguration, products, eventListener, insets, personalizedOfferResolver, tagResolver, timerResolver, ) ``` Параметры запроса: | Параметр | Наличие | Описание | | :---------------------------- | :------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **viewConfiguration** | обязательный | Передайте объект `AdaptyUI.LocalizedViewConfiguration`, содержащий визуальные данные пейвола. Используйте метод `Adapty.getViewConfiguration(paywall)` для его загрузки. Подробнее см. в разделе [Получение визуальной конфигурации пейвола](android-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). | | **products** | необязательный | Передайте массив `AdaptyPaywallProduct`, чтобы оптимизировать время отображения продуктов на экране. Если передать `null`, AdaptyUI автоматически загрузит необходимые продукты. | | **eventListener** | необязательный | Передайте `AdaptyUiEventListener` для отслеживания событий пейвола. Для удобства рекомендуется расширить `AdaptyUiDefaultEventListener`. Подробнее см. в разделе [Обработка событий пейвола](android-handling-events). | | **insets** | необязательный |

Отступы вокруг пейвола, которые предотвращают перекрытие интерактивных элементов системными панелями.

По умолчанию: `UNSPECIFIED` — Adapty автоматически подбирает отступы, что отлично работает для пейволов на весь экран.

Если ваш пейвол не занимает весь экран, возможно, вам потребуется задать пользовательские отступы. Подробнее читайте в разделе [Изменение отступов пейвола](android-present-paywalls#change-paywall-insets) ниже.

| | **personalizedOfferResolver** | необязательный | Чтобы указать персонализированную цену ([подробнее](https://developer.android.com/google/play/billing/integrate#personalized-price)), реализуйте `AdaptyUiPersonalizedOfferResolver` и передайте собственную логику, которая возвращает `true` для `AdaptyPaywallProduct` с персонализированной ценой и `false` в противном случае. | | **tagResolver** | необязательный | Используйте `AdaptyUiTagResolver` для обработки пользовательских тегов в тексте пейвола. Резолвер принимает тег и возвращает соответствующую строку. Подробнее см. в разделе «Пользовательские теги в Paywall Builder». | | **timerResolver** | необязательный | Передайте резолвер, если планируете использовать пользовательский таймер. | :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ## Изменение отступов пейвола \{#change-paywall-insets\} Отступы — это пространство вокруг пейвола, которое не даёт интерактивным элементам скрыться за системными панелями. По умолчанию Adapty автоматически подбирает отступы, что отлично работает для пейволов во весь экран. Если ваш пейвол не занимает весь экран, можно задать отступы вручную: - Если ни строка состояния, ни панель навигации не перекрываются с `AdaptyPaywallView`, используйте `AdaptyPaywallInsets.NONE`. - Для более нестандартных случаев, например если пейвол перекрывается с верхней строкой состояния, но не с нижней панелью, можно установить только `bottomInset` в `0`, как показано в примере ниже: ```kotlin showLineNumbers //create extension function fun View.onReceiveSystemBarsInsets(action: (insets: Insets) -> Unit) { ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets -> val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) ViewCompat.setOnApplyWindowInsetsListener(this, null) action(systemBarInsets) insets } } //and then use it with the view paywallView.onReceiveSystemBarsInsets { insets -> val paywallInsets = AdaptyPaywallInsets.vertical(insets.top, 0) paywallView.showPaywall( viewConfiguration, products, eventListener, paywallInsets, personalizedOfferResolver, tagResolver, timerResolver, ) } ``` ```java showLineNumbers ... ViewCompat.setOnApplyWindowInsetsListener(paywallView, (view, insets) -> { Insets systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()); ViewCompat.setOnApplyWindowInsetsListener(paywallView, null); AdaptyPaywallInsets paywallInsets = AdaptyPaywallInsets.of(systemBarInsets.top, 0); paywallView.showPaywall(paywall, products, viewConfiguration, paywallInsets, productTitleResolver); return insets; }); ``` ## Использование таймеров, заданных разработчиком \{#use-developer-defined-timer\} Чтобы использовать таймеры, заданные разработчиком, в своём мобильном приложении, создайте объект `timerResolver` — словарь или карту, в которых каждый пользовательский таймер сопоставлен со строковым значением, на которое он будет заменён при отображении пейвола. Пример: ```kotlin showLineNumbers ... val customTimers = mapOf( "CUSTOM_TIMER_NY" to Calendar.getInstance(TimeZone.getDefault()).apply { set(2025, 0, 1) }.time, // New Year 2025 ) val timerResolver = AdaptyUiTimerResolver { timerId -> customTimers.getOrElse(timerId, { Date(System.currentTimeMillis() + 3600 * 1000L) /* in 1 hour */ } ) } ``` ```java showLineNumbers ... Map customTimers = new HashMap<>(); customTimers.put( "CUSTOM_TIMER_NY", new Calendar.Builder().setTimeZone(TimeZone.getDefault()).setDate(2025, 0, 1).build().getTime() ); AdaptyUiTimerResolver timerResolver = new AdaptyUiTimerResolver() { @NonNull @Override public Date timerEndAtDate(@NonNull String timerId) { Date date = customTimers.get(timerId); return date != null ? date : new Date(System.currentTimeMillis() + 3600 * 1000L); /* in 1 hour */ } }; ``` В этом примере `CUSTOM_TIMER_NY` — это **Timer ID** таймера, заданного разработчиком в дашборде Adapty. `timerResolver` гарантирует, что приложение динамически обновляет таймер с нужным значением — например, `13d 09h 03m 34s` (вычисляется как время окончания таймера, например Новый год, минус текущее время). ## Использование пользовательских тегов \{#use-custom-tags\} Чтобы использовать пользовательские теги в мобильном приложении, создайте объект `tagResolver` — словарь или карту, которая сопоставляет пользовательские теги со строковыми значениями, подставляемыми при отображении пейвола. Пример: ```kotlin showLineNumbers val customTags = mapOf("USERNAME" to "John") val tagResolver = AdaptyUiTagResolver { tag -> customTags[tag] } ``` ```java showLineNumbers Map customTags = new HashMap<>(); customTags.put("USERNAME", "John"); AdaptyUiTagResolver tagResolver = customTags::get; ``` В этом примере `USERNAME` — это кастомный тег, который вы добавили в дашборде Adapty как ``. `tagResolver` обеспечивает динамическую замену этого тега на указанное значение — например, `John`. Рекомендуем создавать и заполнять `tagResolver` непосредственно перед показом пейвола. Когда он будет готов, передайте его в метод AdaptyUI, который вы используете для отображения пейвола. ## Изменение цвета индикатора загрузки пейвола \{#change-paywall-loading-indicator-color\} Вы можете переопределить цвет индикатора загрузки по умолчанию следующим образом: ```xml showLineNumbers title = "XML" ```
--- # File: android-handle-paywall-actions --- --- title: "Обработка действий флоу - Android" description: "Обрабатывайте действия кнопок из флоу и пейволов в Android-приложении." --- Если вы создаёте флоу или пейволы с помощью Adapty Flow Builder или Paywall Builder, важно правильно настроить кнопки: 1. Добавьте [кнопку в билдере](paywall-buttons) и назначьте ей существующее действие или создайте собственный идентификатор действия. 2. Напишите код в приложении для обработки каждого назначенного действия. В этом гайде показано, как обрабатывать пользовательские и существующие действия в коде. :::warning **Только покупки, восстановление, закрытие флоу/пейвола и открытие URL обрабатываются автоматически.** Все остальные действия кнопок требуют реализации обработчика в коде приложения. ::: ## Закрытие флоу и пейволов \{#close-flows-and-paywalls\} Чтобы добавить кнопку для закрытия флоу или пейвола: 1. В билдере добавьте кнопку и назначьте ей действие **Close**. 2. В коде приложения реализуйте обработчик действия `close`. :::info В Android SDK действие `close` по умолчанию закрывает флоу или пейвол. При необходимости это поведение можно переопределить в коде. Например, закрытие одного флоу может запускать открытие другого. ::: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { AdaptyUI.Action.Close -> (context as? Activity)?.onBackPressed() // default behavior } } ``` ## Открытие URL из флоу и пейволов \{#open-urls-from-flows-and-paywalls\} :::tip Если нужно добавить группу ссылок (например, пользовательское соглашение и восстановление покупок), добавьте элемент **Link** в билдере и обрабатывайте его так же, как кнопки с действием **Open URL**. ::: Чтобы добавить кнопку, открывающую ссылку из вашего флоу или пейвола (например, **Terms of use** или **Privacy policy**): 1. В билдере добавьте кнопку, назначьте ей действие **Open URL** и укажите нужный URL. 2. В коде приложения реализуйте обработчик действия `openUrl`, который открывает полученный URL в браузере. :::info В Android SDK действие `openUrl` по умолчанию открывает URL. Однако при необходимости вы можете переопределить это поведение в своём коде. ::: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.OpenUrl -> { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(action.url)) // default behavior context.startActivity(intent) } } } ``` ## Обработка пользовательских действий \{#handle-custom-actions\} Чтобы добавить кнопку с произвольным действием: 1. В конструкторе добавьте кнопку, назначьте ей действие **Custom** и укажите ID. 2. В коде приложения реализуйте обработчик для созданного вами ID действия. Например, если у вас есть другой набор предложений по подпискам или разовых покупок, можно добавить кнопку, которая откроет другой флоу или пейвол: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.Custom -> { if (action.customId == "openNewPaywall") { // Display another flow or paywall } } } } ``` Если вы создаёте пейволы с помощью Adapty Paywall Builder, важно правильно настроить кнопки: 1. Добавьте [кнопку в Paywall Builder](paywall-buttons) и назначьте ей уже существующее действие или создайте собственный ID действия. 2. Напишите в приложении код для обработки каждого назначенного действия. В этом гайде показано, как обрабатывать пользовательские и встроенные действия в коде. :::warning **Покупки, восстановления, закрытие пейвола и открытие URL обрабатываются автоматически.** Все остальные действия кнопок требуют явной реализации обработки в коде приложения. ::: ## Закрытие пейвола \{#close-paywalls\} Чтобы добавить кнопку закрытия пейвола: 1. В Paywall Builder добавьте кнопку и назначьте ей действие **Close**. 2. В коде приложения реализуйте обработчик действия `close`, который закрывает пейвол. :::info В Android SDK действие `close` по умолчанию закрывает пейвол. Однако при необходимости вы можете переопределить это поведение в своём коде. Например, закрытие одного пейвола может инициировать открытие другого. ::: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { AdaptyUI.Action.Close -> (context as? Activity)?.onBackPressed() // default behavior } } ``` ## Открытие URL из пейволов \{#open-urls-from-paywalls\} :::tip Если вы хотите добавить группу ссылок (например, условия использования и восстановление покупки), добавьте элемент **Link** в Paywall Builder и обрабатывайте его так же, как кнопки с действием **Open URL**. ::: Чтобы добавить кнопку, открывающую ссылку из пейвола (например, **Terms of use** или **Privacy policy**): 1. В Paywall Builder добавьте кнопку, назначьте ей действие **Open URL** и введите URL, который нужно открыть. 2. В коде приложения реализуйте обработчик действия `openUrl`, который открывает полученный URL в браузере. :::info В Android SDK действие `openUrl` по умолчанию открывает URL. Однако при необходимости вы можете переопределить это поведение в своём коде. ::: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.OpenUrl -> { val intent = Intent(Intent.ACTION_VIEW, Uri.parse(action.url)) // default behavior context.startActivity(intent) } } } ``` ## Вход в приложение \{#log-into-the-app\} Чтобы добавить кнопку для входа пользователей в ваше приложение: 1. В Paywall Builder добавьте кнопку и назначьте ей действие **Login**. 2. В коде приложения реализуйте обработчик действия `login`, который идентифицирует пользователя. ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { AdaptyUI.Action.Login -> { val intent = Intent(context, LoginActivity::class.java) context.startActivity(intent) } } } ``` ## Обработка пользовательских действий \{#handle-custom-actions\} Чтобы добавить кнопку с произвольным действием: 1. В Paywall Builder добавьте кнопку, назначьте ей действие **Custom** и укажите идентификатор. 2. В коде приложения реализуйте обработчик для созданного идентификатора действия. Например, если у вас есть другой набор предложений по подпискам или разовых покупок, можно добавить кнопку, которая откроет другой пейвол: ```kotlin override fun onActionPerformed(action: AdaptyUI.Action, context: Context) { when (action) { is AdaptyUI.Action.Custom -> { if (action.customId == "openNewPaywall") { // Display another paywall } } } } ``` --- # File: android-handling-events --- --- title: "Обработка событий флоу и пейвола — Android" description: "Обрабатывайте события флоу и пейвола в Android-приложении." --- :::important Этот гайд охватывает обработку событий для покупок, восстановлений, выбора продукта и рендеринга флоу. Вам также необходимо реализовать обработку кнопок (закрытие флоу, открытие ссылок и т. д.). Подробнее — в нашем [гайде по обработке действий кнопок](android-handle-paywall-actions). ::: Флоу и пейволы, настроенные с помощью [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder), не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют события, на которые ваше приложение может реагировать. К таким событиям относятся нажатия кнопок (кнопки закрытия, URL-ссылки, выбор продуктов и т. д.), а также уведомления о действиях, связанных с покупками. Ниже описано, как обрабатывать эти события. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: Если вам нужно контролировать или отслеживать процессы на экране покупки, реализуйте методы `AdaptyFlowEventListener`. Если вы хотите сохранить поведение по умолчанию в некоторых случаях, вы можете расширить `AdaptyFlowDefaultEventListener` и переопределить только те методы, которые нужно изменить. Ниже приведены значения по умолчанию из `AdaptyFlowDefaultEventListener`. ### События, генерируемые пользователем \{#user-generated-events\} #### Выбор продукта \{#product-selection\} Если продукт выбран для покупки (пользователем или системой), будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" public override fun onProductSelected( product: AdaptyPaywallProduct, context: Context, ) {} ```
Пример события (нажмите, чтобы развернуть) ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
#### Начало покупки \{#started-purchase\} Когда пользователь инициирует процесс покупки, будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseStarted( product: AdaptyPaywallProduct, context: Context, ) {} ```
Пример события (нажмите, чтобы развернуть) ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
Метод не вызывается в режиме Observer. Подробнее см. в разделе [Android — отображение пейволов Paywall Builder в режиме Observer](android-present-paywall-builder-paywalls-in-observer-mode). #### Успешная, отменённая или отложенная покупка \{#successful-canceled-or-pending-purchase\} Если покупка прошла успешно, будет вызван следующий метод: ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseFinished( purchaseResult: AdaptyPurchaseResult, product: AdaptyPaywallProduct, context: Context, ) { if (purchaseResult !is AdaptyPurchaseResult.UserCanceled) context.getActivityOrNull()?.onBackPressed() } ```
Примеры событий (нажмите, чтобы развернуть) ```javascript // Successful purchase { "purchaseResult": { "type": "Success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // Cancelled purchase { "purchaseResult": { "type": "UserCanceled" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // Pending purchase { "purchaseResult": { "type": "Pending" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
В этом случае рекомендуем закрыть экран. Метод не вызывается в Observer mode. Подробнее см. в разделе [Android — Отображение пейволов Paywall Builder в Observer mode](android-present-paywall-builder-paywalls-in-observer-mode). #### Неудачная покупка \{#failed-purchase\} Если покупка завершается с ошибкой, вызывается этот метод. Это включает ошибки Google Play Billing (ограничения платежей, некорректные продукты, сбои сети), ошибки верификации транзакции и системные ошибки. Обратите внимание: отмена покупки пользователем вызывает `onPurchaseFinished` с результатом отмены, а ожидающие платежи этот метод не вызывают. ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseFailure( error: AdaptyError, product: AdaptyPaywallProduct, context: Context, ) {} ```
Пример события (нажмите, чтобы развернуть) ```javascript { "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
Этот метод не вызывается в режиме Observer. Подробнее см. в разделе [Android — отображение пейволов Paywall Builder в режиме Observer](android-present-paywall-builder-paywalls-in-observer-mode). #### Завершение навигации веб-оплаты \{#finished-web-payment-navigation\} Этот метод вызывается после попытки открыть [веб-пейвол](web-paywall) для конкретного продукта. Это касается как успешных, так и неудачных попыток навигации: ```kotlin showLineNumbers title="Kotlin" public override fun onFinishWebPaymentNavigation( product: AdaptyPaywallProduct?, error: AdaptyError?, context: Context, ) {} ``` **Параметры:** | Параметр | Описание | |:------------|:------------------------------------------------------------------------------------------------------------| | **product** | Объект `AdaptyPaywallProduct`, для которого был открыт веб-пейвол. Может быть `null`. | | **error** | Объект `AdaptyError`, если навигация в веб-пейволе завершилась с ошибкой; `null`, если навигация успешна. |
Примеры событий (нажмите, чтобы раскрыть) ```javascript // Successful navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": null } // Failed navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "web_navigation_failed", "message": "Failed to open web paywall", "details": { "underlyingError": "Browser unavailable" } } } ```
#### Успешное восстановление покупки \{#successful-restore\} Если восстановление покупки прошло успешно, будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" public override fun onRestoreSuccess( profile: AdaptyProfile, context: Context, ) {} ```
Пример события (нажмите, чтобы раскрыть) ```javascript { "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } ```
Рекомендуем закрывать экран, если у пользователя есть нужный `accessLevel`. Как его проверить — читайте в разделе [Статус подписки](android-listen-subscription-changes). #### Неудачное восстановление \{#failed-restore\} Если `Adapty.restorePurchases()` завершится с ошибкой, будет вызван следующий метод: ```kotlin showLineNumbers title="Kotlin" public override fun onRestoreFailure( error: AdaptyError, context: Context, ) {} ```
Пример события (нажмите, чтобы развернуть) ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ```
#### Обновление подписки \{#upgrade-subscription\} Когда пользователь пытается приобрести новую подписку, пока активна другая, вы можете управлять тем, как должна обрабатываться новая покупка, переопределив этот метод. У вас есть два варианта: 1. **Замените текущую подписку** на новую: ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback, ): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived( AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) .build() ) return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` 2. **Сохранить обе подписки** (добавить новую отдельно): ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback, ): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty) return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` :::note Если вы не переопределяете этот метод, поведение по умолчанию — сохранить обе подписки активными (эквивалентно использованию `AdaptyPurchaseParameters.Empty`). ::: Вы также можете задать дополнительные параметры покупки при необходимости: ```kotlin AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription .withOfferPersonalized(true) // optional - if using personalized pricing .build() ```
Пример события (нажмите, чтобы развернуть) ```javascript { "product": { "vendorProductId": "premium_yearly", "localizedTitle": "Premium Yearly", "localizedDescription": "Premium subscription for 1 year", "localizedPrice": "$99.99", "price": 99.99, "currencyCode": "USD" }, "subscriptionUpdateParams": { "replacementMode": "with_time_proration" } } ```
### Загрузка данных и рендеринг \{#data-fetching-and-rendering\} #### Ошибки загрузки продуктов \{#product-loading-errors\} Если вы не передаёте продукты при инициализации, AdaptyUI самостоятельно получит необходимые объекты с сервера. Если эта операция завершится неудачно, AdaptyUI сообщит об ошибке, вызвав следующий метод: ```kotlin showLineNumbers title="Kotlin" public override fun onLoadingProductsFailure( error: AdaptyError, context: Context, ): Boolean = false ```
Пример события (нажмите, чтобы развернуть) ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ```
Если вы вернёте `true`, AdaptyUI повторит запрос через 2 секунды. #### Ошибки рендеринга \{#rendering-errors\} Если во время рендеринга интерфейса возникает ошибка, о ней сообщается через вызов этого метода: ```kotlin showLineNumbers title="Kotlin" public override fun onError( error: AdaptyError, context: Context, ) {} ```
Пример события (нажмите, чтобы развернуть) ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render flow interface", "details": { "underlyingError": "Invalid flow configuration" } } } ```
В нормальной ситуации такие ошибки не должны возникать, поэтому, если вы с ними столкнулись, пожалуйста, сообщите нам. ### Навигация \{#navigation\} #### Системная кнопка «Назад» \{#system-back-button\} По умолчанию флоу нельзя закрыть системной кнопкой «Назад» или жестом — пользователь выходит из него только через путь, который вы задаёте: кнопку **Close** или действие `on_device_back` в билдере. Если вы хотите, чтобы системная кнопка «Назад» закрывала флоу, переопределите `onBackPressed` и верните `false`, чтобы хост-активити или фрагмент обработал нажатие: ```kotlin showLineNumbers title="Kotlin" public override fun onBackPressed(context: Context): Boolean { return false // let the host handle the back press (e.g. finish the activity or pop the fragment) } ``` Этот коллбэк вызывается только тогда, когда для текущего экрана не настроено действие `on_device_back` — настроенное действие имеет приоритет и обрабатывается внутри SDK. Верните `true`, чтобы перехватить нажатие (поведение по умолчанию), или `false`, чтобы передать его хосту для самостоятельной обработки. ### Зарезервированные события \{#reserved-events\} `AdaptyFlowEventListener` объявляет несколько колбэков для функциональности, которую флоу пока не используют. Реализовывать их не нужно — `AdaptyFlowDefaultEventListener` уже предоставляет пустые реализации по умолчанию. | Метод | Описание | |:-------|:------------| | **onAnalyticEvent** | Зарезервирован для пользовательских аналитических событий из флоу. Флоу пока не отправляют эти события в ваш код, поэтому реализовывать его не нужно. | | **onShowAppRate** | Зарезервирован для запросов на оценку приложения из флоу. Флоу пока не инициируют запросы на оценку приложения, поэтому реализовывать его не нужно. | | **onShowRequestPermission** | Зарезервирован для запросов системных разрешений (например, push-уведомлений или доступа к камере) из флоу. Флоу пока не инициируют запросы на разрешения, поэтому реализовывать его не нужно. |
:::important Этот гайд охватывает обработку событий для покупок, восстановлений, выбора продуктов и отображения пейвола. Вам также необходимо реализовать обработку кнопок (закрытие пейвола, открытие ссылок и т. д.). Подробности смотрите в нашем [гайде по обработке действий кнопок](android-handle-paywall-actions). ::: Пейволы, настроенные с помощью [Paywall Builder](adapty-paywall-builder), не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют события, на которые ваше приложение может реагировать. Среди них — нажатия кнопок (закрытия, URL, выбора продуктов и т. д.), а также уведомления о действиях, связанных с покупками на пейволе. Узнайте ниже, как реагировать на эти события. :::warning Это руководство предназначено **только для пейволов на новом Paywall Builder**, которые требуют Adapty SDK v3.0 или выше. ::: :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: Если вам нужно управлять процессами на экране покупки или отслеживать их, реализуйте методы `AdaptyUiEventListener`. Если вы хотите сохранить поведение по умолчанию для части случаев, можно унаследоваться от `AdaptyUiDefaultEventListener` и переопределить только те методы, которые нужно изменить. Ниже приведены значения по умолчанию из `AdaptyUiDefaultEventListener`. ### Пользовательские события \{#user-generated-events\} #### Выбор продукта \{#product-selection\} Если продукт выбран для покупки (пользователем или системой), будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" public override fun onProductSelected( product: AdaptyPaywallProduct, context: Context, ) {} ```
Пример события (нажмите, чтобы раскрыть) ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
#### Начало покупки \{#started-purchase\} Если пользователь инициирует процесс покупки, будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseStarted( product: AdaptyPaywallProduct, context: Context, ) {} ```
Пример события (нажмите, чтобы развернуть) ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
Этот метод не будет вызван в режиме Observer. Подробнее см. в статье [Android — отображение пейволов Paywall Builder в режиме Observer](android-present-paywall-builder-paywalls-in-observer-mode). #### Успешная, отменённая или отложенная покупка \{#successful-canceled-or-pending-purchase\} Если покупка прошла успешно, будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseFinished( purchaseResult: AdaptyPurchaseResult, product: AdaptyPaywallProduct, context: Context, ) { if (purchaseResult !is AdaptyPurchaseResult.UserCanceled) context.getActivityOrNull()?.onBackPressed() } ```
Примеры событий (нажмите, чтобы раскрыть) ```javascript // Successful purchase { "purchaseResult": { "type": "Success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // Cancelled purchase { "purchaseResult": { "type": "UserCanceled" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // Pending purchase { "purchaseResult": { "type": "Pending" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
В этом случае рекомендуем закрыть экран. Метод не вызывается в Observer mode. Подробнее см. в разделе [Android — отображение пейволов Paywall Builder в Observer mode](android-present-paywall-builder-paywalls-in-observer-mode). #### Неудачная покупка \{#failed-purchase\} Если покупка завершается ошибкой, этот метод будет вызван. Это включает ошибки Google Play Billing (ограничения платежей, недействительные продукты, сбои сети), ошибки проверки транзакций и системные ошибки. Обратите внимание, что отмена покупки пользователем вызывает `onPurchaseFinished` с результатом отмены, а ожидающие платежи не вызывают этот метод. ```kotlin showLineNumbers title="Kotlin" public override fun onPurchaseFailure( error: AdaptyError, product: AdaptyPaywallProduct, context: Context, ) {} ```
Пример события (нажмите, чтобы развернуть) ```javascript { "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ```
Этот метод не вызывается в режиме Observer. Подробнее см. в разделе [Android — отображение пейволов Paywall Builder в режиме Observer](android-present-paywall-builder-paywalls-in-observer-mode). #### Завершение навигации веб-платежа \{#finished-web-payment-navigation\} Этот метод вызывается после попытки открыть [веб-пейвол](web-paywall) для конкретного продукта. Это касается как успешных, так и неудачных попыток навигации: ```kotlin showLineNumbers title="Kotlin" public override fun onFinishWebPaymentNavigation( product: AdaptyPaywallProduct?, error: AdaptyError?, context: Context, ) {} ``` **Параметры:** | Параметр | Описание | |:------------|:-----------------------------------------------------------------------------------------------------------| | **product** | Объект `AdaptyPaywallProduct`, для которого был открыт веб-пейвол. Может быть `null`. | | **error** | Объект `AdaptyError`, если при навигации на веб-пейвол произошла ошибка; `null`, если навигация успешна. |
Примеры событий (нажмите, чтобы развернуть) ```javascript // Successful navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": null } // Failed navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "web_navigation_failed", "message": "Failed to open web paywall", "details": { "underlyingError": "Browser unavailable" } } } ```
#### Успешное восстановление покупки \{#successful-restore\} Если восстановление покупки прошло успешно, будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" public override fun onRestoreSuccess( profile: AdaptyProfile, context: Context, ) {} ```
Пример события (нажмите, чтобы раскрыть) ```javascript { "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } ```
Мы рекомендуем закрывать экран, если у пользователя есть необходимый `accessLevel`. Подробнее о том, как это проверить, читайте в разделе [Статус подписки](android-listen-subscription-changes). #### Ошибка восстановления \{#failed-restore\} Если `Adapty.restorePurchases()` завершится с ошибкой, будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" public override fun onRestoreFailure( error: AdaptyError, context: Context, ) {} ```
Пример события (нажмите, чтобы развернуть) ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ```
#### Обновление подписки \{#upgrade-subscription\} Когда пользователь пытается купить новую подписку, пока активна другая, вы можете управлять тем, как следует обработать новую покупку, переопределив этот метод. Доступны два варианта: 1. **Замените текущую подписку** на новую: ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback, ): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived( AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) .build() ) return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` 2. **Оставить обе подписки** (добавить новую отдельно): ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback, ): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty) return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` :::note Если вы не переопределяете этот метод, по умолчанию обе подписки остаются активными (эквивалентно использованию `AdaptyPurchaseParameters.Empty`). ::: При необходимости можно задать дополнительные параметры покупки: ```kotlin AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription .withOfferPersonalized(true) // optional - if using personalized pricing .build() ``` Если новая подписка приобретается, пока другая ещё активна, переопределите этот метод, чтобы заменить текущую подписку на новую. Если активная подписка должна оставаться активной, а новая добавляется отдельно, вызовите `onSubscriptionUpdateParamsReceived(null)`: ```kotlin showLineNumbers title="Kotlin" public override fun onAwaitingSubscriptionUpdateParams( product: AdaptyPaywallProduct, context: Context, onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback, ) { onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...)) } ```
Пример события (нажмите, чтобы развернуть) ```javascript { "product": { "vendorProductId": "premium_yearly", "localizedTitle": "Premium Yearly", "localizedDescription": "Premium subscription for 1 year", "localizedPrice": "$99.99", "price": 99.99, "currencyCode": "USD" }, "subscriptionUpdateParams": { "replacementMode": "with_time_proration" } } ```
### Загрузка данных и рендеринг \{#data-fetching-and-rendering\} #### Ошибки загрузки продуктов \{#product-loading-errors\} Если вы не передаёте продукты при инициализации, AdaptyUI самостоятельно получает необходимые объекты с сервера. Если эта операция завершается с ошибкой, AdaptyUI сообщает о ней, вызывая следующий метод: ```kotlin showLineNumbers title="Kotlin" public override fun onLoadingProductsFailure( error: AdaptyError, context: Context, ): Boolean = false ```
Пример события (нажмите, чтобы развернуть) ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ```
Если вы вернёте `true`, AdaptyUI повторит запрос через 2 секунды. #### Ошибки рендеринга \{#rendering-errors\} Если в процессе рендеринга интерфейса возникнет ошибка, она будет передана через этот метод: ```kotlin showLineNumbers title="Kotlin" public override fun onRenderingError( error: AdaptyError, context: Context, ) {} ```
Пример события (нажмите, чтобы развернуть) ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } ```
В нормальных условиях такие ошибки не должны возникать, поэтому если вы столкнулись с одной из них, пожалуйста, сообщите нам.
--- # File: android-use-fallback-paywalls --- --- title: "Android - Использование резервных пейволов" description: "Обрабатывайте случаи, когда пользователи оффлайн или серверы Adapty недоступны." --- :::warning Резервные пейволы поддерживаются Android SDK версии 2.11 и выше. ::: Чтобы поддерживать бесперебойный пользовательский опыт, важно настроить [резервные пейволы](/fallback-paywalls) для флоу, [пейволов](paywalls) и [онбордингов](onboardings). Это позволит приложению продолжить работу при частичной или полной потере интернет-соединения. * **Если приложение не может обратиться к серверам Adapty:** Оно сможет отобразить резервный флоу или пейвол, а также использовать локальную конфигурацию онбординга. * **Если приложение не может подключиться к интернету:** Оно сможет отобразить резервный флоу или пейвол. Онбординги содержат удалённый контент и требуют интернет-соединения для работы. :::important Прежде чем следовать шагам этого гайда, [скачайте](/local-fallback-paywalls) файлы резервной конфигурации из Adapty. ::: ## Настройка \{#configuration\} 1. Переместите файл конфигурации резервного пейвола в директорию `assets` или `res/raw` вашего Android-проекта. 2. Вызовите метод `.setFallback` **до** того, как будете получать нужный флоу, пейвол или онбординг. ```kotlin showLineNumbers //if you put the 'android_fallback.json' file to the 'assets' directory val location = FileLocation.fromAsset("android_fallback.json") //or `FileLocation.fromAsset("/android_fallback.json")` if you placed it in a child folder of 'assets') //if you put the 'android_fallback.json' file to the 'res/raw' directory val location = FileLocation.fromResId(context, R.raw.android_fallback) //you can also pass a file URI val fileUri: Uri = //get Uri for the file with fallback paywalls val location = FileLocation.fromFileUri(fileUri) //pass the file location Adapty.setFallback(location, callback) ``` ```java showLineNumbers //if you put the 'android_fallback.json' file to the 'assets' directory FileLocation location = FileLocation.fromAsset("android_fallback.json"); //or `FileLocation.fromAsset("/android_fallback.json");` if you placed it in a child folder of 'assets') //if you put the 'android_fallback.json' file to the 'res/raw' directory FileLocation location = FileLocation.fromResId(context, R.raw.android_fallback); //you can also pass a file URI Uri fileUri = //get Uri for the file with fallback paywalls FileLocation location = FileLocation.fromFileUri(fileUri); //pass the file location Adapty.setFallback(location, callback); ``` | Параметр | Описание | | :----------- | :----------------------------------------------------------- | | **location** | Объект [FileLocation](https://android.adapty.io/adapty/com.adapty.utils/-file-location/-companion/) для файла резервной конфигурации | --- # File: android-localizations-and-locale-codes --- --- title: "Использование локализаций и кодов локали в Android SDK" description: "Управление локализациями и кодами локали для охвата глобальной аудитории (Android)." --- ## Почему это важно \{#why-this-is-important\} Коды локали используются в нескольких сценариях — например, когда нужно получить правильный пейвол для текущей локализации приложения. Поскольку коды локали устроены непросто и могут различаться в зависимости от платформы, мы используем внутренний стандарт для всех поддерживаемых платформ. Тем не менее именно из-за этой сложности важно понимать, что именно вы отправляете на наш сервер и что происходит дальше — чтобы всегда получать ожидаемый результат. ## Стандарт кодов локали в Adapty \{#locale-code-standard-at-adapty\} Для кодов локали Adapty использует слегка модифицированный стандарт [BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): каждый код состоит из подтегов в нижнем регистре, разделённых дефисами. Примеры: `en` (английский), `pt-br` (португальский (Бразилия)), `zh` (упрощённый китайский), `zh-hant` (традиционный китайский). ## Сопоставление кодов локали \{#locale-code-matching\} Когда Adapty получает запрос от клиентского SDK с кодом локали и начинает искать соответствующую локализацию пейвола, происходит следующее: 1. Входящая строка локали приводится к нижнему регистру, а все символы подчёркивания (`_`) заменяются дефисами (`-`) 2. Затем выполняется поиск локализации с полностью совпадающим кодом локали 3. Если совпадений не найдено, берётся подстрока до первого дефиса (`pt` для `pt-br`) и выполняется поиск по ней 4. Если совпадений снова не найдено, возвращается локализация по умолчанию — `en` Таким образом устройство iOS, отправившее `'pt_BR'`, устройство Android, отправившее `pt-BR`, и другое устройство, отправившее `pt-br`, получат одинаковый результат. ## Рекомендуемый способ реализации локализаций \{#implementing-localizations-recommended-way\} Если вас интересуют локализации, скорее всего, вы уже работаете с файлами локализованных строк в своём проекте. В таком случае мы рекомендуем добавить в каждый из этих файлов пару ключ-значение с нужным кодом локали Adapty для соответствующей локализации, а затем извлекать значение по этому ключу при вызове нашего SDK: ```kotlin showLineNumbers // 1. Modify your strings.xml files /* strings.xml - Spanish */ es /* strings.xml - Portuguese (Brazil) */ pt-br // 2. Extract and use the locale code val localeCode = context.getString(R.string.adapty_paywalls_locale) // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` Это позволяет полностью контролировать, какая локализация будет загружена для каждого пользователя вашего приложения. ## Альтернативный способ реализации локализаций \{#implementing-localizations-the-other-way\} Похожего (но не идентичного) результата можно добиться без явного определения кодов локали для каждой локализации. Для этого можно извлечь код локали из других объектов, предоставляемых платформой: ```kotlin showLineNumbers val locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) context.resources.configuration.locales[0] else context.resources.configuration.locale val localeCode = locale.toLanguageTag() // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` Обратите внимание: этот подход мы не рекомендуем, поскольку сложно предсказать, что именно получит сервер Adapty. Если вы всё же решите использовать этот подход — убедитесь, что учли все актуальные сценарии использования. --- # File: android-web-paywall --- --- title: "Реализация веб-пейволов в Android SDK" description: "Настройте веб-пейвол для приёма платежей без комиссий и проверок Play Store." --- :::important Прежде чем начать, убедитесь, что вы [настроили веб-пейвол в дашборде](web-paywall) и установили Adapty SDK версии 3.15 или выше. ::: ## Открытие веб-пейволов \{#open-web-paywalls\} Если вы работаете с пейволом, разработанным самостоятельно, вам нужно обрабатывать веб-пейволы с помощью метода SDK. Метод `.openWebPaywall`: 1. Генерирует уникальный URL, позволяющий Adapty связать конкретный пейвол, показанный определённому пользователю, с веб-страницей, на которую он будет перенаправлен. 2. Отслеживает момент возврата пользователей в приложение и затем запрашивает `.getProfile` с короткими интервалами, чтобы определить, обновились ли права доступа профиля. Таким образом, если платёж прошёл успешно и права доступа обновились, подписка активируется в приложении практически сразу. :::note После того как пользователи вернутся в приложение, обновите UI, чтобы отразить изменения профиля. Adapty получит и обработает события обновления профиля. ::: ```kotlin showLineNumbers Adapty.openWebPaywall( activity = activity, product = product, ) { error -> if (error == null) { // the web paywall was opened successfully } else { // handle the error } } ``` :::note Существует две версии метода `openWebPaywall`: 1. `openWebPaywall(product)` — генерирует URL по пейволу и добавляет в URL данные о продукте. 2. `openWebPaywall(paywall)` — генерирует URL по пейволу без добавления данных о продукте. Используйте его, когда продукты в пейволе Adapty отличаются от тех, что указаны в веб-пейволе. ::: ## Открытие веб-пейволов во встроенном браузере \{#open-web-paywalls-in-an-in-app-browser\} По умолчанию веб-пейволы открываются во внешнем браузере. Чтобы обеспечить более плавный пользовательский опыт, можно открывать веб-пейволы во встроенном браузере. Это отображает страницу веб-покупки прямо внутри приложения, позволяя пользователям завершать транзакции без переключения между приложениями. Для этого установите параметр `presentation` в значение `AdaptyWebPresentation.InAppBrowser`: ```kotlin showLineNumbers Adapty.openWebPaywall( activity = activity, product = product, presentation = AdaptyWebPresentation.InAppBrowser, ) { error -> if (error == null) { // the web paywall was opened successfully } else { // handle the error val adaptyError = error } } ``` --- # File: android-troubleshoot-paywall-builder --- --- title: "Устранение неполадок Paywall Builder в Android SDK" description: "Устранение неполадок Paywall Builder в Android SDK" --- Этот гайд поможет вам устранить типичные проблемы при использовании пейволов, созданных в Adapty Paywall Builder, в Android SDK. ## Получение конфигурации пейвола завершается ошибкой \{#getting-a-paywall-configuration-fails\} **Проблема**: Метод `getViewConfiguration` не может получить конфигурацию пейвола. **Причина**: Пейвол не включён для отображения на устройстве в Paywall Builder. **Решение**: Включите переключатель **Show on device** в Paywall Builder. ## Число просмотров пейвола слишком велико \{#the-paywall-view-number-is-too-big\} **Проблема**: счётчик просмотров пейвола показывает вдвое больше ожидаемого значения. **Причина**: возможно, вы вызываете `logShowFlow` (Android SDK v4+) / `logShowPaywall` в своём коде, что дублирует счётчик просмотров, если вы используете Paywall Builder или Flow Builder. Для флоу и пейволов, созданных с помощью этих инструментов, аналитика отслеживается автоматически, поэтому использовать данный метод не нужно. **Решение**: убедитесь, что вы не вызываете `logShowFlow` (Android SDK v4+) / `logShowPaywall` в своём коде, если используете Paywall Builder или Flow Builder. ## Другие проблемы \{#other-issues\} **Проблема**: У вас возникают другие проблемы, связанные с Paywall Builder, не описанные выше. **Решение**: При необходимости обновите SDK до последней версии, следуя [гайдам по миграции](android-sdk-migration-guides). Многие проблемы уже исправлены в новых версиях SDK. --- # File: android-quickstart-manual --- --- title: "Включение покупок в кастомном пейволе в Android SDK" description: "Интегрируйте Adapty SDK в свои кастомные Android пейволы для поддержки встроенных покупок." --- В этом гайде описано, как интегрировать Adapty в кастомные пейволы. Вы сохраняете полный контроль над реализацией пейвола, а Adapty SDK берёт на себя получение продуктов, обработку новых покупок и восстановление предыдущих. :::important **Этот гайд предназначен для разработчиков, которые реализуют кастомные пейволы.** Если вы хотите подключить покупки самым простым способом, используйте [Adapty Flow Builder](android-quickstart-paywalls). С Flow Builder вы создаёте флоу в визуальном редакторе без кода, Adapty берёт на себя всю логику покупок, а тестировать разные дизайны можно без повторной публикации приложения. ::: ## Перед началом работы \{#before-you-start\} ### Настройка продуктов \{#set-up-products\} Чтобы включить встроенные покупки, нужно разобраться с тремя ключевыми понятиями: - [**Продукты**](product) – всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ) - [**Пейволы**](paywalls) – конфигурации, определяющие, какие продукты предлагать. В Adapty пейволы — единственный способ получить продукты, но такой подход позволяет менять продукты, цены и офферы без обновления кода приложения. - [**Плейсменты**](placements) – где и когда показывать пейволы в приложении (например, `main`, `onboarding`, `settings`). Вы настраиваете пейволы для плейсментов в дашборде, а затем запрашиваете их по идентификатору плейсмента в коде. Это упрощает проведение A/B-тестов и показ разных пейволов разным пользователям. Убедитесь, что вы понимаете эти концепции, даже если используете собственный пейвол. По сути, это просто способ управлять продуктами, которые вы продаёте в приложении. Чтобы реализовать собственный пейвол, вам нужно создать **пейвол** и добавить его в **плейсмент**. Это позволит вам получать ваши продукты. Чтобы разобраться, что нужно сделать в дашборде, следуйте [quickstart-гайду](quickstart). ### Управление пользователями \{#manage-users\} Вы можете работать как с серверной аутентификацией, так и без неё. Adapty SDK по-разному обрабатывает анонимных и идентифицированных пользователей. Прочитайте [гайд по идентификации](android-quickstart-identify), чтобы разобраться в деталях и корректно работать с пользователями. ## Шаг 1. Получите продукты \{#step-1-get-products\} Чтобы получить продукты для вашего кастомного пейвола, необходимо: 1. Получить объект `flow`, передав ID [плейсмента](placements) в метод `getFlow`. 2. Получить массив продуктов для этого флоу с помощью метода `getPaywallProducts`. ```kotlin showLineNumbers fun loadPaywall() { Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value Adapty.getPaywallProducts(flow) { productResult -> when (productResult) { is AdaptyResult.Success -> { val products = productResult.value // Use products to build your custom paywall UI } is AdaptyResult.Error -> { val error = productResult.error // Handle the error } } } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } } ``` ```java showLineNumbers public void loadPaywall() { Adapty.getFlow("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); Adapty.getPaywallProducts(flow, productResult -> { if (productResult instanceof AdaptyResult.Success) { List products = ((AdaptyResult.Success>) productResult).getValue(); // Use products to build your custom paywall UI } else if (productResult instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) productResult).getError(); // Handle the error } }); } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle the error } }); } ``` ## Шаг 2. Обработка покупок \{#step-2-accept-purchases\} Когда пользователь нажимает на продукт в вашем кастомном пейволе, вызовите метод `makePurchase` с выбранным продуктом. Он обработает процесс покупки и вернёт обновлённый профиль. ```kotlin showLineNumbers fun purchaseProduct(activity: Activity, product: AdaptyPaywallProduct) { Adapty.makePurchase(activity, product) { result -> when (result) { is AdaptyResult.Success -> { when (val purchaseResult = result.value) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile // Purchase successful, profile updated } is AdaptyPurchaseResult.UserCanceled -> { // User canceled the purchase } is AdaptyPurchaseResult.Pending -> { // Purchase is pending (e.g., user will pay offline with cash) } } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } } ``` ```java showLineNumbers public void purchaseProduct(Activity activity, AdaptyPaywallProduct product) { Adapty.makePurchase(activity, product, null, result -> { if (result instanceof AdaptyResult.Success) { AdaptyPurchaseResult purchaseResult = ((AdaptyResult.Success) result).getValue(); if (purchaseResult instanceof AdaptyPurchaseResult.Success) { AdaptyProfile profile = ((AdaptyPurchaseResult.Success) purchaseResult).getProfile(); // Покупка успешна, профиль обновлён } else if (purchaseResult instanceof AdaptyPurchaseResult.UserCanceled) { // Пользователь отменил покупку } else if (purchaseResult instanceof AdaptyPurchaseResult.Pending) { // Покупка ожидает обработки (например, пользователь оплатит наличными офлайн) } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Обработать ошибку } }); } ``` ## Шаг 3. Восстановление покупок \{#step-3-restore-purchases\} Google Play и другие сторы требуют, чтобы все приложения с подписками предоставляли пользователям возможность восстановить покупки. Вызывайте метод `restorePurchases`, когда пользователь нажимает кнопку восстановления. Это синхронизирует историю покупок с Adapty и вернёт обновлённый профиль. ```kotlin showLineNumbers fun restorePurchases() { Adapty.restorePurchases { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // Restore successful, profile updated } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } } ``` ```java showLineNumbers public void restorePurchases() { Adapty.restorePurchases(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); // Restore successful, profile updated } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle the error } }); } ``` ## Следующие шаги \{#next-steps\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Ваш пейвол готов к отображению в приложении. [Протестируйте покупки в Google Play Store](testing-on-android), чтобы убедиться, что тестовая покупка с пейвола проходит успешно. Чтобы увидеть, как это работает в готовом к продакшену приложении, изучите [ProductListFragment.kt](https://github.com/adaptyteam/AdaptySDK-Android/blob/master/app/src/main/java/com/adapty/example/ProductListFragment.kt) в нашем примере приложения — там показана обработка покупок с полноценной обработкой ошибок, обратной связью с интерфейсом и управлением подписками. Затем [проверьте, завершил ли пользователь покупку](android-check-subscription-status), чтобы определить, показывать ли пейвол или предоставлять доступ к платным функциям. --- # File: fetch-paywalls-and-products-android --- --- title: "Получение пейволов и продуктов для пейволов с Remote Config в Android SDK" description: "Получайте пейволы и продукты в Android SDK Adapty для улучшения монетизации пользователей." --- Прежде чем отображать Remote Config и кастомные пейволы, нужно получить информацию о них. Обратите внимание, что этот раздел посвящён Remote Config и кастомным пейволам. Если вас интересует получение флоу или пейволов, настроенных в **Flow Builder** или **Paywall Builder**, обратитесь к разделу [Получение флоу и пейволов](android-get-pb-paywalls). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. :::
Прежде чем начать получать флоу и продукты в мобильном приложении (нажмите, чтобы развернуть) 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте флоу или пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них флоу или пейвол](create-placement) в дашборде Adapty. 4. [Установите SDK Adapty](sdk-installation-android) в мобильном приложении.
## Получение информации о флоу \{#fetch-flow-information\} В Adapty [продукт](product) представляет собой комбинацию продуктов из App Store и Google Play. Эти кросс-платформенные продукты интегрируются во флоу и пейволы, позволяя отображать их в конкретных плейсментах мобильного приложения. Чтобы отобразить продукты, необходимо получить `AdaptyFlow` из одного из ваших [плейсментов](placements) с помощью метода `getFlow`. :::important **Не захардкодивайте идентификаторы продуктов.** Единственный ID, который можно захардкодить — это ID плейсмента. Флоу настраиваются удалённо, поэтому количество продуктов и доступных предложений может меняться в любой момент. Ваше приложение должно обрабатывать эти изменения динамически — если флоу сегодня возвращает два продукта, а завтра три, отображайте все без изменения кода. ::: ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value // the requested flow } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); // the requested flow } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. || **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

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

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

Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.

Adapty SDK хранит флоу и пейволы в двух слоях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](android-use-fallback-paywalls). Также используется CDN для более быстрой загрузки флоу и пейволов, а также отдельный резервный сервер на случай недоступности CDN.

| | **loadTimeout** | по умолчанию: 5 сек |

Это значение ограничивает тайм-аут метода. При его истечении будут возвращены кешированные данные или локальный фолбэк.

Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения `loadTimeout`, поскольку операция может включать несколько запросов.

| Не задавайте product ID жёстко в коде! Так как флоу настраиваются удалённо, набор доступных продуктов, их количество и специальные предложения (например, бесплатные пробные периоды) могут меняться со временем. Убедитесь, что ваш код учитывает эти сценарии. Например, если изначально вы получаете 2 продукта, приложение должно отображать эти 2 продукта. Но если позже вы получите 3 продукта, приложение должно показать все 3 без каких-либо изменений в коде. Единственное, что нужно задать жёстко, — это ID плейсмента. Параметры ответа: | Параметр | Описание | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | Объект `AdaptyFlow`, содержащий плейсмент, идентификаторы (`id`, `variationId`), название, массив `remoteConfigs` (по одной записи на каждую настроенную локаль) и флаг `hasViewConfiguration`. Чтобы получить продукты для флоу, вызовите `getPaywallProducts(flow)`. | :::note В версии v4 параметр `locale` переехал из `getFlow` в `getFlowConfiguration` (используется только при отображении через AdaptyUI). Для кастомных пейволов все доступные локали возвращаются вместе в `flow.remoteConfigs` — выберите ту, которая соответствует языку устройства пользователя или настройке вашего приложения. ::: ## Получение продуктов \{#fetch-products\} Получив флоу, вы можете запросить массив продуктов, соответствующих ему: ```kotlin showLineNumbers Adapty.getPaywallProducts(flow) { result -> when (result) { is AdaptyResult.Success -> { val products = result.value // the requested products } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywallProducts(flow, result -> { if (result instanceof AdaptyResult.Success) { List products = ((AdaptyResult.Success>) result).getValue(); // запрошенные продукты } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // обработка ошибки } }); ``` Параметры ответа: | Параметр | Описание | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Список объектов [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) с: идентификатором продукта, названием продукта, ценой, валютой, длительностью подписки и рядом других свойств. | При реализации собственного дизайна флоу вам, скорее всего, потребуется доступ к этим свойствам объекта [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/). Ниже приведены наиболее часто используемые свойства; полный список доступных свойств см. в документации по ссылке. | Свойство | Описание | |-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Чтобы отобразить название продукта, используйте `product.localizedTitle`. Обратите внимание, что локализация основана на стране стора, выбранной пользователем, а не на локали самого устройства. | | **Price** | Чтобы отобразить цену в локализованном формате, используйте `product.price.localizedString`. Локализация основана на локали устройства. Также можно получить цену как числовое значение через `product.price.amount` — оно будет указано в локальной валюте. Чтобы получить соответствующий символ валюты, используйте `product.price.currencySymbol`. | | **Subscription Period** | Чтобы отобразить период подписки (например, неделя, месяц, год и т. д.), используйте `product.subscriptionDetails?.localizedSubscriptionPeriod`. Локализация основана на локали устройства. Для программного получения периода подписки используйте `product.subscriptionDetails?.subscriptionPeriod`. Из этого объекта можно обратиться к перечислению `unit`, чтобы получить единицу длительности (`DAY`, `WEEK`, `MONTH`, `YEAR` или `UNKNOWN`). Значение `numberOfUnits` возвращает количество единиц периода. Например, для квартальной подписки в свойстве `unit` будет `MONTH`, а в `numberOfUnits` — `3`. | | **Introductory Offer** | Чтобы отобразить бейдж или другой индикатор наличия introductory offer у подписки, проверьте свойство `product.subscriptionDetails?.introductoryOfferPhases`. Это список, который может содержать до двух фаз скидки: фазу бесплатного пробного периода и фазу вводной цены. В каждом объекте фазы доступны следующие полезные свойства:
• `paymentMode`: перечисление со значениями `FREE_TRIAL`, `PAY_AS_YOU_GO`, `PAY_UPFRONT` и `UNKNOWN`. Бесплатные пробные периоды имеют тип `FREE_TRIAL`.
• `price`: сниженная цена в числовом формате. Для бесплатных пробных периодов здесь будет `0`.
• `localizedNumberOfPeriods`: строка, локализованная с учётом локали устройства, описывающая продолжительность предложения. Например, для трёхдневного пробного периода в этом поле будет `3 days`.
• `subscriptionPeriod`: альтернативный способ получить отдельные детали периода предложения. Работает так же, как описано в предыдущем разделе.
• `localizedSubscriptionPeriod`: отформатированный период подписки скидки для локали пользователя. | ## Ускорьте загрузку флоу с помощью флоу для аудитории по умолчанию \{#speed-up-flow-fetching-with-default-audience-flow\} Как правило, флоу загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и плейсментов, а пользователи работают с медленным интернетом, загрузка флоу может занять больше времени, чем хотелось бы. В таких ситуациях имеет смысл показывать флоу по умолчанию, чтобы обеспечить комфортный пользовательский опыт вместо пустого экрана. Чтобы решить эту задачу, можно воспользоваться методом `getFlowForDefaultAudience`, который загружает флоу указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — загружать флоу с помощью метода `getFlow`, как описано в разделе [Получение информации о флоу](fetch-paywalls-and-products-android#fetch-flow-information) выше. :::warning Почему мы рекомендуем использовать `getFlow` Метод `getFlowForDefaultAudience` имеет ряд существенных ограничений: - **Потенциальные проблемы с обратной совместимостью**: Если нужно показывать разные флоу для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать флоу с поддержкой текущей (устаревшей) версии, либо мириться с тем, что пользователи на текущей (устаревшей) версии могут столкнуться с проблемами при отображении флоу. - **Потеря таргетинга**: Все пользователи будут видеть один и тот же флоу, рассчитанный на аудиторию **All Users**, — то есть вы теряете персонализированный таргетинг (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вас устраивают эти недостатки ради более быстрого получения флоу, используйте метод `getFlowForDefaultAudience`, как описано ниже. В противном случае используйте `getFlow`, описанный [выше](fetch-paywalls-and-products-android#fetch-flow-information). ::: ```kotlin showLineNumbers Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value // the requested flow } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); // запрошенный флоу } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // обработайте ошибку } }); ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указывали при создании плейсмента в дашборде Adapty. || **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.

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

Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при переустановке или вручную.

|
Прежде чем работать с Remote Config и кастомными пейволами, нужно загрузить информацию о них. Обратите внимание, что этот раздел относится к Remote Config и кастомным пейволам. Для получения информации о загрузке пейволов, созданных с помощью Paywall Builder, обратитесь к статье [Загрузка пейволов Paywall Builder и их конфигурации](android-get-pb-paywalls). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. :::
Прежде чем начать загрузку пейволов и продуктов в мобильном приложении (нажмите, чтобы развернуть) 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте пейвол и добавьте продукты в него](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте пейвол в плейсмент](create-placement) в дашборде Adapty. 4. [Установите Adapty SDK](sdk-installation-android) в своём мобильном приложении.
## Получение информации о пейволе \{#fetch-paywall-information\} В Adapty [продукт](product) — это сочетание продуктов из App Store и Google Play. Такие кросс-платформенные продукты интегрируются в пейволы, позволяя демонстрировать их в нужных плейсментах мобильного приложения. Чтобы отобразить продукты, нужно получить [пейвол](paywalls) из одного из ваших [плейсментов](placements) с помощью метода `getPaywall`. :::important **Не прописывайте ID продуктов в коде.** Единственный ID, который стоит хардкодить — это ID плейсмента. Пейволы настраиваются удалённо, поэтому количество продуктов и доступных офферов может меняться в любой момент. Ваше приложение должно обрабатывать эти изменения динамически — если сегодня пейвол возвращает два продукта, а завтра три, все они должны отображаться без изменений в коде. ::: ```kotlin showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // the requested paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID", "en", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); // the requested paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** |

необязательный

по умолчанию: `en`

|

Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается в виде языкового кода, состоящего из одного или нескольких подтегов, разделённых символом минус (**-**). Первый подтег обозначает язык, второй — регион.

Пример: `en` — английский, `pt-br` — бразильский португальский.

Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](android-localizations-and-locale-codes).

| | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.

Однако если ваши пользователи часто сталкиваются с нестабильным интернетом, рассмотрите использование `.returnCacheDataElseLoad` — оно возвращает кешированные данные, если они есть. В этом случае пользователи могут не получить самые свежие данные, зато загрузка будет быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.

Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.

Adapty SDK хранит пейволы в двух слоях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](android-use-fallback-paywalls). Для ускорения загрузки пейволов также используется CDN, а в случае его недоступности — отдельный резервный сервер. Такая система гарантирует, что вы всегда получаете актуальную версию пейволов, обеспечивая надёжность даже при слабом интернет-соединении.

| | **loadTimeout** | по умолчанию: 5 сек |

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

Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения, указанного в `loadTimeout`, поскольку операция может включать несколько запросов под капотом.

| Не прописывайте идентификаторы продуктов в коде! Поскольку пейволы настраиваются удалённо, набор доступных продуктов, их количество и специальные предложения (например, бесплатные пробные периоды) могут меняться. Убедитесь, что ваш код учитывает эти сценарии. Например, если изначально вы получаете 2 продукта, приложение должно отображать 2 продукта. Но если позже вы получите 3 продукта, приложение должно показать все 3 — без каких-либо изменений в коде. Единственное, что нужно прописать в коде, — это идентификатор плейсмента. Параметры ответа: | Параметр | Описание | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Объект [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/) содержит: список идентификаторов продуктов, идентификатор пейвола, Remote Config и ряд других свойств. | ## Получение продуктов \{#fetch-products\} Получив пейвол, вы можете запросить список продуктов, связанных с ним: ```kotlin showLineNumbers Adapty.getPaywallProducts(paywall) { result -> when (result) { is AdaptyResult.Success -> { val products = result.value // the requested products } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywallProducts(paywall, result -> { if (result instanceof AdaptyResult.Success) { List products = ((AdaptyResult.Success>) result).getValue(); // the requested products } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` Параметры ответа: | Параметр | Описание | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Список объектов [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/) с идентификатором продукта, его названием, ценой, валютой, длительностью подписки и рядом других свойств. | При реализации собственного дизайна пейвола вам, скорее всего, понадобятся свойства объекта [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/). Ниже приведены наиболее часто используемые свойства, однако полный список доступных свойств можно найти в документации по ссылке. | Свойство | Описание | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Для отображения названия продукта используйте `product.localizedTitle`. Обратите внимание: локализация основана на стране стора, выбранной пользователем, а не на локали устройства. | | **Price** | Для отображения локализованной цены используйте `product.price.localizedString`. Локализация основана на локали устройства. Также можно получить цену в виде числа через `product.price.amount` — значение будет указано в местной валюте. Для получения символа валюты используйте `product.price.currencySymbol`. | | **Subscription Period** | Для отображения периода (например, неделя, месяц, год и т. д.) используйте `product.subscriptionDetails?.localizedSubscriptionPeriod`. Локализация основана на локали устройства. Для программного получения периода подписки используйте `product.subscriptionDetails?.subscriptionPeriod`. Через это свойство можно обратиться к enum `unit`, чтобы узнать единицу времени (DAY, WEEK, MONTH, YEAR или UNKNOWN). Значение `numberOfUnits` содержит количество единиц периода. Например, для квартальной подписки в свойстве `unit` будет `MONTH`, а в `numberOfUnits` — `3`. | | **Introductory Offer** | Чтобы отобразить бейдж или другой индикатор наличия introductory offer в подписке, используйте свойство `product.subscriptionDetails?.introductoryOfferPhases`. Это список, который может содержать до двух фаз скидки: фазу бесплатного пробного периода и фазу вводной цены. Каждый объект фазы содержит следующие полезные свойства:
• `paymentMode`: enum со значениями `FREE_TRIAL`, `PAY_AS_YOU_GO`, `PAY_UPFRONT` и `UNKNOWN`. Бесплатный пробный период соответствует типу `FREE_TRIAL`.
• `price`: цена со скидкой в виде числа. Для бесплатного пробного периода здесь будет `0`.
• `localizedNumberOfPeriods`: строка, локализованная по локали устройства, описывающая продолжительность предложения. Например, для трёхдневного пробного периода в этом поле будет `3 days`.
• `subscriptionPeriod`: альтернативно, можно получить отдельные детали периода предложения с помощью этого свойства. Оно работает для предложений так же, как описано в предыдущем разделе.
• `localizedSubscriptionPeriod`: отформатированный период подписки скидки в локали пользователя. | ## Ускорение загрузки пейвола с помощью пейвола аудитории по умолчанию \{#speed-up-paywall-fetching-with-default-audience-paywall\} Как правило, пейволы загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и пейволов, а пользователи работают с медленным интернетом, загрузка пейвола может занять больше времени, чем хотелось бы. В таких случаях стоит показывать пейвол по умолчанию — это обеспечит комфортный пользовательский опыт вместо пустого экрана. Чтобы решить эту проблему, можно использовать метод `getPaywallForDefaultAudience`, который получает пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать пейвол через метод `getPaywall`, как описано в разделе [Получение информации о пейволе](fetch-paywalls-and-products-android#fetch-paywall-information) выше. :::warning Почему мы рекомендуем использовать `getPaywall` Метод `getPaywallForDefaultAudience` имеет ряд существенных недостатков: - **Возможные проблемы с обратной совместимостью**: если вам нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать пейволы с поддержкой текущей (устаревшей) версии, либо смириться с тем, что пользователи на этой версии могут столкнуться с проблемами отображения пейволов. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, созданный для аудитории **All Users**, — а значит, вы теряете персонализированный таргетинг (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вас устраивают эти ограничения ради ускоренного получения пейвола, используйте метод `getPaywallForDefaultAudience`, как описано ниже. В противном случае используйте `getPaywall`, описанный [выше](fetch-paywalls-and-products-android#fetch-paywall-information). ::: ```kotlin showLineNumbers Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", locale = "en") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // the requested paywall } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); // the requested paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` :::note Метод `getPaywallForDefaultAudience` доступен начиная с Android SDK версии 2.11.3. ::: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указываете при создании плейсмента в дашборде Adapty. | | **locale** |

опциональный

по умолчанию: `en`

|

Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается языковой код, состоящий из одного или нескольких подтегов, разделённых символом минус (**-**). Первый подтег — язык, второй — регион.

Пример: `en` — английский, `pt-br` — бразильский португальский.

Подробнее о кодах локалей и рекомендациях по их использованию — в разделе [Локализации и коды локалей](android-localizations-and-locale-codes).

| | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.

Однако если у ваших пользователей нестабильный интернет, рассмотрите использование `.returnCacheDataElseLoad` для возврата кешированных данных при их наличии. В этом случае данные могут быть не самыми свежими, зато загрузка будет быстрее независимо от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы сократить количество сетевых запросов.

Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.

|
--- # File: present-remote-config-paywalls-android --- --- title: "Отображение пейвола на основе Remote Config в Android SDK" description: "Узнайте, как показывать пейволы Remote Config в Adapty Android SDK для персонализации пользовательского опыта." --- Если вы настроили пейвол с помощью Remote Config, для его отображения пользователям потребуется реализовать рендеринг в коде мобильного приложения. Поскольку Remote Config предоставляет гибкость под ваши нужды, вы сами контролируете, что включить и как будет выглядеть пейвол. Adapty предоставляет метод для получения Remote Config, что даёт вам полную свободу в отображении кастомного пейвола. ## Получение Remote Config флоу и его отображение \{#get-flow-remote-config-and-present-it\} В v4 флоу содержит одну запись `AdaptyRemoteConfig` на каждую настроенную локаль в массиве `remoteConfigs`. Выберите локаль, соответствующую предпочтениям пользователя, и считайте нужные значения. ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val flow = result.value val config = flow.remoteConfigs.firstOrNull { it.locale == "en" } ?: flow.remoteConfigs.firstOrNull() val headerText = config?.dataMap?.get("header_text") as? String } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyFlow flow = ((AdaptyResult.Success) result).getValue(); AdaptyRemoteConfig config = null; for (AdaptyRemoteConfig remoteConfig : flow.getRemoteConfigs()) { if ("en".equals(remoteConfig.getLocale())) { config = remoteConfig; break; } } if (config == null && !flow.getRemoteConfigs().isEmpty()) { config = flow.getRemoteConfigs().get(0); } if (config != null && config.getDataMap().get("header_text") instanceof String) { String headerText = (String) config.getDataMap().get("header_text"); } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` На этом этапе, получив все необходимые значения, можно переходить к отрисовке и сборке визуально привлекательной страницы. Убедитесь, что дизайн адаптируется под различные размеры экранов и ориентации мобильных телефонов, обеспечивая удобный и понятный интерфейс на любых устройствах. :::warning Обязательно [зафиксируйте событие просмотра пейвола](present-remote-config-paywalls-android#track-paywall-view-events), как описано ниже, — это позволит аналитике Adapty собирать данные для воронок и A/B-тестов. ::: После того как пейвол отображён, можно переходить к настройке процесса покупки. Когда пользователь совершает покупку, просто вызовите `.makePurchase()` с продуктом из вашего флоу. Подробнее о методе `.makePurchase()` читайте в разделе [Совершение покупок](android-making-purchases). Рекомендуем [создать резервный пейвол](android-use-fallback-paywalls). Он будет отображаться пользователю при отсутствии интернет-соединения или кеша, обеспечивая бесперебойную работу в таких ситуациях. ## Отслеживание событий просмотра пейвола \{#track-paywall-view-events\} Adapty помогает измерять эффективность ваших флоу и пейволов. Данные о покупках мы собираем автоматически, однако события просмотра нужно логировать самостоятельно — только вы знаете, когда пользователь видит флоу. Чтобы залогировать событие просмотра, вызовите `.logShowFlow(flow)` — это отразится в ваших метриках в воронках и A/B-тестах. :::important Вызывать `.logShowFlow(flow)` не нужно, если вы отображаете флоу или пейволы, созданные в [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder). В этих случаях Adapty отслеживает просмотры автоматически. ::: ```kotlin showLineNumbers Adapty.logShowFlow(flow) ``` Параметры запроса: | Параметр | Наличие | Описание | | :-------- | :------- |:------------------------------------------------------------------------------------------------------| | **flow** | обязательный | Объект `AdaptyFlow`, полученный через `Adapty.getFlow`. | Если вы настроили пейвол с помощью Remote Config, вам нужно реализовать его отображение в коде мобильного приложения. Поскольку Remote Config предоставляет гибкость под ваши задачи, вы сами решаете, что включить и как будет выглядеть пейвол. Мы предоставляем метод для получения Remote Config — вы полностью контролируете, как отобразить свой пейвол. ## Получение Remote Config пейвола и его отображение \{#get-paywall-remote-config-and-present-it\} Чтобы получить Remote Config пейвола, обратитесь к свойству `remoteConfig` и извлеките нужные значения. ```kotlin showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value val headerText = paywall.remoteConfig?.dataMap?.get("header_text") as? String } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success) result).getValue(); AdaptyPaywall.RemoteConfig remoteConfig = paywall.getRemoteConfig(); if (remoteConfig != null) { if (remoteConfig.getDataMap().get("header_text") instanceof String) { String headerText = (String) remoteConfig.getDataMap().get("header_text"); } } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // обработайте ошибку } }); ``` На этом этапе, получив все необходимые значения, можно приступить к рендерингу и сборке визуально привлекательного экрана. Убедитесь, что дизайн адаптирован под различные экраны и ориентации мобильных телефонов, обеспечивая удобный и единообразный пользовательский опыт на разных устройствах. :::warning Обязательно [записывайте событие просмотра пейвола](present-remote-config-paywalls-android#track-paywall-view-events), как описано ниже, чтобы аналитика Adapty могла собирать данные для воронок и A/B-тестов. ::: После отображения пейвола настройте флоу покупки. Когда пользователь совершает покупку, просто вызовите `.makePurchase()` с продуктом из вашего пейвола. Подробнее о методе `.makePurchase()` читайте в разделе [Совершение покупок](android-making-purchases). Рекомендуем [создать резервный пейвол](android-use-fallback-paywalls). Он будет отображаться пользователю при отсутствии интернета или кеша, обеспечивая бесперебойную работу приложения в таких ситуациях. ## Отслеживайте события просмотра пейвола \{#track-paywall-view-events\} Adapty помогает вам измерять эффективность ваших пейволов. Данные о покупках мы собираем автоматически, однако логирование просмотров пейволов требует вашего участия — только вы знаете, когда пользователь видит пейвол. Чтобы зафиксировать событие просмотра пейвола, просто вызовите `.logShowPaywall(paywall)` — это отразится в метриках вашего пейвола в воронках и A/B-тестах. :::important Вызов `.logShowPaywall(paywall)` не нужен, если вы отображаете пейволы, созданные в [Paywall Builder](adapty-paywall-builder). ::: ```kotlin showLineNumbers Adapty.logShowPaywall(paywall) ``` Параметры запроса: | Параметр | Наличие | Описание | | :---------- | :------- |:------------------------------------------------------------------------------------------------------------| | **paywall** | обязательный | Объект [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | --- # File: android-making-purchases --- --- title: "Совершение покупок в мобильном приложении в Android SDK" description: "Руководство по обработке встроенных покупок и подписок с помощью Adapty." --- Отображение пейволов в мобильном приложении — это ключевой шаг для предоставления пользователям доступа к премиум-контенту или сервисам. Однако простого отображения пейволов достаточно для поддержки покупок только в том случае, если вы используете [Paywall Builder](adapty-paywall-builder) для их кастомизации. Если вы не используете Paywall Builder, для совершения покупки и разблокировки нужного контента необходимо вызывать отдельный метод `.makePurchase()`. Этот метод служит точкой входа, через которую пользователи взаимодействуют с пейволами и совершают нужные им транзакции. Если на вашем пейволе есть активный promotional offer для продукта, который пользователь собирается купить, Adapty автоматически применит его в момент покупки. :::warning Имейте в виду, что introductory offer будет применён автоматически только если вы используете пейволы, настроенные с помощью Paywall Builder. В остальных случаях вам нужно [проверить право пользователя на introductory offer на iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios). Пропуск этого шага может привести к отклонению приложения при релизе. Кроме того, пользователям, имеющим право на introductory offer, может быть выставлена полная цена. ::: Убедитесь, что вы [выполнили начальную настройку](quickstart), не пропустив ни одного шага. Без неё мы не сможем валидировать покупки. ## Совершить покупку \{#make-purchase\} :::note **Используете [Paywall Builder](adapty-paywall-builder)?** Покупки обрабатываются автоматически — этот шаг можно пропустить. **Нужна пошаговая инструкция?** Смотрите [гайд по быстрому старту](android-implement-paywalls-manually) — там полная реализация с контекстом. ::: ```kotlin showLineNumbers Adapty.makePurchase(activity, product, null) { result -> when (result) { is AdaptyResult.Success -> { when (val purchaseResult = result.value) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // Grant access to the paid features } } is AdaptyPurchaseResult.UserCanceled -> { // Handle the case where the user canceled the purchase } is AdaptyPurchaseResult.Pending -> { // Handle deferred purchases (e.g., the user will pay offline with cash) } } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } ``` ```java showLineNumbers Adapty.makePurchase(activity, product, null, result -> { if (result instanceof AdaptyResult.Success) { AdaptyPurchaseResult purchaseResult = ((AdaptyResult.Success) result).getValue(); if (purchaseResult instanceof AdaptyPurchaseResult.Success) { AdaptyProfile profile = ((AdaptyPurchaseResult.Success) purchaseResult).getProfile(); AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL"); if (premium != null && premium.isActive()) { // Grant access to the paid features } } else if (purchaseResult instanceof AdaptyPurchaseResult.UserCanceled) { // Handle the case where the user canceled the purchase } else if (purchaseResult instanceof AdaptyPurchaseResult.Pending) { // Handle deferred purchases (e.g., the user will pay offline with cash) } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle the error } }); ``` Параметры запроса: | Параметр | Наличие | Описание | | :---------- | :------- | :-------------------------------------------------------------------------------------------------- | | **Product** | обязательный | Объект [`AdaptyPaywallProduct`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall-product/), полученный из пейвола. | Параметры ответа: | Параметр | Описание | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** |

В случае успешного запроса ответ содержит этот объект. Объект [AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/) предоставляет исчерпывающую информацию об уровнях доступа, подписках и разовых покупках пользователя в приложении.

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

| :::warning **Примечание:** если вы используете Apple StoreKit версии ниже 2.0 и Adapty SDK версии ниже 2.9.0, вам необходимо указать [общий секрет Apple App Store](app-store-connection-configuration#step-5-enter-app-store-shared-secret) вместо этого. Данный метод в настоящее время устарел и не рекомендуется Apple. ::: ## Смена подписки при покупке \{#change-subscription-when-making-a-purchase\} Когда пользователь выбирает новую подписку вместо продления текущей, поведение зависит от стора. В Google Play подписка не обновляется автоматически — вам нужно обработать переключение в коде мобильного приложения, как описано ниже. Чтобы заменить подписку другой на Android, вызовите метод `.makePurchase()` с дополнительным параметром: ```kotlin showLineNumbers Adapty.makePurchase( activity, product, AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(subscriptionUpdateParams) .build() ) { result -> when (result) { is AdaptyResult.Success -> { when (val purchaseResult = result.value) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile // successful cross-grade } is AdaptyPurchaseResult.UserCanceled -> { // user canceled the purchase flow } is AdaptyPurchaseResult.Pending -> { // the purchase has not been finished yet, e.g. user will pay offline by cash } } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } ``` Дополнительный параметр запроса: | Параметр | Наличие | Описание | | :--------------------------- | :------- | :----------------------------------------------------------- | | **subscriptionUpdateParams** | required | объект [`AdaptySubscriptionUpdateParameters`](https://android.adapty.io/adapty/com.adapty.models/-adapty-subscription-update-parameters/). | ```java showLineNumbers Adapty.makePurchase( activity, product, new AdaptyPurchaseParameters.Builder() .withSubscriptionUpdateParams(subscriptionUpdateParams) .build(), result -> { if (result instanceof AdaptyResult.Success) { AdaptyPurchaseResult purchaseResult = ((AdaptyResult.Success) result).getValue(); if (purchaseResult instanceof AdaptyPurchaseResult.Success) { AdaptyProfile profile = ((AdaptyPurchaseResult.Success) purchaseResult).getProfile(); // successful cross-grade } else if (purchaseResult instanceof AdaptyPurchaseResult.UserCanceled) { // user canceled the purchase flow } else if (purchaseResult instanceof AdaptyPurchaseResult.Pending) { // the purchase has not been finished yet, e.g. user will pay offline by cash } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle the error } }); ``` Дополнительный параметр запроса: | Параметр | Наличие | Описание | | :--------------------------- | :------- | :----------------------------------------------------------- | | **subscriptionUpdateParams** | обязательный | объект [`AdaptySubscriptionUpdateParameters`](https://android.adapty.io/adapty/com.adapty.models/-adapty-subscription-update-parameters/). | Подробнее о подписках и режимах замены можно прочитать в документации для разработчиков Google: - [О режимах замены](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Рекомендации Google по режимам замены](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - Режим замены [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE()). Примечание: этот метод доступен только для повышения уровня подписки. Понижение уровня не поддерживается. - Режим замены [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED()). Примечание: реальное изменение подписки произойдёт только по окончании текущего расчётного периода. ### Управление предоплаченными планами \{#manage-prepaid-plans\} Если пользователи вашего приложения могут приобретать [предоплаченные планы](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (например, купить невозобновляемую подписку на несколько месяцев), вы можете включить [отложенные транзакции](https://developer.android.com/google/play/billing/subscriptions#pending) для предоплаченных планов. ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withEnablePendingPrepaidPlans(true) .build() ``` ```java showLineNumbers new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withEnablePendingPrepaidPlans(true) .build(); ``` --- # File: android-restore-purchase --- --- title: "Восстановление покупок в мобильном приложении с Android SDK" description: "Узнайте, как восстановить покупки в Adapty, чтобы обеспечить бесперебойный пользовательский опыт." --- Восстановление покупок — это функция, которая позволяет пользователям снова получить доступ к ранее приобретённому контенту (подпискам или встроенным покупкам) без повторного списания средств. Особенно она полезна тем, кто удалил и переустановил приложение или перешёл на новое устройство и хочет получить доступ к уже оплаченному контенту. :::note В пейволах, созданных с помощью [Paywall Builder](adapty-paywall-builder), покупки восстанавливаются автоматически — никакого дополнительного кода не требуется. Если вы используете именно его, этот шаг можно пропустить. ::: Чтобы восстановить покупку без использования [Paywall Builder](adapty-paywall-builder) для кастомизации пейвола, вызовите метод `.restorePurchases()`: ```kotlin showLineNumbers Adapty.restorePurchases { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // successful access restore } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.restorePurchases(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); if (profile != null) { AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("YOUR_ACCESS_LEVEL"); if (premium != null && premium.isActive()) { // successful access restore } } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` Параметры ответа: | Параметр | Описание | |---------|-----------| | **Profile** |

Объект [`AdaptyProfile`](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/). Содержит информацию об уровнях доступа, подписках и разовых покупках.

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

| :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: --- # File: implement-observer-mode-android --- --- title: "Реализация режима Observer в Android SDK" description: "Реализуйте режим Observer в Adapty для отслеживания событий подписки пользователей в Android SDK." --- Если у вас уже есть собственная инфраструктура для покупок и вы не готовы полностью переходить на Adapty, вы можете воспользоваться [режимом Observer](observer-vs-full-mode). В базовом варианте режим Observer обеспечивает расширенную аналитику и бесшовную интеграцию с системами атрибуции и аналитики. Если это вам подходит, нужно сделать всего два шага: 1. Включить его при настройке Adapty SDK, установив параметр `observerMode` в `true`. Следуйте инструкциям по настройке для [Android](sdk-installation-android#activate-adapty-module-of-adapty-sdk). 2. [Передать транзакции](report-transactions-observer-mode-android) из вашей существующей инфраструктуры покупок в Adapty. ## Настройка режима Observer \{#observer-mode-setup\} Включите режим Observer, если вы самостоятельно обрабатываете покупки и управляете статусом подписки, а Adapty используете только для отправки событий подписки и аналитики. :::important В режиме Observer Adapty SDK не закрывает транзакции, поэтому убедитесь, что вы обрабатываете их самостоятельно. ::: ```kotlin showLineNumbers class MyApplication : Application() { override fun onCreate() { super.onCreate() Adapty.activate( applicationContext, AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObserverMode(true) //default false .build() ) } ``` ```java showLineNumbers public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Adapty.activate( applicationContext, new AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withObserverMode(true) //default false .build() ); } ``` Параметры: | Параметр | Описание | | --------------------------- | ------------------------------------------------------------ | | observerMode | Булево значение, управляющее [режимом Observer](observer-vs-full-mode). Значение по умолчанию — `false`. | ## Использование пейволов Adapty в режиме Observer \{#using-adapty-paywalls-in-observer-mode\} Если вы хотите также использовать пейволы и A/B-тесты Adapty, это возможно — но потребует дополнительной настройки в режиме Observer. Вот что нужно сделать в дополнение к шагам выше: 1. Отображайте пейволы как обычно для [пейволов на Remote Config](present-remote-config-paywalls-android). Для пейволов на Paywall Builder следуйте специальным инструкциям для [Android](android-present-paywall-builder-paywalls-in-observer-mode). 3. [Свяжите пейволы](report-transactions-observer-mode-android) с транзакциями покупок. --- # File: report-transactions-observer-mode-android --- --- title: "Сообщение о транзакциях в режиме Observer Mode в Android SDK" description: "Сообщайте о транзакциях покупок в Observer Mode Adapty для получения аналитики пользователей и отслеживания дохода в Android SDK." --- В режиме Observer Mode SDK Adapty не может самостоятельно отслеживать покупки, совершённые через вашу существующую систему. Вам нужно вручную сообщать о транзакциях из вашего стора. Важно настроить это **до** публикации приложения, чтобы избежать ошибок в аналитике. Используйте `reportTransaction`, чтобы явно сообщать о каждой транзакции — так Adapty сможет её распознать. :::warning **Не пропускайте передачу транзакций!** Если вы не вызываете `reportTransaction`, Adapty не распознает транзакцию: она не появится в аналитике и не будет отправлена в интеграции. ::: Если вы используете пейволы Adapty, передавайте `variationId` при отчёте о транзакции. Это привязывает покупку к пейволу, который её инициировал, и обеспечивает точную аналитику пейволов. ```kotlin showLineNumbers val transactionInfo = TransactionInfo.fromPurchase(purchase) Adapty.reportTransaction(transactionInfo, variationId) { result -> if (result is AdaptyResult.Success) { // success } } ``` Параметры: | Параметр | Обязательность | Описание | | --------------- | -------------- | --------------------------------------------------------- | | transactionInfo | обязательный | TransactionInfo из покупки, где purchase — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) библиотеки billing. | | variationId | опциональный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | ```java showLineNumbers TransactionInfo transactionInfo = TransactionInfo.fromPurchase(purchase); Adapty.reportTransaction(transactionInfo, variationId, result -> { if (result instanceof AdaptyResult.Success) { // success } }); ``` Параметры: | Параметр | Обязательность | Описание | | --------------- | -------------- | --------------------------------------------------------- | | transactionInfo | обязательный | TransactionInfo из покупки, где purchase — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) библиотеки billing. | | variationId | опциональный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | В режиме Observer Mode SDK Adapty не может самостоятельно отслеживать покупки, совершённые через вашу существующую систему. Вам нужно вручную сообщать о транзакциях из стора или восстанавливать их. Важно настроить это **до** публикации приложения, чтобы избежать ошибок в аналитике. Используйте `restorePurchases`, чтобы передать информацию о транзакции в Adapty. :::warning **Не пропускайте восстановление покупок!** Если вы не вызываете `restorePurchases`, Adapty не распознает транзакцию: она не появится в аналитике и не будет отправлена в интеграции. ::: Если вы используете пейволы Adapty, свяжите транзакцию с пейволом, который привёл к покупке, с помощью метода `setVariationId`. Это обеспечивает корректную атрибуцию покупки к соответствующему пейволу для точной аналитики. Этот шаг нужен только при использовании пейволов Adapty. ```kotlin showLineNumbers Adapty.restorePurchases { result -> if (result is AdaptyResult.Success) { // success } } Adapty.setVariationId(transactionId, variationId) { error -> if (error == null) { // success } } ``` Параметры: | Параметр | Обязательность | Описание | | ------------- | -------------- | --------------------------------------------------------- | | transactionId | обязательный | Строковый идентификатор (`purchase.getOrderId`) покупки, где purchase — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) библиотеки billing. | | variationId | обязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | ```java showLineNumbers Adapty.restorePurchases(result -> { if (result instanceof AdaptyResult.Success) { // success } }); Adapty.setVariationId(transactionId, variationId, error -> { if (error == null) { // success } }); ``` Параметры: | Параметр | Обязательность | Описание | | ------------- | -------------- | --------------------------------------------------------- | | transactionId | обязательный | Строковый идентификатор (`purchase.getOrderId`) покупки, где purchase — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) библиотеки billing. | | variationId | обязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | **Передача транзакций** Используйте `restorePurchases`, чтобы сообщить о транзакции в Adapty в Observer Mode, как описано на странице [Восстановление покупок в мобильном коде](android-restore-purchase). :::warning **Не пропускайте передачу транзакций!** Если вы не вызываете `restorePurchases`, Adapty не распознает транзакцию: она не появится в аналитике и не будет отправлена в интеграции. ::: **Привязка пейволов к транзакциям** SDK Adapty не может определить источник покупок, так как их обрабатываете вы. Поэтому, если вы планируете использовать пейволы и/или A/B-тесты в режиме Observer Mode, вам нужно привязать транзакцию из стора к соответствующему пейволу в коде вашего приложения. Важно сделать это правильно до публикации приложения, иначе это приведёт к ошибкам в аналитике. ```kotlin Adapty.setVariationId(transactionId, variationId) { error -> if (error == null) { // success } } ``` Параметры запроса: | Параметр | Обязательность | Описание | | ------------- | -------------- | --------------------------------------------------------- | | transactionId | обязательный | Строковый идентификатор (purchase.getOrderId) покупки, где purchase — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) библиотеки billing. | | variationId | обязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | ```java Adapty.setVariationId(transactionId, variationId, error -> { if (error == null) { // success } }); ``` | Параметр | Обязательность | Описание | | ------------- | -------------- | --------------------------------------------------------- | | transactionId | обязательный | Строковый идентификатор (purchase.getOrderId) покупки, где purchase — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) библиотеки billing. | | variationId | обязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | --- # File: android-present-paywall-builder-paywalls-in-observer-mode --- --- title: "Показ пейволов Paywall Builder в режиме Observer в Android SDK" description: "Узнайте, как показывать пейволы в режиме Observer с помощью Paywall Builder от Adapty." --- Если вы создали флоу или пейвол с помощью Flow Builder или Paywall Builder, вам не нужно беспокоиться о том, как отрисовать его в коде мобильного приложения для показа пользователю. Такой флоу или пейвол уже содержит и то, что должно отображаться, и то, как именно это должно выглядеть. :::warning Этот раздел относится только к [режиму Observer](observer-vs-full-mode). Если вы не работаете в режиме Observer, обратитесь к разделу [Android — показ флоу и пейволов](android-present-paywalls). :::
Перед началом показа флоу (нажмите, чтобы развернуть) 1. Настройте начальную интеграцию Adapty [с Google Play](initial-android). 2. Установите и настройте SDK. Убедитесь, что параметр `observerMode` имеет значение `true`. Следуйте нашим инструкциям для конкретного фреймворка [для Android](sdk-installation-android). 3. [Создайте продукты](create-product) в дашборде Adapty. 4. [Настройте флоу или пейволы в билдерах](create-paywall) и привяжите к ним продукты. 5. [Создайте плейсменты и назначьте им флоу или пейволы](create-placement) в дашборде Adapty. 6. [Загрузите флоу и их конфигурацию](android-get-pb-paywalls) в коде вашего мобильного приложения.

1. Реализуйте `AdaptyUiObserverModeHandler`. Событие `onPurchaseInitiated` сообщает о том, что пользователь инициировал покупку. В ответ на этот коллбэк вы можете запустить собственный флоу покупки: ```kotlin showLineNumbers val observerModeHandler = AdaptyUiObserverModeHandler { product, flow, flowView, onStartPurchase, onFinishPurchase -> onStartPurchase() yourBillingClient.makePurchase( product, onSuccess = { purchase -> onFinishPurchase() //handle success }, onError = { onFinishPurchase() //handle error }, onCancel = { onFinishPurchase() //handle cancel } ) } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = (product, flow, flowView, onStartPurchase, onFinishPurchase) -> { onStartPurchase.invoke(); yourBillingClient.makePurchase( product, purchase -> { onFinishPurchase.invoke(); //handle success }, error -> { onFinishPurchase.invoke(); //handle error }, () -> { //cancellation onFinishPurchase.invoke(); //handle cancel } ); }; ``` Чтобы обрабатывать восстановление покупок в режиме Observer, переопределите `getRestoreHandler()`. По умолчанию метод возвращает `null`, что означает использование встроенного флоу `Adapty.restorePurchases()`. Чтобы задать собственную реализацию восстановления: ```kotlin showLineNumbers val observerModeHandler = object : AdaptyUiObserverModeHandler { // onPurchaseInitiated implementation (see above) override fun getRestoreHandler() = AdaptyUiObserverModeHandler.RestoreHandler { onStartRestore, onFinishRestore -> onStartRestore() yourBillingClient.restorePurchases( onSuccess = { restoredPurchases -> onFinishRestore() //handle successful restore }, onError = { onFinishRestore() //handle error } ) } } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = new AdaptyUiObserverModeHandler() { // onPurchaseInitiated implementation (see above) @Override public RestoreHandler getRestoreHandler() { return (onStartRestore, onFinishRestore) -> { onStartRestore.invoke(); yourBillingClient.restorePurchases( restoredPurchases -> { onFinishRestore.invoke(); //handle successful restore }, error -> { onFinishRestore.invoke(); //handle error } ); }; } }; ``` Не забудьте вызывать следующие колбэки, чтобы уведомить AdaptyUI о процессе покупки или восстановления. Это необходимо для корректной работы флоу, например для отображения загрузчика: | Callback | Описание | | :----------------- |:------------------------------------------------------------------------------------------------------| | onStartPurchase() | Callback следует вызывать, чтобы уведомить AdaptyUI о начале покупки. | | onFinishPurchase() | Callback следует вызывать, чтобы уведомить AdaptyUI о завершении покупки. | | onStartRestore() | Необязательный. Callback можно вызывать, чтобы уведомить AdaptyUI о начале восстановления покупок. | | onFinishRestore() | Необязательный. Callback можно вызывать, чтобы уведомить AdaptyUI о завершении восстановления покупок.| 2. Чтобы отобразить визуальное флоу на экране устройства, его необходимо сначала настроить. Для этого вызовите метод `AdaptyUI.getFlowView()` или создайте `AdaptyFlowView` напрямую: ```kotlin showLineNumbers val flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler, ) ``` ```kotlin showLineNumbers val flowView = AdaptyFlowView(activity) // or retrieve it from xml ... with(flowView) { showFlow( flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler, ) } ``` ```java showLineNumbers AdaptyFlowView flowView = AdaptyUI.getFlowView( activity, flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler ); ``` ```java showLineNumbers AdaptyFlowView flowView = new AdaptyFlowView(activity); //add to the view hierarchy if needed, or you receive it from xml ... flowView.showFlow(flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler); ``` ```xml showLineNumbers ``` После успешного создания представления вы можете добавить его в иерархию представлений и отобразить. Используйте следующую composable-функцию: ```kotlin showLineNumbers AdaptyFlowScreen( flowConfiguration, products, eventListener, insets, customAssets, tagResolver, timerResolver, observerModeHandler, ) ``` Параметры запроса: | Параметр | Обязательность | Описание | |---------|--------|-----------| | **flowConfiguration** | обязательный | Передайте объект `AdaptyUI.FlowConfiguration`, содержащий визуальные данные флоу. Используйте метод `AdaptyUI.getFlowConfiguration(flow)` для его загрузки. Подробнее см. в разделе [Получение конфигурации представления](android-get-pb-paywalls#fetch-the-view-configuration). | | **products** | необязательный | Передайте массив `AdaptyPaywallProduct`, чтобы оптимизировать время отображения продуктов на экране. Если передать `null`, AdaptyUI автоматически загрузит нужные продукты. | | **eventListener** | необязательный | Передайте `AdaptyFlowEventListener` для отслеживания событий флоу. Рекомендуется использовать `AdaptyFlowDefaultEventListener` для упрощения работы. Подробнее см. в разделе [Обработка событий флоу и пейвола](android-handling-events). | | **insets** | необязательный | Отступы вокруг флоу, которые не дают кликабельным элементам скрыться за системными панелями. По умолчанию: `Unspecified` — Adapty настраивает отступы автоматически. См. [Изменение отступов флоу](android-present-paywalls#change-flow-insets). | | **customAssets** | необязательный | Передайте объект `AdaptyCustomAssets`, чтобы заменить изображения и видео во флоу или пейволе во время выполнения. Подробнее см. в разделе [Настройка ресурсов](android-get-pb-paywalls#customize-assets). | | **tagResolver** | необязательный | Используйте `AdaptyUiTagResolver` для обработки пользовательских тегов в тексте флоу. Этот резолвер принимает тег и возвращает соответствующую строку. Подробнее см. в разделе «Пользовательские теги в Paywall Builder». | | **observerModeHandler** | обязательный для режима Observer | Реализованный вами на предыдущем шаге объект `AdaptyUiObserverModeHandler`. | :::warning Не забудьте [связать пейволы с транзакциями покупок](report-transactions-observer-mode-android). Иначе Adapty не сможет определить источник флоу покупки. :::
Before you start presenting paywalls (Click to Expand) 1. Настройте начальную интеграцию Adapty [с Google Play](initial-android) и [с App Store](initial_ios). 2. Установите и настройте Adapty SDK. Убедитесь, что параметр `observerMode` установлен в `true`. Обратитесь к нашим инструкциям для конкретных фреймворков [для Android](sdk-installation-android). 3. [Создайте продукты](create-product) в дашборде Adapty. 4. [Настройте пейволы, назначьте им продукты](create-paywall) и кастомизируйте их с помощью Paywall Builder в дашборде Adapty. 5. [Создайте плейсменты и назначьте им пейволы](create-placement) в дашборде Adapty. 6. [Получите пейволы Paywall Builder и их конфигурацию](android-get-pb-paywalls) в коде вашего мобильного приложения.

1. Реализуйте `AdaptyUiObserverModeHandler`. Событие `onPurchaseInitiated` уведомит вас о том, что пользователь инициировал покупку. В ответ на этот колбэк вы можете запустить свой кастомный флоу покупки: ```kotlin showLineNumbers val observerModeHandler = AdaptyUiObserverModeHandler { product, paywall, paywallView, onStartPurchase, onFinishPurchase -> onStartPurchase() yourBillingClient.makePurchase( product, onSuccess = { purchase -> onFinishPurchase() //handle success }, onError = { onFinishPurchase() //handle error }, onCancel = { onFinishPurchase() //handle cancel } ) } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = (product, paywall, paywallView, onStartPurchase, onFinishPurchase) -> { onStartPurchase.invoke(); yourBillingClient.makePurchase( product, purchase -> { onFinishPurchase.invoke(); //handle success }, error -> { onFinishPurchase.invoke(); //handle error }, () -> { //cancellation onFinishPurchase.invoke(); //handle cancel } ); }; ``` Чтобы обрабатывать восстановление покупок в режиме Observer, переопределите `getRestoreHandler()`. По умолчанию он возвращает `null`, что означает использование встроенного флоу `Adapty.restorePurchases()`. Чтобы предоставить собственную реализацию восстановления: ```kotlin showLineNumbers val observerModeHandler = object : AdaptyUiObserverModeHandler { // onPurchaseInitiated implementation (see above) override fun getRestoreHandler() = AdaptyUiObserverModeHandler.RestoreHandler { onStartRestore, onFinishRestore -> onStartRestore() yourBillingClient.restorePurchases( onSuccess = { restoredPurchases -> onFinishRestore() //handle successful restore }, onError = { onFinishRestore() //handle error } ) } } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = new AdaptyUiObserverModeHandler() { // onPurchaseInitiated implementation (see above) @Override public RestoreHandler getRestoreHandler() { return (onStartRestore, onFinishRestore) -> { onStartRestore.invoke(); yourBillingClient.restorePurchases( restoredPurchases -> { onFinishRestore.invoke(); //handle successful restore }, error -> { onFinishRestore.invoke(); //handle error } ); }; } }; ``` Не забудьте вызывать следующие колбэки, чтобы уведомлять AdaptyUI о процессе покупки или восстановления. Это необходимо для корректной работы пейвола, например для отображения загрузчика: | Callback | Description | | :----------------- |:---------------------------------------------------------------------------------------------------------| | onStartPurchase() | Коллбэк нужно вызвать, чтобы уведомить AdaptyUI о начале покупки. | | onFinishPurchase() | Коллбэк нужно вызвать, чтобы уведомить AdaptyUI о завершении покупки. | | onStartRestore() | Необязательно. Коллбэк можно вызвать, чтобы уведомить AdaptyUI о начале восстановления покупок. | | onFinishRestore() | Необязательно. Коллбэк можно вызвать, чтобы уведомить AdaptyUI о завершении восстановления покупок. | 2. Чтобы отобразить визуальный пейвол на экране устройства, его нужно сначала настроить. Для этого вызовите метод `AdaptyUI.getPaywallView()` или создайте `AdaptyPaywallView` напрямую: ```kotlin showLineNumbers val paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, observerModeHandler, ) ``` ```kotlin showLineNumbers val paywallView = AdaptyPaywallView(activity) // or retrieve it from xml ... with(paywallView) { showPaywall( viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, observerModeHandler, ) } ``` ```java showLineNumbers AdaptyPaywallView paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, observerModeHandler ); ``` ```java showLineNumbers AdaptyPaywallView paywallView = new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml ... paywallView.showPaywall(viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, observerModeHandler); ``` ```xml showLineNumbers ``` После успешного создания представления вы можете добавить его в иерархию представлений и отобразить. Используйте следующую composable-функцию: ```kotlin showLineNumbers AdaptyPaywallScreen( viewConfiguration, products, eventListener, personalizedOfferResolver, tagResolver, timerResolver, ) ``` Параметры запроса: | Параметр | Обязательность | Описание | |---------|--------|-----------| | **Products** | опционально | Передайте массив `AdaptyPaywallProduct`, чтобы оптимизировать время отображения продуктов на экране. Если передать `null`, AdaptyUI автоматически загрузит необходимые продукты. | | **ViewConfiguration** | обязательно | Передайте объект `AdaptyViewConfiguration`, содержащий визуальные настройки пейвола. Используйте метод `Adapty.getViewConfiguration(paywall)` для его загрузки. Подробнее см. в разделе [Получение визуальной конфигурации пейвола](#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). | | **EventListener** | опционально | Передайте `AdaptyUiEventListener` для отслеживания событий пейвола. Для удобства рекомендуется наследоваться от `AdaptyUiDefaultEventListener`. Подробнее см. в разделе [Обработка событий пейвола](android-handling-events). | | **PersonalizedOfferResolver** | опционально | Чтобы указать персонализированное ценообразование ([подробнее](https://developer.android.com/google/play/billing/integrate#personalized-price)), реализуйте `AdaptyUiPersonalizedOfferResolver` и передайте собственную логику, которая возвращает `true` для `AdaptyPaywallProduct`, если цена продукта персонализирована, и `false` в остальных случаях. | | **TagResolver** | опционально | Используйте `AdaptyUiTagResolver` для обработки пользовательских тегов в тексте пейвола. Этот резолвер принимает тег и возвращает соответствующую строку. Подробнее см. в разделе «Пользовательские теги в Paywall Builder». | | **ObserverModeHandler** | обязательно для режима Observer | Реализованный вами на предыдущем шаге `AdaptyUiObserverModeHandler`. | | **variationId** | обязательно | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | | **transaction** | обязательно |

Для iOS, StoreKit 1: объект [`SKPaymentTransaction`](https://developer.apple.com/documentation/storekit/skpaymenttransaction).

Для iOS, StoreKit 2: объект [Transaction](https://developer.apple.com/documentation/storekit/transaction).

Для Android: строковый идентификатор (`purchase.getOrderId()`) покупки, где покупка является экземпляром класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) из библиотеки биллинга.

|
Прежде чем показывать пейволы (нажмите, чтобы развернуть) 1. Настройте начальную интеграцию Adapty [с Google Play](initial-android) и [с App Store](initial_ios). 2. Установите и настройте Adapty SDK. Убедитесь, что параметр `observerMode` установлен в `true`. Обратитесь к инструкциям для вашего фреймворка: [Android](sdk-installation-android), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk) и [Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk). 3. [Создайте продукты](create-product) в дашборде Adapty. 4. [Настройте пейволы, назначьте им продукты](create-paywall) и кастомизируйте их с помощью Paywall Builder в дашборде Adapty. 5. [Создайте плейсменты и назначьте им пейволы](create-placement) в дашборде Adapty. 6. [Получите пейволы Paywall Builder и их конфигурацию](android-get-pb-paywalls) в коде вашего мобильного приложения.
1. Реализуйте `AdaptyUiObserverModeHandler`. Колбэк `AdaptyUiObserverModeHandler` (`onPurchaseInitiated`) срабатывает, когда пользователь инициирует покупку. Вы можете запустить собственный процесс покупки в ответ на этот колбэк: ```kotlin showLineNumbers val observerModeHandler = AdaptyUiObserverModeHandler { product, paywall, paywallView, onStartPurchase, onFinishPurchase -> onStartPurchase() yourBillingClient.makePurchase( product, onSuccess = { purchase -> onFinishPurchase() //handle success }, onError = { onFinishPurchase() //handle error }, onCancel = { onFinishPurchase() //handle cancel } ) } ``` ```java showLineNumbers AdaptyUiObserverModeHandler observerModeHandler = (product, paywall, paywallView, onStartPurchase, onFinishPurchase) -> { onStartPurchase.invoke(); yourBillingClient.makePurchase( product, purchase -> { onFinishPurchase.invoke(); //handle success }, error -> { onFinishPurchase.invoke(); //handle error }, () -> { //cancellation onFinishPurchase.invoke(); //handle cancel } ); }; ``` Также не забудьте вызывать эти коллбэки в AdaptyUI. Это необходимо для правильной работы пейвола, например для отображения загрузчика: | Callback в Kotlin | Callback в Java | Описание | | :---------------- | :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------ | | onStartPurchase() | onStartPurchase.invoke() | Callback нужно вызвать, чтобы уведомить AdaptyUI о том, что покупка начата. | | onFinishPurchase() | onFinishPurchase.invoke() | Callback нужно вызвать, чтобы уведомить AdaptyUI о том, что покупка завершена успешно, завершена с ошибкой или отменена. | 2. Чтобы отобразить визуальный пейвол, его необходимо сначала инициализировать. Для этого вызовите метод `AdaptyUI.getPaywallView()` или создайте `AdaptyPaywallView` напрямую: ```kotlin showLineNumbers val paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, AdaptyPaywallInsets.of(topInset, bottomInset), eventListener, personalizedOfferResolver, tagResolver, observerModeHandler, ) //======= OR ======= val paywallView = AdaptyPaywallView(activity) // or retrieve it from xml ... with(paywallView) { setEventListener(eventListener) setObserverModeHandler(observerModeHandler) showPaywall( viewConfiguration, products, AdaptyPaywallInsets.of(topInset, bottomInset), personalizedOfferResolver, tagResolver, ) } ``` ```java showLineNumbers AdaptyPaywallView paywallView = AdaptyUI.getPaywallView( activity, viewConfiguration, products, AdaptyPaywallInsets.of(topInset, bottomInset), eventListener, personalizedOfferResolver, tagResolver, observerModeHandler ); //======= OR ======= AdaptyPaywallView paywallView = new AdaptyPaywallView(activity); //add to the view hierarchy if needed, or you receive it from xml ... paywallView.setEventListener(eventListener); paywallView.setObserverModeHandler(observerModeHandler); paywallView.showPaywall(viewConfiguration, products, AdaptyPaywallInsets.of(topInset, bottomInset), personalizedOfferResolver); ``` ```xml showLineNumbers ``` После успешного создания представления вы можете добавить его в иерархию представлений и отобразить. Параметры запроса: | Параметр | Наличие | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Products** | опциональный | Передайте массив `AdaptyPaywallProduct`, чтобы оптимизировать время отображения продуктов на экране. Если передать `null`, AdaptyUI автоматически загрузит необходимые продукты. | | **ViewConfiguration** | обязательный | Передайте объект `AdaptyViewConfiguration`, содержащий визуальные параметры пейвола. Используйте метод `Adapty.getViewConfiguration(paywall)` для его загрузки. Подробнее см. в разделе [Получение визуальной конфигурации пейвола](android-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). | | **Insets** | обязательный | Задайте объект `AdaptyPaywallInsets` с информацией об области, перекрываемой системными панелями, чтобы создать вертикальные отступы для контента. Если строка состояния и панель навигации не перекрывают `AdaptyPaywallView`, передайте `AdaptyPaywallInsets.NONE`. В полноэкранном режиме, когда системные панели перекрывают часть интерфейса, получите отступы так, как показано под таблицей. | | **EventListener** | опциональный | Передайте `AdaptyUiEventListener` для отслеживания событий пейвола. Для удобства рекомендуется расширить `AdaptyUiDefaultEventListener`. Подробнее см. в разделе [Обработка событий пейвола](android-handling-events). | | **PersonalizedOfferResolver** | опциональный | Чтобы указать персонализированную цену ([подробнее](https://developer.android.com/google/play/billing/integrate#personalized-price)), реализуйте `AdaptyUiPersonalizedOfferResolver` и передайте собственную логику, которая сопоставляет `AdaptyPaywallProduct` со значением `true`, если цена продукта персонализирована, иначе — `false`. | | **TagResolver** | опциональный | Используйте `AdaptyUiTagResolver` для обработки пользовательских тегов в тексте пейвола. Этот резолвер принимает параметр тега и преобразует его в соответствующую строку. Подробнее см. в разделе «Пользовательские теги в Paywall Builder». | | **ObserverModeHandler** | обязательный для режима Observer | `AdaptyUiObserverModeHandler`, реализованный на предыдущем шаге. | | **variationId** | обязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [`AdaptyPaywall`](https://android.adapty.io/adapty/com.adapty.models/-adapty-paywall/). | | **transaction** | обязательный |

Для iOS, StoreKit 1: объект [`SKPaymentTransaction`](https://developer.apple.com/documentation/storekit/skpaymenttransaction).

Для iOS, StoreKit 2: объект [Transaction](https://developer.apple.com/documentation/storekit/transaction).

Для Android: строковый идентификатор (`purchase.getOrderId()`) покупки, где покупка — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) из библиотеки биллинга.

| Для полноэкранного режима, в котором системные панели перекрывают часть интерфейса, получайте отступы следующим образом: ```kotlin showLineNumbers import androidx.core.graphics.Insets import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat //create extension function fun View.onReceiveSystemBarsInsets(action: (insets: Insets) -> Unit) { ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets -> val systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) ViewCompat.setOnApplyWindowInsetsListener(this, null) action(systemBarInsets) insets } } //and then use it with the view paywallView.onReceiveSystemBarsInsets { insets -> val paywallInsets = AdaptyPaywallInsets.of(insets.top, insets.bottom) paywallView.setEventListener(eventListener) paywallView.setObserverModeHandler(observerModeHandler) paywallView.showPaywall(viewConfig, products, paywallInsets, personalizedOfferResolver, tagResolver) } ``` ```java showLineNumbers import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; ... ViewCompat.setOnApplyWindowInsetsListener(paywallView, (view, insets) -> { Insets systemBarInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()); ViewCompat.setOnApplyWindowInsetsListener(paywallView, null); AdaptyPaywallInsets paywallInsets = AdaptyPaywallInsets.of(systemBarInsets.top, systemBarInsets.bottom); paywallView.setEventListener(eventListener); paywallView.setObserverModeHandler(observerModeHandler); paywallView.showPaywall(viewConfiguration, products, paywallInsets, personalizedOfferResolver, tagResolver); return insets; }); ``` Возвращает: | Объект | Описание | | :------------------ | :------------------------------------------------- | | `AdaptyPaywallView` | объект, представляющий запрошенный экран пейвола. | :::warning Не забудьте [связать пейволы с транзакциями покупок](report-transactions-observer-mode-android). Иначе Adapty не сможет определить, с какого пейвола была совершена покупка. :::
--- # File: android-troubleshoot-purchases --- --- title: "Troubleshoot purchases in Android SDK" description: "Troubleshoot purchases in Android SDK" --- Этот гайд поможет вам решить распространённые проблемы при реализации покупок вручную через Android SDK. ## makePurchase завершается успешно, но профиль не обновляется \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\} **Проблема**: Метод `makePurchase` выполняется успешно, однако профиль пользователя и статус подписки в Adapty не обновляются. **Причина**: Как правило, это указывает на неполную настройку Google Play Store или ошибки конфигурации. **Решение**: Убедитесь, что вы выполнили все [шаги настройки Google Play](initial-android). ## makePurchase вызывается дважды \{#makepurchase-is-invoked-twice\} **Проблема**: Метод `makePurchase` вызывается несколько раз для одной и той же покупки. **Причина**: Обычно это происходит, когда процесс покупки запускается несколько раз из-за проблем с управлением состоянием UI или быстрых повторных действий пользователя. **Решение**: Убедитесь, что вы выполнили все [шаги настройки Google Play](initial-android). ## AdaptyError.cantMakePayments в режиме Observer \{#adaptyerrorcantmakepayments-in-observer-mode\} **Проблема**: При использовании `makePurchase` в режиме observer возникает ошибка `AdaptyError.cantMakePayments`. **Причина**: В режиме observer покупки должны обрабатываться на вашей стороне — метод `makePurchase` из Adapty использовать не следует. **Решение**: Если вы используете `makePurchase` для обработки покупок, отключите режим observer. Вам нужно либо использовать `makePurchase`, либо обрабатывать покупки самостоятельно в режиме observer. Подробнее см. в разделе [Реализация режима Observer](implement-observer-mode-android). ## Ошибка Adapty: (code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\} **Проблема**: Вы получаете ошибку о недоступности биллинга от Google Play Store. **Причина**: Эта ошибка не связана с Adapty. Это ошибка Google Play Billing Library, которая означает, что биллинг недоступен на устройстве. **Решение**: Эта ошибка не связана с Adapty. Подробнее о ней можно узнать в документации Play Store: [Handle BillingResult response codes](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers. ## Not found makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\} **Проблема**: Возникают проблемы — `makePurchasesCompletionHandlers` не найден. **Причина**: Как правило, это связано с проблемами при тестировании в песочнице. **Решение**: Создайте нового пользователя в песочнице и повторите попытку. Обычно это решает проблемы с обработчиком завершения покупки в песочнице. ## Другие проблемы \{#other-issues\} **Проблема**: Вы сталкиваетесь с другими проблемами при покупках, которые не описаны выше. **Решение**: При необходимости обновите SDK до последней версии, следуя [гайдам по миграции](android-sdk-migration-guides). Многие проблемы уже исправлены в новых версиях SDK. --- # File: android-identifying-users --- --- title: "Идентификация пользователей в Android SDK" description: "Идентифицируйте пользователей в Adapty для улучшения персонализированного опыта подписки (Android)." --- Adapty создаёт внутренний ID профиля для каждого пользователя. Однако если у вас есть собственная система аутентификации, вы можете задать свой Customer User ID. Пользователей можно искать по Customer User ID в разделе [Профили](profiles-crm), а также использовать его в [server-side API](getting-started-with-server-side-api) — он будет передаваться во все интеграции. ### Установка customer user ID при конфигурации \{#setting-customer-user-id-on-configuration\} Если у вас есть идентификатор пользователя на этапе конфигурации, передайте его в параметре `customerUserId` метода `.activate()`: ```kotlin showLineNumbers Adapty.activate(applicationContext, "PUBLIC_SDK_KEY", customerUserId = "YOUR_USER_ID") ``` :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Установка идентификатора пользователя после конфигурации \{#setting-customer-user-id-after-configuration\} Если у вас нет идентификатора пользователя при конфигурации SDK, вы можете задать его позже в любой момент с помощью метода `.identify()`. Чаще всего этот метод используется после регистрации или авторизации, когда пользователь переходит из анонимного состояния в аутентифицированное. ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") { error -> if (error == null) { // successful identify } } ``` ```java showLineNumbers Adapty.identify("YOUR_USER_ID", error -> { if (error == null) { // successful identify } }); ``` Параметры запроса: - **Customer User ID** (обязательный): строковый идентификатор пользователя. :::warning Повторная отправка важных пользовательских данных В некоторых случаях, например когда пользователь снова входит в аккаунт, серверы Adapty уже располагают информацией об этом пользователе. В таких ситуациях Adapty SDK автоматически переключится на работу с новым пользователем. Если вы передавали какие-либо данные анонимному пользователю — например, пользовательские атрибуты или атрибуцию из сторонних сетей — эти данные нужно повторно отправить для идентифицированного пользователя. Также важно помнить, что после идентификации пользователя следует повторно запросить все пейволы и продукты, так как данные нового пользователя могут отличаться. ::: ### Выход и вход в систему \{#logging-out-and-logging-in\} Вы можете выйти из аккаунта в любой момент, вызвав метод `.logout()`: ```kotlin showLineNumbers Adapty.logout { error -> if (error == null) { // successful logout } } ``` ```java showLineNumbers Adapty.logout(error -> { if (error == null) { // successful logout } }); ``` После этого вы можете войти снова, используя метод `.identify()`. ### Обнаружение пользователей на разных устройствах \{#detect-users-across-devices\} При активации SDK он автоматически считывает существующие права пользователя из StoreKit (iOS) или Google Play Billing (Android) и синхронизирует их с бэкендом Adapty. Активная подписка появляется в профиле Adapty без вызова `restorePurchases` со стороны приложения. Что **не** происходит автоматически — так это распознавание того, что профиль на новом устройстве принадлежит тому же пользователю, что и профиль на исходном устройстве. Adapty сопоставляет профили по Customer User ID, поэтому непрерывность идентификации зависит от того, что вы используете в качестве CUID. **Что Adapty может определить между устройствами** | Ваша настройка | Что Adapty определяет | Что нужно сделать | | --- | --- | --- | | Customer User ID = `device_id` (без входа в аккаунт) | Новое устройство получает другой CUID и, следовательно, другой профиль. Подписка синхронизируется с новым профилем через событие **Access level updated**, но `subscription_started` не срабатывает — новый профиль считается наследником исходной покупки. Аналитика, основанная на `subscription_started`, будет занижать количество возвращающихся пользователей. | Используйте стабильный идентификатор аккаунта в качестве Customer User ID, чтобы вернувшийся пользователь соответствовал существующему профилю на разных устройствах. | | Customer User ID = стабильный идентификатор аккаунта (вход на каждом устройстве) | SDK автоматически синхронизирует подписку при `activate()`, а `identify()` сопоставляет существующий профиль по CUID. | Никаких дополнительных действий не требуется — и идентификация, и подписка разрешаются автоматически. | | Наследник Apple Family Sharing | Член семьи получает подписку только через событие **Access level updated** — `subscription_started` не срабатывает. | Отслеживайте событие **Access level updated**. Полную матрицу событий см. в разделе [Apple Family Sharing](apple-family-sharing). | | Один аккаунт Apple/Google, разные пользователи внутри приложения | Первый профиль, зафиксировавший покупку, становится родительским. Последующие профили видят подписку через цепочку наследования с одним событием **Access level updated**. | Требуйте входа в аккаунт, затем выберите [режим совместного использования](sharing-paid-access-between-user-accounts), подходящий для вашей модели. | **Восстановление покупок на новом устройстве** Добавьте кнопку «Восстановить покупки», инициируемую пользователем, на свой пейвол. Apple App Review (руководство 3.1.1) её требует, и она служит запасным вариантом на случай, если автоматическая синхронизация пропустит граничный случай. Кнопка должна вызывать `restorePurchases` в вашем SDK. Программный вызов `restorePurchases` при первом запуске не нужен для обычного использования — SDK уже выполняет аналогичную операцию при `activate()`. Программные вызовы стоит использовать только для принудительной проверки чека, например при отладке отсутствующего доступа после завершения `activate()`. --- # File: android-setting-user-attributes --- --- title: "Установка атрибутов пользователя в Android SDK" description: "Узнайте, как устанавливать атрибуты пользователя в Adapty для улучшения сегментации аудитории." --- Вы можете задавать необязательные атрибуты пользователя вашего приложения: email, номер телефона и другие. Атрибуты можно использовать для создания [сегментов](segments) пользователей или просматривать их в CRM. ### Установка атрибутов пользователя \{#setting-user-attributes\} Чтобы установить атрибуты пользователя, вызовите метод `.updateProfile()`: ```kotlin showLineNumbers val builder = AdaptyProfileParameters.Builder() .withEmail("email@email.com") .withPhoneNumber("+18888888888") .withFirstName("John") .withLastName("Appleseed") .withGender(AdaptyProfile.Gender.OTHER) .withBirthday(AdaptyProfile.Date(1970, 1, 3)) Adapty.updateProfile(builder.build()) { error -> if (error != null) { // handle the error } } ``` ```java showLineNumbers AdaptyProfileParameters.Builder builder = new AdaptyProfileParameters.Builder() .withEmail("email@email.com") .withPhoneNumber("+18888888888") .withFirstName("John") .withLastName("Appleseed") .withGender(AdaptyProfile.Gender.OTHER) .withBirthday(new AdaptyProfile.Date(1970, 1, 3)); Adapty.updateProfile(builder.build(), error -> { if (error != null) { // handle the error } }); ``` Обратите внимание: атрибуты, ранее установленные с помощью метода `updateProfile`, не сбрасываются. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Список допустимых ключей \{#the-allowed-keys-list\} Допустимые ключи `` для `AdaptyProfileParameters.Builder` и соответствующие значения ``: | Ключ | Значение | |---|-----| |

email

phoneNumber

firstName

lastName

| String | | gender | Enum, допустимые значения: `female`, `male`, `other` | | birthday | Date | ### Пользовательские атрибуты \{#custom-user-attributes\} Вы можете задавать собственные атрибуты, обычно связанные с использованием вашего приложения. Например, для фитнес-приложений это может быть количество тренировок в неделю, а для приложений по изучению языков — уровень знаний пользователя. Такие атрибуты можно использовать в сегментах для создания таргетированных пейволов и офферов, а также в аналитике для выявления продуктовых метрик, которые больше всего влияют на выручку. ```kotlin showLineNumbers builder.withCustomAttribute("key1", "value1") ``` ```java showLineNumbers builder.withCustomAttribute("key1", "value1"); ``` Чтобы удалить существующий ключ, используйте метод `.withRemoved(customAttributeForKey:)`: ```kotlin showLineNumbers builder.withRemovedCustomAttribute("key2") ``` ```java showLineNumbers builder.withRemovedCustomAttribute("key2"); ``` Иногда нужно узнать, какие пользовательские атрибуты уже установлены. Для этого используйте поле `customAttributes` объекта `AdaptyProfile`. :::warning Имейте в виду, что значение `customAttributes` может быть устаревшим: атрибуты пользователя могут отправляться с разных устройств в любое время, поэтому данные на сервере могут измениться после последней синхронизации. ::: ### Ограничения \{#limits\} - До 30 пользовательских атрибутов на пользователя. - Длина имени ключа — до 30 символов. Имя ключа может содержать буквенно-цифровые символы и любой из следующих: `_` `-` `.` - Значение может быть строкой или числом с плавающей точкой длиной не более 50 символов. --- # File: android-listen-subscription-changes --- --- title: "Проверка статуса подписки в Android SDK" description: "Отслеживайте и управляйте статусом подписки пользователей в Adapty для повышения удержания клиентов в вашем Android-приложении." --- С Adapty отслеживать статус подписки очень просто. Вам не нужно вручную прописывать идентификаторы продуктов в коде — достаточно проверить наличие активного [уровня доступа](access-level), чтобы убедиться, что у пользователя есть подписка. Прежде чем начать проверку статуса подписки, настройте [Real-time Developer Notifications (RTDN)](enable-real-time-developer-notifications-rtdn). ## Уровень доступа и объект AdaptyProfile \{#access-level-and-the-adaptyprofile-object\} Уровни доступа — это свойства объекта [AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/). Рекомендуем получать профиль при запуске приложения — например, когда вы [идентифицируете пользователя](android-identifying-users#setting-customer-user-id-on-configuration) — и обновлять его при каждом изменении. Так вы сможете использовать объект профиля без повторных запросов к серверу. Чтобы получать уведомления об обновлениях профиля, подпишитесь на изменения профиля, как описано в разделе [Прослушивание обновлений профиля, включая уровни доступа](android-listen-subscription-changes) ниже. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ## Получение уровня доступа с сервера \{#retrieving-the-access-level-from-the-server\} Чтобы получить уровень доступа с сервера, используйте метод `.getProfile()`: ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // check the access } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); // check the access } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` Параметры ответа: | Параметр | Описание | | --------- | ------------------------------------------------------------ | | Profile |

Объект [AdaptyProfile](https://android.adapty.io/adapty/com.adapty.models/-adapty-profile/). Как правило, для определения наличия у пользователя премиум-доступа достаточно проверить только статус уровня доступа профиля.

Метод `.getProfile` возвращает наиболее актуальные данные, поскольку всегда пытается обратиться к API. Если по какой-либо причине (например, из-за отсутствия интернета) SDK Adapty не может получить данные с сервера, возвращаются данные из кэша. Важно также отметить, что SDK Adapty регулярно обновляет кэш `AdaptyProfile`, чтобы поддерживать информацию в актуальном состоянии.

| Метод `.getProfile()` возвращает профиль пользователя, из которого можно получить статус уровня доступа. В приложении может быть несколько уровней доступа. Например, в новостном приложении с независимыми подписками на разные тематики можно создать уровни доступа «sports» и «science». Однако в большинстве случаев достаточно одного уровня доступа — тогда можно просто использовать уровень доступа «premium» по умолчанию. Вот пример проверки уровня доступа «premium» по умолчанию: ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value if (profile.accessLevels["premium"]?.isActive == true) { // grant access to premium features } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success) result).getValue(); AdaptyProfile.AccessLevel premium = profile.getAccessLevels().get("premium"); if (premium != null && premium.isActive()) { // grant access to premium features } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` ### Прослушивание обновлений статуса подписки \{#listening-for-subscription-status-updates\} При каждом изменении подписки пользователя Adapty генерирует событие. Чтобы получать сообщения от Adapty, необходимо выполнить дополнительную настройку: ```kotlin showLineNumbers Adapty.setOnProfileUpdatedListener { profile -> // handle any changes to subscription state } ``` ```java showLineNumbers t Adapty.setOnProfileUpdatedListener(profile -> { // handle any changes to subscription state }); ``` Adapty также генерирует событие при запуске приложения. В этом случае передаётся кэшированный статус подписки. ### Кэш статуса подписки \{#subscription-status-cache\} Кэш, реализованный в SDK Adapty, хранит статус подписки профиля. Это означает, что даже при недоступности сервера кэшированные данные позволяют получить информацию о статусе подписки профиля. Однако важно учитывать, что напрямую запросить данные из кэша невозможно. SDK периодически обращается к серверу каждую минуту для проверки обновлений и изменений профиля. При наличии каких-либо изменений — новых транзакций или других обновлений — они отправляются в кэшированные данные, чтобы поддерживать их синхронизацию с сервером. --- # File: kids-mode-android --- --- title: "Режим для детей в Android SDK" description: "Легко включите режим для детей для соответствия политикам Google. GAID и рекламные данные не собираются в Android SDK." --- Если ваше Android-приложение предназначено для детей, вы обязаны соблюдать политики [Google](https://support.google.com/googleplay/android-developer/answer/9893335). Если вы используете Adapty SDK, несколько простых шагов помогут настроить его в соответствии с этими политиками и пройти проверку в сторе. ## Что нужно настроить? \{#whats-required\} Вам нужно настроить SDK, чтобы отключить сбор: - [Android Advertising ID (AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248) - [IP-адреса](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Кроме того, рекомендуем осторожно обращаться с пользовательским ID. ID в формате `` однозначно будет расцениваться как сбор персональных данных — так же, как и использование email. Для режима «Для детей» лучшей практикой является использование случайных или обезличенных идентификаторов (например, хэшированных ID или UUID, сгенерированных на устройстве) — это поможет обеспечить соответствие требованиям. ## Включение режима «Дети» \{#enabling-kids-mode\} ### Обновления в дашборде Adapty В дашборде Adapty нужно отключить сбор IP-адресов. Для этого перейдите в [App settings](https://app.adapty.io/settings/general) и нажмите **Disable IP address collection** в разделе **Collect users' IP address**. ### Обновления в коде вашего мобильного приложения Для соблюдения политик необходимо отключить сбор Android Advertising ID (AAID/GAID) и IP-адреса при инициализации Adapty SDK: **Kotlin:** ```kotlin showLineNumbers override fun onCreate() { super.onCreate() Adapty.activate( applicationContext, AdaptyConfig.Builder("PUBLIC_SDK_KEY") // highlight-start .withAdIdCollectionDisabled(true) // set to `true` .withIpAddressCollectionDisabled(true) // set to `true` // highlight-end .build() ) } ``` **Java:** ```java showLineNumbers @Override public void onCreate() { super.onCreate(); Adapty.activate( applicationContext, new AdaptyConfig.Builder("PUBLIC_SDK_KEY") // highlight-start .withAdIdCollectionDisabled(true) // set to `true` .withIpAddressCollectionDisabled(true) // set to `true` // highlight-end .build() ); } ``` ### Обновления в манифесте Android \{#updates-in-your-android-manifest\} :::note Если ваше приложение ориентировано **исключительно** на детскую аудиторию и компилируется под Android 13 (API 33) или выше, Google Play требует не запрашивать разрешение `AD_ID`. Другой SDK в вашем приложении (аналитика, атрибуция или реклама) может добавить это разрешение через слияние манифестов. Установка `withAdIdCollectionDisabled(true)` запрещает Adapty собирать идентификатор, но не удаляет разрешение, объявленное другим SDK. ::: Чтобы удалить разрешение, добавьте следующее внутри элемента `` в файле `app/src/main/AndroidManifest.xml`. Элемент `` должен объявлять `xmlns:tools="http://schemas.android.com/tools"`. ```xml showLineNumbers title="AndroidManifest.xml" ``` --- # File: android-get-onboardings --- --- title: "Получение онбордингов в Android SDK" description: "Узнайте, как получить онбординги в Adapty для Android." --- :::tip **Начиная с SDK v4**, вы можете создавать [флоу](android-get-pb-paywalls) как более мощную альтернативу онбордингам. В отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает плавную анимацию, единый Android-стиль, быструю загрузку и отсутствие зависимости от WebView. Подробнее: [Получение флоу и пейволов](android-get-pb-paywalls) и [Отображение флоу и пейволов](android-present-paywalls). ::: После того как вы [оформили визуальную часть онбординга](design-onboarding) в Paywall Builder на дашборде Adapty, его можно отобразить в вашем Android-приложении. Первый шаг — получить онбординг, связанный с плейсментом, и его конфигурацию отображения, как описано ниже. Перед началом убедитесь, что: 1. Вы установили [Adapty Android SDK](sdk-installation-android) версии 3.8.0 или выше. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). ## Получение онбординга \{#fetch-onboarding\} Когда вы создаёте [онбординг](onboardings) в нашем no-code конструкторе, он сохраняется как контейнер с конфигурацией, которую приложение должно получить и отобразить. Этот контейнер управляет всем процессом: какой контент показывается, как он представлен и как обрабатываются действия пользователя (например, ответы на вопросы или данные из форм). Контейнер также автоматически отслеживает события аналитики, поэтому отдельно реализовывать отслеживание просмотров не нужно. Для лучшей производительности получайте конфигурацию онбординга заранее — чтобы изображения успели загрузиться до того, как пользователь увидит экран. Чтобы получить онбординг, используйте метод `getOnboarding`: ```kotlin showLineNumbers Adapty.getOnboarding("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val onboarding = result.value // the requested onboarding } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указывали при создании плейсмента в дашборде Adapty. | | **locale** |

опциональный

по умолчанию: `en`

|

Идентификатор локализации онбординга. Ожидается языковой код, состоящий из одного или двух подтегов, разделённых символом минус (**-**). Первый подтег обозначает язык, второй — регион.

Пример: `en` — английский, `pt-br` — португальский (Бразилия).

Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).

| | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.

Однако если ваши пользователи часто работают при нестабильном интернете, рассмотрите использование `.returnCacheDataElseLoad` — оно возвращает кешированные данные при их наличии. В этом случае пользователи могут получить не самые последние данные, зато загрузка будет быстрее независимо от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии для сокращения сетевых запросов.

Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или ручной очистке.

Adapty SDK хранит онбординги локально в двух слоях: регулярно обновляемый кеш, описанный выше, и резервные онбординги. Также используется CDN для ускорения загрузки онбордингов и отдельный резервный сервер на случай недоступности CDN. Эта система обеспечивает получение последней версии онбордингов при надёжной работе даже при нестабильном интернет-соединении.

| | **loadTimeout** | по умолчанию: 5 сек |

Ограничивает время ожидания для данного метода. По истечении таймаута будут возвращены кешированные данные или локальный резервный вариант.

Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения `loadTimeout`, так как операция может включать несколько запросов под капотом.

Для Android: создать `TimeInterval` можно с помощью функций-расширений (например, `5.seconds`, где `.seconds` берётся из `import com.adapty.utils.seconds`) или `TimeInterval.seconds(5)`. Чтобы снять ограничение, используйте `TimeInterval.INFINITE`.

| Параметры ответа: | Параметр | Описание | |:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Объект [`AdaptyOnboarding`](https://android.adapty.io/adapty/com.adapty.models/-adapty-onboarding/) со следующими полями: идентификатор и конфигурация онбординга, Remote Config и ряд других свойств. | ## Ускорьте загрузку онбординга с помощью онбординга для аудитории по умолчанию \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} Как правило, онбординги загружаются почти мгновенно, так что беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и онбордингов, а пользователи работают с медленным интернетом, загрузка онбординга может занять дольше, чем хотелось бы. В таких случаях имеет смысл показывать онбординг по умолчанию — это обеспечит плавный пользовательский опыт вместо полного отсутствия онбординга. Чтобы решить эту проблему, воспользуйтесь методом `getOnboardingForDefaultAudience`, который получает онбординг указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать онбординг через метод `getOnboarding`, как описано в разделе [Получение онбординга](#fetch-onboarding) выше. :::warning Рекомендуем использовать `getOnboarding` вместо `getOnboardingForDefaultAudience`, так как у последнего есть существенные ограничения: - **Проблемы совместимости**: могут возникнуть при поддержке нескольких версий приложения — придётся либо делать обратно совместимые дизайны, либо мириться с некорректным отображением в старых версиях. - **Без персонализации**: показывает контент только для аудитории «All Users», то есть таргетинг по стране, атрибуции или пользовательским атрибутам недоступен. Если для вашего случая скорость загрузки важнее этих недостатков, используйте `getOnboardingForDefaultAudience`, как показано ниже. В противном случае используйте `getOnboarding`, как описано [выше](#fetch-onboarding). ::: ```kotlin Adapty.getOnboardingForDefaultAudience("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val onboarding = result.value // Handle successful onboarding retrieval } is AdaptyResult.Error -> { val error = result.error // Handle error case } } } ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указываете при создании плейсмента в дашборде Adapty. | | **locale** |

необязательный

по умолчанию: `en`

|

Идентификатор локализации онбординга. Ожидается языковой код, состоящий из одного или двух подтегов, разделённых символом минус (**-**). Первый подтег — язык, второй — регион.

Пример: `en` означает английский, `pt-br` — бразильский португальский.

Подробнее о кодах локализации и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).

| | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` |

По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.

Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите вариант `.returnCacheDataElseLoad` — он возвращает кешированные данные, если они есть. В этом случае пользователи могут не получить самые последние данные, зато время загрузки будет меньше вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы сократить количество сетевых запросов.

Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.

Adapty SDK хранит онбординги локально в двух слоях: регулярно обновляемый кеш, описанный выше, и резервные онбординги. Для более быстрой загрузки мы также используем CDN и отдельный резервный сервер на случай недоступности CDN. Эта система гарантирует, что вы всегда получаете актуальную версию онбордингов, обеспечивая надёжность даже при нестабильном интернет-соединении.

| --- # File: android-present-onboardings --- --- title: "Отображение онбордингов в Android SDK" description: "Узнайте, как отображать онбординги на Android для эффективного вовлечения пользователей." --- :::tip **Начиная с SDK v4**, вы можете использовать [флоу](android-get-pb-paywalls) как более мощную альтернативу онбордингам. В отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает плавную анимацию, привычный внешний вид Android, быструю загрузку и отсутствие зависимости от WebView. Подробнее: [Получение флоу и пейволов](android-get-pb-paywalls) и [Отображение флоу и пейволов](android-present-paywalls). ::: Прежде чем начать, убедитесь, что: 1. Вы установили [Adapty Android SDK](sdk-installation-android) версии 3.8.0 или выше. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). Если вы настроили онбординг с помощью Onboarding Builder, вам не нужно беспокоиться о его рендеринге в коде мобильного приложения для отображения пользователю. Такой онбординг содержит как то, что должно быть показано, так и то, как это должно быть показано. Чтобы отобразить визуальный онбординг на экране устройства, его необходимо сначала настроить. Для этого вызовите метод `AdaptyUI.getOnboardingView()` или создайте `OnboardingView` напрямую: ```kotlin val onboardingView = AdaptyUI.getOnboardingView( activity = this, viewConfig = onboardingConfig, eventListener = eventListener ) ``` ```kotlin val onboardingView = AdaptyOnboardingView(activity) onboardingView.show( viewConfig = onboardingConfig, delegate = eventListener ) ``` ```java AdaptyOnboardingView onboardingView = AdaptyUI.getOnboardingView( activity, onboardingConfig, eventListener ); ``` ```java AdaptyOnboardingView onboardingView = new AdaptyOnboardingView(activity); onboardingView.show(onboardingConfig, eventListener); ``` ```xml ``` После успешного создания view вы можете добавить его в иерархию представлений и отобразить на экране устройства. Параметры запроса: | Параметр | Наличие | Описание | | :-------- | :------- |:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **viewConfig** | обязательный | Конфигурация онбординга, полученная из `AdaptyUI.getOnboardingConfiguration()` | | **eventListener** | обязательный | Реализация `AdaptyOnboardingEventListener` для обработки событий онбординга. Подробнее см. в разделе [Обработка событий онбординга](android-handle-onboarding-events). | ## Изменение цвета индикатора загрузки \{#change-loading-indicator-color\} Вы можете переопределить цвет индикатора загрузки по умолчанию следующим образом: ```xml ``` ## Добавьте плавные переходы между сплэш-экраном и онбордингом \{#add-smooth-transitions-between-the-splash-screen-and-onboarding\} По умолчанию между сплэш-экраном и онбордингом отображается экран загрузки, пока онбординг полностью не загрузится. Однако если вы хотите сделать переход плавнее, это можно настроить: либо продлить сплэш-экран, либо показать что-то другое. Для этого создайте файл `adapty_onboarding_placeholder_view.xml` в папке `res/layout` и определите там плейсхолдер (то, что будет отображаться во время загрузки онбординга). Если вы определите плейсмент, онбординг загрузится в фоне и автоматически отобразится, когда будет готов. ## Отключение отступов безопасной зоны \{#disable-safe-area-paddings\} По умолчанию представление онбординга автоматически применяет отступы безопасной зоны, чтобы избежать перекрытия системными элементами интерфейса — строкой состояния и панелью навигации. Если вы хотите отключить это поведение и полностью управлять разметкой самостоятельно, установите параметр `safeAreaPaddings` в значение `false`. ```kotlin val onboardingView = AdaptyUI.getOnboardingView( activity = this, viewConfig = onboardingConfig, eventListener = eventListener, safeAreaPaddings = false ) ``` ```kotlin val onboardingView = AdaptyOnboardingView(activity) onboardingView.show( viewConfig = onboardingConfig, delegate = eventListener, safeAreaPaddings = false ) ``` ```java AdaptyOnboardingView onboardingView = AdaptyUI.getOnboardingView( activity, onboardingConfig, eventListener, false ); ``` ```java AdaptyOnboardingView onboardingView = new AdaptyOnboardingView(activity); onboardingView.show(onboardingConfig, eventListener, false); ``` Кроме того, вы можете управлять этим поведением глобально, добавив булев ресурс в приложение: ```xml false ``` Если `safeAreaPaddings` установлен в `false`, онбординг растянется на весь экран без автоматических отступов — вы получаете полный контроль над компоновкой, и контент онбординга может использовать всё пространство экрана. ## Настройка способа открытия ссылок в онбордингах \{#customize-how-links-open-in-onboardings\} :::important Настройка способа открытия ссылок в онбордингах поддерживается начиная с Adapty SDK v3.15.1. ::: По умолчанию ссылки в онбордингах открываются во встроенном браузере. Это обеспечивает удобство работы, позволяя пользователям просматривать веб-страницы прямо в приложении, не переключаясь между приложениями. Если вы предпочитаете открывать ссылки во внешнем браузере, настройте это поведение, задав параметру `externalUrlsPresentation` значение `AdaptyWebPresentation.ExternalBrowser`: ```kotlin val onboardingConfig = AdaptyUI.getOnboardingConfiguration( onboarding = onboarding, externalUrlsPresentation = AdaptyWebPresentation.ExternalBrowser // default – InAppBrowser ) ``` ```java AdaptyOnboardingConfiguration onboardingConfig = AdaptyUI.getOnboardingConfiguration( onboarding, AdaptyWebPresentation.ExternalBrowser // default – InAppBrowser ); ``` --- # File: android-handle-onboarding-events --- --- title: "Обработка событий онбординга в Android SDK" description: "Обработка событий онбординга в Android с помощью Adapty." --- :::tip **Начиная с SDK v4** вы можете создавать [флоу](android-get-pb-paywalls) как более мощную альтернативу онбордингам. В отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает более плавные анимации, привычный внешний вид Android, быструю загрузку и отсутствие зависимости от WebView. Подробнее в разделах [Получение флоу и пейволов](android-get-pb-paywalls) и [Отображение флоу и пейволов](android-present-paywalls). ::: Прежде чем начать, убедитесь, что: 1. Вы установили [Adapty Android SDK](sdk-installation-android) версии 3.8.0 или новее. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). Онбординги, настроенные через билдер, генерируют события, на которые ваше приложение может реагировать. Ниже описано, как это сделать. Чтобы управлять процессами на экране онбординга в Android-приложении или отслеживать их, реализуйте интерфейс `AdaptyOnboardingEventListener`. ## Пользовательские действия \{#custom-actions\} В конструкторе вы можете добавить **пользовательское** действие к кнопке и назначить ему ID. Затем вы можете использовать этот ID в своём коде и обрабатывать его как пользовательское действие. Например, если пользователь нажмёт кастомную кнопку — скажем, **Login** или **Allow notifications**, — будет вызван метод делегата `onCustomAction` с ID действия из билдера. Вы можете задавать собственные ID, например "allowNotifications". ```kotlin showLineNumbers class YourActivity : AppCompatActivity() { private val eventListener = object : AdaptyOnboardingEventListener { override fun onCustomAction(action: AdaptyOnboardingCustomAction, context: Context) { when (action.actionId) { "allowNotifications" -> { // Request notification permissions } } } override fun onError(error: AdaptyOnboardingError, context: Context) { // Handle errors } // ... other required delegate methods } } ```
Пример события (нажмите, чтобы развернуть) ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ```
## Закрытие онбординга \{#closing-onboarding\} Онбординг считается закрытым, когда пользователь нажимает кнопку с назначенным действием **Close**. Вам нужно управлять тем, что происходит при закрытии онбординга. Например: :::important Вам нужно управлять тем, что происходит при закрытии онбординга. Например, необходимо прекратить отображение самого онбординга. ::: Например: ```kotlin override fun onCloseAction(action: AdaptyOnboardingCloseAction, context: Context) { // Dismiss the onboarding screen (context as? Activity)?.onBackPressed() } ```
Пример события (нажмите, чтобы развернуть) ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ```
## Открытие пейвола \{#opening-a-paywall\} :::tip Обрабатывайте это событие, если хотите открыть пейвол внутри онбординга. Если же нужно открыть пейвол после его закрытия, есть более простой способ — обработайте [`AdaptyOnboardingCloseAction`](#closing-onboarding) и откройте пейвол без использования данных события. ::: Самый удобный подход — сделать ID действия равным ID плейсмента пейвола. Тогда после получения `AdaptyOnboardingOpenPaywallAction` можно сразу использовать ID плейсмента, чтобы получить и открыть пейвол: ```kotlin override fun onOpenPaywallAction(action: AdaptyOnboardingOpenPaywallAction, context: Context) { // Get the paywall using the placement ID from the action Adapty.getPaywall(placementId = action.actionId) { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // Get the paywall configuration AdaptyUI.getViewConfiguration(paywall) { result -> when(result) { is AdaptyResult.Success -> { val paywallConfig = result.value // Create and present the paywall val paywallView = AdaptyUI.getPaywallView( activity = this, viewConfig = paywallConfig, products, eventListener = paywallEventListener ) // Add the paywall view to your layout binding.container.addView(paywallView) } is AdaptyResult.Error -> { val error = result.error // handle the error } } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } } ```
Пример события (нажмите, чтобы развернуть) ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ```
## Завершение загрузки онбординга \{#finishing-loading-onboarding\} Когда онбординг завершает загрузку, вызывается этот метод: ```kotlin override fun onFinishLoading(action: AdaptyOnboardingLoadedAction, context: Context) { // Handle loading completion } ```
Пример события (нажмите, чтобы раскрыть) ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ```
## События навигации \{#navigation-events\} Метод `onAnalyticsEvent` вызывается при различных аналитических событиях во время флоу онбординга. Объект `event` может быть одного из следующих типов: |Тип | Описание | |------------|-------------| | `OnboardingStarted` | Когда онбординг загружен | | `ScreenPresented` | Когда отображается любой экран | | `ScreenCompleted` | Когда экран завершён. Включает необязательный `elementId` (идентификатор завершённого элемента) и необязательный `reply` (ответ пользователя). Срабатывает, когда пользователь выполняет любое действие для выхода с экрана. | | `SecondScreenPresented` | Когда отображается второй экран | | `UserEmailCollected` | Срабатывает, когда email пользователя собирается через поле ввода | | `OnboardingCompleted` | Срабатывает, когда пользователь достигает экрана с идентификатором `final`. Если вам нужно это событие, назначьте идентификатор `final` последнему экрану. | | `Unknown` | Для любого нераспознанного типа события. Включает `name` (название неизвестного события) и `meta` (дополнительные метаданные) | Каждое событие содержит `meta`-информацию: | Поле | Описание | |------------|-------------| | `onboardingId` | Уникальный идентификатор флоу онбординга | | `screenClientId` | Идентификатор текущего экрана | | `screenIndex` | Порядковый номер текущего экрана во флоу | | `totalScreens` | Общее количество экранов во флоу | Пример использования аналитических событий для трекинга: ```kotlin override fun onAnalyticsEvent(event: AdaptyOnboardingAnalyticsEvent, context: Context) { when (event) { is AdaptyOnboardingAnalyticsEvent.OnboardingStarted -> { // Отслеживаем начало онбординга trackEvent("onboarding_started", event.meta) } is AdaptyOnboardingAnalyticsEvent.ScreenPresented -> { // Отслеживаем показ экрана trackEvent("screen_presented", event.meta) } is AdaptyOnboardingAnalyticsEvent.ScreenCompleted -> { // Отслеживаем завершение экрана с ответом пользователя trackEvent("screen_completed", event.meta, event.elementId, event.reply) } is AdaptyOnboardingAnalyticsEvent.OnboardingCompleted -> { // Отслеживаем успешное завершение онбординга trackEvent("onboarding_completed", event.meta) } is AdaptyOnboardingAnalyticsEvent.Unknown -> { // Обрабатываем неизвестные события trackEvent(event.name, event.meta) } // При необходимости обработайте другие случаи } } ```
Примеры событий (нажмите, чтобы раскрыть) ```javascript // OnboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // ScreenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // ScreenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // SecondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // UserEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // OnboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ```
--- # File: android-onboarding-input --- --- title: "Обработка данных из онбордингов в Android SDK" description: "Сохраняйте и используйте данные из онбордингов в Android-приложении с помощью Adapty SDK." --- :::tip **Начиная с SDK v4**, вы можете создавать [флоу](android-get-pb-paywalls) как более мощную альтернативу онбордингам. В отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает плавную анимацию, единый внешний вид в стиле Android, быструю загрузку и отсутствие зависимости от среды выполнения WebView. Смотрите [Получение флоу и пейволов](android-get-pb-paywalls) и [Отображение флоу и пейволов](android-present-paywalls), чтобы начать работу. ::: Когда пользователи отвечают на вопрос викторины или вводят данные в поле ввода, вызывается метод `onStateUpdatedAction`. Вы можете сохранить или обработать тип поля в своём коде. Например: ```kotlin override fun onStateUpdatedAction(action: AdaptyOnboardingStateUpdatedAction, context: Context) { // Store user preferences or responses when (val params = action.params) { is AdaptyOnboardingStateUpdatedParams.Select -> { // Handle single selection } is AdaptyOnboardingStateUpdatedParams.MultiSelect -> { // Handle multiple selections } is AdaptyOnboardingStateUpdatedParams.Input -> { // Handle text input } is AdaptyOnboardingStateUpdatedParams.DatePicker -> { // Handle date selection } } } ``` Смотрите формат действия [здесь](https://android.adapty.io/adapty-ui/com.adapty.ui.onboardings.actions/-adapty-onboarding-state-updated-action/).
Примеры сохранённых данных (формат может отличаться в вашей реализации) ```javascript // Example of a saved select action { "elementId": "preference_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "preferences_screen", "screenIndex": 1, "screensTotal": 3 }, "params": { "type": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" } } } // Example of a saved multi-select action { "elementId": "interests_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "screensTotal": 3 }, "params": { "type": "multiSelect", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ] } } // Example of a saved input action { "elementId": "name_input", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "input", "value": { "type": "text", "value": "John Doe" } } } // Example of a saved date picker action { "elementId": "birthday_picker", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "datePicker", "value": { "day": 15, "month": 6, "year": 1990 } } } ```
## Примеры использования \{#use-cases\} ### Обогащение профилей пользователей данными \{#enrich-user-profiles-with-data\} Если вы хотите сразу связать введённые данные с профилем пользователя и не спрашивать одно и то же дважды, нужно [обновить профиль пользователя](android-setting-user-attributes) с введёнными данными при обработке действия. Например, вы просите пользователей ввести имя в текстовое поле с ID `name` и хотите задать значение этого поля как имя пользователя. Также вы просите ввести email в поле `email`. В коде приложения это может выглядеть так: ```kotlin showLineNumbers override fun onStateUpdatedAction(action: AdaptyOnboardingStateUpdatedAction, context: Context) { // Store user preferences or responses when (val params = action.params) { is AdaptyOnboardingStateUpdatedParams.Input -> { // Handle text input val builder = AdaptyProfileParameters.Builder() // Map elementId to appropriate profile field when (action.elementId) { "name" -> { when (val inputParams = params.params) { is AdaptyOnboardingInputParams.Text -> { builder.withFirstName(inputParams.value) } } } "email" -> { when (val inputParams = params.params) { is AdaptyOnboardingInputParams.Email -> { builder.withEmail(inputParams.value) } } } } Adapty.updateProfile(builder.build()) { error -> if (error != null) { // handle the error } } } } } ``` ### Настройка пейволов на основе ответов \{#customize-paywalls-based-on-answers\} Используя квизы в онбординге, вы можете настраивать пейволы, которые показываете пользователям после завершения онбординга. Например, можно спросить пользователей об их опыте в спорте и показывать разные CTA и продукты разным группам пользователей. 1. [Добавьте квиз](onboarding-quizzes) в конструкторе онбординга и назначьте значимые идентификаторы его вариантам ответов. 2. Обработайте ответы квиза на основе их идентификаторов и [задайте пользователям пользовательские атрибуты](android-setting-user-attributes). ```kotlin showLineNumbers override fun onStateUpdatedAction(action: AdaptyOnboardingStateUpdatedAction, context: Context) { // Handle quiz responses and set custom attributes when (val params = action.params) { is AdaptyOnboardingStateUpdatedParams.Select -> { // Handle quiz selection val builder = AdaptyProfileParameters.Builder() // Map quiz responses to custom attributes when (action.elementId) { "experience" -> { // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.withCustomAttribute("experience", params.params.value) } } Adapty.updateProfile(builder.build()) { error -> if (error != null) { // handle the error } } } } } ``` 3. [Создайте сегменты](segments) для каждого значения кастомного атрибута. 4. Создайте [плейсмент](placements) и добавьте [аудитории](audience) для каждого созданного сегмента. 5. [Отобразите пейвол](android-paywalls) для плейсмента в коде приложения. Если в онбординге есть кнопка, открывающая пейвол, реализуйте код пейвола как [реакцию на действие этой кнопки](android-handle-onboarding-events#opening-a-paywall). --- # File: android-sdk-call-order --- --- title: "Порядок вызовов в Android SDK" description: "Избегайте потери премиум-доступа, пропущенной атрибуции и периодических ошибок ADAPTY_NOT_INITIALIZED, вызывая методы Adapty SDK в правильном порядке." --- `Adapty.activate()` должен завершиться до того, как вы вызовете любой другой метод Adapty SDK. До его завершения SDK не имеет состояния. Любой вызов, выполненный до или параллельно с `activate()`, завершится ошибкой [`ADAPTY_NOT_INITIALIZED`](android-sdk-error-handling). Если ваше приложение аутентифицирует пользователей и вы получаете customer user ID после запуска, вызовите `Adapty.identify()` в этот момент. Не вызывайте пользовательские методы до срабатывания колбэка завершения `identify`. Вызовы, которые идут параллельно с ним, либо возвращают ошибку в своём колбэке, либо применяются к анонимному профилю, созданному при активации. В таком случае атрибуция, MMP-идентификаторы вроде `appsflyer_id` и данные об установке не всегда переносятся на идентифицированный профиль. Если ваше приложение не аутентифицирует пользователей, пропустите `identify` и продолжайте работу с анонимным профилем. MMP и аналитические SDK (AppsFlyer, Adjust, Branch, PostHog) подчиняются тому же правилу. Сначала инициализируйте их и дождитесь колбэков с UID, и только после этого вызывайте `Adapty.activate`. Иначе MMP ID попадёт в кратковременный анонимный профиль и не всегда переносится в идентифицированный. Подробнее об особенностях AppsFlyer см. в разделе [AppsFlyer](appsflyer). ## Правильный порядок \{#the-correct-order\} Ваш путь зависит от двух вещей: когда вы узнаёте customer user ID и используете ли вы MMP или аналитический SDK. - **Шаги 2 и 5**: обязательны для каждого приложения. Активируйте SDK, затем вызывайте его методы. - **Шаги 1 и 3**: нужны только при интеграции MMP или аналитического SDK (AppsFlyer, Adjust, Branch, PostHog). - **Шаг 4**: нужен только если ваше приложение аутентифицирует пользователей и получает customer user ID после запуска. Если вы знаете customer user ID в момент запуска приложения, передайте его в `AdaptyConfig.Builder` до вызова `activate()` (шаг 2a). В этом случае анонимный профиль не создаётся, поэтому шаг 4 не нужен. | Шаг | Вызов | Когда | Примечания | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | Инициализируйте MMP или аналитический SDK (AppsFlyer, Adjust, PostHog, Branch) | При запуске приложения, первым делом | Дождитесь коллбэка с UID от MMP, например `getAppsFlyerUID`. | | 2a | `Adapty.activate(context, AdaptyConfig.Builder("KEY").withCustomerUserId(...).build())` | При запуске приложения, после шага 1, если у вас есть customer user ID | Рекомендуется. Анонимный профиль не создаётся. | | 2b | `Adapty.activate(context, AdaptyConfig.Builder("KEY").build())` без `customerUserId` | При запуске приложения, после шага 1, если у вас нет customer user ID (или вы его не собираете) | Adapty создаёт анонимный профиль. | | 3 | `Adapty.setIntegrationIdentifier("appsflyer_id", uid)` для каждого MMP | После шага 2, до любых вызовов, инициированных действиями пользователя | Обязательно, чтобы ID от MMP попали в нужный профиль. | | 4 | `Adapty.identify("YOUR_USER_ID") { error -> ... }` | После шага 3 (или шага 2, если MMP нет), перед шагом 5 — только при пути 2b с аутентификацией | Используйте коллбэк завершения. Параллельные вызовы во время `identify` могут попасть в анонимный профиль. | | 5 | `getPaywall`, `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution`, `updateProfile` | После шага 4, если вызываете `identify`; иначе после шага 3 (или шага 2, если MMP нет) | Эти вызовы требуют стабильного профиля. | :::important Пропуск этих шагов приводит к потере уровня доступа у вернувшихся пользователей, отсутствию `appsflyer_id` в профилях и показу пейволов не той аудитории. ::: ## Установки через web2app и веб-воронки \{#web2app-and-web-funnel-installs\} Если пользователь совершил покупку через веб-чекаут (Stripe, Paddle) и затем установил нативное приложение, первый вызов `activate()` на устройстве создаёт новый анонимный профиль. Этот профиль не связан с веб-профилем. Если вы можете получить customer user ID до запуска приложения (из процесса авторизации или install referrer), передайте его напрямую в `AdaptyConfig.Builder`. В противном случае веб-покупка останется невидимой на устройстве до тех пор, пока вы не вызовете `identify("YOUR_USER_ID")`, а затем `restorePurchases`. Какие метаданные передавать при каждом веб-чекауте, смотрите здесь: - [Stripe](stripe) - [Paddle](paddle) --- # File: android-optimize-paywall-fetching --- --- title: "Оптимизация загрузки пейвола в Android SDK" description: "Надёжная загрузка пейволов в Adapty: тайминг, кэширование и паттерны резервного отображения для Android." --- Надёжная загрузка пейвола на Android решает три задачи: быстрый рендеринг, возврат пейвола с таргетингом по аудитории и корректный фолбэк при медленной сети. Правила ниже описывают тайминг, кэширование и паттерны резервного отображения. :::tip Предполагается, что `Adapty.activate()` и `Adapty.identify()` уже выполнены. См. [Порядок вызовов в Android SDK](android-sdk-call-order). ::: ## Правила и подводные камни \{#rules-and-pitfalls\} | Делать | Не делать | Почему | |---|---|---| | Загружайте только тот плейсмент, который собираетесь показать. | Предзагружать все плейсменты одновременно при запуске. | Массовая предзагрузка блокирует главный поток и вызывает чёрный экран во время пакетного запроса. | | Вызывайте `getPaywall` после того, как атрибуция успела разрешиться — например, через 1–2 секунды после `activate` или после срабатывания `setOnProfileUpdatedListener`. | Вызывать `getPaywall` в `Application.onCreate()`. | Атрибуция ещё не применилась. Пейвол разрешается по аудитории по умолчанию и молча обходит сегменты и персонализацию ASA. | | Задайте `loadTimeout` и настройте [резервный пейвол](fallback-paywalls) для каждого плейсмента. | Ждать ответа `getPaywall` бесконечно. | Без таймаута пользователи с плохим соединением видят пустой экран до тех пор, пока сеть не ответит — или просто закрывают приложение. | Подробнее о параметрах `fetchPolicy` и `loadTimeout` — в разделе [Получение пейволов и продуктов](fetch-paywalls-and-products-android), о выборе подходящего плейсмента — в разделе [Плейсменты](placements). ## Настройка для слабого интернета \{#tune-for-poor-connectivity\} Для рынков со стабильно слабым интернетом (сельские районы, транспорт, регионы с плохой маршрутизацией): - Устанавливайте `fetchPolicy` в `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad` при каждом запросе, кроме самого первого. - Настройте [резервный пейвол](fallback-paywalls) для каждого плейсмента в дашборде Adapty. - Установите `loadTimeout` в 3–5 секунд и принимайте резервный пейвол при срабатывании таймаута. - Не блокируйте отображение пейвола на `getProfile`. Вызывайте `getPaywall` независимо, чтобы медленная загрузка профиля не тормозила интерфейс. --- # File: android-test --- --- title: "Тест и релиз в Android SDK" description: "Узнайте, как проверить статус подписки в приложении на Android с помощью Adapty." --- Если вы уже интегрировали Adapty SDK в своё Android-приложение, стоит убедиться, что всё настроено правильно и покупки работают как ожидается. Для этого нужно протестировать как интеграцию SDK, так и сам процесс покупки в песочнице Google Play. ## Тестирование приложения \{#test-your-app\} Подробное руководство по тестированию встроенных покупок, включая тестирование в песочнице и проверку в закрытом треке, см. в нашем [гайде по тестированию](testing-on-android). ## Подготовка к релизу \{#prepare-for-release\} Перед отправкой приложения в стор пройдитесь по [чеклисту для релиза](release-checklist) и убедитесь, что: - Подключение к стору и серверные уведомления настроены - Покупки выполняются и передаются в Adapty - Доступ открывается и восстанавливается корректно - Требования к конфиденциальности и ревью соблюдены --- # File: android-sdk-error-handling --- --- title: "Обработка ошибок в Android SDK" description: "Эффективная обработка ошибок Android SDK с помощью руководства по устранению неполадок Adapty." --- Каждая ошибка, возвращаемая SDK, имеет тип `AdaptyError`. :::tip **Включите подробное логирование перед отладкой.** Большинство ошибок `AdaptyError` оборачивают базовую ошибку Play Billing, сети или бэкенда. При включённом подробном логировании (`Adapty.logLevel = AdaptyLogLevel.VERBOSE` — см. [Логирование](sdk-installation-android#logging)) эта ошибка выводится в консоль, что обычно указывает на реальную причину. ::: :::important Если эти решения не помогли, перейдите в раздел [Другие проблемы](#other-issues) и выполните описанные шаги перед обращением в поддержку — это поможет нам быстрее разобраться в ситуации. ::: | Ошибка | Решение | |----------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | UNKNOWN | Неизвестная или непредвиденная ошибка. | | [ITEM_UNAVAILABLE](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_UNAVAILABLE()) | Ошибка чаще всего возникает на этапе тестирования. Возможные причины: продукты отсутствуют в продакшене или пользователь не входит в группу тестировщиков в Google Play. | | ADAPTY_NOT_INITIALIZED | SDK Adapty не активирован.
Чаще всего возникает, когда экран-заставка или ранний UI-хук вызывает методы Adapty до завершения `Adapty.activate`. Проблема непостоянна и может не воспроизводиться на эмуляторе из-за отличий в таймингах реального устройства. Дождитесь завершения `Adapty.activate`, прежде чем вызывать другие методы SDK. Полная последовательность описана в [Порядок вызовов в Android SDK](android-sdk-call-order). Также необходимо правильно [настроить Adapty SDK](sdk-installation-android#activate-adapty-module-of-adapty-sdk) с помощью метода `Adapty.activate`. | | PROFILE_WAS_CHANGED | Профиль пользователя изменился во время выполнения операции.
Это происходит, когда метод вызывается в то время, как `Adapty.identify` ещё не завершился — вызов попадает на профиль, который вот-вот будет заменён, и SDK его отклоняет. Дождитесь завершения `Adapty.identify`, прежде чем вызывать другие методы SDK. См. [Порядок вызовов в Android SDK](android-sdk-call-order). | | PRODUCT_NOT_FOUND | Продукт, запрошенный для покупки, недоступен в сторе. | | INVALID_JSON |

JSON резервного пейвола некорректен.

Исправьте дефолтный английский пейвол, затем замените некорректные локальные пейволы. Подробнее об исправлении пейвола — в разделе [Настройка пейвола с помощью Remote Config](customize-paywall-with-remote-config), о замене локальных пейволов — в разделе [Определение резервных пейволов](fallback-paywalls).

| |

CURRENT_SUBSCRIPTION_TO_UPDATE

\_NOT_FOUND_IN_HISTORY

| Исходная подписка, которую нужно заменить, не найдена в активных подписках. | | [BILLING_SERVICE_TIMEOUT](https://developer.android.com/google/play/billing/errors#service_timeout_error_code_-3) | Запрос достиг максимального таймаута до того, как Google Play успел ответить. Причиной может быть, например, задержка при выполнении действия, запрошенного вызовом Play Billing Library. | | [FEATURE_NOT_SUPPORTED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#FEATURE_NOT_SUPPORTED()) | Запрошенная функция не поддерживается Play Store на данном устройстве. | | [BILLING_SERVICE_DISCONNECTED](https://developer.android.com/google/play/billing/errors#service_disconnected_error_code_-1) | Соединение клиентского приложения с сервисом Google Play Store через `BillingClient` разорвано. | | [BILLING_SERVICE_UNAVAILABLE](https://developer.android.com/google/play/billing/errors#service_unavailable_error_code_2) | Сервис Google Play Billing в данный момент недоступен. В большинстве случаев причиной является проблема с сетевым соединением между клиентским устройством и серверами Google Play Billing. | | [BILLING_UNAVAILABLE](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) |

Ошибка биллинга в процессе покупки. Возможные причины:

1. Приложение Play Store на устройстве отсутствует или устарело.

2. Пользователь находится в неподдерживаемой стране.

3. Пользователь входит в корпоративный аккаунт, в котором администратор отключил покупки.

4. Google Play не смог списать средства со способа оплаты пользователя (например, истёк срок действия карты).

5. Пользователь не авторизован в приложении Play Store.

| | [DEVELOPER_ERROR](https://developer.android.com/google/play/billing/errors#developer_error) | API используется некорректно. | | [BILLING_ERROR](https://developer.android.com/google/play/billing/errors#error_error_code_6) | Внутренняя ошибка самого Google Play. | | [ITEM_ALREADY_OWNED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_ALREADY_OWNED()) | Продукт уже куплен. | | [ITEM_NOT_OWNED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_NOT_OWNED()) | Запрошенное действие с товаром не выполнено, так как он не принадлежит пользователю. | | [BILLING_NETWORK_ERROR](https://developer.android.com/google/play/billing/errors#network_error_error_code_12) | Проблема с сетевым соединением между устройством и серверами Play. | | NO_PRODUCT_IDS_FOUND |

Ни один из продуктов пейвола недоступен в сторе.

Если вы столкнулись с этой ошибкой, выполните следующие шаги:

  1. Проверьте, добавлены ли все продукты в дашборд Adapty.
  2. Убедитесь, что **Package name** вашего приложения совпадает с указанным в Google Play Console.
  3. Проверьте, совпадают ли идентификаторы продуктов из сторов с теми, что добавлены в дашборд. Обратите внимание: идентификаторы не должны содержать Bundle ID, если только он не включён в стор.
  4. Убедитесь, что статус платной версии приложения **Active** в налоговых настройках Google. Проверьте актуальность налоговой информации и действительность сертификатов.
  5. Проверьте, привязан ли банковский счёт к приложению — это необходимо для монетизации.
  6. Убедитесь, что продукты доступны в вашем регионе.
  7. Убедитесь, что приложение находится в одном из треков тестирования. Трек **Internal testing** — самый простой вариант: он не требует проверки и скрывает приложение от пользователей.
| | NO_PURCHASES_TO_RESTORE | Google Play не нашёл покупку для восстановления. | | AUTHENTICATION_ERROR | Необходимо правильно [настроить Adapty SDK](sdk-installation-android#activate-adapty-module-of-adapty-sdk) с помощью метода `Adapty.activate`. | | BAD_REQUEST | Некорректный запрос.
Убедитесь, что выполнены все шаги, необходимые для [интеграции с Google Play](google-play-store-connection-configuration). | | SERVER_ERROR | Ошибка сервера. | | REQUEST_FAILED | Сетевая ошибка, которую не удаётся классифицировать точнее. | | DECODING_FAILED | Не удалось декодировать ответ.
Проверьте код и убедитесь, что передаваемые параметры корректны. Например, эта ошибка может означать, что используется недействительный API-ключ. | | ANALYTICS_DISABLED | Обработка аналитических событий невозможна, так как вы [отключили её](analytics-integration#disabling-external-analytics-for-a-specific-customer). | | WRONG_PARAMETER | Один или несколько параметров некорректны: пустое значение там, где оно недопустимо, неверный тип и т. д. | ## Другие проблемы \{#other-issues\} Если вы ещё не нашли решение, можно попробовать следующее: - **Обновление SDK до последней версии**: мы всегда рекомендуем обновляться до последних версий SDK — они более стабильны и содержат исправления известных проблем. - **Обратитесь в службу поддержки или получите помощь от других разработчиков** на [форуме поддержки](https://adapty.featurebase.app/). - **Напишите в поддержку на [support@adapty.io](mailto:support@adapty.io) или через чат**: если вы не готовы обновлять SDK или это не помогло, свяжитесь с нашей командой поддержки. Обратите внимание, что проблема будет решена быстрее, если вы [включите подробное логирование](sdk-installation-android#logging) и поделитесь логами с командой. Также можно приложить соответствующие фрагменты кода. --- # File: migration-to-android-sdk-v4 --- --- title: "Миграция Adapty Android SDK на v. 4.0" description: "Мигрируйте на Adapty Android SDK v4.0, заменив paywall API на flow API, совместимые как с Flow Builder, так и с Paywall Builder." --- Adapty Android SDK 4.0 вводит флоу и переименовывает paywall API соответствующим образом. Новые API работают как с новым Flow Builder, так и с существующим Paywall Builder — никаких изменений в настройках дашборда Adapty не требуется. ## Краткая справка \{#quick-reference\} | v3 | v4 | |---|---| | `Adapty.getPaywall(placementId, locale)` | `Adapty.getFlow(placementId)` | | `Adapty.getPaywallForDefaultAudience(placementId, locale)` | `Adapty.getFlowForDefaultAudience(placementId)` | | `AdaptyUI.getViewConfiguration(paywall)` | `AdaptyUI.getFlowConfiguration(flow, locale)` | | `AdaptyUI.LocalizedViewConfiguration` | `AdaptyUI.FlowConfiguration` | | `Adapty.getPaywallProducts(paywall)` | `Adapty.getPaywallProducts(flow)` | | `Adapty.logShowPaywall(paywall)` | `Adapty.logShowFlow(flow)` | | `AdaptyPaywall` | `AdaptyFlow` | | `AdaptyUI.getPaywallView(...)` | `AdaptyUI.getFlowView(...)` | | `AdaptyPaywallView` | `AdaptyFlowView` | | `AdaptyPaywallScreen` (Compose) | `AdaptyFlowScreen` | | `showPaywall(...)` | `showFlow(...)` | | `AdaptyPaywallInsets` | `AdaptyFlowInsets` | | `AdaptyUiEventListener` | `AdaptyFlowEventListener` | | `AdaptyUiDefaultEventListener` | `AdaptyFlowDefaultEventListener` | | `onPaywallShown` / `onPaywallClosed` | `onFlowShown` / `onFlowClosed` | | `onRenderingError` | `onError` | | `Adapty.updateAttribution(attribution, source)` (`source: String`) | `Adapty.updateAttribution(attribution, source)` (`source: AdaptyAttributionSource`) | | `Adapty.setIntegrationIdentifier(key, value)` | `Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier)` | `AdaptyPaywallProduct` сохраняет своё название — продукты по-прежнему принадлежат флоу, а `getPaywallProducts` теперь принимает `AdaptyFlow`. Остальные методы `AdaptyFlowEventListener` (`onProductSelected`, `onPurchaseStarted`, `onPurchaseFinished`, `onPurchaseFailure`, `onRestoreSuccess`, `onRestoreFailure`, `onActionPerformed`, `onAwaitingPurchaseParams`, `onLoadingProductsFailure` и т. д.) сохраняют свои названия и сигнатуры. ## Установка \{#installation\} Укажите версию `adapty-bom` `4.0.0` (или новее) и синхронизируйте проект. BOM автоматически подберёт совместимые версии `android-sdk` и `android-ui`. Инструкции по добавлению зависимостей — в разделе [Установка Adapty SDK](sdk-installation-android). ## Удалённые и устаревшие API \{#removed-and-deprecated-apis\} - **`Adapty.makePurchase(activity, product, subscriptionUpdateParams, isOfferPersonalized, callback)`** — удалён. Этот перегруженный метод был помечен как устаревший в v3. Передавайте те же параметры через `AdaptyPurchaseParameters`: ```diff showLineNumbers - Adapty.makePurchase(activity, product, subscriptionUpdateParams, isOfferPersonalized) { result -> /* ... */ } + val params = AdaptyPurchaseParameters.Builder() + .withSubscriptionUpdateParams(subscriptionUpdateParams) + .withOfferPersonalized(isOfferPersonalized) + .build() + Adapty.makePurchase(activity, product, params) { result -> /* ... */ } ``` - **Онбординги устарели.** `AdaptyUI.getOnboardingView` и `AdaptyUI.getOnboardingConfiguration` помечены как `@Deprecated` в версии 4.0 — переносите онбординги во флоу, созданные в [Flow Builder](adapty-flow-builder). ## Получение флоу \{#fetching-flows\} ### getPaywall + getViewConfiguration → getFlow + getFlowConfiguration Тип возвращаемого значения при получении данных изменяется с `AdaptyPaywall` на `AdaptyFlow`, а загрузчик конфигурации переименован с `AdaptyUI.getViewConfiguration` на `AdaptyUI.getFlowConfiguration` (возвращает `AdaptyUI.FlowConfiguration` вместо `AdaptyUI.LocalizedViewConfiguration`). Параметр `locale` перемещён из вызова получения данных в `getFlowConfiguration`: ```diff showLineNumbers - Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en") { result -> + Adapty.getFlow("YOUR_PLACEMENT_ID") { result -> if (result is AdaptyResult.Success) { - val paywall = result.value - if (!paywall.hasViewConfiguration) return@getPaywall - AdaptyUI.getViewConfiguration(paywall) { configResult -> + val flow = result.value + if (!flow.hasViewConfiguration) return@getFlow + AdaptyUI.getFlowConfiguration(flow, locale = "en") { configResult -> if (configResult is AdaptyResult.Success) { val flowConfiguration = configResult.value } } } } ``` ### getPaywallProducts(paywall) → getPaywallProducts(flow) `getPaywallProducts` теперь принимает `AdaptyFlow`, возвращаемый методом `Adapty.getFlow`: ```diff showLineNumbers - Adapty.getPaywallProducts(paywall) { result -> /* products */ } + Adapty.getPaywallProducts(flow) { result -> /* products */ } ``` ## Отслеживание просмотров флоу \{#tracking-flow-views\} ### logShowPaywall → logShowFlow `logShowPaywall` переименован в `logShowFlow` и теперь принимает `AdaptyFlow` вместо `AdaptyPaywall`. Событие по-прежнему фиксируется для той же вариации, поэтому существующие метрики воронок и A/B-тестов продолжат работать без изменений в дашборде. ```diff showLineNumbers - Adapty.logShowPaywall(paywall) + Adapty.logShowFlow(flow) ``` Как и в v3, вам не нужно вызывать этот метод при отображении флоу или пейволов, отрендеренных с помощью [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder) — Adapty отслеживает эти просмотры автоматически. ## Отображение флоу \{#displaying-flows\} ### getPaywallView / AdaptyPaywallView → getFlowView / AdaptyFlowView Переименуйте фабричный метод и тип представления, а также передайте `AdaptyUI.FlowConfiguration`: ```diff showLineNumbers - val paywallView = AdaptyUI.getPaywallView( - activity, - viewConfiguration, - products, - eventListener, - ) + val flowView = AdaptyUI.getFlowView( + activity, + flowConfiguration, + products, + eventListener, + ) ``` Если вы создаёте представление напрямую, метод показа также переименован: ```diff showLineNumbers - val paywallView = AdaptyPaywallView(activity) - paywallView.showPaywall(viewConfiguration, products, eventListener) + val flowView = AdaptyFlowView(activity) + flowView.showFlow(flowConfiguration, products, eventListener) ``` В XML-разметке обновите тег представления: ```diff showLineNumbers - + ``` Необязательный параметр `personalizedOfferResolver` удалён из `getFlowView` / `showFlow` / `AdaptyFlowScreen`. Чтобы указать персонализированную цену, задайте её для каждого продукта через `onAwaitingPurchaseParams` (`AdaptyPurchaseParameters.Builder().withOfferPersonalized(true)`). Новый необязательный параметр `customAssets` позволяет переопределять изображения и видео во время выполнения — подробнее см. в разделе [Кастомизация ресурсов](android-get-pb-paywalls#customize-assets). ### AdaptyPaywallScreen → AdaptyFlowScreen В Jetpack Compose переименуйте компонуемый элемент и обновите параметр конфигурации: ```diff showLineNumbers - AdaptyPaywallScreen( - viewConfiguration, + AdaptyFlowScreen( + flowConfiguration, products, eventListener, ) ``` ## Обработка событий \{#handling-events\} Слушатель событий переименован с `AdaptyUiEventListener` на `AdaptyFlowEventListener` (а `AdaptyUiDefaultEventListener` — на `AdaptyFlowDefaultEventListener`). Большинство названий методов не изменились; переименованы только колбэки жизненного цикла и рендеринга: ```diff showLineNumbers - class YourListener : AdaptyUiDefaultEventListener() { + class YourListener : AdaptyFlowDefaultEventListener() { - override fun onPaywallShown(context: Context) {} - override fun onPaywallClosed() {} + override fun onFlowShown(context: Context) {} + override fun onFlowClosed() {} - override fun onRenderingError(error: AdaptyError, context: Context) {} + override fun onError(error: AdaptyError, context: Context) {} } ``` Тела существующих обработчиков менять не нужно — достаточно переименовать тип и переопределения. `onError` срабатывает для тех же ошибок рендеринга, что и `onRenderingError`, плюс для других ошибок времени выполнения, не связанных с покупками. Полный список коллбэков см. в разделе [Обработка событий флоу и пейвола](android-handling-events). В v4 также добавлен колбэк `onBackPressed(context): Boolean`, и его поведение по умолчанию изменилось. Раньше нажатие системной кнопки «Назад» (или жест «назад») передавалось вашей activity или фрагменту, что обычно закрывало пейвол. В v4 реализация по умолчанию перехватывает это нажатие, поэтому **системная кнопка «Назад» больше не закрывает флоу самостоятельно** — аналогично iOS, где флоу нельзя закрыть системным жестом. Предоставьте пользователям явный способ выйти (кнопку **Close** или действие `on_device_back`), либо переопределите `onBackPressed` и верните `false`, чтобы восстановить прежнее поведение. Подробнее см. в разделе [Системная кнопка «Назад»](android-handling-events#system-back-button). Стандартный обработчик покупки также больше не закрывает экран. В v3 стандартный `onPurchaseFinished` закрывал пейвол после любой завершённой покупки, которая не была отменой со стороны пользователя (успешная или ожидающая покупка). В v4 он ничего не делает, поэтому **флоу остаётся открытым после покупки, пока вы сами его не закроете** — поведение совпадает с iOS. Если вы полагались на автоматическое закрытие, закройте экран самостоятельно после завершения покупки. Пример см. в разделе [Успешная, отменённая или ожидающая покупка](android-handling-events#successful-canceled-or-pending-purchase). ## Идентификаторы атрибуции и интеграций \{#attribution-and-integration-identifiers\} ### updateAttribution Параметр `source` меняется с `String` на новый тип `AdaptyAttributionSource`, а `attribution` теперь является `Map` (также доступна перегрузка с `String` в формате JSON). Используйте один из предопределённых источников: ```diff showLineNumbers - Adapty.updateAttribution(attribution, "appsflyer") { error -> /* handle the error */ } + Adapty.updateAttribution(attribution, AdaptyAttributionSource.APPSFLYER) { error -> /* handle the error */ } ``` Предопределённые источники: `AdaptyAttributionSource.APPLE_ADS`, `.ADJUST`, `.APPSFLYER`, `.BRANCH`, `.TENJIN`. Для любого другого источника создайте его из строки: `AdaptyAttributionSource("your_source")`. ### setIntegrationIdentifier `setIntegrationIdentifier(key, value)` заменён методом, который принимает одно или несколько значений `AdaptyIntegrationIdentifier`. Создавайте каждый идентификатор с помощью удобного метода вместо передачи строкового ключа напрямую: ```diff showLineNumbers - Adapty.setIntegrationIdentifier("appsflyer_id", appsFlyerId) { error -> /* handle the error */ } + Adapty.setIntegrationIdentifier(AdaptyIntegrationIdentifier.appsflyerId(appsFlyerId)) { error -> /* handle the error */ } ``` Можно задать несколько идентификаторов в одном вызове: ```kotlin showLineNumbers Adapty.setIntegrationIdentifier( listOf( AdaptyIntegrationIdentifier.appsflyerId(appsFlyerId), AdaptyIntegrationIdentifier.adjustDeviceId(adjustDeviceId), ) ) { error -> /* handle the error */ } ``` Замените каждую старую строку ключа соответствующим удобным методом: | v3 key | v4 метод `AdaptyIntegrationIdentifier` | |---|---| | `"adjust_device_id"` | `adjustDeviceId(value)` | | `"airbridge_device_id"` | `airbridgeDeviceId(value)` | | `"amplitude_user_id"` | `amplitudeUserId(value)` | | `"amplitude_device_id"` | `amplitudeDeviceId(value)` | | `"appmetrica_device_id"` | `appmetricaDeviceId(value)` | | `"appmetrica_profile_id"` | `appmetricaProfileId(value)` | | `"appsflyer_id"` | `appsflyerId(value)` | | `"branch_id"` | `branchId(value)` | | `"facebook_anonymous_id"` | `facebookAnonymousId(value)` | | `"firebase_app_instance_id"` | `firebaseAppInstanceId(value)` | | `"mixpanel_user_id"` | `mixpanelUserId(value)` | | `"one_signal_subscription_id"` | `oneSignalSubscriptionId(value)` | | `"one_signal_player_id"` | `oneSignalPlayerId(value)` | | `"posthog_distinct_user_id"` | `posthogDistinctUserId(value)` | | `"pushwoosh_hwid"` | `pushwooshHWID(value)` | | `"tenjin_analytics_installation_id"` | `tenjinAnalyticsInstallationId(value)` | Для ключа, которого нет в этом списке, создайте идентификатор напрямую из пользовательского `Key`: `AdaptyIntegrationIdentifier(AdaptyIntegrationIdentifier.Key("custom"), customValue)`. --- # File: migration-to-android-312 --- --- title: "Миграция Adapty Android SDK на v3.12" description: "Перейдите на Adapty Android SDK v3.12 для повышения производительности и новых возможностей монетизации." --- В Adapty SDK 3.12.0 мы удалили метод `logShowOnboarding` из SDK. Если вы использовали этот метод, он будет недоступен после обновления SDK до версии 3.12 и выше. Вместо этого вы можете [создавать онбординги в конструкторе онбордингов Adapty без кода](onboardings). Аналитика по этим онбордингам отслеживается автоматически, и у вас есть широкие возможности для кастомизации. --- # File: migration-to-android-310 --- --- title: "Гайд по миграции на Android Adapty SDK 3.10.0" description: "" --- Adapty SDK 3.10.0 — это мажорный релиз, который принёс ряд улучшений, однако может потребовать нескольких шагов миграции с вашей стороны: 1. `AdaptyUiPersonalizedOfferResolver` был удалён. Если вы его используете, передайте его в коллбэке `onAwaitingPurchaseParams`. 2. Обновите сигнатуру метода `onAwaitingSubscriptionUpdateParams` для пейволов Paywall Builder. ## Обновление коллбэка параметров покупки \{#update-purchase-parameters-callback\} Метод `onAwaitingSubscriptionUpdateParams` был переименован в `onAwaitingPurchaseParams` и теперь использует `AdaptyPurchaseParameters` вместо `AdaptySubscriptionUpdateParameters`. Это позволяет указывать параметры замены подписки (crossgrade) и отмечать, является ли цена персонализированной ([подробнее](https://developer.android.com/google/play/billing/integrate#personalized-price)), а также задавать другие параметры покупки. ```diff showLineNumbers - override fun onAwaitingSubscriptionUpdateParams( - product: AdaptyPaywallProduct, - context: Context, - onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback, - ) { - onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...)) - } + override fun onAwaitingPurchaseParams( + product: AdaptyPaywallProduct, + context: Context, + onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback, + ): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked { + onPurchaseParamsReceived( + AdaptyPurchaseParameters.Builder() + .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) + .withOfferPersonalized(true) + .build() + ) + return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked + } ``` Если дополнительные параметры не нужны, можно воспользоваться упрощённым вариантом: ```kotlin showLineNumbers + override fun onAwaitingPurchaseParams( product: AdaptyPaywallProduct, context: Context, onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback, ): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked { onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty) return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked } ``` --- # File: migration-to-android-sdk-34 --- --- title: "Миграция Android SDK Adapty на версию 3.4" description: "Мигрируйте на Android SDK Adapty v3.4 для улучшения производительности и новых функций монетизации." --- Adapty SDK 3.4.0 — это мажорный релиз, который содержит улучшения, требующие шагов по миграции с вашей стороны. ## Обновите файлы резервных пейволов \{#update-fallback-paywall-files\} Обновите файлы резервных пейволов, чтобы обеспечить совместимость с новой версией SDK: 1. [Скачайте обновлённые файлы резервных пейволов](fallback-paywalls) из дашборда Adapty. 2. [Замените существующие резервные пейволы в своём мобильном приложении](android-use-fallback-paywalls) на новые файлы. ## Обновление реализации Observer Mode \{#update-implementation-of-observer-mode\} Если вы используете Observer Mode, обновите его реализацию. В предыдущих версиях требовалось восстанавливать покупки, чтобы Adapty мог распознавать транзакции, совершённые через вашу собственную инфраструктуру, — в Observer Mode у Adapty не было к ним прямого доступа. Если вы использовали пейволы, также нужно было вручную связывать каждую транзакцию с пейволом, который её инициировал. В новой версии вы должны явно сообщать о каждой транзакции, чтобы Adapty её распознала. Если вы используете пейволы, также нужно передавать ID варианта, чтобы связать транзакцию с использованным пейволом. :::warning **Не пропускайте отчёт о транзакции!** Если вы не вызовете `reportTransaction`, Adapty не распознает транзакцию, она не появится в аналитике и не будет отправлена в интеграции. ::: ```diff showLineNumbers - Adapty.restorePurchases { result -> - if (result is AdaptyResult.Success) { - // success - } - } - - Adapty.setVariationId(transactionId, variationId) { error -> - if (error == null) { - // success - } - } + val transactionInfo = TransactionInfo.fromPurchase(purchase) + + Adapty.reportTransaction(transactionInfo, variationId) { result -> + if (result is AdaptyResult.Success) { + // success + } + } ``` ```diff showLineNumbers - Adapty.restorePurchases(result -> { - if (result instanceof AdaptyResult.Success) { - // success - } - }); - - Adapty.setVariationId(transactionId, variationId, error -> { - if (error == null) { - // success - } - }); + TransactionInfo transactionInfo = TransactionInfo.fromPurchase(purchase); + + Adapty.reportTransaction(transactionInfo, variationId, result -> { + if (result instanceof AdaptyResult.Success) { + // success + } + }); ``` --- # File: migration-to-android330 --- --- title: "Миграция Adapty Android SDK на v3.3" description: "Перейдите на Adapty Android SDK v3.3 для улучшения производительности и новых функций монетизации." --- Adapty SDK 3.3.0 — это мажорный релиз, который принёс ряд улучшений, однако для перехода на него могут потребоваться дополнительные шаги миграции. 1. Обновите способ обработки покупок в пейволах, созданных без Paywall Builder. Перестаньте обрабатывать коды ошибок `USER_CANCELED` и `PENDING_PURCHASE`. Отменённая покупка больше не считается ошибкой и теперь будет отображаться в результатах покупки без ошибок. 2. Замените события `onPurchaseCanceled` и `onPurchaseSuccess` новым событием `onPurchaseFinished` для пейволов, созданных с помощью Paywall Builder. Это изменение связано с той же причиной: отменённые покупки больше не считаются ошибками и будут включены в результаты покупки без ошибок. 3. Измените сигнатуру метода `onAwaitingSubscriptionUpdateParams` для пейволов Paywall Builder. 4. Обновите метод, используемый для предоставления резервных пейволов, если вы передаёте URI файла напрямую. 5. Обновите конфигурации интеграций для Adjust, AirBridge, Amplitude, AppMetrica, Appsflyer, Branch, Facebook Ads, Firebase и Google Analytics, Mixpanel, OneSignal, Pushwoosh. ## Обновление процесса покупки \{#update-making-purchase\} Ранее отменённые и ожидающие покупки считались ошибками и возвращали коды `USER_CANCELED` и `PENDING_PURCHASE` соответственно. Теперь для обозначения отменённых, успешных и ожидающих покупок используется новый класс `AdaptyPurchaseResult`. Обновите код покупки следующим образом: ~~~diff Adapty.makePurchase(activity, product) { result -> when (result) { is AdaptyResult.Success -> { - val info = result.value - val profile = info?.profile - - if (profile?.accessLevels?.get("YOUR_ACCESS_LEVEL")?.isActive == true) { - // Grant access to the paid features - } + when (val purchaseResult = result.value) { + is AdaptyPurchaseResult.Success -> { + val profile = purchaseResult.profile + if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { + // Grant access to the paid features + } + } + + is AdaptyPurchaseResult.UserCanceled -> { + // Handle the case where the user canceled the purchase + } + + is AdaptyPurchaseResult.Pending -> { + // Handle deferred purchases (e.g., the user will pay offline with cash + } + } } is AdaptyResult.Error -> { val error = result.error // Handle the error } } } ~~~ Полный пример кода смотрите на странице [Совершение покупок в мобильном приложении](android-making-purchases#make-purchase). ## Изменение событий покупки в Paywall Builder \{#modify-paywall-builder-purchase-events\} 1. Добавьте событие `onPurchaseFinished`: ```diff showLineNumbers + public override fun onPurchaseFinished( + purchaseResult: AdaptyPurchaseResult, + product: AdaptyPaywallProduct, + context: Context, + ) { + when (purchaseResult) { + is AdaptyPurchaseResult.Success -> { + // Grant access to the paid features + } + is AdaptyPurchaseResult.UserCanceled -> { + // Handle the case where the user canceled the purchase + } + is AdaptyPurchaseResult.Pending -> { + // Handle deferred purchases (e.g., the user will pay offline with cash) + } + } + } ``` Для полного примера кода ознакомьтесь с разделом [Успешная, отменённая или ожидающая покупка](android-handling-events#successful-canceled-or-pending-purchase) и описанием события. 2. Удалите обработку события `onPurchaseCancelled`: ```diff showLineNumbers - public override fun onPurchaseCanceled( - product: AdaptyPaywallProduct, - context: Context, - ) {} ``` 3. Удалите `onPurchaseSuccess`: ```diff showLineNumbers - public override fun onPurchaseSuccess( - profile: AdaptyProfile?, - product: AdaptyPaywallProduct, - context: Context, - ) { - // Your logic on successful purchase - } ``` ## Изменение сигнатуры метода onAwaitingSubscriptionUpdateParams \{#change-the-signature-of-onawaitingsubscriptionupdateparams-method\} Теперь, если новая подписка приобретается, пока другая ещё активна, вызывайте `onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters...))`, если новая подписка должна заменить текущую активную, или `onSubscriptionUpdateParamsReceived(null)`, если активная подписка должна оставаться активной, а новая — добавиться отдельно: ```diff showLineNumbers - public override fun onAwaitingSubscriptionUpdateParams( - product: AdaptyPaywallProduct, - context: Context, - ): AdaptySubscriptionUpdateParameters? { - return AdaptySubscriptionUpdateParameters(...) - } + public override fun onAwaitingSubscriptionUpdateParams( + product: AdaptyPaywallProduct, + context: Context, + onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback, + ) { + onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...)) + } ``` Смотрите раздел документации [Обновление подписки](android-handling-events#upgrade-subscription) с финальным примером кода. ## Обновление передачи резервных пейволов \{#update-providing-fallback-paywalls\} Если вы передаёте URI файла для предоставления резервных пейволов, обновите этот код следующим образом: ```diff showLineNumbers val fileUri: Uri = // Get the URI for the file with fallback paywalls - Adapty.setFallbackPaywalls(fileUri, callback) + Adapty.setFallbackPaywalls(FileLocation.fromFileUri(fileUri), callback) ``` ```diff showLineNumbers Uri fileUri = // Получите URI для файла с резервными пейволами - Adapty.setFallbackPaywalls(fileUri, callback); + Adapty.setFallbackPaywalls(FileLocation.fromFileUri(fileUri), callback); ``` ## Обновление конфигурации SDK сторонних интеграций \{#update-third-party-integration-sdk-configuration\} Чтобы интеграции корректно работали с Adapty Android SDK 3.3.0 и выше, обновите конфигурации SDK для следующих интеграций, как описано в разделах ниже. ### Adjust Обновите код вашего мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с Adjust](adjust#connect-your-app-to-adjust). ```diff showLineNumbers - Adjust.getAttribution { attribution -> - if (attribution == null) return@getAttribution - - Adjust.getAdid { adid -> - if (adid == null) return@getAdid - - Adapty.updateAttribution(attribution, AdaptyAttributionSource.ADJUST, adid) { error -> - // Handle the error - } - } - } + Adjust.getAdid { adid -> + if (adid == null) return@getAdid + + Adapty.setIntegrationIdentifier("adjust_device_id", adid) { error -> + if (error != null) { + // Handle the error + } + } + } + + Adjust.getAttribution { attribution -> + if (attribution == null) return@getAttribution + + Adapty.updateAttribution(attribution, "adjust") { error -> + if (error != null) { + // Handle the error + } + } + } ``` ```diff showLineNumbers val config = AdjustConfig(context, adjustAppToken, environment) config.setOnAttributionChangedListener { attribution -> attribution?.let { attribution -> - Adapty.updateAttribution(attribution, AdaptyAttributionSource.ADJUST) { error -> + Adapty.updateAttribution(attribution, "adjust") { error -> if (error != null) { // Handle the error } } } } Adjust.onCreate(config) ``` ### AirBridge Обновите код вашего мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [Настройка SDK для интеграции с AirBridge](airbridge#connect-your-app-to-airbridge). ```diff showLineNumbers Airbridge.getDeviceInfo().getUUID(object: AirbridgeCallback.SimpleCallback() { override fun onSuccess(result: String) { - val params = AdaptyProfileParameters.Builder() - .withAirbridgeDeviceId(result) - .build() - Adapty.updateProfile(params) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("airbridge_device_id", result) { error -> + if (error != null) { + // Handle the error + } + } } override fun onFailure(throwable: Throwable) { } }) ``` ### Amplitude Обновите код своего мобильного приложения, как показано ниже. Полный пример кода см. в разделе [настройка SDK для интеграции с Amplitude](amplitude#sdk-configuration). ```diff showLineNumbers // For Amplitude maintenance SDK (obsolete) val amplitude = Amplitude.getInstance() val amplitudeDeviceId = amplitude.getDeviceId() val amplitudeUserId = amplitude.getUserId() //for actual Amplitude Kotlin SDK val amplitude = Amplitude( Configuration( apiKey = AMPLITUDE_API_KEY, context = applicationContext ) ) val amplitudeDeviceId = amplitude.store.deviceId val amplitudeUserId = amplitude.store.userId // - val params = AdaptyProfileParameters.Builder() - .withAmplitudeDeviceId(amplitudeDeviceId) - .withAmplitudeUserId(amplitudeUserId) - .build() - Adapty.updateProfile(params) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("amplitude_user_id", amplitudeUserId) { error -> + if (error != null) { + // Handle the error + } + } + Adapty.setIntegrationIdentifier("amplitude_device_id", amplitudeDeviceId) { error -> + if (error != null) { + // Handle the error + } + } ``` ### AppMetrica Обновите код мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [Настройка SDK для интеграции с AppMetrica](appmetrica#sdk-configuration). ```diff showLineNumbers val startupParamsCallback = object: StartupParamsCallback { override fun onReceive(result: StartupParamsCallback.Result?) { val deviceId = result?.deviceId ?: return - val params = AdaptyProfileParameters.Builder() - .withAppmetricaDeviceId(deviceId) - .withAppmetricaProfileId("YOUR_ADAPTY_CUSTOMER_USER_ID") - .build() - Adapty.updateProfile(params) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("appmetrica_device_id", deviceId) { error -> + if (error != null) { + // Handle the error + } + } + + Adapty.setIntegrationIdentifier("appmetrica_profile_id", "YOUR_ADAPTY_CUSTOMER_USER_ID") { error -> + if (error != null) { + // Handle the error + } + } } override fun onRequestError( reason: StartupParamsCallback.Reason, result: StartupParamsCallback.Result? ) { // Handle the error } } AppMetrica.requestStartupParams(context, startupParamsCallback, listOf(StartupParamsCallback.APPMETRICA_DEVICE_ID)) ``` ### AppsFlyer Обновите код своего мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [настройка SDK для интеграции с AppsFlyer](appsflyer#connect-your-app-to-appsflyer). ```diff showLineNumbers val conversionListener: AppsFlyerConversionListener = object : AppsFlyerConversionListener { override fun onConversionDataSuccess(conversionData: Map) { - Adapty.updateAttribution( - conversionData, - AdaptyAttributionSource.APPSFLYER, - AppsFlyerLib.getInstance().getAppsFlyerUID(context) - ) { error -> - if (error != null) { - // Handle the error - } - } + val uid = AppsFlyerLib.getInstance().getAppsFlyerUID(context) + Adapty.setIntegrationIdentifier("appsflyer_id", uid) { error -> + if (error != null) { + // Handle the error + } + } + Adapty.updateAttribution(conversionData, "appsflyer") { error -> + if (error != null) { + // Handle the error + } + } } } ``` ### Branch Обновите код своего мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с Branch](branch#connect-your-app-to-branch). ```diff showLineNumbers // Login and update attribution Branch.getAutoInstance(this) .setIdentity("YOUR_USER_ID") { referringParams, error -> referringParams?.let { data -> - Adapty.updateAttribution(data, AdaptyAttributionSource.BRANCH) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.updateAttribution(data, "branch") { error -> + if (error != null) { + // Handle the error + } + } } } // Logout Branch.getAutoInstance(context).logout() ``` ### Facebook Ads Обновите код мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [настройка SDK для интеграции с Facebook Ads](facebook-ads#connect-your-app-to-facebook-ads). ```diff showLineNumbers - val builder = AdaptyProfileParameters.Builder() - .withFacebookAnonymousId(AppEventsLogger.getAnonymousAppDeviceGUID(context)) - - Adapty.updateProfile(builder.build()) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier( + "facebook_anonymous_id", + AppEventsLogger.getAnonymousAppDeviceGUID(context) + ) { error -> + if (error != null) { + // Handle the error + } + } ``` ### Firebase и Google Analytics \{#firebase-and-google-analytics\} Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с Firebase и Google Analytics](firebase-and-google-analytics). ```diff showLineNumbers // After Adapty.activate() FirebaseAnalytics.getInstance(context).appInstanceId.addOnSuccessListener { appInstanceId -> - Adapty.updateProfile( - AdaptyProfileParameters.Builder() - .withFirebaseAppInstanceId(appInstanceId) - .build() - ) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId) { error -> + if (error != null) { + // Handle the error + } + } } ``` ```diff showLineNumbers // After Adapty.activate() - FirebaseAnalytics.getInstance(context).getAppInstanceId().addOnSuccessListener(appInstanceId -> { - AdaptyProfileParameters params = new AdaptyProfileParameters.Builder() - .withFirebaseAppInstanceId(appInstanceId) - .build(); - - Adapty.updateProfile(params, error -> { - if (error != null) { - // Handle the error - } - }); - }); + FirebaseAnalytics.getInstance(context).getAppInstanceId().addOnSuccessListener(appInstanceId -> { + Adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId, error -> { + if (error != null) { + // Handle the error + } + }); + }); ``` ### Mixpanel Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в [настройке SDK для интеграции с Mixpanel](mixpanel#sdk-configuration). ```diff showLineNumbers - val params = AdaptyProfileParameters.Builder() - .withMixpanelUserId(mixpanelAPI.distinctId) - .build() - - Adapty.updateProfile(params) { error -> - if (error != null) { - // Handle the error - } - } + Adapty.setIntegrationIdentifier("mixpanel_user_id", mixpanelAPI.distinctId) { error -> + if (error != null) { + // Handle the error + } + } ``` ### OneSignal Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с OneSignal](onesignal#sdk-configuration). ```diff showLineNumbers // SubscriptionID val oneSignalSubscriptionObserver = object: IPushSubscriptionObserver { override fun onPushSubscriptionChange(state: PushSubscriptionChangedState) { - val params = AdaptyProfileParameters.Builder() - .withOneSignalSubscriptionId(state.current.id) - .build() - - Adapty.updateProfile(params) { error -> + Adapty.setIntegrationIdentifier("one_signal_subscription_id", state.current.id) { error -> if (error != null) { // Handle the error } } } } ``` ```diff showLineNumbers // SubscriptionID IPushSubscriptionObserver oneSignalSubscriptionObserver = state -> { - AdaptyProfileParameters params = new AdaptyProfileParameters.Builder() - .withOneSignalSubscriptionId(state.getCurrent().getId()) - .build(); - Adapty.updateProfile(params, error -> { + Adapty.setIntegrationIdentifier("one_signal_subscription_id", state.getCurrent().getId(), error -> { if (error != null) { // Handle the error } }); }; ``` ```diff showLineNumbers // PlayerID val osSubscriptionObserver = OSSubscriptionObserver { stateChanges -> stateChanges?.to?.userId?.let { playerId -> - val params = AdaptyProfileParameters.Builder() - .withOneSignalPlayerId(playerId) - .build() - - Adapty.updateProfile(params) { error -> + Adapty.setIntegrationIdentifier("one_signal_player_id", playerId) { error -> if (error != null) { // Handle the error } - } } } ``` ```diff showLineNumbers // PlayerID OSSubscriptionObserver osSubscriptionObserver = stateChanges -> { OSSubscriptionState to = stateChanges != null ? stateChanges.getTo() : null; String playerId = to != null ? to.getUserId() : null; if (playerId != null) { - AdaptyProfileParameters params1 = new AdaptyProfileParameters.Builder() - .withOneSignalPlayerId(playerId) - .build(); - - Adapty.updateProfile(params1, error -> { + Adapty.setIntegrationIdentifier("one_signal_player_id", playerId, error -> { if (error != null) { // Handle the error } - }); } }; ``` ### Pushwoosh Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с Pushwoosh](pushwoosh#sdk-configuration). ```diff showLineNumbers - val params = AdaptyProfileParameters.Builder() - .withPushwooshHwid(Pushwoosh.getInstance().hwid) - .build() - Adapty.updateProfile(params) { error -> + Adapty.setIntegrationIdentifier("pushwoosh_hwid", Pushwoosh.getInstance().hwid) { error -> if (error != null) { // Handle the error } } ``` ```diff showLineNumbers - AdaptyProfileParameters params = new AdaptyProfileParameters.Builder() - .withPushwooshHwid(Pushwoosh.getInstance().getHwid()) - .build(); - - Adapty.updateProfile(params, error -> { + Adapty.setIntegrationIdentifier("pushwoosh_hwid", Pushwoosh.getInstance().getHwid(), error -> { if (error != null) { // Handle the error } }); ``` --- # File: migration-to-android-sdk-v3 --- --- title: "Миграция Adapty Android SDK на v3.0" description: "Мигрируйте на Adapty Android SDK v3.0 для повышения производительности и новых возможностей монетизации." --- Adapty SDK v3.0 добавляет поддержку нового [Adapty Paywall Builder](adapty-paywall-builder) — обновлённой версии no-code инструмента для создания пейволов. Максимальная гибкость и богатые возможности дизайна сделают ваши пейволы эффективнее и прибыльнее. Adapty SDK поставляется как BoM (Bill of Materials), что гарантирует согласованность версий Adapty SDK и AdaptyUI SDK в вашем приложении. Чтобы перейти на v3.0, обновите код следующим образом: ```diff showLineNumbers dependencies { ... - implementation 'io.adapty:android-sdk:2.11.5' - implementation 'io.adapty:android-ui:2.11.3' + implementation platform('io.adapty:adapty-bom:3.0.4') + implementation 'io.adapty:android-sdk' + implementation 'io.adapty:android-ui' } ``` ```diff showLineNumbers dependencies { ... - implementation("io.adapty:android-sdk:2.11.5") - implementation("io.adapty:android-ui:2.11.3") + implementation(platform("io.adapty:adapty-bom:3.0.4")) + implementation("io.adapty:android-sdk") + implementation("io.adapty:android-ui") } ``` ```diff showLineNumbers //libs.versions.toml [versions] .. - adapty = "2.11.5" - adaptyUi = "2.11.3" + adaptyBom = "3.0.4" [libraries] .. - adapty = { group = "io.adapty", name = "android-sdk", version.ref = "adapty" } - adapty-ui = { group = "io.adapty", name = "android-ui", version.ref = "adaptyUi" } + adapty-bom = { module = "io.adapty:adapty-bom", version.ref = "adaptyBom" } + adapty = { module = "io.adapty:android-sdk" } + adapty-ui = { module = "io.adapty:android-ui" } //module-level build.gradle.kts dependencies { ... + implementation(libs.adapty.bom) implementation(libs.adapty) implementation(libs.adapty.ui) } ``` --- # End of Documentation _Generated on: 2026-07-24T13:01:12.696Z_ _Successfully processed: 41/41 files_ # API - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: ru Generated on: 2026-07-24T13:01:12.699Z Total files: 18 --- # File: developer-cli --- --- title: "Developer CLI" description: "Обзор Developer CLI Adapty." --- **Adapty Developer CLI** — это инструмент командной строки для управления аккаунтом Adapty без открытия дашборда. Он предоставляет основные возможности конфигурации, доступные из терминала или автоматизированных сред. **Что можно делать с помощью CLI:** - Создавать и настраивать iOS- и Android-приложения в аккаунте Adapty - Определять уровни доступа — уровни подписки, которые приложение проверяет во время выполнения - Настраивать продукты и сопоставлять их с идентификаторами в App Store и Google Play - Создавать пейволы и назначать на них продукты - Настраивать плейсменты для получения пейволов через SDK :::link Используете AI-ассистент или MCP-клиент? Для работы LLM с CLI доступен [навык Adapty CLI](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli). ::: --- # File: developer-cli-quickstart --- --- title: "Быстрый старт с Adapty Developer CLI" description: "Настройте аккаунт Adapty с нуля через Developer CLI — от создания приложения до живого плейсмента, за несколько команд." --- :::link Используете ИИ-ассистент? Доступен [скилл Adapty CLI](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli), помогающий LLM работать с CLI. ::: Adapty CLI позволяет настроить конфигурацию приложения полностью из командной строки. Используйте его как альтернативу [быстрому старту через дашборд](integrate-payments), если предпочитаете работу в терминале или [MCP-клиенты](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli). :::note Подключение Adapty к App Store Connect и Google Play всё равно требует однократной настройки в дашборде — она описана в шаге 3. ::: По завершении ваше приложение, уровень доступа, продукт, пейвол и плейсмент будут видны в [дашборде Adapty](https://app.adapty.io). ## 1. Установите CLI \{#1-install-the-cli\} Требуется [Node.js](https://nodejs.org/en/download) версии 18 или выше. Чтобы установить CLI, выполните команду: ```bash npm install -g adapty ``` Или напрямую: ```bash npx adapty auth login ``` ## 2. Авторизация \{#2-authenticate\} Выполните команду входа, чтобы связать CLI с вашим аккаунтом Adapty. ```bash adapty auth login ``` CLI откроет вкладку в браузере. Сверьте код, отображаемый в терминале, с кодом в браузере, затем нажмите **Authorize**. Терминал подтвердит успешную аутентификацию. ## 3. Создайте приложение \{#3-create-your-app\} Приложение в Adapty — это ваше мобильное приложение. Одно приложение в Adapty подключается к App Store и Google Play одновременно — достаточно создать одно, сколько бы сторов вы ни использовали. ```bash adapty apps create --title "My App" --platform ios --platform android --apple-bundle-id com.example.app --google-bundle-id com.example.app ``` ```bash adapty apps create --title "My App" --platform ios --apple-bundle-id com.example.app ``` ```bash adapty apps create --title "My App" --platform android --google-bundle-id com.example.app ``` Команда возвращает ``. Используйте этот ID во всех последующих командах. :::important Прежде чем продолжить, подключите приложение к App Store Connect и Google Play в дашборде Adapty. ID продуктов из обоих сторов потребуются на шаге 5. - [Подключить App Store Connect](app-store-connection-configuration) - [Подключить Google Play](google-play-store-connection-configuration) ::: ## 4. Создайте уровень доступа (необязательно) \{#4-create-an-access-level-optional\} [Уровни доступа](access-level) определяют, к чему пользователь получает доступ после покупки. Вместо того чтобы проверять, купил ли пользователь конкретный продукт, приложение проверяет наличие нужного уровня доступа. Это отвязывает логику приложения от конкретных ID продуктов. Уровень доступа `premium` создаётся автоматически при добавлении каждого нового приложения. **Для большинства приложений этот шаг можно пропустить.** Используйте `premium` в качестве ID уровня доступа на шаге 5. Выполняйте эту команду только в том случае, если разные продукты открывают разные функции для разных групп пользователей — например, если подписчик «Basic» и подписчик «Pro» получают доступ к разным частям приложения. ```bash adapty access-levels create --app --sdk-id "pro" --title "Pro" ``` - `--sdk-id` — это идентификатор, который вы будете использовать в коде приложения, чтобы проверить, должна ли функция быть доступна пользователю (например, `if user.hasAccessLevel("pro")`). Если пропустить этот шаг и использовать уровень доступа по умолчанию, его `--sdk-id` равен `premium`. - `--title` — отображаемое название для вашего удобства в дашборде Adapty. Команда возвращает ``. ## 5. Создайте продукт \{#5-create-a-product\} В Adapty [продукт](product) — это всё, что ваше приложение продаёт: подписка или разовая покупка. Позиции из App Store Connect и Google Play можно объединить в один продукт Adapty и управлять ими из одного места. Вам понадобятся идентификаторы продукта из каждого стора: Apple product ID из App Store Connect, а также Android product ID и base plan ID из Google Play Console. Подробнее о том, где их найти, читайте в разделе [Продукты](quickstart-products). Если вы пропустили шаг 4, используйте `default_access_level.id`, возвращённый командой `apps create` на шаге 3, в качестве ``. :::important ID продуктов стора, которые вы указываете здесь (`--ios-product-id`, `--android-product-id`), нельзя изменить после создания. Чтобы использовать другие ID продуктов стора, создайте новый продукт. ::: ```bash adapty products create --app --title "My Product" --access-level-id --period monthly --ios-product-id --android-product-id --android-base-plan-id ``` ```bash adapty products create --app --title "My Product" --access-level-id --period monthly --ios-product-id ``` ```bash adapty products create --app --title "My Product" --access-level-id --period monthly --android-product-id --android-base-plan-id ``` Команда возвращает ``. ## 6. Создайте пейвол \{#6-create-a-paywall\} [Пейвол](paywalls) — это контейнер, в котором хранятся ваши продукты. В Adapty пейволы — единственный способ доставить продукты пользователям. Каждый продукт должен быть добавлен в пейвол, прежде чем он сможет отображаться в приложении. :::important После того как пейвол привязан к плейсменту, изменить его продукты невозможно. Чтобы использовать другие продукты, создайте новый пейвол и обновите плейсмент, чтобы он указывал на него. ::: ```bash adapty paywalls create --app --title "My Paywall" --product-id ``` ```bash adapty paywalls create --app --title "My Paywall" --product-id --product-id ``` Команда возвращает ``. ## 7. Создайте плейсмент \{#create-a-placement\} [Плейсмент](placements) — это точка в приложении, где показывается пейвол. Единственное, что вы жёстко прописываете в коде, — это ID плейсмента. Всё остальное — какой пейвол показывать и каким пользователям — управляется в дашборде без выпуска новой версии приложения. `--developer-id` — строка, на которую вы будете ссылаться в коде приложения, когда запрашиваете у Adapty, какой пейвол показать в этой точке. Выбирайте что-то, отражающее местоположение, например `"main"`, `"onboarding"` или `"settings"`. ```bash adapty placements create --app --title "Main" --developer-id "main" --audiences '[{"segment_ids":[],"paywall_id":"","priority":0}]' ``` Флаг `--audiences` определяет, какой пейвол показывается каким пользователям. В примере выше задана одна дефолтная аудитория — все пользователи на этом плейсменте видят один и тот же пейвол. ## Что дальше \{#whats-next\} Все сущности теперь отображаются в [дашборде Adapty](https://app.adapty.io). Следующие шаги: - [Оформите пейвол](adapty-paywall-builder) — используйте Paywall Builder без кода, чтобы добавить визуальные элементы, разметку и тексты к только что созданному пейволу. - [Интегрируйте Adapty SDK](quickstart-sdk) — добавьте SDK в приложение для получения и отображения плейсмента. --- # File: developer-cli-authentication --- --- title: "Аутентификация в Adapty Developer CLI" description: "Как пройти аутентификацию в Adapty Developer CLI." --- :::link Работаете с AI-ассистентом? Доступен [навык Adapty CLI](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli), помогающий LLM работать с CLI. ::: Для вызова API Adapty CLI требует аутентификации. ## Вход \{#log-in\} Чтобы войти: 1. Выполните в терминале: ```bash adapty auth login ``` 2. CLI выведет код верификации в формате `XXXX-XXXX` и откроет дашборд Adapty в браузере. 3. На странице авторизации убедитесь, что код совпадает с тем, что отображается в терминале. 4. Нажмите **Authorize**. Браузер покажет сообщение «CLI authorized! You can close this tab.» 5. Вернувшись в терминал, вы увидите подтверждение аутентификации. Если код истёк до того, как вы авторизовались, или если вы нажали **Deny**, запустите команду снова, чтобы начать процесс заново: ```bash adapty auth login ``` ## Управление аутентификацией \{#manage-authentication\} ### Проверка статуса аутентификации \{#check-authentication-status\} Чтобы узнать текущий статус аутентификации, выполните: ```bash adapty auth status ``` При успешной аутентификации вывод показывает ваш email, маскированный префикс токена и путь к локальному конфигурационному файлу: ``` Email: you@example.com Token: abcd1234**** Config: ~/.config/adapty/config.json ``` Если вы не аутентифицированы: ``` Not authenticated. Run `adapty auth login`. ``` ### Проверка токена \{#verify-your-token\} Чтобы убедиться, что токен действителен, и посмотреть данные аккаунта, выполните: ```bash adapty auth whoami ``` В отличие от `adapty auth status`, эта команда делает живой запрос к серверу для проверки токена. ### Выход \{#log-out\} Чтобы удалить сохранённые учётные данные локально, выполните: ```bash adapty auth logout ``` Это очистит `~/.config/adapty/config.json`. Токен останется действительным на стороне сервера до истечения срока действия — если нужно немедленно его аннулировать, используйте `adapty auth revoke`. ### Отзыв токена \{#revoke-your-token\} Чтобы аннулировать токен на сервере и удалить его локально, выполните: ```bash adapty auth revoke ``` Используйте это, когда нужно полностью аннулировать токен — например, если ваши учётные данные могли быть скомпрометированы. После отзыва запустите `adapty auth login` для повторной аутентификации. ## Ошибки токена \{#token-errors\} Если токен отозван или стал недействительным, команды CLI возвращают ошибку 401. Чтобы пройти аутентификацию заново, выполните: ```bash adapty auth login ``` --- # File: developer-cli-reference --- --- title: "Полный справочник по Adapty Developer CLI" description: "Полный справочник по всем командам Adapty Developer CLI." --- :::link Используете ИИ-ассистент? Доступен [навык Adapty CLI](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli) для работы с CLI через LLM. ::: В этой статье перечислены все команды Adapty CLI с их аргументами, флагами и допустимыми значениями. :::link Для настройки аутентификации и управления токенами см. [Аутентификация](developer-cli-authentication). ::: ## Глобальные флаги \{#global-flags\} Эти флаги доступны для всех команд. | Флаг | Описание | |---|---| | `--json` | Вывод в формате JSON вместо форматированного текста | | `--help` | Показать справку по команде | Все команды `list` также принимают флаги пагинации: | Флаг | По умолчанию | Описание | |---|---|---| | `--page` | `1` | Номер страницы | | `--page-size` | `20` | Элементов на странице (макс.: 100) | ## Приложения \{#apps\} Управляйте приложениями в вашем аккаунте Adapty. Для настройки через дашборд см. [App settings](general). ### adapty apps list Вывести список всех приложений в вашем аккаунте Adapty. ```bash adapty apps list ``` Принимает [флаги пагинации](#global-flags). ### adapty apps get Получить сведения о конкретном приложении. ```bash adapty apps get ``` | Аргумент | Описание | |---|---| | `app-id` | ID приложения (UUID) | ### adapty apps create Создание нового приложения. ```bash adapty apps create --title "My App" --platform ios --apple-bundle-id com.example.app ``` | Флаг | Обязательный | Описание | |---|---|---| | `--title` | Да | Название приложения | | `--platform` | Да | Платформа: `ios` или `android`. Укажите оба: `--platform ios --platform android` | | `--apple-bundle-id` | Обязателен при `--platform ios` | Apple bundle ID | | `--google-bundle-id` | Обязателен при `--platform android` | Google bundle ID | ### adapty apps update Обновить существующее приложение. ```bash adapty apps update --title "New Name" ``` | Аргумент | Описание | |---|---| | `app-id` | ID приложения (UUID) | | Флаг | Описание | |---|---| | `--title` | Новое название приложения | | `--apple-bundle-id` | Новый Apple bundle ID | | `--google-bundle-id` | Новый Google bundle ID | Необходимо указать хотя бы один флаг. `--platform` нельзя изменить после создания. ## Уровни доступа \{#access-levels\} ### adapty access-levels list Список всех [уровней доступа](access-level) для приложения. ```bash adapty access-levels list --app ``` | Флаг | Обязательный | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | Принимает [флаги пагинации](#global-flags). ### adapty access-levels get Получить детали конкретного [уровня доступа](access-level). ```bash adapty access-levels get --app ``` | Аргумент | Описание | |---|---| | `access-level-id` | ID уровня доступа (UUID) | | Флаг | Обязательный | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | ### adapty access-levels create Создать новый [уровень доступа](access-level). ```bash adapty access-levels create --app --sdk-id "pro" --title "Pro" ``` | Флаг | Обязательный | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | | `--sdk-id` | Да | Идентификатор, используемый в коде приложения для проверки доступа (например, `"pro"` или `"premium"`) | | `--title` | Да | Отображаемое название в дашборде Adapty | ### adapty access-levels update Обновление существующего [уровня доступа](access-level). ```bash adapty access-levels update --app --title "Pro Access" ``` | Аргумент | Описание | |---|---| | `access-level-id` | ID уровня доступа (UUID) | | Флаг | Обязательный | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | | `--title` | Да | Новое отображаемое название | `--sdk-id` нельзя изменить после создания. ## Продукты \{#products\} ### Список продуктов Adapty \{#adapty-products-list\} Список всех [продуктов](product) приложения. ```bash adapty products list --app ``` | Флаг | Обязательный | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | Принимает [флаги пагинации](#global-flags). ### adapty products get Получить информацию о конкретном [продукте](product). ```bash adapty products get --app ``` | Аргумент | Описание | |---|---| | `product-id` | ID продукта (UUID) | | Флаг | Обязательный | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | ### adapty products create Создание нового [продукта](product). :::important Идентификаторы продуктов стора (`--ios-product-id`, `--android-product-id`, `--android-base-plan-id`) нельзя изменить после создания. Чтобы использовать другие идентификаторы, создайте новый продукт. ::: ```bash adapty products create --app --title "Monthly" --access-level-id --period monthly --ios-product-id com.example.monthly ``` | Флаг | Обязательный | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | | `--title` | Да | Название продукта | | `--access-level-id` | Да | ID [уровня доступа](access-level) (UUID), который открывает этот продукт | | `--period` | Да | Период подписки: `weekly`, `monthly`, `2_months`, `3_months`, `6_months`, `yearly`, `lifetime` | | `--ios-product-id` | Требуется хотя бы одна платформа | ID продукта из App Store Connect | | `--android-product-id` | Требуется хотя бы одна платформа | ID продукта из Google Play Console | | `--android-base-plan-id` | Обязателен вместе с `--android-product-id`, если не указан `--period lifetime` | ID базового плана из Google Play Console | ### adapty products update Обновить существующий [продукт](product). Идентификаторы продуктов стора (`--ios-product-id`, `--android-product-id`) нельзя изменить после создания — они недоступны в этой команде. Чтобы использовать другие идентификаторы, создайте новый продукт. ```bash adapty products update --app --title "Monthly" --access-level-id ``` | Аргумент | Описание | |---|---| | `product-id` | ID продукта (UUID) | | Флаг | Обязательный | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | | `--title` | Нет | Название продукта | | `--access-level-id` | Нет | ID (UUID) [уровня доступа](access-level), который открывает этот продукт | ## Пейволы \{#paywalls\} ### Список пейволов Adapty \{#adapty-paywalls-list\} Получить список всех [пейволов](paywalls) приложения. ```bash adapty paywalls list --app ``` | Флаг | Обязательный | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | Принимает [флаги пагинации](#global-flags). ### adapty paywalls get Получить подробную информацию о конкретном [пейволе](paywalls). ```bash adapty paywalls get --app ``` | Аргумент | Описание | |---|---| | `paywall-id` | ID пейвола (UUID) | | Флаг | Обязателен | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | ### adapty paywalls create Создайте новый [пейвол](paywalls). ```bash adapty paywalls create --app --title "Default Paywall" --product-id ``` | Флаг | Обязательный | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | | `--title` | Да | Название пейвола | | `--product-id` | Да | ID [продукта](product) (UUID). Повторите для нескольких продуктов: `--product-id --product-id ` | ### adapty paywalls update Замените все поля существующего [пейвола](paywalls). :::important Если пейвол уже привязан к плейсменту, его продукты нельзя изменить. Чтобы использовать другие продукты в активном пейволе, создайте новый пейвол и обновите плейсмент, чтобы он указывал на него. ::: ```bash adapty paywalls update --app --title "Default Paywall" --product-id ``` Эта команда заменяет все поля пейвола, включая полный список продуктов. | Аргумент | Описание | |---|---| | `paywall-id` | ID пейвола (UUID) | | Флаг | Обязательный | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | | `--title` | Да | Название пейвола | | `--product-id` | Да | ID [продукта](product) (UUID). Повторите для нескольких продуктов: `--product-id --product-id ` | ### adapty paywalls placements Выводит список всех [плейсментов](placements), которые в данный момент используют указанный [пейвол](paywalls). ```bash adapty paywalls placements --app ``` | Аргумент | Описание | |---|---| | `paywall-id` | ID пейвола (UUID) | | Флаг | Обязательный | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | Используйте эту команду перед заменой пейвола, чтобы заранее увидеть, какие плейсменты будут затронуты. ## Плейсменты \{#placements\} ### Список плейсментов Adapty \{#adapty-placements-list\} Выводит все [плейсменты](placements) приложения. ```bash adapty placements list --app ``` | Флаг | Обязательный | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | Принимает [флаги пагинации](#global-flags). ### adapty placements get Получить подробную информацию о конкретном [плейсменте](placements). ```bash adapty placements get --app ``` | Аргумент | Описание | |---|---| | `placement-id` | ID плейсмента (UUID) | | Флаг | Обязательный | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | Ответ содержит массив `audiences`. Каждый элемент — это `{segment_ids, paywall_id, priority}`. Дефолтная аудитория имеет `segment_ids: []` и наибольшее значение приоритета (оценивается последней). В форматированном выводе также отображается `Paywall ID` верхнего уровня, полученный из дефолтной аудитории для удобства. `--json` возвращает исходную форму ответа API без изменений. ### adapty placements create Создать новый [плейсмент](placements). ```bash adapty placements create --app --title "Main" --developer-id "main" --audiences '[{"segment_ids":[],"paywall_id":"","priority":0}]' ``` | Флаг | Обязательный | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | | `--title` | Да | Название плейсмента | | `--developer-id` | Да | Строковый идентификатор, используемый в коде приложения для запроса этого [плейсмента](placements) | | `--audiences` | Один из двух | JSON-массив записей `{segment_ids, paywall_id, priority}`. См. [Форма audiences](#audiences-shape) | | `--paywall-id` | Один из двух | **Устарело.** ID [пейвола](paywalls) (UUID). На стороне клиента оборачивается в единственную дефолтную аудиторию | Передайте ровно один из параметров: `--audiences` или `--paywall-id`. Передача обоих или ни одного из них приведёт к ошибке. :::warning `--paywall-id` устарел и будет удалён. При передаче этого параметра CLI выводит предупреждение в stderr и преобразует значение в аудиторию по умолчанию. Используйте `--audiences` для новой автоматизации. ::: ### adapty placements update Заменяет все поля существующего [плейсмента](placements). ```bash adapty placements update --app --title "Main" --developer-id "main" --audiences '[{"segment_ids":[],"paywall_id":"","priority":0}]' ``` Эта команда заменяет все поля плейсмента, включая полный список аудиторий. | Аргумент | Описание | |---|---| | `placement-id` | ID плейсмента (UUID) | | Флаг | Обязательный | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | | `--title` | Да | Название плейсмента | | `--developer-id` | Да | Строковый идентификатор, используемый в коде приложения для запроса этого [плейсмента](placements) | | `--audiences` | Один из двух | JSON-массив записей `{segment_ids, paywall_id, priority}`. См. [Форма аудиторий](#audiences-shape) | | `--paywall-id` | Один из двух | **Устарело.** ID [пейвола](paywalls) (UUID). Заменяет все аудитории одной аудиторией по умолчанию | :::warning Передача `--paywall-id` перезаписывает все аудитории на плейсменте. Аудитории, привязанные к сегментам, удаляются. Чтобы сохранить их, используйте `--audiences` и включите все нужные записи. ::: #### Форма аудиторий \{#audiences-shape\} Флаг `--audiences` принимает JSON-массив. Каждый элемент содержит: | Поле | Тип | Описание | |---|---|---| | `segment_ids` | `string[]` | Массив ID [сегментов](segments), на которые нацелена данная аудитория. Длина 0 или 1. Пустой массив обозначает **аудиторию по умолчанию** — резервный вариант для пользователей, не попавших ни в один другой сегмент | | `paywall_id` | `string` | ID [пейвола](paywalls) (UUID), который показывается пользователям в данной аудитории | | `priority` | `number` | Нумерация с нуля, уникальная в рамках плейсмента. Аудитории проверяются от меньшего значения к большему; аудитория по умолчанию должна иметь наибольшее значение | Плейсмент должен содержать ровно одну аудиторию по умолчанию. Пример с одной целевой аудиторией и одной аудиторией по умолчанию: ```bash adapty placements update --app --title "Main" --developer-id "main" \ --audiences '[{"segment_ids":[""],"paywall_id":"","priority":0},{"segment_ids":[],"paywall_id":"","priority":1}]' ``` Чтобы заменить пейвол сразу в нескольких плейсментах, не потеряв маршрутизацию по сегментам: 1. Найдите затронутые плейсменты: ```bash adapty paywalls placements --app ``` 2. Для каждого из них получите полный массив `audiences`: ```bash adapty placements get --app --json ``` 3. Замените совпадающие значения `paywall_id` на стороне клиента. 4. Запишите изменённый payload обратно: ```bash adapty placements update --app --title "" --developer-id "<developer-id>" --audiences '<modified-payload>' ``` ## Сегменты \{#segments\} [Сегменты](segments) доступны через CLI только для чтения. Создавайте и редактируйте их в [дашборде Adapty](https://app.adapty.io). Используйте эти команды, чтобы находить идентификаторы сегментов при настройке аудиторий плейсментов. ### Список сегментов adapty \{#adapty-segments-list\} Выводит все [сегменты](segments) приложения. ```bash adapty segments list --app <app-id> ``` | Флаг | Обязателен | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | Поддерживает [флаги пагинации](#global-flags). ### adapty segments get Получить подробную информацию о конкретном [сегменте](segments). ```bash adapty segments get --app <app-id> <segment-id> ``` | Аргумент | Описание | |---|---| | `segment-id` | ID сегмента (UUID) | | Флаг | Обязательный | Описание | |---|---|---| | `--app` | Да | ID приложения (UUID) | Ответ содержит `id`, `title` и `description`. Правила фильтрации через этот API не предоставляются. ## Auth \{#auth\} | Команда | Описание | |---|---| | `adapty auth login` | Аутентификация через браузер с использованием device flow | | `adapty auth logout` | Удалить сохранённые учётные данные локально | | `adapty auth whoami` | Проверить токен на сервере и показать информацию о пользователе | | `adapty auth status` | Показать локальное состояние аутентификации без обращения к серверу | | `adapty auth revoke` | Отозвать токен на сервере и удалить его локально | Подробное описание каждой команды см. в разделе [Аутентификация](developer-cli-authentication). --- # File: getting-started-with-server-side-api --- --- title: "Server-side API" description: "Начните работу с серверным API Adapty для управления подписками." --- :::tip Используете AI-ассистент для разработки? Смотрите [Проверка и предоставление доступа к подписке через бэкенд](server-side-api-with-ai) — всё необходимое на одной странице. ::: С помощью API вы можете: 1. Проверить статус подписки пользователя. 2. Активировать подписку пользователя с уровнем доступа. 3. Получить атрибуты пользователя. 4. Задать атрибуты пользователя. 5. Получить и обновить конфигурации пейволов. <img src="/assets/shared/img/server.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> :::note Для отслеживания событий подписки используйте интеграцию [Webhook](webhook) в Adapty или интегрируйтесь напрямую с вашим существующим сервисом. ::: ::: ## Случай 1: синхронизация подписчиков между вебом и мобильным приложением \{#case-1-sync-subscribers-between-web-and-mobile\} Если вы используете веб-провайдеры платежей, например Stripe, ChargeBee или другие, вы можете легко синхронизировать своих подписчиков. Вот как это сделать: 1. <InlineTooltip tooltip="Назначьте уникальный ID каждому пользователю">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users) и [Unity](unity-identifying-users)</InlineTooltip>. 2. [Проверьте статус подписки](api-adapty/operations/getProfile) через API. 3. Если пользователь на бесплатном плане — покажите пейвол на вашем сайте. 4. После успешной оплаты [обновите статус подписки](api-adapty/operations/setTransaction) в Adapty через API. 5. Ваши подписчики автоматически останутся в синхронизации с мобильным приложением. ## Case 2: Выдать подписку \{#case-2-grant-a-subscription\} :::note По соображениям безопасности выдать подписку через SDK невозможно. ::: Если вы продаёте через собственный интернет-магазин, Amazon Appstore, Microsoft Store или любую другую платформу помимо Google Play и App Store, вам нужно синхронизировать эти транзакции с Adapty, чтобы предоставлять доступ и отслеживать транзакции в аналитике. 1. <InlineTooltip tooltip="Присвойте уникальный ID каждому пользователю">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users) и [Unity](unity-identifying-users)</InlineTooltip>. 2. [Настройте кастомный стор для ваших продуктов в дашборде Adapty](custom-store). 3. Синхронизируйте транзакцию с Adapty с помощью API-запроса [Set transaction](api-adapty/operations/setTransaction). ## Case 3: Предоставление уровня доступа \{#case-3-grant-an-access-level\} Допустим, вы запускаете акцию с 7-дневным бесплатным пробным периодом и хотите обеспечить единый пользовательский опыт на всех платформах. Чтобы синхронизировать это с мобильным приложением: 1. <InlineTooltip tooltip="Присвойте уникальный ID каждому пользователю">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users) и [Unity](unity-identifying-users)</InlineTooltip>. 2. Используйте API, чтобы [предоставить премиум-доступ](api-adapty/operations/grantAccessLevel) на 7 дней. После 7 дней пользователи, которые не оформили подписку, будут переведены на бесплатный уровень. ## Вариант 4: Синхронизация свойств и кастомных атрибутов пользователей \{#case-4-sync-users-properties-and-custom-attributes\} Если у ваших пользователей есть кастомные атрибуты — например, количество выученных слов в приложении для изучения языков — их тоже можно синхронизировать. 1. <InlineTooltip tooltip="Присвойте каждому пользователю уникальный ID">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), and [Unity](unity-identifying-users)</InlineTooltip>. 2. [Обновите атрибут](api-adapty/operations/updateProfile) через API или SDK. Эти пользовательские атрибуты можно использовать для создания сегментов и запуска A/B-тестов. ## Пример 5: Управление конфигурациями пейвола \{#case-5-manage-paywall-configurations\} Вы можете [обновлять Remote Config в пейволах](api-adapty/operations/updatePaywall), чтобы динамически менять внешний вид и поведение пейвола без повторного выпуска приложения. --- **Что дальше:** - Перейдите к [авторизации для серверного API](ss-authorization) - Запросы: - [Получить профиль](api-adapty/operations/getProfile) - [Создать профиль](api-adapty/operations/createProfile) - [Обновить профиль](api-adapty/operations/updateProfile) - [Удалить профиль](api-adapty/operations/deleteProfile) - [Выдать уровень доступа](api-adapty/operations/grantAccessLevel) - [Отозвать уровень доступа](api-adapty/operations/revokeAccessLevel) - [Задать транзакцию](api-adapty/operations/setTransaction) - [Валидировать покупку, предоставить уровень доступа пользователю и импортировать историю транзакций](api-adapty/operations/validateStripePurchase) - [Добавить идентификаторы интеграции](api-adapty/operations/setIntegrationIdentifiers) - [Получить пейвол](api-adapty/operations/getPaywall) - [Список пейволов](api-adapty/operations/listPaywalls) - [Обновить пейвол](api-adapty/operations/updatePaywall) --- # File: ss-authorization --- --- title: "Авторизация и формат запросов Server-side API" description: "" --- ## Авторизация \{#authorization\} Запросы к API должны быть аутентифицированы с помощью секретного или публичного ключа API, передаваемого в заголовке Authorization. Найти их можно в разделе [**App Settings**](https://app.adapty.io/settings/general). Формат значения: `Api-Key {your-secret-api-key}`, например `Api-Key secret_live_...`. :::important Ключи API привязаны к конкретному приложению. Если у вас несколько приложений, убедитесь, что для каждого используется отдельный ключ. ::: ## Формат запроса \{#request-format\} **Заголовки** Запросы к серверному API требуют определённых заголовков и тела в формате JSON. Используйте приведённые ниже сведения для формирования запросов. | **Заголовок** | **Описание** | | --------------------------- | ------------------------------------------------------------ | | **adapty-profile-id** | <p>Adapty profile ID пользователя. Отображается в поле **Adapty ID** на странице [Adapty Dashboard -> **Profiles**](https://app.adapty.io/profiles/users) -> конкретный профиль. </p><p>Взаимозаменяем с **adapty-customer-user-id** — используйте любой из них.</p> | | **adapty-customer-user-id** | <p>ID пользователя в вашей системе. Отображается в поле **Customer user ID** на странице [Adapty Dashboard -> **Profiles**](https://app.adapty.io/profiles/users) -> конкретный профиль. </p><p>Взаимозаменяем с **adapty-profile-id** — используйте любой из них.</p><p> ⚠️ Работает только если вы <InlineTooltip tooltip="идентифицируете пользователей в приложении">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users) и [Unity](unity-identifying-users)</InlineTooltip> в коде приложения с помощью Adapty SDK.</p> | | **adapty-platform** | (необязательно) Укажите платформу устройства, на котором установлено приложение. Рекомендуем задавать этот параметр в запросах [Create profile](api-adapty/operations/createProfile) и [Update profile](api-adapty/operations/updateProfile) при изменении объекта [Installation Meta](server-side-api-objects#installation-meta), поскольку он зависит от устройства пользователя, а у одного пользователя может быть несколько устройств. Допустимые значения: `iOS`, `macOS`, `iPadOS`, `visionOS`, `Android` или `web`. | | **Content-Type** | Укажите `application/json`, чтобы API обрабатывал запрос. | **Body** API ожидает тело запроса в формате JSON с необходимыми данными. ## Ограничения по частоте запросов \{#rate-limits\} Чтобы избежать ограничений по частоте, следите за тем, чтобы количество запросов (на одно приложение) не превышало 40 000 в минуту. При превышении этого лимита система может замедлиться или временно заблокировать дальнейшие запросы — это нужно для поддержания оптимальной производительности для всех пользователей. ## Ротация API-ключей \{#rotate-api-keys\} Если нужно сменить секретные API-ключи: 1. В разделе **Settings → General** нажмите **Generate new key**, затем нажмите на иконку корзины рядом со старым ключом. 2. Обновите ключ в своём приложении. --- **Что дальше: запросы:** - [Получить профиль](api-adapty/operations/getProfile) - [Создать профиль](api-adapty/operations/createProfile) - [Обновить профиль](api-adapty/operations/updateProfile) - [Удалить профиль](api-adapty/operations/deleteProfile) - [Выдать уровень доступа](api-adapty/operations/grantAccessLevel) - [Отозвать уровень доступа](api-adapty/operations/revokeAccessLevel) - [Установить транзакцию](api-adapty/operations/setTransaction) - [Валидировать покупку, предоставить уровень доступа пользователю и импортировать историю транзакций](api-adapty/operations/validateStripePurchase) - [Получить пейвол](api-adapty/operations/getPaywall) - [Список пейволов](api-adapty/operations/listPaywalls) - [Обновить пейвол](api-adapty/operations/updatePaywall) --- # File: server-side-api-specs --- --- title: "Запросы к серверному API" description: "Изучите спецификации серверного API Adapty для расширенной интеграции." --- Серверный API Adapty позволяет программно получать доступ к данным о подписках и управлять ими, обеспечивая бесшовную интеграцию с вашими существующими сервисами и инфраструктурой. Вы можете синхронизировать данные между платформами, предоставлять уровни доступа или валидировать покупки в Stripe — этот API предоставляет всё необходимое для поддержания актуальности данных в ваших системах и вовлечённости пользователей. ## Коллекция и окружение Postman \{#postman-collection-and-environment\} Чтобы упростить работу с нашим серверным API, мы подготовили коллекцию Postman и файл окружения, которые можно скачать и импортировать в Postman. - **Коллекция запросов**: содержит все запросы, доступные в серверном API Adapty. Обратите внимание, что в ней используются переменные, значения которых можно задать в окружении. - **Окружение**: содержит список переменных, значения которых достаточно указать один раз. Мы подготовили единое окружение для серверного API, веб-API и API экспорта аналитики, чтобы упростить вам работу. После активации этого окружения Postman будет автоматически подставлять заданные значения переменных в запросы. :::tip [Скачать коллекцию и окружение](https://raw.githubusercontent.com/adaptyteam/adapty-docs/refs/heads/main/Downloads/Adapty_server_side_API_postman_collection.zip) ::: Информацию о том, как импортировать коллекцию и окружение в Postman, смотрите в [документации Postman](https://learning.postman.com/docs/getting-started/importing-and-exporting/importing-data/). ### Используемые переменные \{#variables-used\} Мы создали единое окружение для серверного API, веб-API и API экспорта аналитики, чтобы упростить вашу работу. Ниже приведены переменные, специфичные для серверного API: | Переменная | Описание | Пример значения | | ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------- | | secret_api_key | Можно найти в поле **Secret key** в разделе [**App settings**](https://app.adapty.io/settings/general). | `secret_live_Pj1P1xzM.2CvSvE1IalQRFjsWy6csBVNpH33atnod` | | adapty-customer-user-id | Идентификатор пользователя в вашей системе. В дашборде Adapty его можно найти в поле **Customer user ID** профиля. | `john.doe@example.com` | | adapty-profile-id | Идентификатор пользователя, присвоенный в Adapty. В дашборде Adapty его можно найти в поле **Adapty ID** профиля. | `3286abd3-48b0-4e9c-a5f6-ac0a006333a6` | | Adapty-platform | Платформа, которую использует пользователь для вашего приложения. Возможные значения: `iOS`, `macOS`, `iPadOS`, `visionOS`, `Android`, `web`. | `iOS` | | stripe_token | Токен объекта Stripe, представляющего уникальную покупку, например подписку (`sub_XXX`) или намерение платежа (`pi_XXX`). | `sub_1JY8xLLy6P12345a` | **Что дальше: Запросы:** - [Получить профиль](api-adapty/operations/getProfile) - [Создать профиль](api-adapty/operations/createProfile) - [Обновить профиль](api-adapty/operations/updateProfile) - [Удалить профиль](api-adapty/operations/deleteProfile) - [Предоставить уровень доступа](api-adapty/operations/grantAccessLevel) - [Отозвать уровень доступа](api-adapty/operations/revokeAccessLevel) - [Установить транзакцию](api-adapty/operations/setTransaction) - [Проверить покупку, предоставить уровень доступа пользователю и импортировать историю его транзакций](api-adapty/operations/validateStripePurchase) - [Добавить идентификаторы интеграции](api-adapty/operations/setIntegrationIdentifiers) - [Получить пейвол](api-adapty/operations/getPaywall) - [Список пейволов](api-adapty/operations/listPaywalls) - [Обновить пейвол](api-adapty/operations/updatePaywall) - [Создать транзакцию виртуальной валюты](api-adapty/operations/createVirtualCurrencyTransaction) - [Список транзакций виртуальной валюты](api-adapty/operations/listVirtualCurrencyTransactions) - [Список балансов виртуальной валюты](api-adapty/operations/listVirtualCurrencyBalances) --- # File: api-guides --- --- title: "Гайды по API" description: "Узнайте, как выполнять конкретные задачи с помощью серверного API." --- В этом разделе вы найдёте гайды по различным сценариям использования, которые помогут выполнять конкретные задачи с помощью серверного API и SDK Adapty. <CustomDocCardList /> --- # File: sync-subscribers-from-web --- --- title: "Синхронизация покупок между вебом и мобилкой" description: "Синхронизация подписчиков на вебе и мобилке." --- Если ваши пользователи могут купить продукт на **сайте**, вы можете автоматически синхронизировать их уровни доступа с **мобильным приложением**. В этом гайде вы узнаете, как это сделать с помощью Adapty API и SDK. #### Пример сценария \{#sample-use-case\} Допустим, в вашем приложении пользователи могут зарегистрироваться по freemium-плану как на мобильном устройстве, так и в вебе. Вы разрешаете им перейти на Premium-план на вашем сайте через Stripe или Chargebee. Как только пользователь оформляет подписку в вебе, вы хотите, чтобы он сразу получил доступ к Premium в мобильном приложении — без ожидания и повторного входа. Именно это и помогает автоматизировать Adapty. ## Шаг 1. Идентификация пользователей \{#step-1-identify-users\} Adapty использует `customer_user_id` для идентификации пользователей на разных платформах. Создайте этот ID один раз и передавайте его как в мобильный SDK, так и в веб-бэкенд. ### Регистрация через веб \{#sign-up-from-web\} Когда пользователи регистрируются на вашем сайте, нужно создать для них профиль в Adapty через серверный API. Описание метода — [здесь](api-adapty/operations/createProfile). ```bash curl --request POST \ --url https://api.adapty.io/api/v2/server-side-api/profile/ \ --header 'Accept: application/json' \ --header 'Authorization: Api-Key YOUR_SECRET_API_KEY' \ --header 'Content-Type: application/json' \ --header 'adapty-customer-user-id: YOUR_CUSTOMER_USER_ID' ``` ### Регистрация из приложения \{#sign-up-from-app\} Когда пользователь впервые регистрируется в приложении, можно передать его customer user ID при активации SDK. Если же SDK был активирован раньше, чем прошла регистрация, используйте метод `identify`, чтобы создать новый профиль и назначить ему customer user ID. :::important Если вы идентифицируете новых пользователей уже после активации SDK, то сначала SDK создаст анонимный профиль — без него он работать не может. Затем, когда вы идентифицируете пользователя и назначите ему новый customer user ID, будет создан новый профиль. Это поведение совершенно нормально и не влияет на точность аналитики. Подробнее [здесь](ios-quickstart-identify). ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { try await Adapty.identify("YOUR_USER_ID") // Unique for each user } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID") { error in if let error { // handle the error } } ``` </TabItem> <TabItem value="android" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") { error -> // Unique for each user if (error == null) { // successful identify } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID", error -> { if (error == null) { // successful identify } }); ``` </TabItem> <TabItem value="react-native" label="React Native" default> ```typescript showLineNumbers try { await adapty.identify("YOUR_USER_ID"); // Unique for each user // successfully identified } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers try { await Adapty().identify(customerUserId); // Unique for each user } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers Adapty.Identify("YOUR_USER_ID", (error) => { // Unique for each user if(error == null) { // successful identify } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform" default> ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") // Unique for each user .onSuccess { // successful identify } .onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor" default> ```typescript showLineNumbers try { await adapty.identify({ customerUserId: "YOUR_USER_ID" }); // successfully identified } catch (error) { // handle the error } ``` </TabItem> </Tabs> ## Шаг 2. Проверка статуса подписки через API \{#step-2-check-subscription-status-via-api\} Когда пользователь входит на ваш сайт, получите его профиль Adapty через API. Если у пользователя нет активной подписки, вы можете показать пейвол. Описание метода — [здесь](api-adapty/operations/getProfile). ```bash curl --request GET \ --url https://api.adapty.io/api/v2/server-side-api/profile/ \ --header 'Accept: application/json' \ --header 'Authorization: Api-Key YOUR_SECRET_API_KEY' \ --header 'adapty-customer-user-id: YOUR_USER_ID' \ ``` ## Шаг 3. Отображение пейвола на сайте \{#step-3-display-a-paywall-on-your-website\} На сайте показывайте пейвол пользователям бесплатного плана. Вы можете использовать любой платёжный провайдер (Stripe, Chargebee, LemonSqueezy и др.). ## Шаг 4. Обновите статус подписки в Adapty \{#step-4-update-subscription-status-in-adapty\} После завершения оплаты на вашем сайте вызовите API Adapty, чтобы обновить уровень доступа пользователя в соответствии с купленным продуктом. Справочник по методу — [здесь](api-adapty/operations/grantAccessLevel). ```bash curl --request POST \ --url https://api.adapty.io/api/v2/server-side-api/purchase/profile/grant/access-level/ \ --header 'Accept: application/json' \ --header 'Authorization: Api-Key YOUR_SECRET_API_KEY' \ --header 'Content-Type: application/json' \ --header 'adapty-customer-user-id: YOUR_USER_ID' \ --data '{ "access_level_id": "YOUR_ACCESS_LEVEL" }' ``` ## Шаг 5. Синхронизация статуса в приложении \{#step-5-sync-status-in-the-app\} Когда пользователь открывает ваше мобильное приложение, получите обновлённый профиль и откройте доступ к платным функциям. Нужно либо получить профиль вручную, либо синхронизировать его автоматически. Затем извлеките из него уровень доступа. Ниже показано, как получить профиль и проверить его статус. Подробнее — [здесь](ios-check-subscription-status). <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { let profile = try await Adapty.getProfile() if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers Adapty.getProfile { result in if let profile = try? result.get() { // check the access if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } } ``` </TabItem> <TabItem value="android" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // grant access to premium features } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success<AdaptyProfile>) result).getValue(); // check the access if (profile.getAccessLevels().get("YOUR_ACCESS_LEVEL") != null && profile.getAccessLevels().get("YOUR_ACCESS_LEVEL").getIsActive()) { // grant access to premium features } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` </TabItem> <TabItem value="react-native" label="React Native" default> ```typescript showLineNumbers try { const profile = await adapty.getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers try { final profile = await Adapty().getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false) { // grant access to premium features } } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // check the access if (profile.AccessLevels["YOUR_ACCESS_LEVEL"]?.IsActive ?? false) { // grant access to premium features } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform" default> ```kotlin showLineNumbers Adapty.getProfile() .onSuccess { profile -> // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // grant access to premium features } } .onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor" default> ```typescript showLineNumbers try { const profile = await adapty.getProfile(); // проверьте доступ if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive) { // предоставьте доступ к премиум-функциям } } catch (error) { // обработайте ошибку } ``` </TabItem> </Tabs> --- # File: sync-purchases-from-custom-stores --- --- title: "Синхронизация транзакций из кастомных сторов" description: "Синхронизируйте транзакции из кастомных сторов в Adapty для предоставления доступа и отслеживания дохода." --- Если вы продаёте подписки или встроенные покупки через **кастомные сторы** — например, Amazon Appstore, Microsoft Store или собственную платёжную платформу — вы можете синхронизировать эти транзакции с Adapty, чтобы автоматически управлять уровнями доступа и отслеживать выручку в аналитике. В этом гайде вы узнаете, как связать покупки из кастомных сторов с Adapty через SDK и API. #### Пример использования \{#sample-use-case\} Допустим, вы распространяете приложение через Amazon Appstore или создали собственный веб-магазин для прямых продаж. Когда пользователь совершает покупку на этих платформах, вы хотите: - Автоматически предоставить ему доступ к премиум-функциям в мобильном приложении - Отслеживать транзакцию в аналитике Adapty рядом с доходами из App Store и Google Play - Запускать интеграции и вебхуки так же, как для любой другой подписки Именно для этого и нужна данная интеграция. ## Шаг 1. Идентифицируйте пользователей \{#step-1-identify-users\} Adapty использует `customer_user_id` для идентификации пользователей на всех платформах. Этот ID нужно создать один раз и передать как в мобильный SDK, так и в веб-бэкенд. Когда пользователи впервые регистрируются через приложение, можно передать `customer_user_id` при активации SDK. Если же SDK был активирован до этапа регистрации, используйте метод `identify`, чтобы создать новый профиль и привязать к нему `customer_user_id`. :::important Если вы идентифицируете пользователей после активации SDK, сначала будет создан анонимный профиль (без него SDK не может работать). Когда вы вызовете `identify` с customer user ID, будет создан новый профиль. Это нормальное поведение, которое не влияет на точность аналитики. Подробнее [здесь](ios-quickstart-identify). ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { try await Adapty.identify("YOUR_USER_ID") // Unique for each user } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID") { error in if let error { // handle the error } } ``` </TabItem> <TabItem value="android" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") { error -> // Unique for each user if (error == null) { // successful identify } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID", error -> { if (error == null) { // successful identify } }); ``` </TabItem> <TabItem value="react-native" label="React Native" default> ```typescript showLineNumbers try { await adapty.identify("YOUR_USER_ID"); // Unique for each user // successfully identified } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers try { await Adapty().identify(customerUserId); // Unique for each user } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers Adapty.Identify("YOUR_USER_ID", (error) => { // Unique for each user if(error == null) { // successful identify } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform" default> ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") // Unique for each user .onSuccess { // successful identify } .onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor" default> ```typescript showLineNumbers try { await adapty.identify({ customerUserId: "YOUR_USER_ID" }); // successfully identified } catch (error) { // handle the error } ``` </TabItem> </Tabs> ## Шаг 2. Создайте продукты в кастомном сторе в дашборде Adapty \{#step-2-create-products-in-a-custom-store-in-adapty-dashboard\} Чтобы Adapty мог сопоставлять транзакции кастомного стора с вашими продуктами, нужно добавить продукты и настроить для них данные кастомного стора. 1. Откройте [**Products**](https://app.adapty.io/settings/general) в левом меню дашборда Adapty и нажмите **Create product**. Или нажмите на существующий продукт, чтобы отредактировать его. 2. Убедитесь, что выбран [уровень доступа](access-level), который вы хотите предоставлять пользователям при покупке продукта. 3. Нажмите **+** и выберите **Add a custom store**. 4. Нажмите **Create new custom store**. 5. Задайте имя стора (например, «Amazon Appstore», «Microsoft Store» или «Web Store») и его ID. Нажмите **Create custom store**. 6. Затем нажмите **Save changes**, чтобы привязать продукт к пользовательскому стору. 7. Введите **Store product ID** для продукта, чтобы сопоставить его с продуктом в этом сторе. Затем нажмите **Save**. ## Шаг 3. Синхронизация транзакций через API \{#step-3-sync-transactions-via-api\} Когда покупка завершается в вашем кастомном сторе, нужно синхронизировать её с Adapty через серверный API. Этот вызов API выполнит следующее: - Запишет транзакцию в Adapty - Предоставит пользователю соответствующий уровень доступа - Запустит все настроенные интеграции и вебхуки - Отобразит транзакцию в вашей аналитике Полная документация по методу — [здесь](api-adapty/operations/setTransaction). ```bash curl --request POST \ --url https://api.adapty.io/api/v2/server-side-api/purchase/set/transaction/ \ --header 'Accept: application/json' \ --header 'Authorization: Api-Key YOUR_SECRET_API_KEY' \ --header 'Content-Type: application/json' \ --header 'adapty-customer-user-id: YOUR_CUSTOMER_USER_ID' \ --data '{ "purchase_type": "PRODUCT_PERIOD", "store": "YOUR_CUSTOM_STORE", "environment": "production", "store_product_id": "YOUR_STORE_PRODUCT_ID", "store_transaction_id": "STORE_TRANSACTION_ID", "store_original_transaction_id": "ORIGINAL_TRANSACTION_ID", "price": { "country": "COUNTRY_CODE", "currency": "CURRENCY_CODE", "value": "YOUR_PRICE" }, "purchased_at": "2024-01-15T10:30:00Z" }' ``` :::important Важные параметры: - **store**: ID вашего кастомного стора из шага 2 - **store_product_id**: ID продукта в сторе из шага 2 - **store_transaction_id**: Уникальный идентификатор транзакции - **purchased_at**: Временная метка в формате ISO 8601, когда была совершена покупка - **price**: Сумма, уплаченная пользователем ::: ## Шаг 4. Проверьте доступ в приложении \{#step-4-verify-access-in-the-app\} После синхронизации транзакции профиль пользователя автоматически обновится с новым уровнем доступа. Когда пользователь откроет ваше мобильное приложение, получите его профиль, чтобы проверить статус подписки и открыть доступ к premium-функциям. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { let profile = try await Adapty.getProfile() if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers Adapty.getProfile { result in if let profile = try? result.get() { // check the access if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } } ``` </TabItem> <TabItem value="android" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // grant access to premium features } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success<AdaptyProfile>) result).getValue(); // check the access if (profile.getAccessLevels().get("YOUR_ACCESS_LEVEL") != null && profile.getAccessLevels().get("YOUR_ACCESS_LEVEL").getIsActive()) { // grant access to premium features } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // handle the error } }); ``` </TabItem> <TabItem value="react-native" label="React Native" default> ```typescript showLineNumbers try { const profile = await adapty.getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers try { final profile = await Adapty().getProfile(); // проверьте доступ if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false) { // предоставить доступ к премиум-функциям } } on AdaptyError catch (adaptyError) { // обработайте ошибку } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // check the access if (profile.AccessLevels["YOUR_ACCESS_LEVEL"]?.IsActive ?? false) { // grant access to premium features } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform" default> ```kotlin showLineNumbers Adapty.getProfile() .onSuccess { profile -> // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // grant access to premium features } } .onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor" default> ```typescript showLineNumbers try { const profile = await adapty.getProfile(); // проверяем доступ if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive) { // открываем доступ к premium-функциям } } catch (error) { // обрабатываем ошибку } ``` </TabItem> </Tabs> --- # File: grant-access-level --- --- title: "Вручную предоставить уровни доступа" description: "Вручную разблокируйте платные функции для конкретных пользователей или групп пользователей" --- Если нужно **вручную разблокировать премиум-функции** для конкретных пользователей или групп, это можно сделать через Adapty API. Это удобно для промо-кампаний, доступа для инвесторов или решения нестандартных обращений в поддержку. В этом гайде вы узнаете, как идентифицировать пользователей и программно предоставлять им уровни доступа. #### Примеры использования - **Промокоды**: когда пользователь вводит в приложении действующий промокод, автоматически открывайте ему доступ к премиум-функциям. - **Доступ для инвесторов и бета-тестеров**: предоставляйте премиум-доступ инвесторам или бета-тестерам, проверяя их пользовательские атрибуты. :::note **Промокоды Google Play**: покупка, совершённая по промокоду Google Play, может поступить без `orderId`. Валидация разовых (не подписочных) покупок в Adapty требует наличия `orderId`, поэтому такие активации не проходят валидацию и доступ не предоставляется автоматически. Выдайте доступ вручную, следуя инструкциям ниже — Server-Side API не зависит от `orderId`. ::: ## Шаг 1. Идентификация пользователей \{#step-1-identify-users\} Adapty использует `customer_user_id` для идентификации пользователей на разных платформах и устройствах. Это необходимо, чтобы пользователи сохраняли доступ после переустановки приложения или смены устройства. ID нужно создать один раз. Когда пользователь впервые регистрируется в приложении, можно передать `customer_user_id` при активации SDK или воспользоваться методом `identify`, если SDK был активирован до регистрации. :::important Если вы идентифицируете пользователей после активации SDK, SDK сначала создаст анонимный профиль (без него работа невозможна). Когда вы вызовете `identify` с customer user ID, будет создан новый профиль. Это нормальное поведение, которое не влияет на точность аналитики. Подробнее [здесь](ios-quickstart-identify). ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { try await Adapty.identify("YOUR_USER_ID") // Unique for each user } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID") { error in if let error { // handle the error } } ``` </TabItem> <TabItem value="android" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") { error -> // Unique for each user if (error == null) { // successful identify } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers // User IDs must be unique for each user Adapty.identify("YOUR_USER_ID", error -> { if (error == null) { // successful identify } }); ``` </TabItem> <TabItem value="react-native" label="React Native" default> ```typescript showLineNumbers try { await adapty.identify("YOUR_USER_ID"); // Unique for each user // successfully identified } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers try { await Adapty().identify(customerUserId); // Unique for each user } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers Adapty.Identify("YOUR_USER_ID", (error) => { // Unique for each user if(error == null) { // successful identify } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform" default> ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") // Unique for each user .onSuccess { // successful identify } .onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor" default> ```typescript showLineNumbers try { await adapty.identify({ customerUserId: "YOUR_USER_ID" }); // successfully identified } catch (error) { // handle the error } ``` </TabItem> </Tabs> ## Шаг 2. Предоставление уровня доступа через API \{#step-2-grant-access-level-via-api\} Как только пользователь идентифицирован с помощью `customer_user_id`, вы можете предоставить ему уровни доступа через серверный API. Этот вызов API предоставит пользователю уровень доступа, чтобы он мог пользоваться платными функциями без фактической оплаты. Полное описание метода — [здесь](api-adapty/operations/grantAccessLevel). :::tip Вы можете управлять доступом пользователей, добавив пользовательский атрибут (например, Beta tester или Investor) в дашборде Adapty. При запуске приложения [проверьте этот атрибут в профиле пользователя](subscription-status), чтобы автоматически предоставить доступ. Чтобы обновить доступ, просто измените атрибут в дашборде. ::: ```bash curl --request POST \ --url https://api.adapty.io/api/v2/server-side-api/purchase/profile/grant/access-level/ \ --header 'Accept: application/json' \ --header 'Authorization: Api-Key YOUR_SECRET_API_KEY' \ --header 'Content-Type: application/json' \ --header 'adapty-customer-user-id: CUSTOMER_USER_ID' \ --data '{ "access_level_id": "YOUR_ACCESS_LEVEL" }' ``` ## Шаг 3. Проверьте доступ в приложении \{#step-3-verify-access-in-the-app\} После предоставления доступа через API профиль пользователя обновится автоматически. Получите профиль, чтобы проверить статус подписки и открыть доступ к премиум-функциям. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { let profile = try await Adapty.getProfile() if profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive ?? false { // grant access to premium features } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers Adapty.getProfile { result in if let profile = try? result.get() { // check the access if profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive ?? false { // grant access to premium features } } } ``` </TabItem> <TabItem value="android" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive == true) { // grant access to premium features } } is AdaptyResult.Error -> { val error = result.error // handle the error } } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success<AdaptyProfile>) result).getValue(); // проверяем доступ if (profile.getAccessLevels().get("YOUR_ACCESS_LEVEL_ID") != null && profile.getAccessLevels().get("YOUR_ACCESS_LEVEL_ID").getIsActive()) { // открываем доступ к премиум-функциям } } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // обрабатываем ошибку } }); ``` </TabItem> <TabItem value="react-native" label="React Native" default> ```typescript showLineNumbers try { const profile = await adapty.getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```dart showLineNumbers try { final profile = await Adapty().getProfile(); // проверьте доступ if (profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive ?? false) { // предоставьте доступ к премиум-функциям } } on AdaptyError catch (adaptyError) { // обработайте ошибку } catch (e) { } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // check the access if (profile.AccessLevels["YOUR_ACCESS_LEVEL_ID"]?.IsActive ?? false) { // grant access to premium features } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform" default> ```kotlin showLineNumbers Adapty.getProfile() .onSuccess { profile -> // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive == true) { // grant access to premium features } } .onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor" default> ```typescript showLineNumbers try { const profile = await adapty.getProfile(); // check the access if (profile.accessLevels["YOUR_ACCESS_LEVEL_ID"]?.isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` </TabItem> </Tabs> --- # File: web-api --- --- title: Adapty Web API description: "" --- Web API — это расширение серверного API, предназначенное для использования с веб-приложениями. Оно позволяет получать нужный пейвол по связанному идентификатору плейсмента и фиксировать просмотры пейвола для точного отслеживания конверсий. Это помогает использовать A/B-тесты и персонализацию пейволов в Adapty, а также отслеживать, какие пейволы работают лучше всего. ## Сценарий использования: запись транзакции из веб-приложения и её привязка к использованному пейволу \{#use-case-record-a-transaction-from-your-web-app-and-link-it-to-the-used-paywall\} Допустим, вы продаёте продукты в своём веб-приложении. Вам нужно показать пользователям пейвол, дать им возможность совершить покупку, а затем добавить детали транзакции в Adapty. Важно связать эти транзакции с конкретными пейволами, через которые пользователь совершил покупку, чтобы аналитика отражала точные данные. Всё это легко реализовать с помощью Adapty API. ### Предварительные требования \{#prerequisites\} 1. [Создайте продукты](create-product), которые будете использовать в пейволе, в дашборде Adapty. 2. [Создайте пейвол](create-paywall) в дашборде Adapty. [Используйте Remote Config](customize-paywall-with-remote-config) для оформления вашего веб-пейвола. 3. [Настройте плейсмент](create-placement) и привяжите к нему пейвол в дашборде Adapty. ### Шаги с использованием Adapty API \{#steps-with-adapty-api\} 1. **Создайте профиль пользователя:** Adapty требует наличия профиля перед запросом пейвола, чтобы персонализировать результат под конкретного пользователя. Используйте запрос [Create profile](api-adapty/operations/createProfile) для создания профиля пользователя. 2. **Получите и отобразите пейвол:** Когда пользователь достигает плейсмента в вашем веб-приложении, где должен отображаться пейвол, используйте запрос [Get paywall](api-web/operations/getPaywall) для получения пейвола по [идентификатору плейсмента](placements). В результате вы получите пейвол для [аудитории](audience), соответствующей вашему пользователю. Отобразите пейвол с помощью своего кода, используя возвращённые продукты и (при необходимости) [Remote Config](customize-paywall-with-remote-config) этого пейвола. 3. **Зафиксируйте просмотр пейвола:** Используйте запрос [Record paywall view](api-web/operations/recordPaywallView) для регистрации просмотра пейвола в Adapty, чтобы аналитика точно отражала это событие. Это важно для корректного отслеживания конверсий. 4. **Запишите покупку:** Если пользователь завершил покупку, отправьте детали транзакции в Adapty через Adapty API. Включите **variation ID** в этот запрос, чтобы связать транзакцию с конкретным отображённым пейволом. За подробностями обратитесь к нашей странице о [связывании пейволов с транзакциями в мобильных приложениях](report-transactions-observer-mode) — тот же подход применяется и к веб-приложениям. 5. **Добавьте данные маркетинговой атрибуции (если применимо):** Если у вас есть данные маркетинговой атрибуции (например, информация о кампании или рекламе), используйте запрос [Add attribution](api-web/operations/addAttribution), чтобы добавить их в профиль пользователя и обогатить аналитику — так вы узнаете больше об эффективности рекламы в Adapty. --- **Что дальше:** - Перейдите к [авторизации Web API](web-api-authorization) - Запросы: - [Add attribution](api-web/operations/addAttribution) - [Get paywall](api-web/operations/getPaywall) - [Record paywall view](api-web/operations/recordPaywallView) --- # File: web-api-authorization --- --- title: Авторизация и формат запросов Web API description: "" --- ## Авторизация \{#authorization\} API-запросы должны быть аутентифицированы с помощью вашего публичного API-ключа, передаваемого в заголовке **Authorization** в формате `Api-Key {your_public_api_key}`, например `Api-Key public_live_...`. Найти этот ключ можно в [дашборд Adapty -> **App Settings** -> вкладка **General** -> раздел **API keys**](https://app.adapty.io/settings/general). :::important API-ключи привязаны к конкретному приложению. Если у вас несколько приложений, убедитесь, что для каждого из них используется отдельный ключ. ::: ## Формат запроса \{#request-format\} - **Заголовок Content-Type**: Укажите заголовок **Content-Type** со значением `application/json`, чтобы API обработал ваш запрос. - **Тело запроса**: API ожидает тело запроса в формате JSON. --- # File: web-api-requests --- --- title: " Web API Requests" description: "" --- Серверный API Adapty позволяет программно получать доступ к данным подписок и управлять ими, обеспечивая бесшовную интеграцию с вашими существующими сервисами и инфраструктурой. Будь то синхронизация данных между платформами, предоставление уровней доступа или валидация покупок в Stripe — этот API даёт все необходимые инструменты для синхронизации систем и вовлечения пользователей. ## Коллекция и окружение Postman \{#postman-collection-and-environment\} Чтобы упростить работу с нашим web API, мы подготовили коллекцию Postman и файл окружения, которые можно скачать и импортировать в Postman. - **Коллекция запросов**: включает все запросы, доступные в web API Adapty. Обратите внимание, что в ней используются переменные, которые можно определить в окружении. - **Окружение**: содержит список переменных, значения которых достаточно задать один раз. Мы подготовили единое окружение для серверного API, web API и API экспорта аналитики, чтобы упростить работу. После активации этого окружения Postman будет автоматически подставлять заданные значения переменных в запросы. :::tip [Скачать коллекцию и окружение](https://raw.githubusercontent.com/adaptyteam/adapty-docs/refs/heads/main/Downloads/Adapty_Web_API_postman_collection.zip) ::: Инструкцию по импорту коллекции и окружения в Postman см. в [документации Postman](https://learning.postman.com/docs/getting-started/importing-and-exporting/importing-data/). ## Используемые переменные \{#variables-used\} Мы создали единое окружение для серверного API, web API и API экспорта аналитики, чтобы упростить рабочий процесс. Ниже перечислены переменные, относящиеся к web API: | Переменная | Описание | Пример значения | | ----------------------- | ------------------------------------------------------------ | ------------------------------------------------------- | | public_api_key | Находится в поле **Public SDK key** в [**App settings**](https://app.adapty.io/settings/general). | `public_live_Pj1P1xzM.2CvSvE1IalQRFjsWy6csBVNpH33atnod` | | adapty-customer-user-id | Идентификатор пользователя в вашей системе. В дашборде Adapty его можно найти в поле **Customer user ID** в профиле. | `john.doe@example.com` | | adapty-profile-id | Идентификатор пользователя, присвоенный в Adapty. В дашборде Adapty его можно найти в поле **Adapty ID** в профиле. | `3286abd3-48b0-4e9c-a5f6-ac0a006333a6` | **Что дальше: Запросы:** - [Получить пейвол](api-web/operations/getPaywall) - [Зафиксировать просмотр пейвола](api-web/operations/recordPaywallView) - [Добавить атрибуцию](api-web/operations/addAttribution) --- # File: export-analytics-api --- --- title: Экспорт аналитики через API --- Экспорт аналитики в CSV даёт возможность глубже изучить метрики производительности приложения, настроить отчёты и анализировать тенденции со временем. С помощью Adapty API можно легко выгрузить подробную аналитику в формате CSV — это удобно для отслеживания, передачи и дальнейшей работы с данными. :::tip Используете AI-агента или LLM для выгрузки аналитики? Смотрите [Экспорт аналитики с помощью AI-агента](export-analytics-with-ai). ::: ## Начало работы с API для экспорта аналитики \{#getting-started-with-the-api-for-analytics-export\} С помощью API экспорта аналитики вы можете, например: 1. **Анализировать MRR по маркетинговым кампаниям**: измерять эффект от прошлогодних маркетинговых кампаний в определённой стране, чтобы понять, какие из них принесли наибольшую выручку, с разбивкой по неделям. Используйте метод [Получить данные аналитики](api-export-analytics/operations/retrieveAnalyticsData). 2. **Отслеживайте удержание когорты с течением времени**: Следите за удержанием по когортам, чтобы выявлять точки оттока и сравнивать когорты в динамике — это помогает обнаружить тенденции и ключевые моменты, где стратегии вовлечения могут повысить удержание. Доступно с фильтрацией по конкретному стору, стране и продукту. Используйте метод [Получение данных когорты](api-export-analytics/operations/retrieveCohortData) для этого. 3. **Оцените конверсию по каналам**: Проанализируйте конверсию по ключевым каналам привлечения, чтобы понять, какие из них эффективнее всего приводят к первой покупке. Это поможет направить маркетинговый бюджет туда, где он работает лучше. Используйте метод [Получить данные о конверсии](api-export-analytics/operations/retrieveConversionData). 4. **Изучите Churn Rate**: Отслеживайте, как быстро пользователи отписываются, чтобы выявить паттерны оттока или оценить эффективность мер по удержанию — с фокусом на конкретной стране и конкретном продукте. Используйте для этого метод [Retrieve funnel data](api-export-analytics/operations/retrieveFunnelData). 5. **Оцените LTV по сегментам пользователей**: определите пожизненную ценность разных сегментов пользователей, чтобы понять, какие группы приносят наибольший доход с течением времени. Сосредоточьтесь на ценных сегментах — например, на долгосрочных подписчиках — и используйте полученные данные для уточнения стратегий привлечения. Для этого воспользуйтесь методом [Получить данные LTV](api-export-analytics/operations/retrieveLTVData). 6. **Проверьте удержание по странам**: изучите показатели удержания по регионам, чтобы найти рынки с высокой вовлечённостью и выработать стратегии локализации или регионального продвижения. Используйте метод [Retrieve retention data](api-export-analytics/operations/retrieveRetentionData). --- **Что дальше**: - [Авторизация и формат запросов](export-analytics-api-authorization) - [Запросы к API экспорта аналитики](export-analytics-api-requests) --- # File: export-analytics-api-authorization --- --- title: Авторизация и формат запросов для API экспорта аналитики --- ## Авторизация \{#authorization\} Для аутентификации API-запросов используйте секретный API-ключ в заголовке Authorization. Его можно найти в [App Settings](https://app.adapty.io/settings/general). Формат: `Api-Key {YOUR_SECRET_API_KEY}`, например: `Api-Key secret_live_...`. :::important API-ключи привязаны к конкретному приложению. Если у вас несколько приложений, используйте для каждого отдельный ключ. ::: ## Формат запроса \{#request-format\} **Заголовки** Запросы к серверному API требуют определённых заголовков и тела в формате JSON. Используйте следующие параметры для формирования запросов: | Заголовок | Описание | | ------------ | ------------------------------------------------------------ | | Content-Type | (Обязательный) Укажите `application/json`, чтобы API обработал запрос. | | Adapty-Tz | (Необязательный) Укажите временную зону для определения способа группировки и отображения данных. Используйте формат [базы данных часовых поясов IANA](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) (например, `Europe/Berlin`). | ## Тело запроса \{#body\} API ожидает тело запроса в формате JSON с необходимыми данными. ## Ограничения по частоте запросов \{#rate-limits\} Максимум — 2 запроса в секунду на один API-ключ. При превышении этого лимита возвращается ошибка `429 Too Many Requests`. ## Ротация API-ключей \{#rotate-api-keys\} Если нужно сменить секретные API-ключи: 1. В разделе **Settings → General** нажмите **Generate new key**, затем нажмите значок корзины рядом со старым ключом. 2. Обновите ключ в своём приложении. --- **Что дальше: Запросы:** - [Получить данные аналитики](api-export-analytics/operations/retrieveAnalyticsData) - [Получить данные когорт](api-export-analytics/operations/retrieveCohortData) - [Получить данные конверсий](api-export-analytics/operations/retrieveConversionData) - [Получить данные воронки](api-export-analytics/operations/retrieveFunnelData) - [Получить данные Lifetime Value (LTV)](api-export-analytics/operations/retrieveLTVData) - [Получить данные удержания](api-export-analytics/operations/retrieveRetentionData) --- # File: export-analytics-api-requests --- --- title: Экспорт аналитики через API --- Экспорт аналитических данных в CSV позволяет глубже изучить метрики производительности приложения, настраивать отчёты и отслеживать тенденции во времени. С помощью API Adapty вы можете легко выгружать подробную аналитику в формат CSV — это удобно для отслеживания, обмена данными и их дальнейшего анализа. ## Коллекция и окружение Postman \{#postman-collection-and-environment\} Чтобы упростить работу с нашим API для экспорта аналитики, мы подготовили коллекцию Postman и файл окружения, которые можно скачать и импортировать в Postman. - **Коллекция запросов**: содержит все запросы, доступные в API экспорта аналитики Adapty. Обратите внимание, что в ней используются переменные, значения которых задаются в окружении. - **Окружение**: содержит список переменных, значения которых достаточно задать один раз. Мы подготовили единое окружение для серверного API, веб-API и API экспорта аналитики, чтобы вам было удобнее работать. После активации этого окружения Postman будет автоматически подставлять значения переменных в ваши запросы. :::tip [Скачать коллекцию и окружение](https://raw.githubusercontent.com/adaptyteam/adapty-docs/refs/heads/main/Downloads/Adapty_export_analytics_API_postman_collection.zip) ::: Информацию о том, как импортировать коллекцию и окружение в Postman, см. в [документации Postman](https://learning.postman.com/docs/getting-started/importing-and-exporting/importing-data/). ### Используемые переменные \{#variables-used\} Мы создали единое окружение для серверного API, веб-API и API экспорта аналитики, чтобы упростить вашу работу. Ниже перечислены переменные, специфичные для API экспорта аналитики: | Переменная | Описание | Пример значения | | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | | secret_api_key | Находится в поле **Secret key** в разделе [**App settings**](https://app.adapty.io/settings/general). | `secret_live_Pj1P1xzM.2CvSvE1IalQRFjsWy6csBVNpH33atnod` | **Запросы:** - [Получить данные аналитики](api-export-analytics/operations/retrieveAnalyticsData) - [Получить данные когорт](api-export-analytics/operations/retrieveCohortData) - [Получить данные конверсий](api-export-analytics/operations/retrieveConversionData) - [Получить данные воронки](api-export-analytics/operations/retrieveFunnelData) - [Получить данные Lifetime Value (LTV)](api-export-analytics/operations/retrieveLTVData) - [Получить данные удержания](api-export-analytics/operations/retrieveRetentionData) --- # End of Documentation _Generated on: 2026-07-24T13:01:12.708Z_ _Successfully processed: 17/18 files_ # CAPACITOR - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: ru Generated on: 2026-07-24T13:01:12.709Z Total files: 45 --- # File: capacitor-sdk-overview --- --- title: "Обзор Capacitor SDK" description: "Узнайте об Adapty Capacitor SDK и его ключевых возможностях." --- [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Capacitor.svg?style=flat&logo=capacitor)](https://github.com/adaptyteam/AdaptySDK-Capacitor/releases) Добро пожаловать! Мы здесь, чтобы сделать встроенные покупки простыми и понятными 🚀 Мы создали [Adapty Capacitor SDK](https://github.com/adaptyteam/AdaptySDK-Capacitor/), чтобы избавить вас от головной боли с встроенными покупками — и вы могли сосредоточиться на том, что у вас получается лучше всего: создавать классные приложения. Вот что мы берём на себя: - Управляйте покупками, валидацией чеков и подписками прямо из коробки - Создавайте и тестируйте пейволы без обновления приложения - Получайте детальную аналитику покупок без настройки — когорты, LTV, отток и воронки уже включены - Поддерживайте актуальный статус подписки пользователя во всех сессиях и на всех устройствах - Интегрируйте приложение с сервисами маркетинговой атрибуции и аналитики одной строкой кода :::note Прежде чем погружаться в код, вам нужно интегрировать Adapty с App Store Connect и Google Play Console, а затем настроить продукты в дашборде. Ознакомьтесь с нашим [гайдом по быстрому старту](quickstart), чтобы сначала всё настроить. ::: ## Начало работы \{#get-started\} For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. Вот что мы рассмотрим в этом гайде по интеграции: 1. [Установите и настройте SDK](sdk-installation-capacitor): Добавьте SDK как [зависимость](https://www.npmjs.com/package/@adapty/capacitor) в проект и активируйте его в коде. 2. [Настройте покупки через пейволы](capacitor-quickstart-paywalls): Настройте флоу покупки, чтобы пользователи могли приобретать продукты. 3. [Проверьте статус подписки](capacitor-check-subscription-status): Автоматически проверяйте состояние подписки пользователя и управляйте его доступом к платному контенту. 4. [Идентифицируйте пользователей (опционально)](capacitor-quickstart-identify): Свяжите пользователей с их профилями Adapty, чтобы их данные корректно сохранялись на всех устройствах. ### Смотрите в действии \{#see-it-in-action\} Хотите увидеть, как всё работает вместе? Мы подготовили примеры: **Примеры приложений**: Готовые примеры с полной настройкой: - [React](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-react-example) - [Vue.js](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-vue-example) - [Angular](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-angular-example) - [Инструменты для разработки](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/adapty-devtools) ## Основные концепции \{#main-concepts\} Прежде чем погружаться в код, давайте познакомимся с ключевыми концепциями, которые лежат в основе работы Adapty. Главное преимущество подхода Adapty в том, что в коде приложения жёстко прописаны только плейсменты. Всё остальное — продукты, дизайн пейволов, цены и офферы — можно гибко настраивать прямо из дашборда Adapty без обновления приложения: 1. **Продукт** — всё, что можно купить в вашем приложении: подписка, расходуемая покупка или пожизненный доступ. 2. **Флоу или пейвол** — продукты с настройками, привязанные к плейсменту. Два варианта: - **[Флоу](adapty-flow-builder)** — визуальный no-code интерфейс, созданный во Flow Builder. Adapty отрисовывает UI и обрабатывает покупку за вас. - **[Пейвол](paywalls)** — без визуальной настройки; вы создаёте UI в своём коде и сами вызываете `makePurchase`. См. [Реализация пейволов вручную](capacitor-quickstart-manual). В коде SDK оба варианта получают через один и тот же метод `getFlow`. 3. **Placement** - Стратегическая точка в пользовательском пути, где вы хотите показать пейвол. Плейсменты отвечают на вопросы «где» и «когда» в вашей стратегии монетизации. Распространённые плейсменты: - `main` — основное место показа пейвола - `onboarding` — показывается во время онбординга пользователя - `settings` — доступен из настроек приложения Начните с базовых плейсментов `main` или `onboarding` при первой интеграции, а затем подумайте, в каких ещё местах приложения пользователи могут быть готовы к покупке. 4. **Profile** - Когда пользователи приобретают продукт, их профилю присваивается **уровень доступа**, который вы используете для определения доступа к платным функциям. --- # File: sdk-installation-capacitor --- --- title: "Capacitor — установка и настройка Adapty SDK" description: "Пошаговое руководство по установке Adapty SDK на Capacitor для приложений на основе подписок." --- SDK Adapty включает два ключевых модуля для интеграции в ваше приложение на Capacitor: - **Core Adapty**: Этот модуль необходим для корректной работы Adapty в вашем приложении. - **AdaptyUI**: Этот модуль нужен, если вы используете [Adapty Paywall Builder](adapty-paywall-builder) — удобный инструмент без кода для создания кросс-платформенных пейволов. AdaptyUI активируется автоматически вместе с основным модулем. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples) — они демонстрируют полную настройку, включая отображение пейволов, совершение покупок и другую базовую функциональность. ::: ## Требования \{#requirements\} [Adapty Capacitor SDK](https://github.com/adaptyteam/AdaptySDK-Capacitor/) имеет следующие требования к версиям: | Версия Adapty SDK | Версия Capacitor | Версия iOS | |--------------------|-------------------|-------------| | 3.16.0+ | 8 | 15.0+ | | 3.15 | 7 | 14.0+ | Capacitor версии 6 и ниже не поддерживается. Для сборки под iOS с Adapty SDK v4 (beta) требуется **Xcode 26** или новее — нативный iOS SDK использует Swift tools 6.2. Требования к iOS 15.0+, Capacitor 8 и Android minSdk 24 такие же, как для SDK 3.16+. :::info Начиная с SDK v3.17, Adapty SDK использует Google Play Billing Library v8.0.0 по умолчанию. ::: :::info Установка SDK — это шаг 5 настройки Adapty. Прежде чем покупки заработают в вашем приложении, вам также нужно подключить приложение к сторам, а затем создать продукты, пейвол и плейсмент в дашборде Adapty. [Гайд по быстрому старту](quickstart) описывает все необходимые шаги. ::: ## Установка Adapty SDK \{#install-adapty-sdk\} :::important Шаги ниже устанавливают Adapty SDK 3.x. SDK v4 (бета) — необходим для [Flow Builder](adapty-flow-builder) и используется в [быстром старте](capacitor-quickstart-paywalls) — устанавливается иначе: следуйте инструкции [Adapty SDK 4.0 (бета)](#adapty-sdk-40-beta) ниже или воспользуйтесь [гайдом по миграции](migration-to-capacitor-sdk-v4). ::: [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Capacitor.svg?style=flat&logo=capacitor)](https://github.com/adaptyteam/AdaptySDK-Capacitor/releases) Установите Adapty SDK: ```sh npm install @adapty/capacitor npx cap sync ``` ### Adapty SDK 4.0 (beta) Capacitor SDK 4.0 — который добавляет поддержку [Flow Builder](adapty-flow-builder) — является предрелизной версией. Установите точную версию (npm не разрешает предрелизы через диапазоны с `^`/`~`), затем выполните синхронизацию: ```sh npm install @adapty/capacitor@4.0.0-beta.2 ``` ```sh npx cap sync ``` На iOS v4 подтягивает нативные SDK Adapty через **Swift Package Manager** — podspec для CocoaPods был удалён ([репозиторий спецификаций CocoaPods станет доступен только для чтения в декабре 2026 года](https://blog.cocoapods.org/CocoaPods-Specs-Repo/)). iOS-проект вашего приложения должен использовать SPM-интеграцию Capacitor: - Для новых приложений добавьте платформу iOS с менеджером пакетов SPM: ```sh npx cap add ios --packagemanager SPM ``` - Для существующих приложений перенесите iOS-проект с CocoaPods на SPM, следуя [руководству Capacitor по использованию SPM в существующем проекте](https://capacitorjs.com/docs/ios/spm#using-spm-in-an-existing-capacitor-project). Полный список изменений API в v4 см. в разделе [Миграция Adapty Capacitor SDK на v4](migration-to-capacitor-sdk-v4). ## Активация модуля Adapty в SDK Adapty \{#activate-adapty-module-of-adapty-sdk\} :::note SDK Adapty нужно активировать в приложении только один раз. ::: Чтобы получить **Public SDK Key**: 1. Откройте дашборд Adapty и перейдите в [**App settings → General**](https://app.adapty.io/settings/general). 2. В разделе **Api keys** скопируйте **Public SDK Key** (НЕ Secret Key). 3. Замените `"YOUR_PUBLIC_SDK_KEY"` в коде. Или получите его программно с помощью [Adapty CLI](developer-cli): ``` npm install -g adapty adapty auth login adapty apps list ``` Или напрямую: ``` npx adapty auth login adapty apps list ``` - Убедитесь, что для инициализации Adapty вы используете **Public SDK key** — **Secret key** предназначен только для [серверного API](getting-started-with-server-side-api). - **SDK-ключи** уникальны для каждого приложения, поэтому если у вас несколько приложений, выберите нужный ключ. Скопируйте следующий код в любой файл приложения для активации Adapty: ```typescript showLineNumbers try { await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { // verbose logging is recommended for the development purposes and for the first production release logLevel: 'verbose', // in the development environment, use this variable to avoid multiple activation errors. Set it to your development environment variable __ignoreActivationOnFastRefresh: true, } }); console.log('Adapty activated successfully!'); } catch (error) { console.error('Failed to activate Adapty SDK:', error); } ``` :::important Дождитесь завершения `activate` перед вызовом любых других методов Adapty SDK. Полная последовательность описана в разделе [Порядок вызовов в Capacitor SDK](capacitor-sdk-call-order). ::: :::tip Чтобы избежать ошибок активации в среде разработки, воспользуйтесь [советами](#development-environment-tips). ::: Теперь настройте пейволы в своём приложении: - Если вы используете [Adapty Paywall Builder](adapty-paywall-builder), следуйте [быстрому старту с Paywall Builder](capacitor-quickstart-paywalls). - Если вы создаёте собственный UI пейвола, смотрите [быстрый старт для кастомных пейволов](capacitor-quickstart-manual). ## Активация модуля AdaptyUI \{#activate-adaptyui-module-of-adapty-sdk\} Если вы планируете использовать [Paywall Builder](adapty-paywall-builder), вам понадобится модуль AdaptyUI. Он активируется автоматически при активации основного модуля — дополнительных действий не требуется. ## Опциональная настройка \{#optional-setup\} ### Логирование \{#logging\} #### Настройка системы логирования \{#set-up-the-logging-system\} Adapty записывает ошибки и другую важную информацию, чтобы вы понимали, что происходит. Доступны следующие уровни логирования: | Level | Description | | ---------- | ------------------------------------------------------------ | | `error` | Будут записываться только ошибки | | `warn` | Будут записываться ошибки и сообщения от SDK, которые не вызывают критических ошибок, но заслуживают внимания | | `info` | Будут записываться ошибки, предупреждения и различные информационные сообщения | | `verbose` | Будет записываться любая дополнительная информация, которая может быть полезна при отладке: вызовы функций, запросы к API и т. д. | Вы можете установить уровень логирования в приложении до или во время конфигурации Adapty: ```typescript showLineNumbers // Set log level before activation adapty.setLogLevel({ logLevel: 'verbose' }); // Or set it during configuration await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { logLevel: 'verbose', } }); ``` ### Политики обработки данных \{#data-policies\} Adapty не хранит персональные данные ваших пользователей, если вы явно их не передаёте, но вы можете настроить дополнительные политики безопасности данных в соответствии с требованиями стора или законодательства конкретной страны. #### Отключение сбора и передачи IP-адресов \{#disable-ip-address-collection-and-sharing\} При активации модуля Adapty установите `ipAddressCollectionDisabled` в значение `true`, чтобы отключить сбор и передачу IP-адресов пользователей. Значение по умолчанию — `false`. Используйте этот параметр для защиты конфиденциальности пользователей, соблюдения региональных требований по защите данных (например, GDPR или CCPA) или уменьшения объёма собираемых данных, если функции на основе IP-адреса не нужны вашему приложению. ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { ipAddressCollectionDisabled: true, } }); ``` #### Отключение сбора и передачи рекламного идентификатора \{#disable-advertising-id-collection-and-sharing\} При активации модуля Adapty установите `ios.idfaCollectionDisabled` (iOS) или `android.adIdCollectionDisabled` (Android) в значение `true`, чтобы отключить сбор рекламных идентификаторов. По умолчанию используется значение `false`. Используйте этот параметр для соответствия политикам App Store/Play Store, чтобы не вызывать запрос App Tracking Transparency, или если ваше приложение не использует рекламную атрибуцию или аналитику на основе рекламных идентификаторов. ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { ios: { idfaCollectionDisabled: true, }, android: { adIdCollectionDisabled: true, }, } }); ``` #### Настройка конфигурации кэша медиа для AdaptyUI \{#set-up-media-cache-configuration-for-adaptyui\} По умолчанию AdaptyUI кэширует медиафайлы (изображения и видео) для повышения производительности и снижения нагрузки на сеть. Вы можете настроить параметры кэша, предоставив собственную конфигурацию. Используйте `mediaCache` для переопределения настроек кэша по умолчанию: ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { mediaCache: { memoryStorageTotalCostLimit: 200 * 1024 * 1024, // Optional: memory cache size in bytes memoryStorageCountLimit: 2147483647, // Optional: max number of items in memory diskStorageSizeLimit: 200 * 1024 * 1024, // Optional: disk cache size in bytes }, } }); ``` | Параметр | Обязательный | Описание | |-----------|----------|-------------| | memoryStorageTotalCostLimit | нет | Общий размер кэша в памяти в байтах. По умолчанию используется платформенное значение. | | memoryStorageCountLimit | нет | Максимальное количество элементов в памяти. По умолчанию используется платформенное значение. | | diskStorageSizeLimit | нет | Максимальный размер файла на диске в байтах. По умолчанию используется платформенное значение. | ### Включение локальных уровней доступа (Android) \{#enable-local-access-levels-android\} По умолчанию [локальные уровни доступа](local-access-levels) включены на iOS и отключены на Android. Чтобы включить их и на Android, установите `localAccessLevelAllowed` в `true`: ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { android: { localAccessLevelAllowed: true, }, } }); ``` ### Очистка данных при восстановлении из резервной копии \{#clear-data-on-backup-restore\} Если для `clearDataOnBackup` установлено значение `true`, SDK определяет, что приложение было восстановлено из резервной копии iCloud, и удаляет все локально сохранённые данные SDK, включая кэшированную информацию профиля, сведения о продуктах и пейволах. После этого SDK инициализируется с чистого состояния. Значение по умолчанию — `false`. :::note Удаляется только локальный кэш SDK. История транзакций в Apple и данные пользователей на серверах Adapty остаются без изменений. ::: ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { ios: { clearDataOnBackup: true, }, } }); ``` ## Советы по среде разработки \{#development-environment-tips\} #### Устранение ошибок активации SDK при live-reload в Capacitor \{#troubleshoot-sdk-activation-errors-on-capacitors-live-reload\} При разработке с Adapty SDK в Capacitor вы можете столкнуться с ошибкой: `Adapty can only be activated once. Ensure that the SDK activation call is not made more than once.` Она возникает из-за того, что функция live-reload в Capacitor инициирует несколько вызовов активации в процессе разработки. Чтобы этого избежать, используйте опцию `__ignoreActivationOnFastRefresh`, установив её в флаг режима разработки Capacitor — он будет отличаться в зависимости от используемого бандла. ```typescript showLineNumbers try { await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { // Set your development environment variable __ignoreActivationOnFastRefresh: true, } }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); // Handle the error appropriately for your app } ``` ## Устранение неполадок \{#troubleshooting\} #### Ошибка минимальной версии iOS \{#minimum-ios-version-error\} :::note Это касается проектов на CocoaPods с **SDK 3.x**. SDK 4.0 устанавливается на iOS через Swift Package Manager (без `Podfile`) и требует iOS 15.0 — установите deployment target на 15.0 в Xcode. ::: Если при использовании SDK 3.x возникает ошибка минимальной версии iOS, обновите Podfile: ```diff -platform :ios, min_ios_version_supported +platform :ios, '15.0' ``` #### Правила резервного копирования Android (настройка Auto Backup) \{#android-backup-rules-auto-backup-configuration\} Некоторые SDK (включая Adapty) поставляются с собственной конфигурацией Android Auto Backup. Если вы используете несколько SDK, каждый из которых определяет правила резервного копирования, слияние манифестов Android может завершиться ошибкой, связанной с `android:fullBackupContent`, `android:dataExtractionRules` или `android:allowBackup`. Типичные симптомы ошибки: `Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/your_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` :::note Эти изменения нужно вносить в директорию Android-платформы (обычно находится в папке `android/` вашего проекта). ::: Чтобы решить проблему, необходимо: - Указать механизму слияния манифестов использовать значения вашего приложения для атрибутов, связанных с резервным копированием. - Создать файлы правил резервного копирования, объединяющие правила Adapty с правилами других SDK. #### 1. Добавьте пространство имён `tools` в манифест В файле `AndroidManifest.xml` убедитесь, что корневой тег `<manifest>` включает tools: ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.app"> ... </manifest> ``` #### 2. Переопределите атрибуты резервного копирования в `<application>` В том же файле `AndroidManifest.xml` обновите тег `<application>`, чтобы ваше приложение предоставляло итоговые значения и указывало механизму слияния манифестов заменять значения библиотек: ```xml <application android:name=".App" android:allowBackup="true" android:fullBackupContent="@xml/sample_backup_rules" android:dataExtractionRules="@xml/sample_data_extraction_rules" tools:replace="android:fullBackupContent,android:dataExtractionRules"> ... </application> ``` Если какой-либо SDK также задаёт `android:allowBackup`, включите его в `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. Создайте объединённые файлы правил резервного копирования Создайте XML-файлы в директории `res/xml/` вашего Android-проекта, объединяющие правила Adapty с правилами других SDK. Android использует разные форматы правил резервного копирования в зависимости от версии ОС, поэтому создание обоих файлов обеспечивает совместимость со всеми версиями Android, которые поддерживает ваше приложение. :::note В примерах ниже в качестве стороннего SDK используется AppsFlyer. Замените или добавьте правила для других SDK, которые используются в вашем приложении. ::: **Для Android 12 и выше** (используется новый формат правил извлечения данных): ```xml title="sample_data_extraction_rules.xml" <?xml version="1.0" encoding="utf-8"?> <data-extraction-rules> <cloud-backup> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </cloud-backup> <device-transfer> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </device-transfer> </data-extraction-rules> ``` **Для Android 11 и ниже** (используется устаревший формат полного резервного копирования): ```xml title="sample_backup_rules.xml" <?xml version="1.0" encoding="utf-8"?> <full-backup-content> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> :::tip После изменения нативных Android-файлов выполните `npx cap sync android`, чтобы Capacitor подхватил обновлённые ресурсы при повторной генерации платформы. ::: #### Покупки завершаются с ошибкой после возврата из другого приложения на Android \{#purchases-fail-after-returning-from-another-app-in-android\} Если Activity, запускающий флоу покупки, использует нестандартный `launchMode`, Android может некорректно пересоздать или повторно использовать его при возврате пользователя из Google Play, банковского приложения или браузера. Это может привести к тому, что результат покупки будет потерян или воспринят как отмена. Чтобы покупки работали корректно, используйте только режимы запуска `standard` или `singleTop` для Activity, из которой запускается флоу покупки, и избегайте любых других режимов. В файле `AndroidManifest.xml` убедитесь, что для Activity, запускающей флоу покупки, задан режим `standard` или `singleTop`: ```xml <activity android:name=".MainActivity" android:launchMode="standard" /> ``` #### Ошибки сборки Swift 6, вызванные переопределением SWIFT_VERSION в Podfile \{#swift-6-build-errors-caused-by-podfile-swift_version-override\} :::note Это применимо к проектам на CocoaPods с **SDK 3.x**. SDK 4.0 устанавливает нативные SDK через Swift Package Manager, поэтому файл `Podfile` изменять не нужно. ::: При сборке Capacitor-приложения для iOS вы можете столкнуться с ошибками компиляции Swift 6 в pod-таргетах Adapty. Типичные симптомы: несоответствия `@Sendable` в `AdaptyUIBuilderLogic`, отсутствие соответствия `Sendable` у типов Adapty или ошибки изоляции акторов. Поды Adapty объявляют `s.swift_version = '6.0'` и требуют Swift 6 для сборки. Ваш собственный код приложения может оставаться на Swift 5 — только целевые поды Adapty (`Adapty`, `AdaptyUI`, `AdaptyUIBuilder`, `AdaptyLogger`, `AdaptyPlugin`) должны собираться с Swift 6. Наиболее распространённая причина — хук `post_install` в `ios/App/Podfile`, который перезаписывает `SWIFT_VERSION` для каждого пода: ```ruby showLineNumbers title="ios/App/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` **Исправление**: Исключите pod-таргеты Adapty из переопределения: ```ruby showLineNumbers title="ios/App/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| next if %w[Adapty AdaptyUI AdaptyUIBuilder AdaptyLogger AdaptyPlugin].include?(target.name) target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` Затем выполните `npx cap sync ios` и пересоберите проект. Чтобы проверить результат, откройте `ios/App/Pods/Pods.xcodeproj`, выберите таргет пода `Adapty` → **Build Settings** → **Swift Language Version**. Там должно быть указано **Swift 6**. --- # File: capacitor-quickstart-paywalls --- --- title: "Включение покупок с помощью Flow Builder в Capacitor SDK" description: "Краткое руководство по включению встроенных покупок с помощью Adapty Flow Builder." --- Чтобы включить встроенные покупки, нужно понять три ключевых понятия: - [**Продукты**](product) – всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ) - [**Флоу**](adapty-flow-builder) – последовательности экранов, которые представляют продукты пользователям, созданные в no-code Flow Builder. SDK получает их через `getFlow`. Если вы предпочитаете строить UI в собственном коде, используйте пейвол — см. [Реализация пейволов вручную](capacitor-quickstart-manual). - [**Плейсменты**](placements) – где и когда вы показываете флоу в приложении (например, `main`, `onboarding`, `settings`). Вы привязываете флоу к плейсментам в дашборде, а затем запрашиваете их по ID плейсмента в коде. Это упрощает проведение A/B-тестов и показ разных флоу разным пользователям. Adapty предлагает три способа подключить покупки в вашем приложении. Выберите подходящий в зависимости от требований вашего приложения: | Реализация | Сложность | Когда использовать | |---|---|---| | Adapty Flow Builder | ✅ Легко | Вы [создаёте полноценный флоу с поддержкой покупок в конструкторе без кода](quickstart-paywalls). Adapty автоматически отображает его и берёт на себя весь процесс покупки, валидацию чеков и управление подписками. | | Самостоятельно созданные пейволы | 🟡 Средне | Вы реализуете интерфейс пейвола в коде приложения, но по-прежнему получаете объект флоу от Adapty, сохраняя гибкость в настройке продуктов. См. [гайд](capacitor-quickstart-manual). | | Observer mode | 🔴 Сложно | У вас уже есть собственная инфраструктура обработки покупок, и вы хотите продолжать её использовать. Обратите внимание, что observer mode имеет ограничения в Adapty. См. [статью](observer-vs-full-mode). | :::important **Шаги ниже показывают, как реализовать флоу, созданный в Adapty Flow Builder.** Если вы предпочитаете строить UI пейвола самостоятельно, см. [Реализация пейволов вручную](capacitor-quickstart-manual). ::: Чтобы отобразить флоу, созданный в Adapty Flow Builder, в коде вашего приложения нужно только: 1. **Получить флоу**: Запросить его из Adapty. 2. **Отобразить его — Adapty сам обработает покупки**: Показать представление в вашем приложении. 3. **Обработать действия кнопок**: Связать взаимодействия пользователя с реакцией приложения на них. Например, открывать ссылки или закрывать флоу по нажатию кнопок. ## Перед началом \{#before-you-start\} Перед началом выполните следующие шаги: 1. Подключите приложение к [App Store](initial_ios) и/или [Google Play](initial-android) в дашборде Adapty. 2. [Создайте продукты](create-product) в Adapty. 3. [Создайте пейвол и добавьте в него продукты](create-paywall). 4. [Создайте плейсмент и добавьте в него пейвол](create-placement). 5. [Установите и активируйте Adapty SDK](sdk-installation-capacitor) в коде приложения. :::tip Быстрее всего выполнить эти шаги поможет [quickstart-гайд](quickstart) или создание пейволов и плейсментов с помощью [Developer CLI](developer-cli-quickstart). ::: ## 1. Получите флоу \{#1-get-the-flow\} Флоу привязаны к плейсментам, настроенным в дашборде. Плейсменты позволяют показывать разные флоу разным аудиториям или запускать [A/B-тесты](ab-tests). Чтобы получить флоу, созданный в Adapty Flow Builder, запросите объект `flow` по ID [плейсмента](placements) с помощью метода `getFlow`. Флоу содержит элементы интерфейса и стили, необходимые для его отображения. ```typescript showLineNumbers title="Capacitor" try { const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID', }); // the requested flow } catch (error) { // handle the error } ``` ## 2. Отображение флоу \{#2-display-the-flow\} Теперь, когда у вас есть флоу, достаточно добавить несколько строк для его отображения. Создайте `view` с помощью метода `createFlowView`, задайте обработчики событий, затем вызовите `view.present()`. Каждый `view` можно использовать только один раз. Если нужно показать флоу повторно, вызовите `createFlowView` ещё раз, чтобы создать новый экземпляр `view`. ```typescript showLineNumbers title="Capacitor" try { const view = await createFlowView(flow); await view.setEventHandlers({ onPurchaseCompleted(purchaseResult, product) { return purchaseResult.type === 'success'; // close the flow on a successful purchase, keep it open for cancelled or pending purchases }, }); await view.present(); } catch (error) { // handle the error } ``` :::tip Подробнее о том, как отобразить флоу, читайте в нашем [гайде](capacitor-present-paywalls). ::: ## 3. Обработка нажатий на кнопки \{#handle-button-actions\} Когда пользователи нажимают кнопки во флоу, Capacitor SDK автоматически обрабатывает покупки, восстановление, закрытие флоу и открытие URL. Однако у других кнопок есть пользовательские или предопределённые идентификаторы, и их действия нужно обрабатывать в вашем коде. Также вы можете переопределить поведение по умолчанию. Например, вот поведение кнопки закрытия по умолчанию. Добавлять его в код не нужно, но здесь видно, как это делается при необходимости. ```typescript showLineNumbers title="Capacitor" const unsubscribe = await view.setEventHandlers({ onCloseButtonPress() { return true; // allow the flow to close }, }); ``` :::tip Читайте наши гайды о том, как обрабатывать [действия кнопок](capacitor-handle-paywall-actions) и [события](capacitor-handling-events). ::: ## Следующие шаги \{#next-steps\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Ваш пейвол готов к отображению в приложении. Протестируйте покупки в [песочнице App Store](test-purchases-in-sandbox) или в [Google Play Store](testing-on-android), чтобы убедиться, что тестовая покупка из пейвола проходит успешно. Теперь нужно [проверить уровень доступа пользователей](capacitor-check-subscription-status), чтобы показывать пейвол или предоставлять доступ к платным функциям нужным пользователям. ## Полный пример \{#full-example\} Вот как все шаги из этого гайда можно объединить в вашем приложении. ```typescript showLineNumbers title="Capacitor" export async function showFlow() { try { const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID', }); const view = await createFlowView(flow); await view.setEventHandlers({ onCloseButtonPress() { return true; }, onPurchaseCompleted(purchaseResult, product) { return purchaseResult.type === 'success'; // close the flow on a successful purchase, keep it open for cancelled or pending purchases }, }); await view.present(); } catch (error) { // handle any error that may occur during the process console.warn('Error showing flow:', error); } } ``` --- # File: capacitor-check-subscription-status --- --- title: "Проверка статуса подписки в Capacitor SDK" description: "Узнайте, как проверить статус подписки в приложении на Capacitor с помощью Adapty." --- Чтобы решить, может ли пользователь получить доступ к платному контенту или нужно показать ему пейвол, необходимо проверить его [уровень доступа](access-level) в профиле. В этой статье описано, как читать состояние профиля и решать, что показывать пользователю — пейвол или платный контент. ## Получение статуса подписки \{#get-subscription-status\} Когда нужно решить, показать пейвол или платный контент, вы проверяете [уровень доступа](access-level) в профиле пользователя. Есть два варианта: - Вызвать `getProfile`, если нужны актуальные данные прямо сейчас (например, при запуске приложения) или требуется принудительное обновление. - Настроить **автоматическое обновление профиля**, чтобы хранить локальную копию, которая автоматически обновляется при изменении статуса подписки. ### Получить профиль \{#get-profile\} Самый простой способ узнать статус подписки — использовать метод `getProfile` для получения профиля: ```typescript showLineNumbers try { const profile = await adapty.getProfile(); } catch (error) { // handle the error } ``` ### Прослушивание обновлений подписки \{#listen-to-subscription-updates\} Чтобы автоматически получать обновления профиля в приложении: 1. Используйте `adapty.addListener('onLatestProfileLoad')` для отслеживания изменений профиля — Adapty автоматически вызовет этот метод при каждом изменении статуса подписки пользователя. 2. Сохраняйте обновлённые данные профиля при вызове этого метода, чтобы использовать их в приложении без дополнительных сетевых запросов. ```typescript showLineNumbers class SubscriptionManager { private currentProfile: any = null; constructor() { // Listen for profile updates adapty.addListener('onLatestProfileLoad', (data) => { this.currentProfile = data.profile; // Update UI, unlock content, etc. }); } // Use stored profile instead of calling getProfile() hasAccess(): boolean { return this.currentProfile?.accessLevels?.['YOUR_ACCESS_LEVEL']?.isActive ?? false; } } ``` :::note Adapty автоматически вызывает слушатель события `onLatestProfileLoad` при запуске приложения, предоставляя кешированные данные подписки даже если устройство офлайн. ::: ## Привяжите профиль к логике пейвола \{#connect-profile-with-paywall-logic\} Когда нужно мгновенно решить, показывать ли пейвол или открывать доступ к платным функциям, можно напрямую проверить профиль пользователя. Этот подход удобен при запуске приложения, входе в премиум-разделы или перед показом определённого контента. ```typescript showLineNumbers const checkAccessLevel = async () => { try { const profile = await adapty.getProfile(); return profile?.accessLevels?.['YOUR_ACCESS_LEVEL']?.isActive === true; } catch (error) { console.warn('Error checking access level:', error); return false; // Show paywall if access check fails } }; const getAccessLevel = (profile: AdaptyProfile) => { return profile.accessLevels?.['YOUR_ACCESS_LEVEL']; }; const initializePaywall = async () => { try { await loadPaywall(); const hasAccess = await checkAccessLevel(); if (!hasAccess) { // Show paywall if no access } } catch (error) { console.warn('Error initializing paywall:', error); } }; ``` ## Дальнейшие шаги \{#next-steps\} Теперь, когда вы знаете, как отслеживать статус подписки, узнайте, как [работать с профилями пользователей](capacitor-quickstart-identify), чтобы они могли получить доступ к тому, за что заплатили. --- # File: capacitor-quickstart-identify --- --- title: "Идентификация пользователей в Capacitor SDK" description: "Быстрый старт по настройке Adapty для управления встроенными подписками в Capacitor." --- Управление покупками пользователей зависит от модели аутентификации вашего приложения: - Если приложение не использует бэкенд-аутентификацию и не хранит данные пользователей, см. [раздел об анонимных пользователях](#anonymous-users). - Если приложение имеет (или будет иметь) бэкенд-аутентификацию, см. [раздел об идентифицированных пользователях](#identified-users). :::tip **Ключевые понятия**: - **Профили** — сущности, необходимые для работы SDK. Adapty создаёт их автоматически. Они могут быть анонимными (без customer user ID) или идентифицированными (с customer user ID). - **Customer user ID** — необязательные идентификаторы, которые **вы создаёте**, чтобы Adapty мог связать ваших пользователей с их профилями в Adapty. ::: Вот в чём разница между анонимными и идентифицированными пользователями: | | Анонимные пользователи | Идентифицированные пользователи | |-------------------------|---------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------| | **Purchase management** | Восстановление покупок на уровне стора | История покупок сохраняется на всех устройствах через customer user ID | | **Profile management** | Новый профиль при каждой переустановке | Один и тот же профиль во всех сессиях и на всех устройствах | | **Data persistence** | Данные анонимных пользователей привязаны к устройству/установке | Данные идентифицированных пользователей сохраняются на всех устройствах и сессиях | ## Анонимные пользователи \{#anonymous-users\} Если у вас нет серверной аутентификации, **никакой дополнительной обработки аутентификации в коде приложения не требуется**: 1. Когда SDK активируется при первом запуске приложения, Adapty **создаёт новый профиль для пользователя**. 2. Когда пользователь совершает покупку в приложении, она **привязывается к его профилю Adapty и аккаунту в сторе**. 3. Когда пользователь **переустанавливает** приложение или устанавливает его на **новом устройстве**, Adapty **создаёт новый пустой профиль при активации**. 4. Если пользователь ранее совершал покупки в вашем приложении, по умолчанию они автоматически синхронизируются из App Store при активации SDK. :::note Восстановление из резервной копии работает иначе, чем переустановка. По умолчанию при восстановлении из резервной копии SDK сохраняет кешированные данные и не создаёт новый профиль. Вы можете настроить это поведение с помощью параметра `clearDataOnBackup`. [Подробнее](sdk-installation-capacitor#clear-data-on-backup-restore). ::: ## Идентифицированные пользователи \{#identified-users\} - Если у профиля ещё нет customer user ID (то есть **пользователь не вошёл в систему**), при отправке customer user ID он будет привязан к этому профилю. - Если это **переустановка, повторный вход или установка на новом устройстве**, и вы уже отправляли customer user ID этого пользователя ранее, новый профиль не создаётся — вместо этого происходит переключение на существующий профиль, связанный с данным customer user ID. У вас есть два способа идентифицировать пользователей в приложении: - [**При входе/регистрации:**](#during-loginsignup) Если пользователи входят в систему после запуска приложения, вызовите `identify()` с customer user ID при аутентификации. - [**При активации SDK:**](#during-the-sdk-activation) Если customer user ID уже сохранён на момент запуска приложения, передайте его при вызове `activate()`. :::important По умолчанию, когда Adapty получает покупку от Customer User ID, который в данный момент связан с другим Customer User ID, уровень доступа становится общим — таким образом, оба профиля получают платный доступ. Вы можете настроить это поведение так, чтобы платный доступ передавался от одного профиля другому, или полностью отключить совместный доступ. Подробнее — в [статье](general#6-sharing-paid-access-between-user-accounts). ::: <img src="/assets/shared/img/identify-diagram.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### В момент входа или регистрации \{#during-loginsignup\} Если вы идентифицируете пользователей после запуска приложения (например, после входа в аккаунт или регистрации), используйте метод `identify`, чтобы задать их customer user ID. - Если вы **раньше не использовали этот customer user ID**, Adapty автоматически привяжет его к текущему профилю. - Если вы **уже использовали этот customer user ID для идентификации пользователя**, Adapty переключится на работу с профилем, связанным с этим customer user ID. :::tip При создании customer user ID сохраните его вместе с данными пользователя, чтобы отправлять тот же ID при входе с новых устройств или переустановке приложения. ::: Всегда используйте `await` для `identify` перед вызовом других методов SDK. Конкурентные вызовы приводят к ошибке `#3006 profileWasChanged` или обращаются к анонимному профилю. См. [Порядок вызовов в Capacitor SDK](capacitor-sdk-call-order). ```typescript showLineNumbers try { await adapty.identify({ customerUserId: "YOUR_USER_ID" }); // successfully identified } catch (error) { // handle the error } ``` ### При активации SDK \{#during-the-sdk-activation\} Если вы уже знаете customer user ID на момент активации SDK, можно передать его сразу в методе `activate` — тогда отдельно вызывать `identify` не нужно. Если customer user ID известен, но вы задаёте его только после активации, при инициализации Adapty создаст новый пустой профиль и переключится на существующий только после вызова `identify`. Вы можете передать как существующий customer user ID (тот, что использовался раньше), так и новый. Если передать новый, созданный при активации профиль автоматически привяжется к этому customer user ID. :::tip Чтобы исключить созданные пустые профили из аналитики дашборда, перейдите в **App settings** и настройте [**Installs definition for analytics**](general#4-installs-definition-for-analytics). ::: ```typescript showLineNumbers await adapty.activate({ apiKey: "YOUR_PUBLIC_SDK_KEY", params: { customerUserId: "YOUR_USER_ID" } }); ``` ### Выход пользователей из системы \{#log-users-out\} Если в приложении есть кнопка выхода, используйте метод `logout`. При этом для пользователя создаётся новый анонимный ID профиля. ```typescript showLineNumbers try { await adapty.logout(); // successful logout } catch (error) { // handle the error } ``` :::info Для повторного входа пользователя в приложение используйте метод `identify`. ::: ### Разрешение покупок без входа в систему \{#allow-purchases-without-login\} Если пользователи могут совершать покупки как до, так и после входа в приложение, никаких дополнительных настроек не требуется: Вот как это работает: 1. Когда незалогиненный пользователь совершает покупку, Adapty привязывает её к анонимному идентификатору профиля. 2. Когда пользователь входит в аккаунт, Adapty переключается на работу с его идентифицированным профилем. - Если customer user ID уже существует (уже привязан к профилю), Adapty автоматически синхронизирует транзакции. - Если customer user ID новый (например, покупка была сделана до регистрации), Adapty присваивает его текущему профилю, сохраняя всю историю покупок. --- # File: adapty-sdk-integration-skill-capacitor --- --- title: "Интеграция Adapty в приложение Capacitor с помощью навыка SDK integration" description: "Используйте навык adapty-sdk-integration для сквозной интеграции SDK Adapty в ваше приложение Capacitor с помощью AI-инструмента для написания кода." --- [Скилл adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) автоматизирует интеграцию Adapty от начала до конца: настройку дашборда, установку SDK, пейвол и проверку на каждом этапе. Он автоматически определяет вашу платформу и подгружает нужную документацию Adapty на каждом шаге. **Поддерживаемые инструменты**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Для установки выберите команду для своего инструмента. Полный список — в [README скилла](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex или любой другой инструмент** — используйте [skills CLI](https://skills.sh) (обратите внимание, что скиллы, установленные таким способом, не обновляются автоматически): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` Либо клонируйте репозиторий и скопируйте `skills/adapty-sdk-integration/` в директорию скиллов вашего инструмента. После установки запустите скилл в вашем проекте: ``` /adapty-sdk-integration ``` Скилл задаст несколько вопросов по настройке, а затем проведёт через настройку дашборда, установку SDK, пейвол и проверку. :::important Навык находится в бета-версии. Если он зависнет или будет вести себя неожиданно, воспользуйтесь [пошаговым гайдом по интеграции](adapty-cursor-capacitor) — он проведёт ваш AI-инструмент через каждый этап с нужной документацией. ::: [Скилл adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) автоматизирует интеграцию Adapty от начала до конца: настройку дашборда, установку SDK, пейвол и проверку на каждом этапе. Он автоматически определяет вашу платформу и подгружает нужную документацию Adapty на каждом шаге. **Поддерживаемые инструменты**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Для установки выберите команду для своего инструмента. Полный список — в [README скилла](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex или любой другой инструмент** — используйте [skills CLI](https://skills.sh) (обратите внимание, что скиллы, установленные таким способом, не обновляются автоматически): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` Либо клонируйте репозиторий и скопируйте `skills/adapty-sdk-integration/` в директорию скиллов вашего инструмента. После установки запустите скилл в вашем проекте: ``` /adapty-sdk-integration ``` Скилл задаст несколько вопросов по настройке, а затем проведёт через настройку дашборда, установку SDK, пейвол и проверку. --- # File: adapty-cursor-capacitor --- --- title: "Интеграция Adapty в Capacitor-приложение с помощью ИИ" description: "Пошаговый гайд по интеграции Adapty в Capacitor-приложение с использованием Cursor, Context7, ChatGPT, Claude и других ИИ-инструментов." --- Этот гайд поможет вам шаг за шагом интегрировать Adapty в ваше Capacitor-приложение с помощью инструмента AI-кодинга — нужно лишь подавать ему нужную документацию Adapty в правильном порядке. For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## Перед началом: настройка дашборда \{#before-you-start-dashboard-setup\} Adapty требует предварительной настройки дашборда перед написанием кода SDK. Это можно сделать с помощью интерактивного LLM-навыка или вручную через дашборд. ### Подход с использованием skill (рекомендуется) \{#skill-approach-recommended\} Skill Adapty CLI позволяет вашему LLM настраивать приложение, продукты, уровни доступа, пейволы и плейсменты напрямую — без необходимости открывать дашборд на каждом шаге. Вам нужно только [подключить сторы](integrate-payments) в дашборде. ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` После добавления skill запустите `/adapty-cli` в вашем агенте. Он проведёт вас через каждый шаг — включая момент, когда нужно открыть дашборд для подключения сторов. ### Подход через дашборд \{#dashboard-approach\} Если вы предпочитаете настраивать всё вручную, вот что нужно сделать до написания кода. Ваша языковая модель не сможет найти значения из дашборда — их нужно предоставить самостоятельно. 1. **Подключите сторы**: В дашборде Adapty перейдите в **App settings → General**. Подключите App Store и Google Play, если ваше приложение на Capacitor поддерживает обе платформы. Это обязательно для работы покупок. [Подключить сторы](integrate-payments) 2. **Скопируйте публичный SDK-ключ**: В дашборде Adapty перейдите в **App settings → General** и найдите раздел **API keys**. В коде это строка, которую вы передаёте в `adapty.activate()`. 3. **Создайте хотя бы один продукт**: В дашборде Adapty перейдите на страницу **Products**. Ссылаться на продукты напрямую в коде не нужно — Adapty передаёт их через пейволы. [Добавить продукты](quickstart-products) 4. **Создайте пейвол и плейсмент**: В дашборде Adapty создайте пейвол на странице **Paywalls**, затем привяжите его к плейсменту на странице **Placements**. В коде идентификатор плейсмента — это строка, которую вы передаёте в `adapty.getFlow()`. [Создать пейвол](quickstart-paywalls) 5. **Настройте уровни доступа**: В дашборде Adapty настройте каждый продукт на странице **Products**. В коде строка проверяется как `profile.accessLevels['premium']?.isActive`. Уровень доступа `premium` по умолчанию подходит большинству приложений. Если платящие пользователи получают доступ к разным функциям в зависимости от продукта (например, план `basic` и план `pro`), [создайте дополнительные уровни доступа](assigning-access-level-to-a-product) до начала разработки. :::tip Когда у вас есть все пять элементов, можно приступать к написанию кода. Скажите своему LLM: «Мой публичный SDK-ключ — X, мой ID плейсмента — Y» — и он сгенерирует правильный код инициализации и получения флоу. ::: ### Настройте, когда будете готовы \{#set-up-when-ready\} Это не обязательно для начала разработки, но пригодится по мере развития интеграции: - **A/B-тесты**: Настраиваются на странице **Placements**. Изменения кода не требуются. [A/B-тесты](ab-tests) - **Дополнительные пейволы и плейсменты**: Добавьте больше вызовов `getPaywall` с разными идентификаторами плейсментов. - **Интеграции аналитики**: Настраиваются на странице **Integrations**. Процесс настройки зависит от интеграции. См. [интеграции аналитики](analytics-integration) и [интеграции атрибуции](attribution-integration). ## Загрузите документацию Adapty в свою LLM \{#feed-adapty-docs-to-your-llm\} ### Используйте Context7 (рекомендуется) \{#use-context7-recommended\} [Context7](https://context7.com) — это MCP-сервер, который даёт вашей LLM прямой доступ к актуальной документации Adapty. LLM автоматически подтягивает нужные доки в зависимости от вашего запроса — никакого ручного копирования ссылок. Context7 работает с **Cursor**, **Claude Code**, **Windsurf** и другими MCP-совместимыми инструментами. Для настройки выполните: ``` npx ctx7 setup ``` Команда определит ваш редактор и настроит сервер Context7. Для ручной настройки см. [репозиторий Context7 на GitHub](https://github.com/upstash/context7). После настройки обращайтесь к библиотеке Adapty в своих промптах: ``` Use the adaptyteam/adapty-docs library to look up how to install the Capacitor SDK ``` :::warning Несмотря на то что Context7 избавляет от необходимости вставлять ссылки на документацию вручную, порядок внедрения имеет значение. Следуйте [пошаговому руководству по внедрению](#implementation-walkthrough) ниже — шаг за шагом, чтобы всё работало корректно. ::: ### Используйте документацию в виде обычного текста \{#use-plain-text-docs\} Любую страницу документации Adapty можно получить в виде обычного текста Markdown. Для этого добавьте `.md` в конец URL или нажмите **Copy for LLM** под заголовком статьи. Например: [adapty-cursor-capacitor.md](https://adapty.io/docs/ru/adapty-cursor-capacitor.md). Каждый этап [пошагового руководства по интеграции](#implementation-walkthrough) ниже содержит блок «Отправьте это в свой LLM» со ссылками `.md` для копирования. Чтобы получить сразу несколько страниц документации, воспользуйтесь [индексными файлами и наборами для конкретных платформ](#plain-text-doc-index-files) ниже. ## Пошаговая реализация \{#implementation-walkthrough\} Дальше в этом гайде — интеграция Adapty в порядке реализации. Для каждого этапа указаны документы, которые нужно передать LLM, что должно получиться в итоге и типичные проблемы. ### Планирование интеграции \{#plan-your-integration\} Прежде чем переходить к коду, попросите LLM проанализировать ваш проект и составить план реализации. Если ваш AI-инструмент поддерживает режим планирования (например, план-режим в Cursor или Claude Code), используйте его — так LLM сможет изучить структуру вашего проекта и документацию Adapty до написания кода. Укажите LLM, какой подход к покупкам вы используете — от этого зависит, какие гайды ему нужно применять: - [**Adapty Flow Builder**](adapty-flow-builder): Вы создаёте флоу в визуальном редакторе Adapty, а SDK отображает их автоматически. - [**Вручную созданные пейволы**](capacitor-making-purchases): Вы сами строите UI пейвола в коде, но используете Adapty для получения продуктов и обработки покупок. - [**Режим Observer**](observer-vs-full-mode): Вы сохраняете существующую инфраструктуру покупок и используете Adapty только для аналитики и интеграций. Не знаете, что выбрать? Прочитайте [таблицу сравнения в быстром старте](capacitor-quickstart-paywalls). ### Установка и настройка SDK \{#install-and-configure-the-sdk\} Добавьте зависимость Adapty SDK через npm и активируйте её с помощью публичного ключа SDK. Это основа — без неё ничто остальное работать не будет. **Гайд:** [Установка и настройка Adapty SDK](sdk-installation-capacitor) :::info Это руководство описывает Adapty Capacitor SDK v4 (beta) — API, с которым работает [quickstart](capacitor-quickstart-paywalls). v4 — предрелизная версия, поэтому убедитесь, что ваш LLM указывает точную версию (`npm install @adapty/capacitor@4.0.0-beta.2`), а не устанавливает последнюю стабильную 3.x. См. [раздел установки SDK 4.0](sdk-installation-capacitor#adapty-sdk-40-beta) и [руководство по миграции](migration-to-capacitor-sdk-v4). ::: Отправьте это в свой LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/sdk-installation-capacitor.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** Приложение собирается и запускается на iOS и Android. В консоли отображается лог активации Adapty. - **Частая проблема:** «Public API key is missing» → проверьте, что заменили плейсхолдер реальным ключом из **App settings**. ::: ### Показ пейволов и обработка покупок \{#show-paywalls-and-handle-purchases\} Получите пейвол по ID плейсмента, отобразите его и обработайте события покупок. Нужные вам гайды зависят от того, как вы обрабатываете покупки. Тестируйте каждую покупку в песочнице по мере работы — не откладывайте это на конец. Инструкции по настройке см. в разделе [Тестирование покупок в песочнице](test-purchases-in-sandbox). <Tabs groupId="paywall-approach"> <TabItem value="builder" label="Flow Builder" default> **Гайды:** - [Включение покупок с помощью флоу (быстрый старт)](capacitor-quickstart-paywalls) - [Получение флоу и пейволов](capacitor-get-pb-paywalls) - [Отображение флоу и пейволов](capacitor-present-paywalls) - [Обработка событий](capacitor-handling-events) - [Реакция на действия](capacitor-handle-paywall-actions) ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/capacitor-quickstart-paywalls.md - https://adapty.io/docs/ru/capacitor-get-pb-paywalls.md - https://adapty.io/docs/ru/capacitor-present-paywalls.md - https://adapty.io/docs/ru/capacitor-handling-events.md - https://adapty.io/docs/ru/capacitor-handle-paywall-actions.md ``` :::tip[Checkpoint] - **Ожидается:** Флоу отображается с настроенными продуктами. Нажатие на продукт запускает диалог покупки в песочнице. - **Возможные проблемы:** Пустой флоу или ошибка `getFlow` → убедитесь, что ID плейсмента точно совпадает с указанным в дашборде и что плейсменту назначена аудитория. ::: </TabItem> <TabItem value="manual" label="Manual paywalls"> **Гайды:** - [Включить покупки в кастомном пейволе (быстрый старт)](capacitor-quickstart-manual) - [Получить пейволы и продукты](fetch-paywalls-and-products-capacitor) - [Отобразить пейвол на основе Remote Config](present-remote-config-paywalls-capacitor) - [Совершить покупки](capacitor-making-purchases) - [Восстановить покупки](capacitor-restore-purchase) ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/capacitor-quickstart-manual.md - https://adapty.io/docs/ru/fetch-paywalls-and-products-capacitor.md - https://adapty.io/docs/ru/present-remote-config-paywalls-capacitor.md - https://adapty.io/docs/ru/capacitor-making-purchases.md - https://adapty.io/docs/ru/capacitor-restore-purchase.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** Ваш кастомный пейвол отображает продукты, полученные из Adapty. Нажатие на продукт вызывает диалог покупки в песочнице. - **Частая ошибка:** Пустой массив продуктов → проверьте, что пейволу назначены продукты в дашборде и у плейсмента есть аудитория. ::: </TabItem> <TabItem value="observer" label="Observer mode"> **Гайды:** - [Обзор Observer mode](observer-vs-full-mode) - [Реализация Observer mode](implement-observer-mode-capacitor) - [Отчёт о транзакциях в Observer mode](report-transactions-observer-mode-capacitor) ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/observer-vs-full-mode.md - https://adapty.io/docs/ru/implement-observer-mode-capacitor.md - https://adapty.io/docs/ru/report-transactions-observer-mode-capacitor.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После тестовой покупки в песочнице через ваш текущий флоу покупки транзакция появляется в **Event Feed** дашборда Adapty. - **Частая проблема:** Нет событий → убедитесь, что вы передаёте транзакции в Adapty и серверные уведомления настроены для обоих сторов. ::: </TabItem> </Tabs> ### Проверка статуса подписки \{#check-subscription-status\} После покупки проверьте профиль пользователя на наличие активного уровня доступа, чтобы открыть доступ к платным функциям. **Гайд:** [Проверка статуса подписки](capacitor-check-subscription-status) Отправьте это в свой LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/capacitor-check-subscription-status.md ``` :::tip[Контрольная точка] - **Ожидаемый результат:** После покупки в песочнице `profile.accessLevels['premium']?.isActive` возвращает `true`. - **Частая ошибка:** Пустой `accessLevels` после покупки → проверьте, что продукту назначен уровень доступа в дашборде. ::: ### Идентификация пользователей \{#identify-users\} Свяжите аккаунты пользователей вашего приложения с профилями Adapty, чтобы покупки сохранялись на всех устройствах. :::important Пропустите этот шаг, если в вашем приложении нет авторизации. ::: **Гайд:** [Идентификация пользователей](capacitor-quickstart-identify) Отправьте это в ваш LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/capacitor-quickstart-identify.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После вызова `adapty.identify()` в разделе дашборда **Profiles** отображается ваш пользовательский ID. - **Важно:** Вызывайте `identify` после активации, но до получения пейволов — иначе события могут быть привязаны к анонимному профилю. ::: ### Подготовка к релизу \{#prepare-for-release\} Как только интеграция заработает в песочнице, пройдитесь по чеклисту релиза, чтобы убедиться, что всё готово к продакшену. **Гайд:** [Чеклист релиза](release-checklist) Отправьте это в свой LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/ru/release-checklist.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** Все пункты чеклиста подтверждены: подключение сторов, серверные уведомления, флоу покупки, проверка уровней доступа и требования конфиденциальности. - **Частая проблема:** Серверные уведомления не настроены → настройте App Store Server Notifications в **App settings → iOS SDK** и Google Play Real-Time Developer Notifications в **App settings → Android SDK**. ::: ## Текстовые индексные файлы документации \{#plain-text-doc-index-files\} Если вам нужно дать вашей языковой модели более широкий контекст, чем отдельные страницы, мы публикуем индексные файлы, которые перечисляют или объединяют всю документацию Adapty: - [`llms.txt`](https://adapty.io/docs/ru/llms.txt): Список всех страниц со ссылками в формате `.md`. [Формирующийся стандарт](https://llmstxt.org/) для обеспечения доступности сайтов LLM-моделям. Обратите внимание, что для некоторых AI-агентов (например, ChatGPT) потребуется скачать `llms.txt` и загрузить файл в чат. - [`llms-full.txt`](https://adapty.io/docs/ru/llms-full.txt): Вся документация Adapty, объединённая в один файл. Очень большой — используйте только тогда, когда нужна полная картина. - Файлы для Capacitor: [`capacitor-llms.txt`](https://adapty.io/docs/ru/capacitor-llms.txt) и [`capacitor-llms-full.txt`](https://adapty.io/docs/ru/capacitor-llms-full.txt) — подмножества документации для конкретной платформы, которые экономят токены по сравнению с полным сайтом. --- # File: capacitor-paywalls --- --- title: "Флоу и пейволы - Capacitor" description: "Отображение и управление флоу и пейволами, созданными с помощью Adapty Flow Builder или Paywall Builder в вашем приложении Capacitor." --- ## Отображение пейволов \{#display-paywalls\} ### Adapty Flow Builder и Paywall Builder <CustomDocCardList ids={['capacitor-get-pb-paywalls', 'capacitor-present-paywalls', 'capacitor-handling-events', 'capacitor-handle-paywall-actions']} /> :::tip Чтобы быстро начать работу с флоу и пейволами Adapty, смотрите наш [гайд по быстрому старту](capacitor-quickstart-paywalls). ::: ### Реализация пейволов вручную <CustomDocCardList ids={['capacitor-quickstart-manual', 'fetch-paywalls-and-products-capacitor', 'present-remote-config-paywalls-capacitor', 'capacitor-making-purchases']} /> Дополнительные гайды по реализации пейволов и обработке покупок вручную см. в [разделе](capacitor-implement-paywalls-manually). ## Полезные функции \{#useful-features\} <CustomDocCardList ids={['capacitor-use-fallback-paywalls', 'capacitor-web-paywall']} /> --- # File: capacitor-get-pb-paywalls --- --- title: "Получение флоу и пейволов - Capacitor" description: "Получайте флоу и пейволы из Adapty в вашем Capacitor-приложении." --- <SDKv4> <MethodPromo method="getFlow" /> После того как вы [разработали флоу или пейвол в Paywall Builder](adapty-paywall-builder), его можно отобразить в мобильном приложении. Первый шаг — получить флоу или пейвол, связанный с плейсментом, и его конфигурацию отображения, как описано ниже. Обратите внимание, что эта тема посвящена флоу и пейволам, настроенным в Paywall Builder. Если вы реализуете пейволы вручную, обратитесь к разделу [Получение пейволов и продуктов для пейволов с Remote Config в мобильном приложении](fetch-paywalls-and-products-capacitor). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Прежде чем начать показывать флоу и пейволы в мобильном приложении (нажмите, чтобы развернуть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте флоу/пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них флоу/пейвол](create-placement) в дашборде Adapty. 4. Установите [Adapty SDK](sdk-installation-capacitor) в своём мобильном приложении. </details> ## Получение флоу/пейвола \{#fetch-flowpaywall\} Если вы создали флоу или пейвол с помощью Flow Builder или Paywall Builder, вам не нужно беспокоиться о его рендеринге в коде мобильного приложения для отображения пользователю. Такой флоу или пейвол уже содержит и то, что должно отображаться, и то, как именно это должно выглядеть. Тем не менее вам нужно получить его ID через плейсмент, конфигурацию отображения, а затем показать его в мобильном приложении. Получите флоу или пейвол и создайте его [представление](capacitor-get-pb-paywalls#fetch-the-view-configuration) как можно раньше — в идеале задолго до того, как вы его покажете. Метод `createFlowView` загружает конфигурацию представления и запускает фоновую загрузку и кеширование изображений. Чем раньше вы его вызовете, тем больше времени у загрузок будет на завершение. К моменту отображения флоу или пейвола его конфигурация и изображения уже могут быть закешированы и готовы к показу. Чтобы получить флоу или пейвол, используйте метод `getFlow`: ```typescript showLineNumbers try { const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID', }); // запрошенный флоу/пейвол } catch (error) { // обработка ошибки } ``` Параметры: | Параметр | Наличие | Описание | |-------------------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `'reload_revalidating_cache_data'` | <p>Передаётся внутри опционального объекта `params`. По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают самые актуальные данные.</p><p></p><p>Однако если вы считаете, что у ваших пользователей нестабильный интернет, рассмотрите использование `'return_cache_data_else_load'` — тогда кеш будет возвращаться, если он существует. В этом случае пользователи могут не получить самые последние данные, зато загрузка будет быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии во избежание лишних сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при переустановке или вручную.</p><p></p><p>Adapty SDK хранит пейволы локально в двух слоях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](fallback-paywalls). Для более быстрой загрузки пейволов также используется CDN, а при недоступности CDN — отдельный резервный сервер. Такая система гарантирует, что вы всегда получаете актуальную версию пейволов, обеспечивая надёжность даже при слабом интернет-соединении.</p> | | **loadTimeoutMs** | по умолчанию: 5 сек | <p>Передаётся внутри опционального объекта `params`. Это значение ограничивает тайм-аут данного метода. По истечении тайм-аута будут возвращены кешированные данные или локальный резервный вариант.</p><p>Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения, указанного в `loadTimeoutMs`, поскольку операция может включать несколько запросов под капотом.</p> | **Не хардкодьте идентификаторы продуктов.** Единственный ID, который стоит хардкодить, — это ID плейсмента. Флоу и пейволы настраиваются удалённо, поэтому количество продуктов и доступных офферов может меняться в любой момент. Ваше приложение должно обрабатывать эти изменения динамически: если сегодня пейвол возвращает два продукта, а завтра три — отображайте все без изменений в коде. Параметры ответа: | Параметр | Описание | | :-------- |:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Flow | Объект `AdaptyFlow` с идентификаторами флоу (`id`, `variationId`), названием, плейсментом, вариантами пейволов (`paywalls`) и Remote Config (`remoteConfigs`). | ## Получение конфигурации представления \{#fetch-the-view-configuration\} :::important Убедитесь, что в билдере включён переключатель **Show on device**. Если эта опция отключена, конфигурация представления не будет доступна для получения. ::: Если плейсмент создан в **Flow Builder** или **Paywall Builder**, Adapty самостоятельно отрисовывает UI. Создайте представление с помощью `createFlowView`, затем [покажите флоу или пейвол](capacitor-present-paywalls). Если плейсмент — это кастомный пейвол без UI в билдере, [обработайте его как пейвол с Remote Config](present-remote-config-paywalls-capacitor). В Capacitor SDK вызывайте `createFlowView` напрямую — предварительно получать конфигурацию представления не нужно. :::warning Результат метода `createFlowView` можно использовать только один раз. Если он понадобится снова, вызовите `createFlowView` заново. Повторный вызов без пересоздания может привести к ошибке. ::: ```typescript showLineNumbers try { const view = await createFlowView(flow); } catch (error) { // handle the error } ``` Параметры: | Параметр | Наличие | Описание | | :------------------- | :------- | :----------------------------------------------------------- | | **flow** | обязательный | Объект `AdaptyFlow` для получения контроллера нужного флоу/пейвола. | | **customTags** | необязательный | Словарь пользовательских тегов и их значений. Теги служат плейсхолдерами в контенте и динамически заменяются конкретными строками для персонализации контента во флоу/пейволе. Подробнее см. в разделе о пользовательских тегах в Paywall Builder. | | **prefetchProducts** | необязательный | Включите для оптимизации времени отображения продуктов на экране. При значении `true` AdaptyUI автоматически загружает нужные продукты. По умолчанию: `true`. | | **android.enableSafeArea** | необязательный | Только для Android (игнорируется на iOS). Указывается в ключе `android`. При значении `true` представление флоу применяет отступы безопасной зоны. По умолчанию: `true`. Значение по умолчанию подходит для большинства случаев. | :::note Если вы используете несколько языков, узнайте, как добавить [локализацию флоу](add-paywall-locale-in-adapty-paywall-builder) и как правильно использовать коды локалей [здесь](capacitor-localizations-and-locale-codes). ::: Когда вью готово, [отобразите флоу/пейвол](capacitor-present-paywalls). ## Получите флоу или пейвол для дефолтной аудитории, чтобы загрузить его быстрее \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} Как правило, флоу и пейволы загружаются практически мгновенно, так что беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и плейсментов, а пользователи работают при слабом интернет-соединении, загрузка флоу или пейвола может занять больше времени, чем хотелось бы. В таких ситуациях имеет смысл показывать дефолтный флоу или пейвол, чтобы обеспечить комфортный пользовательский опыт вместо пустого экрана. Чтобы решить эту проблему, можно воспользоваться методом `getFlowForDefaultAudience`, который получает флоу или пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать флоу или пейвол с помощью метода `getFlow`, как описано в разделе [Получение флоу/пейвола](#fetch-flowpaywall) выше. :::warning Почему мы рекомендуем использовать `getFlow` Метод `getFlowForDefaultAudience` имеет ряд существенных недостатков: - **Потенциальные проблемы с обратной совместимостью**: если вам нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать пейволы с поддержкой текущей (устаревшей) версии, либо смириться с тем, что пользователи этой версии могут столкнуться с проблемами при отображении пейволов. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, настроенный для аудитории **All Users**, — а значит, вы теряете персонализированный таргетинг (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вы готовы принять эти недостатки ради более быстрого получения флоу или пейвола, используйте метод `getFlowForDefaultAudience` следующим образом. В противном случае используйте `getFlow`, описанный [выше](#fetch-flowpaywall). ::: ```typescript showLineNumbers try { const flow = await adapty.getFlowForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', }); // the requested flow/paywall } catch (error) { // handle the error } ``` | Параметр | Наличие | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `'reload_revalidating_cache_data'` | <p>Передаётся внутри необязательного объекта `params`. По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите вариант `'return_cache_data_else_load'` — он возвращает кешированные данные, если они есть. В этом случае пользователи могут получить не самые последние данные, зато загрузка будет быстрее вне зависимости от качества связи. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии для снижения количества сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при переустановке или вручную.</p> | ## Настройка ресурсов \{#customize-assets\} Чтобы настроить изображения и видео в вашем флоу/пейволе, реализуйте пользовательские ресурсы. Hero-изображения и видео имеют предопределённые идентификаторы: `hero_image` и `hero_video`. В бандле пользовательских ресурсов вы обращаетесь к этим элементам по их идентификаторам и настраиваете их поведение. Для остальных изображений и видео необходимо [задать пользовательский идентификатор](custom-media) в дашборде Adapty. Например, вы можете: - Показывать разные изображения или видео разным пользователям. - Показывать локальное превью-изображение, пока загружается основное удалённое. - Показывать превью-изображение перед воспроизведением видео. Вот пример того, как можно передать пользовательские ресурсы через простой словарь: ```typescript showLineNumbers const customAssets: Record<string, AdaptyCustomAsset> = { 'custom_image': { type: 'image', relativeAssetPath: 'custom_image.png' }, 'hero_video': { type: 'video', fileLocation: { ios: { fileName: 'custom_video.mp4' }, android: { relativeAssetPath: 'videos/custom_video.mp4' } } } }; const view = await createFlowView(flow, { customAssets }); ``` :::note Если ресурс не найден, флоу/пейвол вернётся к стандартному внешнему виду. ::: </SDKv4> <SDKv3> После того как вы [создали визуальную часть пейвола](adapty-paywall-builder) с помощью нового Paywall Builder в дашборде Adapty, его можно показать в мобильном приложении. Первый шаг — получить пейвол, привязанный к плейсменту, и его конфигурацию отображения, как описано ниже. Обратите внимание, что эта тема посвящена пейволам, настроенным через Paywall Builder. Подробнее о получении пейволов с Remote Config см. в разделе [Получение пейволов и продуктов для пейволов с Remote Config в вашем мобильном приложении](fetch-paywalls-and-products-capacitor). <details> <summary>Прежде чем начать показывать пейволы в мобильном приложении (нажмите, чтобы развернуть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них пейвол](create-placement) в дашборде Adapty. 4. Установите [Adapty SDK](sdk-installation-capacitor) в своё мобильное приложение. </details> ## Получение пейвола, созданного в Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\} Если вы [создали пейвол в Paywall Builder](adapty-paywall-builder), вам не нужно самостоятельно реализовывать его отображение в коде мобильного приложения. Такой пейвол содержит как описание того, что нужно показать, так и то, как именно это отображается. Тем не менее, вам нужно получить его ID через плейсмент, конфигурацию отображения, а затем показать пейвол в приложении. Для оптимальной производительности важно получать пейвол и его [конфигурацию представления](capacitor-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) как можно раньше, чтобы изображения успели загрузиться до того, как пользователь увидит экран. Чтобы получить пейвол, используйте метод `getPaywall`: ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', }); // the requested paywall } catch (error) { // handle the error } ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указываете при создании плейсмента в дашборде Adapty. | | **locale** | <p>необязательный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-paywall-locale-in-adapty-paywall-builder). Ожидается, что этот параметр будет представлять собой код языка из одного или двух подтегов, разделённых символом минус (**-**). Первый подтег обозначает язык, второй — регион.</p><p></p><p>Пример: `en` означает английский, `pt-br` — бразильский португальский.</p><p>Подробнее о кодах локализации и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).</p> | | **params** | необязательный | Дополнительные параметры для получения пейвола. | **Не хардкодьте идентификаторы продуктов.** Единственный ID, который стоит хардкодить — это ID плейсмента. Пейволы настраиваются удалённо, поэтому количество продуктов и доступных офферов может меняться в любой момент. Ваше приложение должно обрабатывать эти изменения динамически: если сегодня пейвол возвращает два продукта, а завтра три — отображайте все без изменений в коде. Параметры ответа: | Параметр | Описание | | :-------- |:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Paywall | Объект [`AdaptyPaywall`](https://capacitor.adapty.io/interfaces/adaptypaywall) со списком ID продуктов, идентификатором пейвола, Remote Config и рядом других свойств. | ## Получение конфигурации представления пейвола, созданного в Paywall Builder \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important Убедитесь, что включён переключатель **Show on device** в Paywall Builder. Если эта опция не активирована, конфигурация представления не будет доступна для получения. ::: После получения пейвола проверьте, содержит ли он `ViewConfiguration` — это означает, что пейвол был создан с помощью Paywall Builder. Это поможет вам определить, как отображать пейвол. Если `ViewConfiguration` присутствует, обрабатывайте его как пейвол Paywall Builder; если нет — [обработайте его как пейвол с Remote Config](present-remote-config-paywalls-capacitor). В Capacitor SDK вызывайте метод `createPaywallView` напрямую, без предварительного получения конфигурации представления вручную. :::warning Результат метода `createPaywallView` можно использовать только один раз. Если вам нужно использовать его снова, вызовите метод `createPaywallView` заново. ::: ```typescript showLineNumbers if (paywall.hasViewConfiguration) { try { const view = await createPaywallView(paywall); } catch (error) { // handle the error } } else { // use your custom logic } ``` Параметры: | Параметр | Наличие | Описание | | :------------------- | :------- | :----------------------------------------------------------- | | **paywall** | обязательный | Объект `AdaptyPaywall` для получения контроллера нужного пейвола. | | **customTags** | необязательный | Словарь пользовательских тегов и их значений. Пользовательские теги служат плейсхолдерами в содержимом пейвола и динамически заменяются конкретными строками для персонализации контента. Подробнее см. в разделе «Пользовательские теги в Paywall Builder». | | **prefetchProducts** | необязательный | Включите, чтобы оптимизировать время отображения продуктов на экране. При значении `true` AdaptyUI автоматически загружает необходимые продукты. По умолчанию: `false`. | :::note Если вы используете несколько языков, узнайте, как добавить [локализацию Paywall Builder](add-paywall-locale-in-adapty-paywall-builder) и как правильно использовать коды локалей [здесь](capacitor-localizations-and-locale-codes). ::: Когда у вас есть представление, [отобразите пейвол](capacitor-present-paywalls). ## Получите пейвол для аудитории по умолчанию, чтобы ускорить загрузку \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} Как правило, пейволы загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и пейволов, а пользователи работают при слабом интернет-соединении, загрузка пейвола может занять больше времени, чем хотелось бы. В таких случаях имеет смысл показывать пейвол по умолчанию — это обеспечит плавный пользовательский опыт вместо того, чтобы не показывать пейвол вовсе. Чтобы решить эту задачу, используйте метод `getPaywallForDefaultAudience`, который получает пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать пейвол через метод `getPaywall`, как описано в разделе [Получение информации о пейволе](#fetch-paywall-designed-with-paywall-builder) выше. :::warning Почему мы рекомендуем использовать `getPaywall` У метода `getPaywallForDefaultAudience` есть несколько существенных недостатков: - **Потенциальные проблемы с обратной совместимостью**: если нужно показывать разные пейволы для разных версий приложения (текущей и будущих), придётся либо проектировать пейволы с поддержкой текущей (legacy) версии, либо мириться с тем, что у пользователей текущей (legacy) версии пейволы могут не отображаться. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, настроенный для аудитории **All Users**, — а значит, вы теряете персонализированный таргетинг (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вы готовы принять эти недостатки ради более быстрого получения пейвола, используйте метод `getPaywallForDefaultAudience` следующим образом. В противном случае используйте `getPaywall`, описанный [выше](#fetch-paywall-designed-with-paywall-builder). ::: ```typescript showLineNumbers try { const paywall = await adapty.getPaywallForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', }); // the requested paywall } catch (error) { // handle the error } ``` | Параметр | Обязательность | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>необязательный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается языковой код, состоящий из одного или нескольких подтегов, разделённых символом «минус» (**-**). Первый подтег — язык, второй — регион.</p><p></p><p>Пример: `en` — английский, `pt-br` — бразильский португальский.</p><p></p><p>Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](capacitor-localizations-and-locale-codes).</p> | | **params** | необязательный | Дополнительные параметры для получения пейвола. | ## Настройка ассетов \{#customize-assets\} Чтобы кастомизировать изображения и видео в пейволе, используйте пользовательские ассеты. У hero-изображений и видео есть предопределённые идентификаторы: `hero_image` и `hero_video`. В бандле пользовательских ассетов вы обращаетесь к этим элементам по их идентификаторам и настраиваете их поведение. Для остальных изображений и видео нужно [задать пользовательский идентификатор](custom-media) в дашборде Adapty. Например, вы можете: - Показывать разные изображения или видео разным пользователям. - Показывать локальное превью, пока загружается основное удалённое изображение. - Показывать изображение-превью перед воспроизведением видео. Вот пример того, как можно передать пользовательские ресурсы через простой словарь: ```typescript showLineNumbers const customAssets: Record<string, AdaptyCustomAsset> = { 'custom_image': { type: 'image', relativeAssetPath: 'custom_image.png' }, 'hero_video': { type: 'video', fileLocation: { ios: { fileName: 'custom_video.mp4' }, android: { relativeAssetPath: 'videos/custom_video.mp4' } } } }; view = await createPaywallView(paywall, { customAssets }); ``` :::note Если ресурс не найден, пейвол вернётся к внешнему виду по умолчанию. ::: </SDKv3> --- # File: capacitor-present-paywalls --- --- title: "Отображение флоу и пейволов — Capacitor" description: "Показывайте флоу и пейволы пользователям в вашем Capacitor-приложении с помощью Adapty." --- <SDKv4> Если вы создали флоу или пейвол в Flow Builder, вам не нужно беспокоиться об его отображении в коде мобильного приложения — такой флоу уже содержит и что показывать, и как показывать. Перед началом убедитесь, что вы: 1. [Создали флоу или пейвол](create-paywall). 2. Добавили его в [плейсмент](placements). 3. [Получили флоу и подготовили отображение](capacitor-get-pb-paywalls). :::warning Этот гайд предназначен **только для флоу и пейволов Paywall Builder**, для которых требуется SDK v4.0 или выше. Процесс отображения флоу отличается для пейволов на основе Remote Config. - Для отображения **пейволов на основе Remote Config** смотрите [Отображение пейвола, созданного с помощью Remote Config](present-remote-config-paywalls-capacitor). ::: Чтобы отобразить флоу или пейвол как отдельный экран, используйте метод `view.present()` на `view`, созданном методом [`createFlowView`](capacitor-get-pb-paywalls#fetch-the-view-configuration). Каждый `view` можно использовать только один раз. Если нужно показать флоу повторно, вызовите `createFlowView` ещё раз, чтобы создать новый экземпляр `view`. :::warning Повторно использовать один и тот же `view` без пересоздания запрещено. Это приведёт к ошибке. ::: ```typescript showLineNumbers const view = await createFlowView(flow); // Optional: handle flow events (close, purchase, restore, etc) // await view.setEventHandlers({ ... }); try { await view.present(); } catch (error) { // handle the error } ``` :::important Повторный вызов `setEventHandlers` переопределит обработчики, которые вы передаёте, заменив как обработчики по умолчанию, так и ранее установленные обработчики для указанных событий. ::: ## Настройка стиля отображения на iOS \{#configure-ios-presentation-style\} Настройте способ отображения флоу на iOS, передав параметр `iosPresentationStyle` в метод `present()`. Параметр принимает значения `'full_screen'` (по умолчанию) или `'page_sheet'`. На Android флоу всегда отображается как полноэкранная активность. ```typescript showLineNumbers try { await view.present({ iosPresentationStyle: 'page_sheet' }); } catch (error) { // handle the error } ``` ## Использование таймера, заданного разработчиком \{#use-developer-defined-timer\} Чтобы использовать таймеры, заданные разработчиком, в своём мобильном приложении, укажите `timerId` — в данном примере `CUSTOM_TIMER_NY`, **Timer ID** таймера, который вы задали в дашборде Adapty. Это гарантирует, что приложение будет динамически обновлять таймер с правильным значением — например, `13d 09h 03m 34s` (рассчитывается как время окончания таймера, например Новый год, минус текущее время). ```typescript showLineNumbers const customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) }; const view = await createFlowView(flow, { customTimers }); ``` В этом примере `CUSTOM_TIMER_NY` — это **Timer ID** таймера, заданного разработчиком в дашборде Adapty. Таймер обеспечивает динамическое обновление значения в приложении — например, `13d 09h 03m 34s` (вычисляется как разница между временем окончания таймера, например Новым годом, и текущим временем). ## Показ диалогового окна \{#show-dialog\} Используйте этот метод вместо нативных диалогов-алертов, когда на Android отображается флоу. На Android обычные алерты появляются за флоу и становятся невидимы для пользователей. Этот метод гарантирует корректное отображение диалогового окна поверх флоу на всех платформах. ```typescript showLineNumbers try { const action = await view.showDialog({ title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', }); if (action === 'secondary') { // User confirmed - close the flow await view.dismiss(); } // If primary - do nothing, user stays } catch (error) { // handle error } ``` ## Замена одной подписки на другую \{#replace-one-subscription-with-another\} Когда пользователь пытается купить новую подписку при наличии активной на Android, вы можете управлять тем, как должна обрабатываться новая покупка, передав параметры обновления подписки при создании представления флоу. Чтобы заменить текущую подписку на новую, используйте `productPurchaseParams` в `createFlowView` с параметрами `oldSubVendorProductId` и `prorationMode`. ```typescript showLineNumbers const productPurchaseParams = flow.paywalls .flatMap((paywall) => paywall.productIdentifiers) .map((productId) => { const params: MakePurchaseParamsInput = {}; if (Capacitor.getPlatform() === 'android') { params.android = { subscriptionUpdateParams: { oldSubVendorProductId: 'PRODUCT_ID_OF_THE_CURRENT_ACTIVE_SUBSCRIPTION', prorationMode: 'with_time_proration', }, }; } return { productId, params }; }); const view = await createFlowView(flow, { productPurchaseParams }); ``` </SDKv4> <SDKv3> Если вы настроили пейвол с помощью Paywall Builder, вам не нужно беспокоиться о его отображении в коде мобильного приложения — он уже содержит всё необходимое: и что показывать, и как показывать. :::warning Этот гайд предназначен **только для пейволов, созданных в Paywall Builder**. Процесс отображения пейволов на основе Remote Config отличается. Подробнее см. в разделе [Отображение пейвола на основе Remote Config](present-remote-config-paywalls). ::: Чтобы отобразить пейвол, используйте метод `view.present()` на объекте `view`, созданном методом [`createPaywallView`](capacitor-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). Каждый `view` можно использовать только один раз. Если нужно показать пейвол повторно, вызовите `createPaywallView` ещё раз, чтобы создать новый экземпляр `view`. :::warning Повторное использование одного и того же `view` без его пересоздания может привести к ошибке. ::: ```typescript showLineNumbers const view = await createPaywallView(paywall); view.setEventHandlers({ onUrlPress(url) { window.open(url, '_blank'); return false; }, }); try { await view.present(); } catch (error) { // handle the error } ``` ## Использование таймера, заданного разработчиком \{#use-developer-defined-timer\} Чтобы использовать таймеры, заданные разработчиком, в своём мобильном приложении, укажите `timerId` — в данном примере `CUSTOM_TIMER_NY`, **Timer ID** таймера, который вы задали в дашборде Adapty. Это позволяет приложению динамически обновлять таймер с нужным значением — например, `13d 09h 03m 34s` (вычисляется как время окончания таймера, например Новый год, минус текущее время). ```typescript showLineNumbers const customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) }; const view = await createPaywallView(paywall, { customTimers }); ``` В этом примере `CUSTOM_TIMER_NY` — это **Timer ID** таймера, заданного разработчиком в дашборде Adapty. Таймер динамически обновляет значение в приложении — например, `13d 09h 03m 34s` (рассчитывается как разница между временем окончания таймера, например Новым годом, и текущим временем). ## Показ диалогов \{#show-dialog\} Используйте этот метод вместо нативных диалоговых окон, когда на Android отображается пейвол. На Android обычные алерты появляются позади пейвола и становятся невидимы для пользователей. Этот метод обеспечивает корректное отображение диалогов поверх пейвола на всех платформах. ```typescript showLineNumbers title="Capacitor" try { const action = await view.showDialog({ title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', }); if (action === 'secondary') { // User confirmed - close the paywall await view.dismiss(); } // If primary - do nothing, user stays } catch (error) { // handle error } ``` ## Настройка стиля отображения на iOS \{#configure-ios-presentation-style\} Укажите, как пейвол отображается на iOS, передав параметр `iosPresentationStyle` в метод `present()`. Параметр принимает значения `'full_screen'` (по умолчанию) или `'page_sheet'`. ```typescript showLineNumbers await view.present({ iosPresentationStyle: 'page_sheet' }); ``` </SDKv3> --- # File: capacitor-handle-paywall-actions --- --- title: "Реагирование на действия флоу - Capacitor" description: "Обрабатывайте действия кнопок из флоу и пейволов в Capacitor с помощью Adapty для лучшей монетизации приложения." --- <SDKv4> Если вы создаёте флоу или пейволы с помощью [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder) от Adapty, важно правильно настроить кнопки: 1. Добавьте [кнопку в конструкторе](paywall-buttons) и назначьте ей готовое действие или создайте собственный идентификатор действия. 2. Напишите код в приложении для обработки каждого из назначенных действий. В этом гайде показано, как обрабатывать пользовательские и готовые действия в коде. :::warning **Покупки, восстановления, закрытие флоу и пейволов, а также открытие URL обрабатываются автоматически.** Вы можете настроить их поведение по умолчанию или реализовать реакцию на пользовательские действия. ::: :::note Установка обработчика события полностью заменяет его поведение по умолчанию. Обработчики, которые вы не задаёте, сохраняют поведение по умолчанию. ::: ## Закрытие флоу и пейволов \{#close-flows-and-paywalls\} Чтобы добавить кнопку закрытия флоу или пейвола: 1. В конструкторе добавьте кнопку и назначьте ей действие **Close**. 2. В коде приложения реализуйте обработчик действия `close`, который закрывает флоу или пейвол. :::info В Capacitor SDK действие `close` по умолчанию закрывает флоу или пейвол. Однако при необходимости вы можете переопределить это поведение в своём коде. Например, закрытие одного флоу может инициировать открытие другого. ::: ```typescript showLineNumbers const view = await createFlowView(flow); const unsubscribe = await view.setEventHandlers({ onCloseButtonPress() { return true; // allow the flow to close }, }); ``` На Android системная кнопка **Back** и жест «назад» вызывают отдельное событие `onAndroidSystemBack`. В SDK v4 оно больше не закрывает флоу по умолчанию. Верните `true` из обработчика, если хотите, чтобы кнопка **Back** закрывала флоу: ```typescript showLineNumbers const unsubscribe = await view.setEventHandlers({ onAndroidSystemBack() { return true; // close the flow when the Back button is pressed }, }); ``` ## Открытие URL из флоу и пейволов \{#open-urls-from-flows-and-paywalls\} :::tip Если вы хотите добавить группу ссылок (например, условия использования и восстановление покупок), добавьте элемент **Link** в билдере и обрабатывайте его так же, как кнопки с действием **Open URL**. ::: Чтобы добавить кнопку, открывающую ссылку из флоу или пейвола (например, **Terms of use** или **Privacy policy**): 1. В билдере добавьте кнопку, назначьте ей действие **Open URL** и введите нужный URL. 2. При необходимости в коде приложения реализуйте обработчик действия `openUrl`, который откроет полученный URL нужным вам способом. :::info В Capacitor SDK нажатие на URL по умолчанию открывает его в нативном браузере: SDK вызывает `adapty.openWebUrl({ url, openIn })`, учитывая параметр **Open in**, заданный в билдере, и оставляет флоу открытым. При необходимости вы можете переопределить это поведение в своём коде. ::: ```typescript showLineNumbers const unsubscribe = await view.setEventHandlers({ onUrlPress(url) { // Open the URL your own way, e.g. with the Capacitor Browser plugin Browser.open({ url }); return false; // keep the flow open }, }); ``` ## Обработка пользовательских действий \{#handle-custom-actions\} Чтобы добавить кнопку, обрабатывающую произвольные действия: 1. В билдере добавьте кнопку, назначьте ей действие **Custom** и задайте идентификатор. 2. В коде приложения реализуйте обработчик для созданного идентификатора действия. Например, если у вас есть другой набор предложений по подписке или разовые покупки, можно добавить кнопку, которая будет открывать другой флоу или пейвол: ```typescript showLineNumbers const unsubscribe = await view.setEventHandlers({ onCustomAction(actionId) { if (actionId === 'openNewPaywall') { // Display another flow or paywall } }, }); ``` </SDKv4> <SDKv3> Если вы создаёте пейволы с помощью Adapty Paywall Builder, важно правильно настроить кнопки: 1. Добавьте [кнопку в Paywall Builder](paywall-buttons) и назначьте ей готовое действие или создайте собственный ID действия. 2. Напишите код в приложении для обработки каждого назначенного действия. В этом гайде показано, как обрабатывать пользовательские и готовые действия в коде. ## Закрытие пейволов \{#close-paywalls\} Чтобы добавить кнопку закрытия пейвола: 1. В Paywall Builder добавьте кнопку и назначьте ей действие **Close**. 2. В коде приложения реализуйте обработчик действия `close`, который закрывает пейвол. :::info В Capacitor SDK действие `close` по умолчанию закрывает пейвол. Однако при необходимости вы можете переопределить это поведение в своём коде. Например, закрытие одного пейвола может инициировать открытие другого. ::: ```typescript showLineNumbers const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { console.log('User closed paywall'); return true; // Allow the paywall to close } }); ``` ## Открытие URL из пейволов \{#open-urls-from-paywalls\} :::tip Если нужно добавить группу ссылок (например, условия использования и восстановление покупок), добавьте элемент **Link** в Paywall Builder и обрабатывайте его так же, как кнопки с действием **Open URL**. ::: Чтобы добавить кнопку, открывающую ссылку из пейвола (например, **Terms of use** или **Privacy policy**): 1. В Paywall Builder добавьте кнопку, назначьте ей действие **Open URL** и укажите нужный URL. 2. В коде приложения реализуйте обработчик действия `openUrl`, который открывает полученный URL в браузере. :::info В Capacitor SDK действие `window.open` по умолчанию открывает URL. При необходимости вы можете переопределить это поведение в своём коде. ::: ```typescript showLineNumbers const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onUrlPress(url) { window.open(url, '_blank'); return false; // Don't close the paywall }, }); ``` ## Вход в приложение \{#log-into-the-app\} Чтобы добавить кнопку для входа пользователей в ваше приложение: 1. В Paywall Builder добавьте кнопку и назначьте ей действие **Login**. 2. В коде приложения реализуйте обработчик действия `login`, который идентифицирует пользователя. ```typescript showLineNumbers const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onCustomAction(actionId) { if (actionId === 'login') { // Navigate to login screen console.log('User requested login'); } } }); ``` ## Обработка пользовательских действий \{#handle-custom-actions\} Чтобы добавить кнопку, которая обрабатывает любые другие действия: 1. В Paywall Builder добавьте кнопку, назначьте ей действие **Custom** и задайте ей ID. 2. В коде приложения реализуйте обработчик для созданного вами ID действия. Например, если у вас есть другой набор предложений по подписке или разовые покупки, вы можете добавить кнопку, которая будет отображать другой пейвол: ```typescript showLineNumbers const unsubscribe = view.setEventHandlers({ onCustomAction(actionId) { if (actionId === 'openNewPaywall') { // Display another paywall } }, }); ``` </SDKv3> --- # File: capacitor-handling-events --- --- title: "Обработка событий флоу и пейвола - Capacitor" description: "Обрабатывайте события флоу и пейвола в вашем приложении на Capacitor с помощью SDK Adapty." --- <SDKv4> :::important Этот гайд охватывает обработку событий для покупок, восстановлений, выбора продукта и отображения флоу. Вы также можете настроить обработку кнопок (закрытие флоу, открытие ссылок, пользовательские действия и т. д.). Подробнее см. в нашем [гайде по обработке действий кнопок](capacitor-handle-paywall-actions). ::: Флоу и пейволы, созданные с помощью [Flow Builder](adapty-flow-builder), не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют ряд событий, на которые ваше приложение может реагировать. К таким событиям относятся нажатия кнопок (кнопки закрытия, URL-ссылки, выбор продуктов и т. д.), а также уведомления о действиях, связанных с покупками в рамках флоу. Узнайте, как реагировать на эти события, ниже. Чтобы управлять процессами, происходящими на экране флоу, или отслеживать их в своём мобильном приложении, реализуйте метод `view.setEventHandlers`: :::important Можно задать только один обработчик на каждое событие: повторный вызов `setEventHandlers` заменит ранее установленные обработчики для указанных событий, включая дефолтные. Обработчики, которые вы не задаёте, сохраняют поведение по умолчанию. `setEventHandlers` возвращает функцию отписки, а `view.dismiss()` удаляет все обработчики. ::: ```typescript showLineNumbers const view = await createFlowView(flow); const unsubscribe = await view.setEventHandlers({ onCloseButtonPress() { return true; // close the flow (default behavior) }, onAndroidSystemBack() { return true; // close the flow; by default, it stays open }, onPurchaseCompleted(purchaseResult, product) { return purchaseResult.type === 'success'; // close the flow on a successful purchase, keep it open for cancelled or pending purchases }, onPurchaseStarted(product) { /***/ }, onPurchaseFailed(error, product) { /***/ }, onRestoreCompleted(profile) { /***/ }, onRestoreFailed(error) { /***/ }, onProductSelected(productId) { /***/ }, onError(error) { /***/ }, onLoadingProductsFailed(error) { /***/ }, onUrlPress(url, openIn) { adapty.openWebUrl({ url, openIn }).catch(console.warn); // same as the SDK default return false; // keep the flow open }, onAppeared() { /***/ }, onDisappeared() { /***/ }, onWebPaymentNavigationFinished() { /***/ }, }); ``` <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> Примеры ниже показывают свойства, доступные в каждом обработчике, с иллюстративными значениями в комментариях. ```typescript // onUrlPress url; // 'https://example.com/terms' openIn; // 'browser_in_app' or 'browser_out_app' // onCustomAction actionId; // 'login' // onProductSelected productId; // 'premium_monthly' // onPurchaseStarted, onPurchaseCompleted, onPurchaseFailed product.vendorProductId; // 'premium_monthly' product.localizedTitle; // 'Premium Monthly' product.localizedDescription; // 'Premium subscription for 1 month' product.price?.amount; // 9.99 product.price?.currencyCode; // 'USD' product.price?.localizedString; // '$9.99' // onPurchaseCompleted purchaseResult.type; // 'success', 'pending', or 'user_cancelled' if (purchaseResult.type === 'success') { purchaseResult.profile.accessLevels['premium']?.isActive; // true } // onRestoreCompleted profile.accessLevels['premium']?.isActive; // true // onPurchaseFailed, onRestoreFailed, onError, onLoadingProductsFailed error.message; // 'Purchase failed due to insufficient funds' ``` </Details> Вы можете зарегистрировать только нужные обработчики событий, пропустив остальные — лишние слушатели событий при этом не создаются. Обязательных обработчиков нет. Обработчики событий возвращают булево значение. Если возвращается `true`, процесс отображения считается завершённым: экран флоу закрывается, а слушатели событий для этого представления удаляются. У некоторых обработчиков событий есть поведение по умолчанию, которое при необходимости можно переопределить: - `onCloseButtonPress`: закрывает флоу при нажатии кнопки закрытия. - `onUrlPress`: открывает нажатый URL в браузере через `adapty.openWebUrl`, учитывая параметр **Open in**, заданный в билдере, и оставляет флоу открытым. - `onAndroidSystemBack`: оставляет флоу открытым при нажатии кнопки **Back**. Верните `true`, чтобы закрыть его. - `onPurchaseCompleted`: оставляет флоу открытым после завершения покупки. Верните `true`, чтобы закрыть его. - `onRestoreCompleted`: оставляет флоу открытым после успешного восстановления покупок. Верните `true`, чтобы закрыть его. - `onError`: закрывает флоу, если его рендеринг завершился ошибкой. ### Обработчики событий \{#event-handlers\} | Обработчик событий | Описание | |:-----------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onCustomAction** | Вызывается, когда пользователь выполняет пользовательское действие, например нажимает [кастомную кнопку](paywall-buttons). | | **onUrlPress** | Вызывается, когда пользователь нажимает на URL в вашем флоу. | | **onAndroidSystemBack** | Вызывается, когда пользователь нажимает системную кнопку Android **Back**. По умолчанию флоу остаётся открытым; верните `true`, чтобы закрыть его. | | **onCloseButtonPress** | Вызывается, когда кнопка закрытия видна и пользователь нажимает её. Рекомендуется закрывать экран флоу в этом обработчике. | | **onPurchaseCompleted** | Вызывается по завершении покупки — независимо от того, была ли она успешной, отменена пользователем или ожидает подтверждения. В случае успешной покупки предоставляет обновлённый `AdaptyProfile`. Отмены пользователем и платежи в ожидании (например, требующие родительского подтверждения) вызывают это событие, а не `onPurchaseFailed`. | | **onPurchaseStarted** | Вызывается, когда пользователь нажимает кнопку действия «Купить» для начала процесса покупки. | | **onPurchaseFailed** | Вызывается при ошибке в процессе покупки (например, ограничения платежей, недействительные продукты, сетевые сбои, ошибки верификации транзакции). Не вызывается при отменах пользователем или платежах в ожидании — в этих случаях вызывается `onPurchaseCompleted`. | | **onRestoreStarted** | Вызывается, когда пользователь начинает процесс восстановления покупок. | | **onRestoreCompleted** | Вызывается при успешном восстановлении покупок и предоставляет обновлённый `AdaptyProfile`. Рекомендуется закрывать экран, если у пользователя есть необходимый `accessLevel`. Подробнее о том, как это проверить, см. в разделе [Статус подписки](capacitor-listen-subscription-changes). | | **onRestoreFailed** | Вызывается при ошибке процесса восстановления и предоставляет `AdaptyError`. | | **onProductSelected** | Вызывается, когда в представлении флоу выбирается какой-либо продукт — позволяет отслеживать, что пользователь выбирает перед покупкой. | | **onError** | Вызывается при возникновении ошибки во время отрисовки представления и предоставляет `AdaptyError`. Такие ошибки не должны возникать — если вы столкнулись с одной из них, пожалуйста, сообщите нам. | | **onLoadingProductsFailed** | Вызывается при ошибке загрузки продуктов и предоставляет `AdaptyError`. Если при создании представления вы не установили `prefetchProducts: true`, AdaptyUI самостоятельно получит необходимые объекты с сервера. | | **onAppeared** | Вызывается, когда флоу отображается пользователю. На iOS также вызывается, когда пользователь нажимает [кнопку веб-пейвола](web-paywall#step-2a-add-a-web-purchase-button) внутри флоу и веб-пейвол открывается во встроенном браузере. | | **onDisappeared** | Вызывается, когда пользователь закрывает флоу. На iOS также вызывается, когда [веб-пейвол](web-paywall#step-2a-add-a-web-purchase-button), открытый из флоу во встроенном браузере, исчезает с экрана. | | **onWebPaymentNavigationFinished** | Вызывается после попытки открыть [веб-пейвол](web-paywall) для покупки — независимо от того, была ли она успешной или завершилась ошибкой. | | **onRequestAppReview** | Зарезервировано для запросов оценки приложения из флоу. Флоу пока не инициируют запросы оценки, поэтому реализовывать этот обработчик не нужно. | | **onAnalytics** | Зарезервировано для пользовательских аналитических событий из флоу. Флоу пока не передают их в ваш код, поэтому реализовывать этот обработчик не нужно. | | **onRequestPermission** | Зарезервировано для запросов системных разрешений (например, push-уведомлений или доступа к камере) из флоу. Флоу пока не инициируют запросы разрешений, поэтому реализовывать этот обработчик не нужно. | | **onObserverPurchaseInitiated** | Только для режима наблюдателя: вызывается, когда пользователь нажимает кнопку покупки в флоу. Adapty не выполняет покупку — выполните её собственным кодом, затем сообщите о транзакции в Adapty. См. раздел [Обработка покупок в режиме наблюдателя](#handle-purchases-in-observer-mode) ниже. | | **onObserverRestoreInitiated** | Только для режима наблюдателя: вызывается, когда пользователь нажимает кнопку восстановления в флоу. Adapty не выполняет восстановление — выполните его самостоятельно, затем сообщите о восстановленных транзакциях. См. раздел [Обработка покупок в режиме наблюдателя](#handle-purchases-in-observer-mode) ниже. | ### Обработка покупок в режиме наблюдателя \{#handle-purchases-in-observer-mode\} Если вы активировали SDK в [режиме наблюдателя](implement-observer-mode-capacitor) (`observerMode: true`) и показываете флоу, отрисованный Adapty, SDK не выполняет покупки самостоятельно. Когда пользователь нажимает кнопку покупки или восстановления, SDK вызывает `onObserverPurchaseInitiated` или `onObserverRestoreInitiated`, чтобы вы могли выполнить покупку или восстановление своим кодом. Подробная настройка описана в разделе [Показ флоу в режиме наблюдателя](capacitor-present-flows-in-observer-mode). </SDKv4> <SDKv3> :::important Этот гайд охватывает обработку событий для покупок, восстановлений, выбора продуктов и отображения пейволов. Вам также необходимо реализовать обработку кнопок (закрытие пейвола, открытие ссылок и т. д.). Подробнее см. в нашем [гайде по обработке действий кнопок](capacitor-handle-paywall-actions). ::: Пейволы, созданные с помощью [Paywall Builder](adapty-paywall-builder), не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют ряд событий, на которые ваше приложение может реагировать. К ним относятся нажатия кнопок (кнопки закрытия, URL, выбор продуктов и т. д.), а также уведомления о действиях, связанных с покупками на пейволе. Ниже описано, как обрабатывать эти события. Чтобы управлять процессами на экране пейвола или отслеживать их в вашем мобильном приложении, реализуйте метод `view.setEventHandlers`: ```typescript showLineNumbers const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { console.log('User closed paywall'); return true; // Allow the paywall to close }, onAndroidSystemBack() { console.log('User pressed back button'); return true; // Allow the paywall to close }, onAppeared() { console.log('Paywall appeared'); return false; // Don't close the paywall }, onDisappeared() { console.log('Paywall disappeared'); }, onPurchaseCompleted(purchaseResult, product) { console.log('Purchase completed:', purchaseResult); return purchaseResult.type !== 'user_cancelled'; // Close if not cancelled }, onPurchaseStarted(product) { console.log('Purchase started:', product); return false; // Don't close the paywall }, onPurchaseFailed(error, product) { console.error('Purchase failed:', error); return false; // Don't close the paywall }, onRestoreCompleted(profile) { console.log('Restore completed:', profile); return true; // Close the paywall after successful restore }, onRestoreFailed(error) { console.error('Restore failed:', error); return false; // Don't close the paywall }, onProductSelected(productId) { console.log('Product selected:', productId); return false; // Don't close the paywall }, onRenderingFailed(error) { console.error('Rendering failed:', error); return false; // Don't close the paywall }, onLoadingProductsFailed(error) { console.error('Loading products failed:', error); return false; // Don't close the paywall }, onUrlPress(url) { window.open(url, '_blank'); return false; // Don't close the paywall }, }); ``` <Details> <summary>Примеры событий (нажмите, чтобы раскрыть)</summary> ```typescript // onCloseButtonPress { "event": "close_button_press" } // onAndroidSystemBack { "event": "android_system_back" } // onAppeared { "event": "paywall_shown" } // onDisappeared { "event": "paywall_closed" } // onUrlPress { "event": "url_press", "url": "https://example.com/terms" } // onCustomAction { "event": "custom_action", "actionId": "login" } // onProductSelected { "event": "product_selected", "productId": "premium_monthly" } // onPurchaseStarted { "event": "purchase_started", "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // onPurchaseCompleted - Success { "event": "purchase_completed", "purchaseResult": { "type": "success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // onPurchaseCompleted - Cancelled { "event": "purchase_completed", "purchaseResult": { "type": "user_cancelled" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } // onPurchaseFailed { "event": "purchase_failed", "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } } } // onRestoreCompleted { "event": "restore_completed", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } // onRestoreFailed { "event": "restore_failed", "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } // onRenderingFailed { "event": "rendering_failed", "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } // onLoadingProductsFailed { "event": "loading_products_failed", "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ``` </Details> Вы можете регистрировать только нужные обработчики событий, пропуская те, которые не нужны. В этом случае лишние слушатели событий созданы не будут. Обязательных обработчиков событий нет. Обработчики событий возвращают булево значение. Если возвращается `true`, процесс отображения считается завершённым: экран пейвола закрывается, а слушатели событий для этого представления удаляются. Некоторые обработчики событий имеют поведение по умолчанию, которое при необходимости можно переопределить: - `onCloseButtonPress`: закрывает пейвол при нажатии кнопки закрытия. - `onAndroidSystemBack`: закрывает пейвол при нажатии кнопки **Back**. - `onRestoreCompleted`: закрывает пейвол после успешного восстановления покупок. - `onPurchaseCompleted`: закрывает пейвол, если пользователь не отменил покупку. - `onRenderingFailed`: закрывает пейвол, если его отрисовка завершилась с ошибкой. - `onUrlPress`: открывает URL в системном браузере и оставляет пейвол открытым. ### Обработчики событий \{#event-handlers\} | Обработчик событий | Описание | |:----------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onCustomAction** | Вызывается, когда пользователь выполняет пользовательское действие, например нажимает [кастомную кнопку](paywall-buttons). | | **onUrlPress** | Вызывается, когда пользователь нажимает на URL в пейволе. | | **onAndroidSystemBack** | Вызывается, когда пользователь нажимает системную кнопку Android **Back**. | | **onCloseButtonPress** | Вызывается, когда кнопка закрытия видима и пользователь нажимает её. Рекомендуется закрывать экран пейвола в этом обработчике. | | **onPurchaseCompleted** | Вызывается при завершении покупки — независимо от того, была ли она успешной, отменена пользователем или ожидает подтверждения. В случае успешной покупки предоставляет обновлённый `AdaptyProfile`. Отмены пользователем и ожидающие платежи (например, требующие родительского подтверждения) тоже вызывают это событие, а не `onPurchaseFailed`. | | **onPurchaseStarted** | Вызывается, когда пользователь нажимает кнопку «Купить», чтобы начать процесс покупки. | | **onPurchaseCancelled** | Вызывается, когда пользователь инициирует процесс покупки и вручную прерывает его (отменяет диалог оплаты). | | **onPurchaseFailed** | Вызывается, когда покупка завершается с ошибкой (например, ограничения платежей, недействительные продукты, сбои сети, ошибки верификации транзакции). Не вызывается при отменах пользователем или ожидающих платежах — для них срабатывает `onPurchaseCompleted`. | | **onRestoreStarted** | Вызывается, когда пользователь начинает процесс восстановления покупок. | | **onRestoreCompleted** | Вызывается при успешном восстановлении покупок и предоставляет обновлённый `AdaptyProfile`. Рекомендуется закрывать экран, если у пользователя есть нужный `accessLevel`. Подробнее о проверке статуса подписки — в разделе [Статус подписки](capacitor-listen-subscription-changes). | | **onRestoreFailed** | Вызывается при сбое процесса восстановления и предоставляет `AdaptyError`. | | **onProductSelected** | Вызывается, когда пользователь выбирает любой продукт в пейволе — позволяет отслеживать выбор до совершения покупки. | | **onAppeared** | Вызывается, когда экран пейвола появляется на экране. На iOS также вызывается, когда пользователь нажимает [кнопку веб-пейвола](web-paywall#step-2a-add-a-web-purchase-button) внутри пейвола и веб-пейвол открывается во встроенном браузере. | | **onDisappeared** | Вызывается, когда экран пейвола исчезает с экрана. На iOS также вызывается, когда [веб-пейвол](web-paywall#step-2a-add-a-web-purchase-button), открытый из пейвола во встроенном браузере, исчезает с экрана. | | **onRenderingFailed** | Вызывается при возникновении ошибки во время рендеринга представления и предоставляет `AdaptyError`. Такие ошибки не должны возникать, поэтому если вы столкнулись с подобной, пожалуйста, сообщите нам. | | **onLoadingProductsFailed** | Вызывается при сбое загрузки продуктов и предоставляет `AdaptyError`. Если вы не указали `prefetchProducts: true` при создании представления, AdaptyUI самостоятельно загрузит необходимые объекты с сервера. | </SDKv3> --- # File: capacitor-use-fallback-paywalls --- --- title: "Capacitor - Использование резервных пейволов" description: "Обработка ситуаций, когда пользователи офлайн или серверы Adapty недоступны" --- Чтобы поддерживать бесперебойный пользовательский опыт, важно настроить [резервные пейволы](/fallback-paywalls) для флоу, [пейволов](paywalls) и [онбордингов](onboardings). Это позволит приложению продолжить работу при частичной или полной потере интернет-соединения. * **Если приложение не может обратиться к серверам Adapty:** Оно сможет отобразить резервный флоу или пейвол, а также использовать локальную конфигурацию онбординга. * **Если приложение не может подключиться к интернету:** Оно сможет отобразить резервный флоу или пейвол. Онбординги содержат удалённый контент и требуют интернет-соединения для работы. :::important Прежде чем следовать шагам этого гайда, [скачайте](/local-fallback-paywalls) файлы резервной конфигурации из Adapty. ::: ## Настройка \{#configuration\} ### Android 1. Добавьте файл резервной конфигурации в своё приложение. Выберите одну из следующих директорий: * **android/app/src/main/assets/** * **android/app/src/main/res/raw/** :::note Папка `res/raw` требует особого формата именования файлов (название должно начинаться с буквы, без заглавных букв, без специальных символов кроме нижнего подчёркивания и без пробелов). ::: 2. Обновите свойство `android` константы `FileLocation`: * Если файл находится в директории `assets`, передайте путь к файлу относительно этой директории. * Если файл находится в директории `res/raw`, передайте имя файла без расширения. ### iOS \{#ios\} 1. Добавьте резервный JSON-файл в бандл проекта: откройте меню **File** в XCode и выберите пункт **Add Files to "YourProjectName"**. 2. Передайте имя конфигурационного файла в свойство `ios` константы `FileLocation`. ## Пример \{#example\} ```typescript showLineNumbers const fileLocation = { ios: { fileName: 'ios_fallback.json' }, android: { //if the file is located in 'android/app/src/main/assets/' relativeAssetPath: 'android_fallback.json' } }; await adapty.setFallback({ fileLocation }); ``` :::important `setFallback` должен выполниться до того, как SDK загрузит целевой флоу, пейвол или онбординг. ::: Параметры: | Parameter | Description | | :------------------- | :------------------------------------------------------- | | **fileLocation** | Объект, указывающий местоположение файла резервной конфигурации. | --- # File: capacitor-localizations-and-locale-codes --- --- title: "Использование локализаций и кодов локали в Capacitor SDK" description: "Узнайте, как локализовать пейволы в приложении Capacitor с помощью Adapty SDK." --- <SDKv4> ## Почему это важно \{#why-this-is-important\} Коды локалей используются, когда Adapty подбирает локализацию для флоу и когда вы читаете Remote Config для кастомного пейвола. Коды локалей бывают непростыми и могут различаться в зависимости от платформы, поэтому Adapty опирается на единый внутренний стандарт для всех поддерживаемых платформ. Понимание этого стандарта помогает предсказать, какую локализацию получит пользователь. ## Стандарт кодов локализации в Adapty \{#locale-code-standard-at-adapty\} В кодах локализации Adapty использует слегка изменённый стандарт [BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): каждый код состоит из подтегов в нижнем регистре, разделённых дефисами. Например: `en` (английский), `pt-br` (португальский (Бразилия)), `zh` (упрощённый китайский), `zh-hant` (традиционный китайский). ## Сопоставление кода локали \{#locale-code-matching\} Когда Adapty ищет локализацию, соответствующую локали пользователя, происходит следующее: 1. Строка локали приводится к нижнему регистру, а все символы подчёркивания (`_`) заменяются дефисами (`-`) 2. Adapty ищет локализацию с полностью совпадающим кодом локали 3. Если совпадение не найдено, Adapty берёт подстроку до первого дефиса (`pt` для `pt-br`) и ищет соответствующую локализацию 4. Если совпадение снова не найдено, Adapty возвращает локализацию по умолчанию — `en` Таким образом, `'pt_BR'`, `pt-BR` и `pt-br` — все они разрешаются в одну и ту же локализацию. ## Реализация локализаций \{#implementing-localizations\} В SDK v4 при получении флоу передавать код локали не нужно. - **Пейволы из Flow Builder и Paywall Builder**: Adapty автоматически определяет локализацию по настройкам устройства и тем локализациям, которые вы настроили в билдере. Отрендерите флоу с помощью `createFlowView` — код локали не нужен. - **Кастомные пейволы (Remote Config)**: `getFlow` возвращает все настроенные локализации в `flow.remoteConfigs`. Каждая запись содержит код `lang` и объект `data`. Выберите запись, подходящую пользователю, реализовав собственный фолбэк: ```typescript showLineNumbers const flow = await adapty.getFlow({ placementId: 'placement_id' }); const config = flow.remoteConfigs?.find((c) => c.lang === 'en') ?? flow.remoteConfigs?.[0]; // read your values from config?.data ``` Описанные выше правила сопоставления кодов локали объясняют, как Adapty нормализует коды `lang`, хранящиеся в каждом Remote Config. </SDKv4> <SDKv3> ## Почему это важно \{#why-this-is-important\} Коды локалей используются в нескольких сценариях — например, когда нужно получить правильный пейвол для текущей локализации приложения. Поскольку коды локалей сложны и могут отличаться от платформы к платформе, мы опираемся на внутренний стандарт для всех поддерживаемых платформ. Именно поэтому важно понимать, что именно вы отправляете на наш сервер для получения правильной локализации и что происходит дальше — чтобы вы всегда получали ожидаемый результат. ## Стандарт кодов локалей в Adapty \{#locale-code-standard-at-adapty\} Для кодов локалей Adapty использует слегка модифицированный [стандарт BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): каждый код состоит из подтегов в нижнем регистре, разделённых дефисами. Несколько примеров: `en` (английский), `pt-br` (португальский (Бразилия)), `zh` (китайский упрощённый), `zh-hant` (китайский традиционный). ## Сопоставление кода локали \{#locale-code-matching\} Когда Adapty получает вызов от SDK с кодом локали и начинает искать соответствующую локализацию пейвола, происходит следующее: 1. Входящая строка локали приводится к нижнему регистру, а все символы подчёркивания (`_`) заменяются дефисами (`-`) 2. Затем выполняется поиск локализации с полностью совпадающим кодом локали 3. Если совпадение не найдено, берётся подстрока до первого дефиса (`pt` для `pt-br`) и выполняется поиск совпадающей локализации 4. Если совпадение снова не найдено, возвращается локализация по умолчанию — `en` Таким образом, устройства iOS, отправившее `'pt_BR'`, Android-устройство, отправившее `pt-BR`, и другое устройство, отправившее `pt-br`, получат один и тот же результат. ## Реализация локализаций: рекомендуемый способ \{#implementing-localizations-recommended-way\} Если вы думаете о локализациях, скорее всего, вы уже работаете с локализованными строковыми файлами в вашем проекте. В таком случае мы рекомендуем добавить пару ключ-значение с нужным кодом локали Adapty в каждый из ваших файлов для соответствующих локализаций. Затем извлекайте значение для этого ключа при вызове SDK: ```javascript showLineNumbers // 1. Modify your localization files (e.g., using react-i18next) /* en.json */ { "adapty_paywalls_locale": "en" } /* es.json */ { "adapty_paywalls_locale": "es" } /* pt-BR.json */ { "adapty_paywalls_locale": "pt-br" } // 2. Extract and use the locale code const MyComponent = () => { const { t } = useTranslation(); const fetchPaywall = async () => { const locale = t('adapty_paywalls_locale'); // pass locale code to adapty.getPaywall or adapty.getPaywallForDefaultAudience method const paywall = await adapty.getPaywallForDefaultAudience('placement_id', locale); }; }; ``` Таким образом вы можете быть уверены, что полностью контролируете, какая локализация будет получена для каждого пользователя вашего приложения. ## Альтернативный способ реализации локализаций \{#implementing-localizations-the-other-way\} Похожего (но не идентичного) результата можно добиться, не задавая явно коды локалей для каждой локализации. Для этого достаточно извлечь код локали из других объектов, предоставляемых платформой, например: ```javascript showLineNumbers const getLocaleCode = () => { if (Capacitor.getPlatform() === 'ios') { return navigator.language || 'en'; } else { return navigator.language || 'en'; } }; const fetchPaywall = async () => { const locale = getLocaleCode(); // pass locale code to adapty.getPaywall or adapty.getPaywallForDefaultAudience method const paywall = await adapty.getPaywallForDefaultAudience('placement_id', locale); }; ``` Обратите внимание, что мы не рекомендуем этот подход по нескольким причинам: 1. На iOS предпочитаемые языки и текущая локаль — это не одно и то же. Чтобы локализация выбиралась корректно, придётся либо положиться на логику Apple, которая работает из коробки при рекомендуемом подходе с локализованными файлами строк, либо воспроизвести её самостоятельно. 2. Сложно предсказать, что именно получит сервер Adapty. Например, на iOS с устройства можно передать локаль вида `ar_OM@numbers='latn'`, и в ответ на этот запрос вы получите не локализацию `ar-om`, которую ожидали, а `ar` — что, скорее всего, будет неожиданностью. Should you decide to use this approach anyway — make sure you've covered all the relevant use cases. </SDKv3> --- # File: capacitor-web-paywall --- --- title: "Реализация веб-пейволов" description: "Узнайте, как реализовать веб-пейволы в вашем Capacitor-приложении с помощью Adapty SDK." --- :::important Прежде чем начать, убедитесь, что вы [настроили веб-пейвол в дашборде](web-paywall) и установили Adapty SDK версии 3.6.1 или выше. ::: ## Открытие веб-пейволов \{#open-web-paywalls\} Если вы используете собственный пейвол, открытие веб-пейволов нужно реализовать вручную с помощью метода SDK. Метод `.openWebPaywall`: 1. Генерирует уникальный URL, который позволяет Adapty связать конкретный показанный пользователю пейвол с веб-страницей, на которую он перенаправляется. 2. Отслеживает возврат пользователя в приложение и затем с короткими интервалами вызывает `.getProfile`, чтобы определить, обновились ли права доступа профиля. Таким образом, если платёж прошёл успешно и права доступа обновились, подписка активируется в приложении практически мгновенно. ```typescript showLineNumbers try { await adapty.openWebPaywall({ paywallOrProduct: product }); } catch (error) { console.error('Failed to open web paywall:', error); } ``` :::note Существует две версии метода `openWebPaywall`: 1. `openWebPaywall({ paywallOrProduct: product })` — генерирует URL по пейволу и добавляет в URL данные о продукте. 2. `openWebPaywall({ paywallOrProduct: paywall })` — генерирует URL по пейволу без добавления данных о продукте. Используйте этот вариант, если продукты в пейволе Adapty отличаются от продуктов в веб-пейволе. В SDK v4 сторона пейвола у `paywallOrProduct` принимает `AdaptyFlowPaywall` — вариацию пейвола полученного флоу. Перед обращением по индексу убедитесь, что `flow.paywalls` не пуст, например: `flow.paywalls[0]`. ::: #### Обработка ошибок \{#handle-errors\} | Ошибка | Описание | Рекомендуемое действие | |-----------------------------------------|-------------------------------------------------------------------|-------------------------------------------------------------------------------------| | AdaptyError.paywallWithoutPurchaseUrl | У пейвола не настроен URL для веб-покупки | Проверьте, правильно ли настроен пейвол в дашборде Adapty | | AdaptyError.productWithoutPurchaseUrl | У продукта отсутствует URL для веб-покупки | Проверьте настройки продукта в дашборде Adapty | | AdaptyError.failedOpeningWebPaywallUrl | Не удалось открыть URL в браузере | Проверьте настройки устройства или предложите альтернативный способ оплаты | | AdaptyError.failedDecodingWebPaywallUrl | Не удалось корректно закодировать параметры в URL | Убедитесь, что параметры URL корректны и правильно отформатированы | ## Получите URL веб-пейвола без его открытия \{#get-the-web-paywall-url-without-opening-it\} Если вы хотите самостоятельно открыть страницу покупки в веб-среде, а не доверять это SDK, используйте `createWebPaywallUrl`. Метод возвращает тот же уникальный URL, который открыл бы `openWebPaywall`, — его можно отрисовать в собственном веб-вью или обработать редирект на своих условиях. Принимает тот же аргумент `paywallOrProduct` — вариант пейвола из полученного флоу (`AdaptyFlowPaywall`) или `AdaptyPaywallProduct`. ```typescript showLineNumbers try { const url = await adapty.createWebPaywallUrl({ paywallOrProduct: product }); // open `url` in your own web view, or handle the redirect yourself } catch (error) { console.error('Failed to create web paywall URL:', error); } ``` :::note Чтобы открыть произвольный URL (не веб-пейвол) через нативный браузер — например, по нажатию кнопки во флоу — используйте [`adapty.openWebUrl`](capacitor-handle-paywall-actions#open-urls-from-flows-and-paywalls). ::: ## Открытие веб-пейволов во встроенном браузере \{#open-web-paywalls-in-an-in-app-browser\} :::important Открытие веб-пейволов во встроенном браузере поддерживается начиная с Adapty SDK v3.15. ::: По умолчанию веб-пейволы открываются во внешнем браузере. Чтобы обеспечить бесшовный пользовательский опыт, вы можете открывать веб-пейволы во встроенном браузере. Это отображает страницу веб-покупки прямо внутри вашего приложения, позволяя пользователям завершать транзакции без переключения между приложениями. Чтобы включить это, установите `openIn` в значение `WebPresentation.BrowserInApp` в `openWebPaywall`: ```typescript showLineNumbers try { await adapty.openWebPaywall({ paywallOrProduct: product, openIn: WebPresentation.BrowserInApp, // default – WebPresentation.BrowserOutApp }); } catch (error) { console.error('Failed to open web paywall:', error); } ``` --- # File: capacitor-present-flows-in-observer-mode --- --- title: "Отображение флоу в режиме Observer в Capacitor SDK" description: "Отображайте флоу и пейволы Paywall Builder в режиме Observer в вашем Capacitor-приложении, обрабатывая покупки собственным кодом." --- Если вы настроили флоу или пейвол с помощью билдера, вам не нужно беспокоиться об их отображении в коде мобильного приложения — всё, что нужно показать и как именно это показать, уже задано внутри самого флоу или пейвола. :::warning Этот раздел относится только к [режиму Observer](observer-vs-full-mode). Если вы не работаете в режиме Observer, обратитесь к теме [Отображение флоу и пейволов](capacitor-present-paywalls). ::: :::info Эта функция требует Adapty Capacitor SDK версии 4.0 или выше — ранее она была доступна только в нативных SDK для iOS и Android. Ознакомьтесь с [руководством по миграции](migration-to-capacitor-sdk-v4) для обновления. ::: <details> <summary>Перед тем как начать показывать флоу (нажмите, чтобы раскрыть)</summary> 1. Настройте начальную интеграцию Adapty [с App Store](initial_ios) и [с Google Play](initial-android). 2. Установите и настройте Adapty SDK. Обязательно установите параметр `observerMode` в значение `true`. Обратитесь к [гайду по установке Capacitor SDK](sdk-installation-capacitor#activate-adapty-module-of-adapty-sdk). 3. [Создайте продукты](create-product) в дашборде Adapty. 4. [Настройте флоу или пейволы в билдерах](create-paywall) и привяжите к ним продукты. 5. [Создайте плейсменты и назначьте им флоу или пейволы](create-placement). 6. [Загрузите флоу и их конфигурацию](capacitor-get-pb-paywalls) в коде мобильного приложения. </details> В режиме Observer SDK не выполняет покупки за вас. Когда пользователь нажимает кнопку покупки или восстановления во флоу или пейволе, отрендеренном Adapty, SDK вызывает ваш обработчик события `onObserverPurchaseInitiated` или `onObserverRestoreInitiated` — именно там нужно выполнить покупку или восстановление с помощью вашего кода. 1. Установите обработчики событий режима Observer на представление. В отличие от других платформ, отдельного объекта resolver нет — обработчики являются частью обычных [обработчиков событий](capacitor-handling-events), поэтому устанавливайте их для каждого создаваемого представления: ```typescript showLineNumbers title="Capacitor" import { adapty, createFlowView } from '@adapty/capacitor'; const view = await createFlowView(flow); const unsubscribe = await view.setEventHandlers({ onObserverPurchaseInitiated(product, onStartPurchase, onFinishPurchase) { onStartPurchase(); // the view shows its loading indicator myPurchaseApi(product.vendorProductId) .then((transactionId) => adapty.reportTransaction({ transactionId, variationId: flow.variationId }), ) .finally(() => onFinishPurchase()); // the view hides the loading indicator return false; // keep the flow open; dismiss it yourself after success }, onObserverRestoreInitiated(onStartRestore, onFinishRestore) { onStartRestore(); myRestoreApi().finally(() => onFinishRestore()); return false; }, }); ``` `onObserverPurchaseInitiated` сообщает, что пользователь инициировал покупку, а `onObserverRestoreInitiated` — что пользователь инициировал восстановление. В ответ на эти события запустите свой кастомный флоу покупки или восстановления. Также не забудьте вызвать следующие коллбэки, чтобы уведомить AdaptyUI о ходе покупки или восстановления. Это необходимо для корректной работы флоу, например для отображения загрузчика: | Callback | Description | | :----------------- | :---------------------------------------------------------------------------------------------------- | | onStartPurchase() | Этот колбэк нужно вызвать, чтобы уведомить AdaptyUI о начале покупки. | | onFinishPurchase() | Этот колбэк нужно вызвать, чтобы уведомить AdaptyUI о завершении покупки. | | onStartRestore() | Этот колбэк нужно вызвать, чтобы уведомить AdaptyUI о начале восстановления покупок. | | onFinishRestore() | Этот колбэк нужно вызвать, чтобы уведомить AdaptyUI о завершении восстановления покупок. | 2. Отобразите флоу как обычно: [получите флоу и создайте его представление](capacitor-get-pb-paywalls), затем [покажите его](capacitor-present-paywalls). Дополнительные параметры не нужны — обработчики срабатывают только тогда, когда SDK был активирован с `observerMode: true`. :::warning Не забудьте [сообщить о транзакции и привязать её к пейволу](report-transactions-observer-mode-capacitor). Иначе Adapty не распознает транзакцию и не определит исходный пейвол покупки. ::: --- # File: capacitor-implement-paywalls-manually --- --- title: "Реализация пейволов вручную" description: "Узнайте, как реализовать пейволы вручную в вашем Capacitor-приложении с помощью Adapty SDK." --- ## Приём платежей \{#accept-purchases\} Если вы работаете с пейволами, которые реализовали самостоятельно, вы можете делегировать обработку покупок Adapty с помощью метода `makePurchase`. В этом случае мы возьмём на себя все пользовательские сценарии, а вам останется только обрабатывать результаты покупок. :::important `makePurchase` работает с продуктами, созданными в дашборде Adapty. Убедитесь, что вы настроили продукты и способы их получения в дашборде, следуя [быстрому старту](quickstart). ::: <CustomDocCardList ids={['capacitor-quickstart-manual', 'fetch-paywalls-and-products-capacitor', 'present-remote-config-paywalls-capacitor', 'capacitor-making-purchases', 'capacitor-restore-purchase']} /> ## Режим наблюдателя \{#observer-mode\} Если вы хотите реализовать собственную логику обработки покупок с нуля, но при этом воспользоваться расширенной аналитикой Adapty, вы можете использовать режим наблюдателя. :::important Ознакомьтесь с ограничениями режима наблюдателя [здесь](observer-vs-full-mode). ::: <CustomDocCardList ids={['implement-observer-mode-capacitor', 'report-transactions-observer-mode-capacitor']} /> --- # File: capacitor-quickstart-manual --- --- title: "Включение покупок в вашем кастомном пейволе в Capacitor SDK" description: "Интегрируйте Adapty SDK в ваши кастомные пейволы на Capacitor для включения встроенных покупок." --- Это руководство описывает интеграцию Adapty в ваши кастомные пейволы. Вы сохраняете полный контроль над реализацией пейвола, а SDK Adapty получает продукты, обрабатывает новые покупки и восстанавливает предыдущие. Руководство использует API Adapty Capacitor SDK v4 — если вы используете v3, обратитесь к [руководству по миграции](migration-to-capacitor-sdk-v4) за соответствующими названиями методов. :::important **Это руководство для разработчиков, которые реализуют пользовательские пейволы.** Если вы хотите подключить покупки максимально просто, используйте [Adapty Paywall Builder](capacitor-quickstart-paywalls). С Paywall Builder вы создаёте пейволы в визуальном редакторе без кода, Adapty берёт на себя всю логику покупок, а тестировать разные дизайны можно без повторной публикации приложения. ::: ## Прежде чем начать \{#before-you-start\} ### Настройка продуктов \{#set-up-products\} Чтобы подключить встроенные покупки, нужно разобраться в трёх ключевых понятиях: - [**Продукты**](product) – всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ) - [**Пейволы**](paywalls) – конфигурации, определяющие, какие продукты предлагать. В Adapty пейволы — единственный способ получить продукты, но такая архитектура позволяет менять продукты, цены и офферы без изменения кода приложения. В SDK v4 варианты пейвола для плейсмента передаются через объект **flow** — вы получаете флоу и запрашиваете его продукты. - [**Плейсменты**](placements) – где и когда вы показываете пейволы в приложении (например, `main`, `onboarding`, `settings`). Вы настраиваете пейволы для плейсментов в дашборде, а затем запрашиваете их по ID плейсмента в коде. Это упрощает проведение A/B-тестов и показ разных пейволов разным пользователям. Убедитесь, что вы понимаете эти концепции, даже если используете собственный пейвол. По сути, это просто способ управлять продуктами, которые вы продаёте в приложении. Чтобы реализовать собственный пейвол, вам нужно создать **пейвол** и добавить его в **плейсмент**. Это позволит вам получать свои продукты. Чтобы разобраться, что нужно сделать в дашборде, следуйте [этому](quickstart) гайду по быстрому старту. ### Управление пользователями \{#manage-users\} Вы можете работать как с аутентификацией на стороне бэкенда, так и без неё. Однако SDK обрабатывает анонимных и идентифицированных пользователей по-разному. Прочитайте [гайд по быстрому старту с идентификацией](capacitor-quickstart-identify), чтобы понять особенности и убедиться, что вы правильно работаете с пользователями. ## Шаг 1. Получение продуктов \{#step-1-get-products\} Чтобы получить продукты для вашего кастомного пейвола, нужно: 1. Получить объект `flow`, передав ID [плейсмента](placements) в метод `getFlow`. 2. Получить массив продуктов для этого флоу с помощью метода `getPaywallProducts`. ```typescript showLineNumbers async function loadPaywall() { try { const flow: AdaptyFlow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID' }); const products: AdaptyPaywallProduct[] = await adapty.getPaywallProducts({ flow }); // Use products to build your custom paywall UI } catch (error) { // Handle the error } } ``` ## Шаг 2. Принимайте покупки \{#step-2-accept-purchases\} Когда пользователь нажимает на продукт в вашем кастомном пейволе, вызовите метод `makePurchase` с выбранным продуктом. Это запустит флоу покупки и вернёт обновлённый профиль. ```typescript showLineNumbers async function purchaseProduct(product: AdaptyPaywallProduct) { try { const result: AdaptyPurchaseResult = await adapty.makePurchase({ product }); if (result.type === 'success') { // Purchase successful, profile updated } else if (result.type === 'user_cancelled') { // User canceled the purchase } else if (result.type === 'pending') { // Purchase is pending (e.g., user will pay offline with cash) } } catch (error) { // Handle the error } } ``` ## Шаг 3. Восстановление покупок \{#step-3-restore-purchases\} Сторы требуют, чтобы все приложения с подписками предоставляли пользователям возможность восстановить покупки. Вызывайте метод `restorePurchases`, когда пользователь нажимает кнопку восстановления. Это синхронизирует историю покупок с Adapty и вернёт обновлённый профиль. ```typescript showLineNumbers async function restorePurchases() { try { const profile: AdaptyProfile = await adapty.restorePurchases(); // Restore successful, profile updated } catch (error) { // Handle the error } } ``` ## Шаг 4. Проверьте статус подписки \{#step-4-check-the-subscription-status\} После покупки или восстановления проверьте [уровень доступа](access-level) пользователя, чтобы решить, показывать ли пейвол или открыть платные функции. Методы `makePurchase` и `restorePurchases` уже возвращают обновлённый профиль; если вам нужен текущий статус в другом месте приложения, используйте метод `getProfile`: ```typescript showLineNumbers async function hasPremiumAccess(): Promise<boolean> { try { const profile = await adapty.getProfile(); return profile.accessLevels?.['premium']?.isActive ?? false; } catch (error) { // Handle the error } return false; } ``` Подробнее о способах проверки и отслеживания статуса подписки, включая получение обновлений в реальном времени, см. в разделе [Проверка статуса подписки](capacitor-check-subscription-status). ## Следующие шаги \{#next-steps\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Ваш пейвол готов к отображению в приложении. Протестируйте покупки в [песочнице App Store](test-purchases-in-sandbox) или в [Google Play Store](testing-on-android), чтобы убедиться, что тестовая покупка проходит корректно. Чтобы увидеть, как это работает в production-ready реализации, посмотрите на [App.tsx](https://github.com/adaptyteam/AdaptySDK-Capacitor/blob/master/examples/adapty-devtools/src/screens/app/App.tsx) в нашем примере приложения — там показана обработка покупок с правильной обработкой ошибок, состояниями загрузки и полной интеграцией SDK. --- # File: fetch-paywalls-and-products-capacitor --- --- title: "Получение пейволов и продуктов для пейволов с Remote Config в Capacitor SDK" description: "Получайте пейволы и продукты в Adapty Capacitor SDK для улучшения монетизации пользователей." --- <SDKv4> Прежде чем отображать Remote Config и кастомные пейволы, вам нужно получить информацию о них. Обратите внимание, что этот раздел посвящён Remote Config и кастомным пейволам. Чтобы узнать, как получить флоу или пейволы, настроенные в **Flow Builder** или **Paywall Builder**, обратитесь к статье [Получение флоу Flow Builder и пейволов Paywall Builder и их конфигурации](capacitor-get-pb-paywalls). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Прежде чем начать получать флоу и продукты в мобильном приложении (нажмите, чтобы развернуть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте флоу или пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них флоу или пейвол](create-placement) в дашборде Adapty. 4. [Установите Adapty SDK](sdk-installation-capacitor) в своём мобильном приложении. </details> ## Получение информации о флоу \{#fetch-flow-information\} В Adapty [продукт](product) объединяет в себе продукты из App Store и Google Play. Эти кроссплатформенные продукты интегрируются во флоу и пейволы, что позволяет отображать их в конкретных плейсментах мобильного приложения. Чтобы отобразить продукты, необходимо получить объект `AdaptyFlow` из одного из ваших [плейсментов](placements) с помощью метода `getFlow`. :::important **Не указывайте product ID в коде.** Единственный ID, который стоит хардкодить — это ID плейсмента. Флоу настраиваются удалённо, поэтому количество продуктов и доступных офферов может меняться в любой момент. Ваше приложение должно обрабатывать эти изменения динамически — если сегодня флоу возвращает два продукта, а завтра три, отображайте все из них без изменений в коде. ::: ```typescript showLineNumbers try { const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID', params: { fetchPolicy: 'reload_revalidating_cache_data', // Load from server, fallback to cache loadTimeoutMs: 5000 // 5 second timeout } }); // the requested flow } catch (error) { console.error('Failed to fetch flow:', error); } ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **params.fetchPolicy** | <p>опциональный</p><p>по умолчанию: `'reload_revalidating_cache_data'`</p> | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кэшированные данные в случае сбоя. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `'return_cache_data_else_load'` для возврата кэшированных данных, если они существуют. В этом случае пользователи могут не получить самые последние данные, но время загрузки будет меньше вне зависимости от качества соединения. Кэш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание, что кэш сохраняется при перезапуске приложения и очищается только при переустановке или ручной очистке.</p><p></p><p>Adapty SDK хранит флоу и пейволы в двух слоях: регулярно обновляемый кэш, описанный выше, и [резервные пейволы](capacitor-use-fallback-paywalls). Также используется CDN для более быстрой загрузки флоу и пейволов, а также отдельный резервный сервер на случай недоступности CDN. Эта система обеспечивает получение актуальной версии флоу и надёжность даже при слабом интернет-соединении.</p> | | **params.loadTimeoutMs** | <p>опциональный</p><p>по умолчанию: 5000 мс</p> | <p>Это значение ограничивает таймаут (в миллисекундах) для данного метода. По истечении таймаута будут возвращены кэшированные данные или локальный резервный вариант.</p><p></p><p>Обратите внимание, что в редких случаях метод может завершиться с небольшой задержкой относительно значения, указанного в `loadTimeoutMs`, так как операция может включать несколько запросов под капотом.</p> | :::note В версии 4 метод `getFlow` больше не принимает параметр `locale`. Для кастомных пейволов все доступные локали возвращаются в Remote Config флоу (`flow.remoteConfigs`) — выберите ту, которая соответствует языку устройства или настройкам приложения. ::: Не хардкодьте ID продуктов! Поскольку флоу настраиваются удалённо, доступные продукты, их количество и специальные предложения (например, бесплатные пробные периоды) могут меняться со временем. Убедитесь, что ваш код учитывает эти сценарии. Например, если изначально вы получаете 2 продукта, приложение должно отображать их. Но если позднее вы получите 3 продукта, приложение должно показать все 3 без каких-либо изменений в коде. Единственное, что нужно хардкодить, — это ID плейсмента. Параметры ответа: | Параметр | Описание | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | Объект `AdaptyFlow`, содержащий плейсмент, идентификаторы (`id`, `variationId`), название, вариации пейвола (`paywalls`) и массив `remoteConfigs` (по одной записи на каждую настроенную локаль). Чтобы получить продукты для флоу, вызовите `getPaywallProducts({ flow })`. | ## Получение продуктов \{#fetch-products\} После того как вы получили флоу, можно запросить массив продуктов, соответствующих ему: ```typescript showLineNumbers try { const products = await adapty.getPaywallProducts({ flow }); // the requested products list } catch (error) { console.error('Failed to fetch products:', error); } ``` Параметры ответа: | Параметр | Описание | | :-------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | Список объектов [`AdaptyPaywallProduct`](https://capacitor.adapty.io/interfaces/adaptypaywallproduct) с идентификатором продукта, названием, ценой, валютой, длительностью подписки и рядом других свойств. | При реализации собственного дизайна пейвола вам, скорее всего, понадобятся свойства объекта [`AdaptyPaywallProduct`](https://capacitor.adapty.io/interfaces/adaptypaywallproduct). Ниже приведены наиболее часто используемые свойства; полный список доступных свойств см. в связанной документации. | Свойство | Описание | |-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Чтобы отобразить название продукта, используйте `product.localizedTitle`. Обратите внимание: локализация основана на стране стора, выбранной пользователем, а не на локали устройства. | | **Price** | Чтобы отобразить цену в локализованном формате, используйте `product.price?.localizedString`. Локализация основана на локали устройства. Также можно получить цену как число через `product.price?.amount` — значение будет в местной валюте. Для получения символа валюты используйте `product.price?.currencySymbol`. | | **Subscription Period** | Чтобы отобразить период (например, неделя, месяц, год и т. д.), используйте `product.subscription?.localizedSubscriptionPeriod`. Локализация основана на локали устройства. Для программного получения периода подписки используйте `product.subscription?.subscriptionPeriod`. Оттуда можно обратиться к свойству `unit`, чтобы получить единицу периода (`'day'`, `'week'`, `'month'`, `'year'` или `'unknown'`). Свойство `numberOfUnits` даёт количество единиц периода. Например, для квартальной подписки в `unit` будет `'month'`, а в `numberOfUnits` — `3`. | | **Introductory Offer** | Чтобы показать значок или другой индикатор наличия introductory offer у подписки, проверьте свойство `product.subscription?.offer?.phases`. Это список, который может содержать до двух фаз скидки: фазу бесплатного пробного периода и фазу вводной цены. Каждый объект фазы содержит следующие полезные свойства:<br/>• `paymentMode`: строка со значениями `'free_trial'`, `'pay_as_you_go'`, `'pay_up_front'` и `'unknown'`. Бесплатные пробные периоды имеют тип `'free_trial'`.<br/>• `price`: цена со скидкой в виде числа. Для бесплатных пробных периодов ожидайте `0`.<br/>• `localizedNumberOfPeriods`: строка, локализованная по локали устройства, описывающая длительность предложения. Например, для трёхдневного пробного периода в этом поле будет `'3 days'`.<br/>• `subscriptionPeriod`: позволяет получить отдельные детали периода предложения — работает так же, как описано в предыдущем разделе.<br/>• `localizedSubscriptionPeriod`: отформатированный период подписки скидки для локали пользователя. | ## Ускорьте загрузку флоу с помощью флоу для аудитории по умолчанию \{#speed-up-flow-fetching-with-default-audience-flow\} Как правило, флоу загружаются почти мгновенно, так что беспокоиться об этом не нужно. Однако если у вас много аудиторий и плейсментов, а соединение у пользователя слабое, загрузка флоу может занять больше времени, чем хотелось бы. В таких случаях лучше показать флоу по умолчанию, чтобы обеспечить плавный пользовательский опыт, а не оставлять экран пустым. Чтобы решить эту проблему, можно использовать метод `getFlowForDefaultAudience`, который получает флоу указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать флоу с помощью метода `getFlow`, как описано в разделе [Получение информации о флоу](fetch-paywalls-and-products-capacitor#fetch-flow-information) выше. :::warning Почему мы рекомендуем использовать `getFlow` Метод `getFlowForDefaultAudience` имеет ряд существенных недостатков: - **Потенциальные проблемы с обратной совместимостью**: если вам нужно показывать разные флоу для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать флоу, совместимые с текущей (устаревшей) версией, либо мириться с тем, что у пользователей с текущей (устаревшей) версией флоу могут не отображаться. - **Потеря таргетинга**: все пользователи будут видеть один и тот же флоу, настроенный для аудитории **All Users**, — то есть вы теряете персонализированный таргетинг (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вас устраивают эти недостатки ради более быстрой загрузки флоу, используйте метод `getFlowForDefaultAudience`, как описано ниже. В противном случае используйте `getFlow`, описанный [выше](fetch-paywalls-and-products-capacitor#fetch-flow-information). ::: ```typescript showLineNumbers try { const flow = await adapty.getFlowForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', params: { fetchPolicy: 'reload_revalidating_cache_data' // Load from server, fallback to cache } }); // the requested flow } catch (error) { console.error('Failed to fetch default audience flow:', error); } ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указываете при создании плейсмента в дашборде Adapty. | | **params.fetchPolicy** | <p>опциональный</p><p>по умолчанию: `'reload_revalidating_cache_data'`</p> | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные при сбое. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают самые актуальные данные.</p><p></p><p>Однако если вы предполагаете, что у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `'return_cache_data_else_load'`: в этом случае возвращаются кешированные данные, если они есть. Пользователи могут не получить самые свежие данные, зато загрузка будет быстрее независимо от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при переустановке или ручной очистке.</p> | </SDKv4> <SDKv3> Прежде чем использовать Remote Config и кастомные пейволы, нужно получить информацию о них. Обратите внимание, что эта тема касается Remote Config и кастомных пейволов. Для получения пейволов, созданных с помощью Paywall Builder, см. [Получение пейволов Paywall Builder и их конфигурации](capacitor-get-pb-paywalls). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Прежде чем начать получать пейволы и продукты в мобильном приложении (нажмите, чтобы развернуть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте пейвол в плейсмент](create-placement) в дашборде Adapty. 4. [Установите Adapty SDK](sdk-installation-capacitor) в своё мобильное приложение. </details> ## Получение информации о пейволе \{#fetch-paywall-information\} В Adapty [продукт](product) объединяет в себе продукты из App Store и Google Play. Эти кроссплатформенные продукты подключаются к пейволам, что позволяет отображать их в нужных плейсментах вашего мобильного приложения. Чтобы показать продукты, необходимо получить [пейвол](paywalls) из одного из ваших [плейсментов](placements) с помощью метода `getPaywall`. ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', params: { fetchPolicy: 'reload_revalidating_cache_data', // Load from server, fallback to cache loadTimeoutMs: 5000 // 5 second timeout } }); // the requested paywall } catch (error) { console.error('Failed to fetch paywall:', error); } ``` | Параметр | Обязательность | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение, которое вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается код языка, состоящий из одного или нескольких подтегов, разделённых символом минус (**-**). Первый подтег — язык, второй — регион.</p><p></p><p>Пример: `en` означает английский, `pt-br` — бразильский португальский.</p><p></p><p>Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](capacitor-localizations-and-locale-codes).</p> | | **params.fetchPolicy** | <p>опциональный</p><p>по умолчанию: `'reload_revalidating_cache_data'`</p> | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `'return_cache_data_else_load'` — оно возвращает кешированные данные, если они существуют. В этом случае пользователи могут получать не самые свежие данные, но загрузка будет быстрее независимо от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы сократить количество сетевых запросов.</p><p></p><p>Обратите внимание, что кеш сохраняется после перезапуска приложения и очищается только при его переустановке или вручную.</p> | | **params.loadTimeoutMs** | <p>опциональный</p><p>по умолчанию: 5000 мс</p> | <p>Это значение ограничивает таймаут (в миллисекундах) для данного метода. При достижении таймаута будут возвращены кешированные данные или локальный резервный пейвол.</p><p></p><p>Обратите внимание, что в редких случаях метод может завершиться с небольшой задержкой относительно значения, указанного в `loadTimeoutMs`, поскольку операция может включать несколько запросов под капотом.</p> | **Не прописывайте идентификаторы продуктов в коде.** Единственный ID, который нужно хардкодить, — это ID плейсмента. Пейволы настраиваются удалённо, поэтому количество продуктов и доступных предложений может измениться в любой момент. Приложение должно обрабатывать эти изменения динамически — если сегодня пейвол возвращает два продукта, а завтра три, все они должны отображаться без изменений в коде. Параметры ответа: | Параметр | Описание | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Объект [`AdaptyPaywall`](https://capacitor.adapty.io/interfaces/adaptypaywall), содержащий: список идентификаторов продуктов, идентификатор пейвола, Remote Config и ряд других свойств. | ## Получение продуктов \{#fetch-products\} Получив пейвол, вы можете запросить массив продуктов, который ему соответствует: ```typescript showLineNumbers try { const products = await adapty.getPaywallProducts({ paywall }); // the requested products list } catch (error) { console.error('Failed to fetch products:', error); } ``` Параметры ответа: | Параметр | Описание | | :-------- |:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | Список объектов [`AdaptyPaywallProduct`](https://capacitor.adapty.io/interfaces/adaptypaywallproduct) с идентификатором продукта, названием, ценой, валютой, длительностью подписки и рядом других свойств. | При реализации собственного дизайна пейвола вам, скорее всего, понадобятся свойства объекта [`AdaptyPaywallProduct`](https://capacitor.adapty.io/interfaces/adaptypaywallproduct). Ниже приведены наиболее часто используемые из них, однако полный список всех доступных свойств можно найти в документации по ссылке. | Свойство | Описание | |-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Чтобы отобразить название продукта, используйте `product.localizedTitle`. Локализация основана на выбранной пользователем стране стора, а не на локали устройства. | | **Price** | Чтобы отобразить цену в локализованном формате, используйте `product.price?.localizedString`. Локализация основана на локали устройства. Цену как число можно получить через `product.price?.amount` — значение будет в местной валюте. Символ валюты доступен через `product.price?.currencySymbol`. | | **Subscription Period** | Чтобы отобразить период (например, неделя, месяц, год и т. д.), используйте `product.subscription?.localizedSubscriptionPeriod`. Локализация основана на локали устройства. Для программного получения периода подписки используйте `product.subscription?.subscriptionPeriod`. Через свойство `unit` можно узнать единицу периода: `'day'`, `'week'`, `'month'`, `'year'` или `'unknown'`. Свойство `numberOfUnits` возвращает количество таких единиц. Например, для квартальной подписки в `unit` будет `'month'`, а в `numberOfUnits` — `3`. | | **Introductory Offer** | Чтобы отобразить бейдж или другой индикатор наличия introductory offer, используйте свойство `product.subscription?.offer?.phases`. Это список, который может содержать до двух фаз скидки: фазу бесплатного пробного периода и фазу вводной цены. Каждый объект фазы содержит следующие полезные свойства:<br/>• `paymentMode`: строка со значениями `'free_trial'`, `'pay_as_you_go'`, `'pay_up_front'` и `'unknown'`. Бесплатный пробный период имеет тип `'free_trial'`.<br/>• `price`: сниженная цена в виде числа. Для бесплатного пробного периода здесь будет `0`.<br/>• `localizedNumberOfPeriods`: строка, локализованная под локаль устройства и описывающая длительность предложения. Например, для трёхдневного пробного периода здесь будет `'3 days'`.<br/>• `subscriptionPeriod`: позволяет получить отдельные параметры периода предложения — работает так же, как описано в предыдущем разделе.<br/>• `localizedSubscriptionPeriod`: отформатированный период подписки скидки для локали пользователя. | ## Ускорьте загрузку пейвола с помощью пейвола для аудитории по умолчанию \{#speed-up-paywall-fetching-with-default-audience-paywall\} Как правило, пейволы загружаются почти мгновенно, так что беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и пейволов, а пользователи работают с медленным интернетом, загрузка пейвола может занять больше времени, чем хотелось бы. В таких ситуациях имеет смысл показывать пейвол по умолчанию, чтобы не оставлять пользователя ни с чем. Чтобы решить эту задачу, используйте метод `getPaywallForDefaultAudience`, который получает пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать пейвол методом `getPaywall`, как описано в разделе [Получение информации о пейволе](fetch-paywalls-and-products-capacitor#fetch-paywall-information) выше. :::warning Почему мы рекомендуем использовать `getPaywall` Метод `getPaywallForDefaultAudience` имеет ряд существенных недостатков: - **Возможные проблемы с обратной совместимостью**: если вам нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть трудности. Придётся либо проектировать пейволы с поддержкой текущей (старой) версии, либо смириться с тем, что пользователи на ней могут столкнуться с проблемами — пейволы просто не отобразятся. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, созданный для аудитории **All Users**, — а значит, вы лишаетесь персонализированного таргетинга (по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вы готовы принять эти ограничения ради более быстрого получения пейвола, используйте метод `getPaywallForDefaultAudience`, как описано ниже. В противном случае используйте метод `getPaywall`, описанный [выше](fetch-paywalls-and-products-capacitor#fetch-paywall-information). ::: ```typescript showLineNumbers try { const paywall = await adapty.getPaywallForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', params: { fetchPolicy: 'reload_revalidating_cache_data' // Load from server, fallback to cache } }); // the requested paywall } catch (error) { console.error('Failed to fetch default audience paywall:', error); } ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>необязательный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается языковой код, состоящий из одного или нескольких подтегов, разделённых символом минус (**-**). Первый подтег обозначает язык, второй — регион.</p><p></p><p>Пример: `en` — английский язык, `pt-br` — бразильский португальский.</p><p></p><p>Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](capacitor-localizations-and-locale-codes).</p> | | **params.fetchPolicy** | <p>необязательный</p><p>по умолчанию: `'reload_revalidating_cache_data'`</p> | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае неудачи. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите вариант `'return_cache_data_else_load'` — он возвращает кешированные данные, если они есть. В этом случае данные могут быть не самыми свежими, зато загрузка будет быстрее вне зависимости от качества связи. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или ручной очистке.</p> | </SDKv3> --- # File: present-remote-config-paywalls-capacitor --- --- title: "Отображение пейвола на основе Remote Config в Capacitor SDK" description: "Узнайте, как отображать пейволы на основе Remote Config в Adapty Capacitor SDK для персонализации пользовательского опыта." --- <SDKv4> Если вы настроили флоу с помощью Remote Config, вам нужно реализовать рендеринг в коде мобильного приложения, чтобы отображать его пользователям. Поскольку Remote Config предоставляет гибкость под ваши нужды, вы сами определяете, что в него включить и как будет выглядеть ваш флоу. Мы предоставляем метод для получения Remote Config — так что вы полностью управляете тем, как отображать настроенный через Remote Config флоу. ## Получение Remote Config флоу и его отображение \{#get-flow-remote-config-and-present-it\} В v4 флоу содержит по одному элементу `AdaptyRemoteConfig` на каждый настроенный язык в массиве `remoteConfigs`. Выберите язык, соответствующий предпочтениям пользователя, и считайте нужные значения из его поля `data`. ```typescript showLineNumbers try { const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID' }); const config = flow.remoteConfigs?.find((c) => c.lang === 'en') ?? flow.remoteConfigs?.[0]; const headerText = config?.data?.['header_text']; } catch (error) { console.error('Failed to fetch flow:', error); } ``` На этом этапе, получив все необходимые значения, можно переходить к рендерингу и сборке визуально привлекательной страницы. Убедитесь, что дизайн адаптирован под различные экраны мобильных телефонов и ориентации, обеспечивая удобный и понятный пользовательский опыт на всех устройствах. :::warning Обязательно [зафиксируйте событие просмотра пейвола](present-remote-config-paywalls-capacitor#track-paywall-view-events), как описано ниже, чтобы аналитика Adapty могла собирать данные для воронок и A/B-тестов. ::: После отображения флоу переходите к настройке процесса покупки. Когда пользователь совершает покупку, просто вызовите `.makePurchase()` с продуктом из вашего флоу. Подробнее о методе `.makePurchase()` читайте в разделе [Совершение покупок](capacitor-making-purchases). Рекомендуем [создать резервный пейвол](capacitor-use-fallback-paywalls). Он будет отображаться пользователю при отсутствии интернет-соединения или кэша, обеспечивая бесперебойную работу даже в таких ситуациях. ## Отслеживание событий просмотра пейвола \{#track-paywall-view-events\} Adapty помогает измерять эффективность ваших флоу. Данные о покупках собираются автоматически, однако логирование просмотров флоу требует вашего участия — только вы знаете, когда пользователь видит флоу. Чтобы залогировать событие просмотра флоу, вызовите `.logShowFlow({ flow })` — это отразится в метриках вашего пейвола в воронках и A/B-тестах. :::important Вызывать `.logShowFlow({ flow })` не нужно, если вы отображаете флоу или пейволы, отрисованные с помощью [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder). В этих случаях Adapty отслеживает просмотры автоматически. ::: ```typescript showLineNumbers await adapty.logShowFlow({ flow }); ``` Параметры запроса: | Параметр | Наличие | Описание | | :-------- | :------- |:-------------------------------------------------------------------------------------| | **flow** | обязательный | Объект `AdaptyFlow`, полученный через `adapty.getFlow({ placementId })`. | </SDKv4> <SDKv3> Если вы настроили пейвол с помощью Remote Config, вам нужно реализовать рендеринг в коде мобильного приложения, чтобы показывать его пользователям. Поскольку Remote Config гибко подстраивается под ваши потребности, вы сами определяете, что включить и как будет выглядеть пейвол. Мы предоставляем метод для получения Remote Config — всё остальное в ваших руках. ## Получение Remote Config пейвола и его отображение \{#get-paywall-remote-config-and-present-it\} Чтобы получить Remote Config пейвола, обратитесь к свойству `remoteConfig` и извлеките нужные значения. ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID', params: { fetchPolicy: 'reload_revalidating_cache_data', // Load from server, fallback to cache loadTimeoutMs: 5000 // 5 second timeout } }); const headerText = paywall.remoteConfig?.data?.['header_text']; } catch (error) { console.error('Failed to fetch paywall:', error); } ``` На этом этапе, получив все необходимые значения, можно приступать к отрисовке и сборке привлекательной страницы. Убедитесь, что дизайн адаптируется под различные экраны и ориентации мобильных устройств, обеспечивая удобный пользовательский опыт на разных девайсах. :::warning Не забудьте [зафиксировать событие просмотра пейвола](present-remote-config-paywalls-capacitor#track-paywall-view-events-1), как описано ниже — это позволяет аналитике Adapty собирать данные для воронок и A/B-тестов. ::: После того как пейвол отображён, переходите к настройке процесса покупки. Когда пользователь совершает покупку, просто вызовите `.makePurchase()` с продуктом из вашего пейвола. Подробнее о методе `.makePurchase()` читайте в разделе [Совершение покупок](capacitor-making-purchases). Рекомендуем [создать резервный пейвол](capacitor-use-fallback-paywalls). Он будет показан пользователю при отсутствии интернета или кэша, что обеспечит бесперебойную работу приложения в таких ситуациях. ## Отслеживание событий просмотра пейвола \{#track-paywall-view-events\} Adapty помогает измерять эффективность ваших пейволов. Данные о покупках мы собираем автоматически, но логирование просмотров пейвола требует вашего участия — только вы знаете, когда пользователь видит пейвол. Чтобы зафиксировать событие просмотра пейвола, вызовите `.logShowPaywall(paywall)` — это отразится в метриках пейвола в воронках и A/B-тестах. :::important Вызывать `.logShowPaywall(paywall)` не нужно, если вы показываете пейволы, созданные в [Paywall Builder](adapty-paywall-builder). ::: ```typescript showLineNumbers try { await adapty.logShowPaywall({ paywall }); } catch (error) { console.error('Failed to log paywall view:', error); } ``` Параметры запроса: | Параметр | Обязательность | Описание | | :---------- | :------------- | :--------------------------------------------------------- | | **paywall** | обязательный | Объект [`AdaptyPaywall`](https://capacitor.adapty.io/interfaces/adaptypaywall). | </SDKv3> --- # File: capacitor-making-purchases --- --- title: "Совершение покупок в мобильном приложении с помощью Capacitor SDK" description: "Гайд по обработке встроенных покупок и подписок с помощью Adapty." --- Отображение пейволов в мобильном приложении — необходимый шаг для предоставления пользователям доступа к премиум-контенту или услугам. Однако простого показа пейволов достаточно для поддержки покупок только в том случае, если вы используете [Paywall Builder](adapty-paywall-builder) для настройки пейволов. Если вы не используете Paywall Builder, для совершения покупки и открытия доступа к нужному контенту необходимо вызывать отдельный метод `.makePurchase()`. Именно через него пользователи взаимодействуют с пейволами и проводят нужные транзакции. Если для продукта, который пользователь хочет купить, настроен активный promotional offer, Adapty автоматически применит его в момент покупки. Убедитесь, что вы выполнили [начальную настройку](quickstart), не пропустив ни одного шага. Без неё мы не сможем валидировать покупки. ## Совершение покупки \{#make-purchase\} :::note **Используете [Paywall Builder](adapty-paywall-builder)?** Покупки обрабатываются автоматически — этот шаг можно пропустить. **Нужно пошаговое руководство?** Ознакомьтесь с [гайдом по быстрому старту](capacitor-implement-paywalls-manually) — там есть полные инструкции по реализации с необходимым контекстом. ::: ```typescript showLineNumbers try { const result = await adapty.makePurchase({ product }); if (result.type === 'success') { const isSubscribed = result.profile?.accessLevels?.['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features console.log('User is now subscribed!'); } } else if (result.type === 'user_cancelled') { console.log('Purchase cancelled by user'); } else if (result.type === 'pending') { console.log('Purchase is pending'); } } catch (error) { console.error('Purchase failed:', error); } ``` | Параметр | Наличие | Описание | | :---------- | :------- |:----------------------------------------------------------------------------------------------------------------------------| | **product** | required | Объект [`AdaptyPaywallProduct`](https://capacitor.adapty.io/interfaces/adaptypaywallproduct), полученный из флоу через `getPaywallProducts`. | Параметры ответа: | Параметр | Описание | |---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **result** | Объект [`AdaptyPurchaseResult`](https://capacitor.adapty.io/types/adaptypurchaseresult) с полем `type`, указывающим результат покупки (`'success'`, `'user_cancelled'` или `'pending'`), и полем `profile`, содержащим обновлённый [`AdaptyProfile`](https://capacitor.adapty.io/interfaces/adaptyprofile) при успешной покупке. | ## Смена подписки при совершении покупки \{#change-subscription-when-making-a-purchase\} Когда пользователь выбирает новую подписку вместо продления текущей, поведение зависит от стора: - В App Store подписка обновляется автоматически в рамках группы подписок. Если пользователь приобретает подписку из одной группы, уже имея активную подписку из другой, обе подписки будут активны одновременно. - В Google Play подписка не обновляется автоматически. Вам нужно будет управлять переходом в коде мобильного приложения, как описано ниже. Чтобы заменить одну подписку на другую в Android, вызовите метод `.makePurchase()` с дополнительным параметром: ```typescript showLineNumbers try { const result = await adapty.makePurchase({ product, params: { android: { subscriptionUpdateParams: { oldSubVendorProductId: 'old_product_id', prorationMode: 'charge_prorated_price' }, isOfferPersonalized: true } } }); if (result.type === 'success') { const isSubscribed = result.profile?.accessLevels?.['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features console.log('Subscription updated successfully!'); } } else if (result.type === 'user_cancelled') { console.log('Purchase cancelled by user'); } else if (result.type === 'pending') { console.log('Purchase is pending'); } } catch (error) { console.error('Purchase failed:', error); } ``` Дополнительный параметр запроса: | Параметр | Наличие | Описание | | :--------- | :------- | :----------------------------------------------------------- | | **params** | опционально | Объект типа [`MakePurchaseParamsInput`](https://capacitor.adapty.io/types/makepurchaseparamsinput), содержащий платформо-зависимые параметры покупки. | Структура `MakePurchaseParamsInput` включает: ```typescript { android: { subscriptionUpdateParams: { oldSubVendorProductId: 'old_product_id', prorationMode: 'charge_prorated_price' }, isOfferPersonalized: true } } ``` Подробнее о подписках и режимах замены можно прочитать в документации Google для разработчиков: - [О режимах замены](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Рекомендации Google по режимам замены](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - Режим замены [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE()). Примечание: этот метод доступен только для повышения уровня подписки. Понижение уровня не поддерживается. - Режим замены [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED()). Примечание: фактическая смена подписки произойдёт только по окончании текущего расчётного периода. ### Управление предоплаченными планами (Android) \{#manage-prepaid-plans-android\} Если пользователи вашего приложения могут приобретать [предоплаченные планы](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (например, покупать неавтоматически возобновляемую подписку на несколько месяцев), вы можете включить [ожидающие транзакции](https://developer.android.com/google/play/billing/subscriptions#pending) для таких планов. ```typescript showLineNumbers await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { android: { pendingPrepaidPlansEnabled: true, }, } }); ``` ## Активация промокодов в iOS \{#redeem-offer-codes-in-ios\} <Details> <summary>Об офферных кодах</summary> Офферные коды позволяют предоставлять скидки или бесплатные пробные периоды конкретным пользователям. В отличие от обычных офферов, которые применяются автоматически, офферные коды распространяются за пределами приложения — через email-рассылки, социальные сети или печатные материалы. Пользователи активируют их, вводя код в App Store, переходя по ссылке для активации или через диалог внутри приложения. Чтобы настроить офферные коды, откройте подписку в App Store Connect и перейдите в раздел **Offer Codes**. Вы можете создать [три вида](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes) офферных кодов: - **Free** — подписка бесплатна на заданный период, следующее продление — по полной цене. - **Pay as you go** — пользователь платит сниженную цену в каждом расчётном периоде на протяжении заданного срока, после чего подписка продлевается по полной цене. - **Pay up front** — пользователь единовременно платит сниженную цену за весь срок оффера, после чего подписка продлевается по полной цене. Добавлять офферные коды в Adapty не нужно. Apple помечает каждую транзакцию в период действия оффера категорией офферного кода. Это касается как первоначальной активации, так и всех последующих продлений со скидкой. Adapty обнаруживает метку и записывает каждую транзакцию с категорией оффера `offer_code`. Как только период оффера заканчивается и подписка продлевается по полной цене, метка исчезает. Вы можете фильтровать аналитику по типу оффера **Offer Code** в [дашборде Adapty](controls-filters-grouping-compare-proceeds). #### Устранение расхождений в выручке \{#revenue-discrepancy-troubleshooting\} Если транзакция по офферному коду отображается в Adapty по полной цене продукта вместо сниженной цены оффера, проверьте следующее в App Store Connect: - Для офферного кода настроены корректные цены для всех регионов, где пользователи могут его активировать. - Цена оффера задана для конкретной страны или региона пользователя. Apple передаёт региональную цену в транзакции. Если для оффера не настроена региональная цена, Apple может передать полную цену продукта. Вы можете фильтровать и проверять транзакции по офферным кодам в [дашборде Adapty](controls-filters-grouping-compare-proceeds) по фильтрам типа оффера **Offer Code** и **Offer Discount Type**. #### Устаревшие промокоды (deprecated) \{#legacy-promo-codes-deprecated\} :::warning Apple прекратила поддержку промокодов для встроенных покупок в марте 2026 года. Офферные коды заменяют их с расширенными возможностями: настраиваемые условия применения, сроки действия и до 1 миллиона кодов в квартал. Если вы ранее использовали промокоды для встроенных покупок, перейдите на офферные коды в App Store Connect. ::: Устаревшие промокоды (не более 100 на приложение на версию) предоставляли бесплатный доступ к подписке. В отличие от офферных кодов, Apple не включала информацию о скидке в транзакции по промокодам — в чеке указывалась полная цена продукта. В результате Adapty записывал эти транзакции по полной цене, что приводило к расхождениям в выручке между аналитикой Adapty и App Store Connect. Если вы видите исторические транзакции по полной цене, которые должны были быть бесплатными, скорее всего, они связаны с устаревшими промокодами. Поскольку эти коды больше не поддерживаются, перейдите на офферные коды для точного учёта выручки. </Details> Чтобы отобразить экран активации промокода в вашем приложении: ```typescript showLineNumbers try { await adapty.presentCodeRedemptionSheet(); } catch (error) { console.error('Failed to present code redemption sheet:', error); } ``` :::danger По нашим наблюдениям, экран активации промокода в некоторых приложениях может работать нестабильно. Рекомендуем перенаправлять пользователя напрямую в App Store. Чтобы сделать это, нужно открыть URL следующего формата: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: --- # File: capacitor-restore-purchase --- --- title: "Восстановление покупок в мобильном приложении в Capacitor SDK" description: "Узнайте, как восстанавливать покупки в Adapty для обеспечения бесперебойного пользовательского опыта." --- Восстановление покупок на iOS и Android — это функция, позволяющая пользователям повторно получить доступ к ранее приобретённому контенту (подпискам или встроенным покупкам) без повторного списания средств. Она особенно полезна тем, кто удалил и переустановил приложение или сменил устройство и хочет получить доступ к ранее купленному контенту, не платя снова. :::note В пейволах, созданных с помощью [Paywall Builder](adapty-paywall-builder), покупки восстанавливаются автоматически без дополнительного кода с вашей стороны. Если это ваш случай — этот шаг можно пропустить. ::: Чтобы восстановить покупку, если вы не используете [Paywall Builder](adapty-paywall-builder) для кастомизации пейвола, вызовите метод `.restorePurchases()`: ```typescript showLineNumbers try { const profile = await adapty.restorePurchases(); const isSubscribed = profile.accessLevels?.['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Restore access to paid features console.log('Access restored successfully!'); } else { console.log('No active subscriptions found'); } } catch (error) { console.error('Failed to restore purchases:', error); } ``` Параметры ответа: | Параметр | Описание | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **profile** | Объект [`AdaptyProfile`](https://capacitor.adapty.io/interfaces/adaptyprofile). Эта модель содержит информацию об уровнях доступа, подписках и разовых покупках. Проверьте **статус уровня доступа**, чтобы определить, есть ли у пользователя доступ к приложению. | --- # File: implement-observer-mode-capacitor --- --- title: "Реализация режима Observer в Capacitor SDK" description: "Реализуйте режим observer в Adapty для отслеживания событий подписок пользователей в Capacitor SDK." --- Если у вас уже есть собственная инфраструктура покупок и вы не готовы полностью переходить на Adapty, вы можете воспользоваться [режимом Observer](observer-vs-full-mode). В базовом варианте Observer Mode предоставляет расширенную аналитику и бесшовную интеграцию с системами атрибуции и аналитики. Если это соответствует вашим потребностям, вам нужно лишь: 1. Включить его при настройке SDK, установив параметр `observerMode` в значение `true`. Следуйте инструкциям по настройке для [Capacitor](sdk-installation-capacitor#activate-adapty-module-of-adapty-sdk). 2. [Передавать транзакции](report-transactions-observer-mode-capacitor) из вашей существующей инфраструктуры покупок в Adapty. :::tip В SDK v4 вы также можете показывать флоу и пейволы, отрендеренные Adapty, в режиме Observer: когда пользователь нажимает кнопку покупки или восстановления, SDK передаёт действие вашему коду, чтобы вы могли самостоятельно выполнить покупку или восстановление. См. [Представление флоу в режиме Observer](capacitor-present-flows-in-observer-mode). ::: ### Настройка режима наблюдателя \{#observer-mode-setup\} Включите режим наблюдателя, если вы самостоятельно обрабатываете покупки и статус подписки, а Adapty используете только для отправки событий подписки и аналитики. :::important В режиме наблюдателя Adapty SDK не закрывает транзакции, поэтому убедитесь, что вы обрабатываете их самостоятельно. ::: ```typescript showLineNumbers try { await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { observerMode: true // Enable observer mode } }); } catch (error) { console.error('Failed to activate Adapty:', error); } ``` | Параметр | Описание | | --------------------------- | ------------------------------------------------------------ | | **observerMode** | Булево значение, которое включает [режим Observer](observer-vs-full-mode). Значение по умолчанию: `false`. | ## Использование пейволов Adapty в режиме Observer \{#using-adapty-paywalls-in-observer-mode\} Если вы также хотите использовать пейволы Adapty и функции A/B-тестирования — это возможно, но в режиме Observer потребует дополнительной настройки. Вам нужно будет выполнить следующие шаги в дополнение к описанным выше: 1. Отображайте пейволы как обычно для [пейволов на Remote Config](present-remote-config-paywalls-capacitor). 2. [Связывайте пейволы](report-transactions-observer-mode-capacitor) с транзакциями покупок. --- # File: report-transactions-observer-mode-capacitor --- --- title: "Сообщение о транзакциях в режиме Observer Mode в Capacitor SDK" description: "Сообщайте о транзакциях покупок в Adapty Observer Mode для отслеживания пользователей и доходов в Capacitor SDK." --- В режиме Observer Mode SDK Adapty не может самостоятельно отслеживать покупки, совершённые через вашу существующую систему. Вам нужно вручную сообщать о транзакциях из стора. Важно настроить это **до** релиза приложения, чтобы избежать ошибок в аналитике. Используйте `reportTransaction`, чтобы явно сообщать о каждой транзакции — это позволит Adapty её распознать. :::warning **Не пропускайте отправку транзакций!** Если вы не вызываете `reportTransaction`, Adapty не распознает транзакцию: она не появится в аналитике и не будет передана в интеграции. ::: Если вы используете пейволы Adapty, передавайте `variationId` при сообщении о транзакции. Это связывает покупку с пейволом, который её инициировал, и обеспечивает точную аналитику пейволов. ```typescript showLineNumbers const variationId = paywall.variationId; try { await adapty.reportTransaction({ transactionId: 'your_transaction_id', variationId: variationId }); } catch (error) { console.error('Failed to report transaction:', error); } ``` Параметры: | Параметр | Обязательность | Описание | | ------------- | -------- | ------------------------------------------------------------ | | **transactionId** | обязательный | <ul><li>Для iOS: идентификатор транзакции.</li><li>Для Android: строковый идентификатор (`purchase.getOrderId`) покупки, где покупка — это экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) из библиотеки биллинга.</li></ul> | | **variationId** | необязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://capacitor.adapty.io/interfaces/adaptypaywall). | --- # File: capacitor-user --- --- title: "Пользователи и доступ" description: "Узнайте, как работать с пользователями и уровнями доступа в вашем приложении на Capacitor с помощью Adapty SDK." --- <CustomDocCardList /> --- # File: capacitor-identifying-users --- --- title: "Идентификация пользователей в Capacitor SDK" description: "Узнайте, как идентифицировать пользователей в вашем приложении на Capacitor с помощью Adapty SDK." --- Adapty создаёт внутренний ID профиля для каждого пользователя. Однако если у вас есть собственная система аутентификации, вы можете задать свой Customer User ID. По нему можно находить пользователей в разделе [Профили](profiles-crm) и использовать его в [server-side API](getting-started-with-server-side-api) — он будет передаваться во все интеграции. ### Передача идентификатора пользователя при конфигурации \{#setting-customer-user-id-on-configuration\} Если у вас есть идентификатор пользователя на момент конфигурации, просто передайте его в параметре `customerUserId` метода `.activate()`: ```typescript showLineNumbers try { await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { customerUserId: 'YOUR_USER_ID' } }); } catch (error) { console.error('Failed to activate Adapty:', error); } ``` ### Установка идентификатора пользователя после конфигурации \{#setting-customer-user-id-after-configuration\} Если при конфигурации SDK у вас не было идентификатора пользователя, его можно задать позже в любой момент с помощью метода `.identify()`. Чаще всего этот метод используется после регистрации или авторизации — когда анонимный пользователь становится аутентифицированным. ```typescript showLineNumbers try { await adapty.identify({ customerUserId: 'YOUR_USER_ID' }); console.log('User identified successfully'); } catch (error) { console.error('Failed to identify user:', error); } ``` | Параметр | Обязательный | Описание | |---------|--------|-----------| | **customerUserId** | обязательный | Строковый идентификатор пользователя. | :::warning Повторная отправка важных данных пользователя В некоторых случаях, например когда пользователь снова входит в свой аккаунт, серверы Adapty уже располагают информацией об этом пользователе. В таких сценариях Adapty SDK автоматически переключится на работу с новым пользователем. Если вы передавали какие-либо данные анонимному пользователю — например, пользовательские атрибуты или атрибуции из сторонних сетей — необходимо повторно отправить эти данные для идентифицированного пользователя. Также важно отметить, что после идентификации пользователя необходимо повторно запросить все пейволы и продукты, так как данные нового пользователя могут отличаться. ::: ### Выход и вход пользователя \{#logging-out-and-logging-in\} Вы можете выйти из аккаунта пользователя в любой момент, вызвав метод `.logout()`: ```typescript showLineNumbers try { await adapty.logout(); console.log('User logged out successfully'); } catch (error) { console.error('Failed to logout user:', error); } ``` После этого можно авторизовать пользователя с помощью метода `.identify()`. ## Назначение `appAccountToken` (iOS) [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) — это **UUID**, который позволяет связывать транзакции App Store с внутренним идентификатором пользователя. StoreKit прикрепляет этот токен к каждой транзакции, поэтому бэкенд может сопоставлять данные App Store с вашими пользователями. Используйте стабильный UUID, сгенерированный для каждого пользователя, и применяйте его для одного и того же аккаунта на всех устройствах. Это гарантирует, что покупки и уведомления App Store будут правильно привязаны. Токен можно задать двумя способами — при активации SDK или при идентификации пользователя. :::important `appAccountToken` нужно всегда передавать вместе с `customerUserId`. Если передать только токен, он не попадёт в транзакцию. ::: ```typescript showLineNumbers // При настройке: await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { customerUserId: 'YOUR_USER_ID', ios: { appAccountToken: "YOUR_APP_ACCOUNT_TOKEN" }, } }); // Или при идентификации пользователей await adapty.identify({ customerUserId: 'YOUR_USER_ID', params: { ios: { appAccountToken: 'YOUR_APP_ACCOUNT_TOKEN' }, } }); ``` ### Установите обфусцированные идентификаторы аккаунта (Android) \{#set-obfuscated-account-ids-android\} Google Play требует обфусцированные идентификаторы аккаунта для ряда сценариев — это помогает защитить конфиденциальность и безопасность пользователей. Такие идентификаторы позволяют Google Play отслеживать покупки, не раскрывая личные данные пользователей, что особенно важно для предотвращения мошенничества и аналитики. Устанавливать эти идентификаторы нужно, если приложение работает с чувствительными пользовательскими данными или если вы обязаны соблюдать определённые нормы конфиденциальности. Обфусцированные идентификаторы позволяют Google Play отслеживать покупки, не раскрывая реальные пользовательские данные. ```typescript showLineNumbers // При настройке: await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { android: { obfuscatedAccountId: 'YOUR_OBFUSCATED_ACCOUNT_ID' }, } }); // Или при идентификации пользователей await adapty.identify({ customerUserId: 'YOUR_USER_ID', params: { android: { obfuscatedAccountId: 'YOUR_OBFUSCATED_ACCOUNT_ID' }, } }); ``` ## Обнаружение пользователей на разных устройствах \{#detect-users-across-devices\} При активации SDK он автоматически считывает существующие права пользователя из StoreKit (iOS) или Google Play Billing (Android) и синхронизирует их с бэкендом Adapty. Активная подписка появляется в профиле Adapty без вызова `restorePurchases` со стороны приложения. Что **не** происходит автоматически — так это распознавание того, что профиль на новом устройстве принадлежит тому же пользователю, что и профиль на исходном устройстве. Adapty сопоставляет профили по Customer User ID, поэтому непрерывность идентификации зависит от того, что вы используете в качестве CUID. **Что Adapty может определить между устройствами** | Ваша настройка | Что Adapty определяет | Что нужно сделать | | --- | --- | --- | | Customer User ID = `device_id` (без входа в аккаунт) | Новое устройство получает другой CUID и, следовательно, другой профиль. Подписка синхронизируется с новым профилем через событие **Access level updated**, но `subscription_started` не срабатывает — новый профиль считается наследником исходной покупки. Аналитика, основанная на `subscription_started`, будет занижать количество возвращающихся пользователей. | Используйте стабильный идентификатор аккаунта в качестве Customer User ID, чтобы вернувшийся пользователь соответствовал существующему профилю на разных устройствах. | | Customer User ID = стабильный идентификатор аккаунта (вход на каждом устройстве) | SDK автоматически синхронизирует подписку при `activate()`, а `identify()` сопоставляет существующий профиль по CUID. | Никаких дополнительных действий не требуется — и идентификация, и подписка разрешаются автоматически. | | Наследник Apple Family Sharing | Член семьи получает подписку только через событие **Access level updated** — `subscription_started` не срабатывает. | Отслеживайте событие **Access level updated**. Полную матрицу событий см. в разделе [Apple Family Sharing](apple-family-sharing). | | Один аккаунт Apple/Google, разные пользователи внутри приложения | Первый профиль, зафиксировавший покупку, становится родительским. Последующие профили видят подписку через цепочку наследования с одним событием **Access level updated**. | Требуйте входа в аккаунт, затем выберите [режим совместного использования](sharing-paid-access-between-user-accounts), подходящий для вашей модели. | **Восстановление покупок на новом устройстве** Добавьте кнопку «Восстановить покупки», инициируемую пользователем, на свой пейвол. Apple App Review (руководство 3.1.1) её требует, и она служит запасным вариантом на случай, если автоматическая синхронизация пропустит граничный случай. Кнопка должна вызывать `restorePurchases` в вашем SDK. Программный вызов `restorePurchases` при первом запуске не нужен для обычного использования — SDK уже выполняет аналогичную операцию при `activate()`. Программные вызовы стоит использовать только для принудительной проверки чека, например при отладке отсутствующего доступа после завершения `activate()`. --- # File: capacitor-setting-user-attributes --- --- title: "Установка атрибутов пользователя в Capacitor SDK" description: "Узнайте, как обновлять атрибуты пользователя и данные профиля в приложении на Capacitor с помощью Adapty SDK." --- Вы можете задавать опциональные атрибуты пользователя, такие как email, номер телефона и другие. Атрибуты можно использовать для создания [сегментов](segments) пользователей или просматривать их в CRM. ### Установка атрибутов пользователя \{#setting-user-attributes\} Чтобы задать атрибуты пользователя, вызовите метод `.updateProfile()`: ```typescript showLineNumbers const params = { email: 'email@email.com', phoneNumber: '+18888888888', firstName: 'John', lastName: 'Appleseed', gender: 'other', birthday: new Date().toISOString(), }; try { await adapty.updateProfile(params); console.log('Profile updated successfully'); } catch (error) { console.error('Failed to update profile:', error); } ``` Обратите внимание, что атрибуты, которые вы ранее задали с помощью метода `updateProfile`, не будут сброшены. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Список допустимых ключей \{#the-allowed-keys-list\} Допустимые ключи `AdaptyProfileParameters` и их значения перечислены ниже: | Ключ | Значение | |---|-----| | **email** | String | | **phoneNumber** | String | | **firstName** | String | | **lastName** | String | | **gender** | Enum, допустимые значения: `'female'`, `'male'`, `'other'` | | **birthday** | Строка с датой в формате ISO | ### Пользовательские атрибуты \{#custom-user-attributes\} Вы можете задавать собственные атрибуты. Как правило, они связаны с использованием вашего приложения. Например, для фитнес-приложений это может быть количество тренировок в неделю, для приложений для изучения языков — уровень знаний пользователя и т. д. Их можно использовать в сегментах для создания целевых пейволов и офферов, а также в аналитике, чтобы понять, какие продуктовые метрики больше всего влияют на выручку. ```typescript showLineNumbers try { await adapty.updateProfile({ codableCustomAttributes: { key_1: 'value_1', key_2: 2, }, }); console.log('Custom attributes updated successfully'); } catch (error) { console.error('Failed to update custom attributes:', error); } ``` Чтобы удалить существующие ключи, передайте `null` в качестве их значений: ```typescript showLineNumbers try { // to remove keys, pass null as their values await adapty.updateProfile({ codableCustomAttributes: { key_1: null, key_2: null, }, }); console.log('Custom attributes removed successfully'); } catch (error) { console.error('Failed to remove custom attributes:', error); } ``` Иногда нужно узнать, какие пользовательские атрибуты уже были установлены. Для этого используйте поле `customAttributes` объекта `AdaptyProfile`. :::warning Имейте в виду, что значение `customAttributes` может быть устаревшим: атрибуты пользователя могут отправляться с разных устройств в любое время, поэтому данные на сервере могут измениться после последней синхронизации. ::: ### Ограничения \{#limits\} - До 30 пользовательских атрибутов на одного пользователя - Длина имени ключа — не более 30 символов. Имя ключа может содержать буквенно-цифровые символы, а также следующие: `_` `-` `.` - Значение может быть строкой или числом с плавающей точкой длиной не более 50 символов. --- # File: capacitor-listen-subscription-changes --- --- title: "Проверка статуса подписки в Capacitor SDK" description: "Отслеживайте и управляйте статусом подписки пользователя в Adapty для повышения удержания клиентов в вашем Capacitor-приложении." --- С Adapty отслеживать статус подписки очень просто. Вам не нужно вручную прописывать ID продуктов в коде. Достаточно проверить наличие активного [уровня доступа](access-level), чтобы убедиться в статусе подписки пользователя. <details> <summary>Перед проверкой статуса подписки (нажмите, чтобы развернуть)</summary> - Для iOS настройте [App Store Server Notifications](enable-app-store-server-notifications) - Для Android настройте [Real-time Developer Notifications (RTDN)](enable-real-time-developer-notifications-rtdn) </details> ## Уровень доступа и объект AdaptyProfile \{#access-level-and-the-adaptyprofile-object\} Уровни доступа — это свойства объекта [AdaptyProfile](https://capacitor.adapty.io/interfaces/adaptyprofile). Рекомендуем получать профиль при запуске приложения — например, когда вы [идентифицируете пользователя](capacitor-identifying-users#setting-customer-user-id-on-configuration), — и обновлять его при каждом изменении. Так вы сможете работать с объектом профиля без лишних запросов. Чтобы получать уведомления об обновлениях профиля, подпишитесь на изменения профиля, как описано в разделе [Отслеживание изменений профиля, включая уровни доступа](capacitor-listen-subscription-changes) ниже. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ## Получение уровня доступа с сервера \{#retrieving-the-access-level-from-the-server\} Чтобы получить уровень доступа с сервера, используйте метод `.getProfile()`: ```typescript showLineNumbers try { const profile = await adapty.getProfile(); console.log('Profile retrieved successfully'); } catch (error) { console.error('Failed to get profile:', error); } ``` Параметры ответа: | Параметр | Описание | | --------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **profile** | Объект [AdaptyProfile](https://capacitor.adapty.io/interfaces/adaptyprofile). Как правило, достаточно проверить статус уровня доступа профиля, чтобы определить, есть ли у пользователя премиум-доступ к приложению. Метод `.getProfile` всегда запрашивает API и возвращает максимально актуальные данные. Если по какой-либо причине (например, при отсутствии интернета) SDK не может получить данные с сервера, возвращаются данные из кеша. Важно учитывать, что SDK регулярно обновляет кеш `AdaptyProfile`, чтобы информация оставалась как можно более актуальной. | Метод `.getProfile()` возвращает профиль пользователя, из которого можно получить статус уровня доступа. В одном приложении может быть несколько уровней доступа. Например, если у вас новостное приложение и вы продаёте подписки на разные темы отдельно, можно создать уровни доступа «sports» и «science». Но в большинстве случаев достаточно одного уровня доступа — тогда можно просто использовать стандартный уровень «premium». Вот пример проверки стандартного уровня доступа «premium»: ```typescript showLineNumbers try { const profile = await adapty.getProfile(); const isActive = profile.accessLevels?.['premium']?.isActive; if (isActive) { // Grant access to premium features console.log('User has premium access'); } else { console.log('User does not have premium access'); } } catch (error) { console.error('Failed to check subscription status:', error); } ``` ### Прослушивание обновлений статуса подписки \{#listening-for-subscription-status-updates\} Когда статус подписки пользователя меняется, Adapty генерирует событие. Чтобы получать сообщения от Adapty, необходима дополнительная настройка: ```typescript showLineNumbers // Create an "onLatestProfileLoad" event listener adapty.addListener('onLatestProfileLoad', (data) => { const profile = data.profile; const isActive = profile.accessLevels?.['premium']?.isActive; if (isActive) { console.log('Subscription status updated: User has premium access'); } else { console.log('Subscription status updated: User does not have premium access'); } }); ``` Adapty также отправляет событие при запуске приложения. В этом случае передаётся кешированный статус подписки. ### Кэш статуса подписки \{#subscription-status-cache\} Кэш в SDK хранит статус подписки профиля. Это значит, что даже если сервер недоступен, приложение может обратиться к кэшированным данным и получить информацию о статусе подписки пользователя. Однако важно учитывать, что прямые запросы данных из кэша невозможны. SDK периодически обращается к серверу каждую минуту, чтобы проверить наличие обновлений или изменений в профиле. Если есть какие-либо изменения — например, новые транзакции или другие обновления — они будут переданы в кэшированные данные, чтобы поддерживать их синхронизацию с сервером. --- # File: capacitor-deal-with-att --- --- title: "Работа с ATT в Capacitor SDK" description: "Начните работу с Adapty на Capacitor для упрощения настройки подписок и управления ими." --- Если ваше приложение использует фреймворк AppTrackingTransparency и запрашивает у пользователя разрешение на отслеживание, необходимо передавать [статус авторизации](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/) в Adapty. ```typescript showLineNumbers try { await adapty.updateProfile({ appTrackingTransparencyStatus: AppTrackingTransparencyStatus.Authorized, }); console.log('ATT status updated successfully'); } catch (error) { console.error('Failed to update ATT status:', error); } ``` :::warning Настоятельно рекомендуем передавать это значение как можно раньше при его изменении — только в этом случае данные будут своевременно отправлены в настроенные вами интеграции. ::: --- # File: capacitor-onboardings --- --- title: "Онбординги" description: "Узнайте, как работать с онбордингами в вашем приложении Capacitor с помощью Adapty SDK." --- :::warning **Онбординги устарели в SDK v4 и будут удалены в одном из будущих релизов.** Они больше не получают исправлений или улучшений. Используйте [флоу](capacitor-get-pb-paywalls) вместо них: в отличие от онбордингов, работающих внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает более плавные анимации, стабильный нативный внешний вид, быструю загрузку и отсутствие зависимости от WebView. См. [Получение флоу и пейволов](capacitor-get-pb-paywalls) и [Отображение флоу и пейволов](capacitor-present-paywalls) для начала работы. ::: <CustomDocCardList /> --- # File: capacitor-get-onboardings --- --- title: "Получение онбордингов в Capacitor SDK" description: "Узнайте, как получать онбординги в Adapty для Capacitor." --- :::warning **Онбординги устарели в SDK v4 и будут удалены в одном из будущих релизов.** Они больше не получают исправлений и улучшений. Используйте [флоу](capacitor-get-pb-paywalls) вместо них: в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает более плавную анимацию, единообразный нативный внешний вид, быструю загрузку и отсутствие зависимости от WebView. Смотрите [Получение флоу и пейволов](capacitor-get-pb-paywalls) и [Отображение флоу и пейволов](capacitor-present-paywalls) для начала работы. ::: После того как вы [оформили визуальную часть онбординга](design-onboarding) в Adapty Dashboard с помощью билдера, его можно отобразить в вашем Capacitor-приложении. Первый шаг — получить онбординг, связанный с плейсментом, и конфигурацию его представления, как описано ниже. Прежде чем начать, убедитесь, что: 1. Вы [создали онбординг](create-onboarding). 2. Вы добавили онбординг в [плейсмент](placements). ## Получение онбординга \{#fetch-onboarding\} Когда вы создаёте [онбординг](onboardings) в нашем no-code конструкторе, он сохраняется в виде контейнера с конфигурацией, которую приложение должно получить и отобразить. Этот контейнер управляет всем процессом: каким будет контент, как он подаётся и как обрабатываются действия пользователя (например, ответы на вопросы квиза или ввод данных в форму). Контейнер также автоматически отслеживает аналитические события, поэтому отдельно реализовывать трекинг просмотров не нужно. Для лучшей производительности получайте конфигурацию онбординга заранее — чтобы изображения успели загрузиться до того, как пользователь увидит экран. Чтобы получить онбординг, используйте метод `getOnboarding`: ```typescript showLineNumbers try { const onboarding = await adapty.getOnboarding({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', params: { fetchPolicy: 'reload_revalidating_cache_data', // Load from server, fallback to cache loadTimeoutMs: 5000 // 5 second timeout } }); console.log('Onboarding fetched successfully'); } catch (error) { console.error('Failed to fetch onboarding:', error); } ``` Затем вызовите метод `createOnboardingView`, чтобы создать экземпляр представления. :::warning Результат метода `createOnboardingView` можно использовать только один раз. Если вам нужно использовать его повторно, вызовите метод `createOnboardingView` заново. ::: ```typescript showLineNumbers if (onboarding.hasViewConfiguration) { try { const view = await createOnboardingView(onboarding); console.log('Onboarding view created successfully'); } catch (error) { console.error('Failed to create onboarding view:', error); } } else { // Use your custom logic console.log('Onboarding does not have view configuration'); } ``` Параметры: | Параметр | Обязательность | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указываете при создании плейсмента в дашборде Adapty. | | **locale** | <p>необязательный</p><p>по умолчанию: `en`</p> | <p>Идентификатор локализации онбординга. Ожидается языковой код, состоящий из одного или двух подтегов, разделённых символом минуса (**-**). Первый подтег — язык, второй — регион.</p><p></p><p>Например: `en` — английский, `pt-br` — бразильский португальский.</p><p>Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).</p> | | **params.fetchPolicy** | <p>необязательный</p><p>по умолчанию: `'reload_revalidating_cache_data'`</p> | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кэшированные данные в случае ошибки. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите вариант `'return_cache_data_else_load'` — он возвращает кэшированные данные, если они есть. В таком случае пользователи могут получать не самые свежие данные, зато загрузка будет быстрее вне зависимости от качества соединения. Кэш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы сократить количество сетевых запросов.</p><p></p><p>Обратите внимание: кэш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.</p> | | **params.loadTimeoutMs** | <p>необязательный</p><p>по умолчанию: 5000 мс</p> | <p>Ограничивает таймаут (в миллисекундах) для этого метода. По истечении таймаута будут возвращены кэшированные данные или локальный резервный пейвол.</p><p>Обратите внимание: в редких случаях метод может завершиться чуть позже указанного в `loadTimeoutMs` значения, так как операция может включать несколько запросов под капотом.</p> | Параметры ответа: | Параметр | Описание | |:----------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onboarding** | Объект [`AdaptyOnboarding`](https://capacitor.adapty.io/interfaces/adaptyonboarding) со следующими свойствами: идентификатор и конфигурация онбординга, Remote Config и ряд других параметров. | ## Ускорьте загрузку онбординга с помощью онбординга для аудитории по умолчанию \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} Как правило, онбординги загружаются почти мгновенно, так что беспокоиться об этом не стоит. Однако если у вас много аудиторий и онбордингов, а у пользователей слабое интернет-соединение, загрузка онбординга может занять больше времени, чем хотелось бы. В таких случаях имеет смысл показывать онбординг по умолчанию — это обеспечит плавный пользовательский опыт вместо полного отсутствия экрана. Чтобы решить эту проблему, используйте метод `getOnboardingForDefaultAudience`, который получает онбординг указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать онбординг с помощью метода `getOnboarding`, как описано в разделе [Получить онбординг](#fetch-onboarding) выше. :::warning Рекомендуем использовать `getOnboarding` вместо `getOnboardingForDefaultAudience`, поскольку у последнего есть важные ограничения: - **Проблемы совместимости**: Могут возникнуть трудности при поддержке нескольких версий приложения — придётся либо делать обратно совместимые дизайны, либо мириться с тем, что в старых версиях отображение будет некорректным. - **Отсутствие персонализации**: Показывает контент только для аудитории «All Users», без таргетинга по стране, атрибуции или пользовательским атрибутам. Если для вашего случая скорость загрузки важнее этих недостатков, используйте `getOnboardingForDefaultAudience`, как показано ниже. В противном случае используйте `getOnboarding`, как описано [выше](#fetch-onboarding). ::: ```typescript showLineNumbers try { const onboarding = await adapty.getOnboardingForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en', params: { fetchPolicy: 'reload_revalidating_cache_data' // Загрузка с сервера, фолбэк на кэш } }); console.log('Default audience onboarding fetched successfully'); } catch (error) { console.error('Failed to fetch default audience onboarding:', error); } ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указываете при создании плейсмента в дашборде Adapty. | | **locale** | <p>необязательный</p><p>по умолчанию: `en`</p> | <p>Идентификатор локализации онбординга. Ожидается языковой код из одного или двух подтегов, разделённых дефисом (**-**). Первый подтег — язык, второй — регион.</p><p></p><p>Пример: `en` — английский язык, `pt-br` — бразильский португальский.</p><p>Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).</p> | | **params.fetchPolicy** | <p>необязательный</p><p>по умолчанию: `'reload_revalidating_cache_data'`</p> | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кэшированные данные в случае неудачи. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите вариант `'return_cache_data_else_load'`: он возвращает кэшированные данные, если они есть. В этом случае пользователи могут не получить самые свежие данные, зато загрузка будет быстрее вне зависимости от качества соединения. Кэш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы сократить количество сетевых запросов.</p><p></p><p>Обратите внимание: кэш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.</p> | --- # File: capacitor-present-onboardings --- --- title: "Отображение онбордингов в Capacitor SDK" description: "Узнайте, как отображать онбординги в Capacitor для повышения конверсии и дохода." --- :::warning **Онбординги устарели в SDK v4 и будут удалены в одном из следующих релизов.** Они больше не получают исправлений и улучшений. Используйте [флоу](capacitor-get-pb-paywalls) вместо них: в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает более плавные анимации, единообразный нативный вид, быструю загрузку и отсутствие зависимости от WebView. См. [Получение флоу и пейволов](capacitor-get-pb-paywalls) и [Отображение флоу и пейволов](capacitor-present-paywalls), чтобы начать. ::: Если вы настроили онбординг с помощью конструктора, вам не нужно беспокоиться о его отрисовке в коде мобильного приложения для отображения пользователю. Такой онбординг содержит как то, что должно быть показано, так и то, как именно это должно быть показано. Прежде чем начать, убедитесь, что: 1. Вы [создали онбординг](create-onboarding). 2. Вы добавили онбординг в [плейсмент](placements). ## Показ онбординга \{#present-onboarding\} Чтобы отобразить онбординг, вызовите метод `view.present()` на объекте `view`, созданном методом `createOnboardingView`. Каждый `view` можно использовать только один раз. Если нужно показать онбординг повторно, снова вызовите `createOnboardingView`, чтобы создать новый экземпляр `view`. :::warning Повторное использование одного и того же `view` без его пересоздания может привести к ошибке. ::: ```typescript showLineNumbers try { const view = await createOnboardingView(onboarding); view.setEventHandlers({ onClose: (actionId, meta) => { console.log('Onboarding closed:', actionId); return true; // Allow the onboarding to close }, onCustom: (actionId, meta) => { console.log('Custom action:', actionId); return false; // Don't close the onboarding } }); await view.present(); console.log('Onboarding presented successfully'); } catch (error) { console.error('Failed to present onboarding:', error); } ``` ## Настройка стиля отображения на iOS \{#configure-ios-presentation-style\} Настройте способ отображения онбординга на iOS, передав параметр `iosPresentationStyle` в метод `present()`. Параметр принимает значения `'full_screen'` (по умолчанию) или `'page_sheet'`. ```typescript showLineNumbers await view.present({ iosPresentationStyle: 'page_sheet' }); ``` ## Настройте открытие ссылок в онбординге \{#customize-how-links-open-in-onboardings\} :::important Настройка открытия ссылок в онбординге поддерживается начиная с Adapty SDK v3.15. ::: По умолчанию ссылки в онбординге открываются во встроенном браузере. Это обеспечивает удобный пользовательский опыт: веб-страницы отображаются прямо внутри приложения, не требуя переключения между приложениями. Если вы хотите открывать ссылки во внешнем браузере, задайте параметру `openIn` значение `browser_out_app`: ```typescript showLineNumbers await view.present({ openIn: 'browser_out_app' }); // default — browser_in_app ``` ## Дальнейшие шаги \{#next-steps\} После отображения онбординга вам потребуется [обрабатывать действия пользователя и события](capacitor-handling-onboarding-events). Узнайте, как работать с событиями онбординга, чтобы реагировать на действия пользователей и отслеживать аналитику. --- # File: capacitor-handling-onboarding-events --- --- title: "Обработка событий онбординга в Capacitor SDK" description: "Обрабатывайте события, связанные с онбордингом, в Capacitor с помощью Adapty." --- :::warning **Онбординги объявлены устаревшими в SDK v4 и будут удалены в одном из следующих релизов.** Они больше не получают исправлений и улучшений. Используйте [флоу](capacitor-get-pb-paywalls): в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — обеспечивая более плавные анимации, единый нативный внешний вид, быструю загрузку и отсутствие зависимости от WebView. Подробнее: [Получение флоу и пейволов](capacitor-get-pb-paywalls) и [Отображение флоу и пейволов](capacitor-present-paywalls). ::: Онбординги, настроенные с помощью билдера, генерируют события, на которые может реагировать ваше приложение. Используйте метод `setEventHandlers` для обработки этих событий при самостоятельном отображении экранов. Прежде чем начать, убедитесь, что: 1. Вы [создали онбординг](create-onboarding). 2. Вы добавили онбординг в [плейсмент](placements). ## Настройка обработчиков событий \{#set-up-event-handlers\} Для обработки событий онбордингов используйте метод `view.setEventHandlers`: ```typescript showLineNumbers try { const view = await createOnboardingView(onboarding); view.setEventHandlers({ onAnalytics(event, meta) { console.log('Analytics event:', event); }, onClose(actionId, meta) { console.log('Onboarding closed:', actionId); return true; // Allow the onboarding to close }, onCustom(actionId, meta) { console.log('Custom action:', actionId); return false; // Don't close the onboarding }, onPaywall(actionId, meta) { console.log('Paywall action:', actionId); view.dismiss().then(() => { openPaywall(actionId); }); }, onStateUpdated(action, meta) { console.log('State updated:', action); }, onFinishedLoading(meta) { console.log('Onboarding finished loading'); }, onError(error) { console.error('Onboarding error:', error); }, }); await view.present(); } catch (error) { console.error('Failed to present onboarding:', error); } ``` ## Типы событий \{#event-types\} В следующих разделах описаны различные типы событий, которые можно обрабатывать. ### Обработка пользовательских действий \{#handle-custom-actions\} В конструкторе вы можете добавить к кнопке действие типа **custom** и задать ему идентификатор. <img src="/assets/shared/img/ios-events-1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Затем вы можете использовать этот ID в своём коде и обрабатывать его как кастомное действие. Например, если пользователь нажимает кастомную кнопку — **Login** или **Allow notifications** — обработчик события срабатывает с параметром `actionId`, который соответствует **Action ID** из билдера. Вы можете задавать собственные ID, например `"allowNotifications"`. ```typescript showLineNumbers view.setEventHandlers({ onCustom(actionId, meta) { switch (actionId) { case 'login': console.log('Login action triggered'); break; case 'allow_notifications': console.log('Allow notifications action triggered'); break; } return false; // Don't close the onboarding }, }); ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "actionId": "allow_notifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ``` </Details> ### Завершение загрузки онбординга \{#finishing-loading-onboarding\} Когда онбординг завершает загрузку, срабатывает следующее событие: ```typescript showLineNumbers view.setEventHandlers({ onFinishedLoading(meta) { console.log('Onboarding loaded:', meta.onboardingId); }, }); ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ``` </Details> ### Закрытие онбординга \{#closing-onboarding\} Онбординг считается закрытым, когда пользователь нажимает кнопку с назначенным действием **Close**. <img src="/assets/shared/img/ios-events-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important Обратите внимание, что вам нужно самостоятельно обрабатывать закрытие онбординга. Например, необходимо скрыть сам экран онбординга. ::: ```typescript showLineNumbers view.setEventHandlers({ onClose(actionId, meta) { console.log('Onboarding closed:', actionId); return true; // Allow the onboarding to close }, }); ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> ### Открытие пейвола \{#opening-a-paywall\} :::tip Обработайте это событие, чтобы открыть пейвол внутри онбординга. Если вы хотите открыть пейвол после его закрытия, есть более простой способ — обработайте действие закрытия и откройте пейвол, не опираясь на данные события. ::: Самый удобный способ работы с пейволами в онбординге — сделать идентификатор действия равным идентификатору плейсмента пейвола. :::note Обратите внимание: в iOS одновременно на экране может отображаться только один экран (пейвол или онбординг). Если вы показываете пейвол поверх онбординга, вы не можете программно управлять онбордингом в фоне. Попытка закрыть онбординг закроет вместо него пейвол, и онбординг останется видимым. Чтобы избежать этого, всегда закрывайте экран онбординга перед показом пейвола. ::: ```typescript showLineNumbers view.setEventHandlers({ onPaywall(actionId, meta) { // Dismiss onboarding before presenting paywall view.dismiss().then(() => { openPaywall(actionId); }); }, }); async function openPaywall(placementId: string) { // Implement your paywall opening logic here } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ``` </Details> ### Отслеживание навигации \{#tracking-navigation\} Вы получаете аналитическое событие при различных навигационных действиях в процессе онбординга: ```typescript showLineNumbers view.setEventHandlers({ onAnalytics(event, meta) { console.log('Analytics event:', event.type, meta.onboardingId); }, }); ``` Объект `event` может быть одного из следующих типов: | Тип | Описание | |------------|-------------| | `onboardingStarted` | Когда онбординг загружен | | `screenPresented` | Когда показывается любой экран | | `screenCompleted` | Когда экран завершён. Включает необязательный `elementId` (идентификатор завершённого элемента) и необязательный `reply` (ответ пользователя). Срабатывает, когда пользователь выполняет любое действие для выхода с экрана. | | `secondScreenPresented` | Когда показывается второй экран | | `userEmailCollected` | Срабатывает, когда email пользователя собирается через поле ввода | | `onboardingCompleted` | Срабатывает, когда пользователь достигает экрана с ID `final`. Если вам нужно это событие, [назначьте ID `final` последнему экрану](design-onboarding). | | `unknown` | Для любого нераспознанного типа события. Включает `name` (название неизвестного события) и `meta` (дополнительные метаданные) | Каждое событие содержит метаинформацию `meta` со следующими полями: | Поле | Описание | |------------|-------------| | `onboardingId` | Уникальный идентификатор флоу онбординга | | `screenClientId` | Идентификатор текущего экрана | | `screenIndex` | Позиция текущего экрана во флоу | | `screensTotal` | Общее количество экранов во флоу | <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```javascript // onboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // screenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // screenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // secondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // userEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // onboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> --- # File: capacitor-onboarding-input --- --- title: "Обработка данных из онбордингов в Capacitor SDK" description: "Сохраняйте и используйте данные из онбордингов в вашем приложении на Capacitor с помощью Adapty SDK." --- :::warning **Онбординги объявлены устаревшими в SDK v4 и будут удалены в одном из следующих релизов.** Они больше не получают исправлений или улучшений. Используйте [флоу](capacitor-get-pb-paywalls): в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает плавные анимации, нативный внешний вид, быструю загрузку и отсутствие зависимости от WebView. См. [Получение флоу и пейволов](capacitor-get-pb-paywalls) и [Отображение флоу и пейволов](capacitor-present-paywalls). ::: Когда пользователь отвечает на вопрос викторины или вводит данные в поле ввода, вызывается метод `onStateUpdated`. Вы можете сохранить или обработать тип поля в своём коде. Например: ```typescript view.setEventHandlers({ onStateUpdated(action, meta) { // Process data }, }); ``` Формат action описан [здесь](https://capacitor.adapty.io/types/onboardingstateupdatedaction). <Details> <summary>Примеры сохранённых данных (формат может отличаться в вашей реализации)</summary> ```javascript // Example of a saved select action { "elementId": "preference_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "preferences_screen", "screenIndex": 1, "screensTotal": 3 }, "params": { "type": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" } } } // Example of a saved multi-select action { "elementId": "interests_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "screensTotal": 3 }, "params": { "type": "multiSelect", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ] } } // Example of a saved input action { "elementId": "name_input", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "input", "value": { "type": "text", "value": "John Doe" } } } // Example of a saved date picker action { "elementId": "birthday_picker", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "datePicker", "value": { "day": 15, "month": 6, "year": 1990 } } } ``` </Details> ## Сценарии использования \{#use-cases\} ### Обогащение профилей пользователей данными \{#enrich-user-profiles-with-data\} Если вы хотите сразу связать введённые данные с профилем пользователя и не спрашивать его дважды об одном и том же, нужно [обновить профиль пользователя](capacitor-setting-user-attributes) с введёнными данными при обработке действия. Например, вы просите пользователей ввести имя в текстовое поле с ID `name` и хотите установить значение этого поля как имя пользователя. Также вы просите ввести email в поле `email`. В коде вашего приложения это может выглядеть так: ```typescript showLineNumbers view.setEventHandlers({ onStateUpdated(action, meta) { // Store user preferences or responses if (action.elementType === 'input') { const profileParams: any = {}; // Map elementId to appropriate profile field switch (action.elementId) { case 'name': if (action.value.type === 'text') { profileParams.firstName = action.value.value; } break; case 'email': if (action.value.type === 'email') { profileParams.email = action.value.value; } break; } // Update profile if we have data to update if (Object.keys(profileParams).length > 0) { adapty.updateProfile({ params: profileParams }).catch((error) => { // handle the error }); } } }, }); ``` ### Настройка пейволов на основе ответов \{#customize-paywalls-based-on-answers\} Используя квизы в онбордингах, вы можете настраивать пейволы, которые показываются пользователям после прохождения онбординга. Например, можно спросить пользователей об их спортивном опыте и показывать разные CTA и продукты разным группам пользователей. 1. [Добавьте квиз](onboarding-quizzes) в конструкторе онбординга и присвойте понятные идентификаторы его вариантам ответов. 2. Обрабатывайте ответы квиза по их идентификаторам и [задавайте пользовательские атрибуты](capacitor-setting-user-attributes) для пользователей. ```typescript showLineNumbers view.setEventHandlers({ onStateUpdated(action, meta) { // Handle quiz responses and set custom attributes if (action.elementType === 'select') { const profileParams: any = {}; // Map quiz responses to custom attributes switch (action.elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) profileParams.codableCustomAttributes = { experience: action.value.value }; break; } // Update profile if we have data to update if (Object.keys(profileParams).length > 0) { adapty.updateProfile({ params: profileParams }).catch((error) => { // handle the error }); } } }, }); ``` 3. [Создайте сегменты](segments) для каждого значения пользовательского атрибута. 4. Создайте [плейсмент](placements) и добавьте [аудитории](audience) для каждого созданного сегмента. 5. [Отобразите пейвол](capacitor-paywalls) для плейсмента в коде приложения. Если в вашем онбординге есть кнопка, открывающая пейвол, реализуйте код пейвола как [реакцию на действие этой кнопки](capacitor-handling-onboarding-events#opening-a-paywall). --- # File: capacitor-best-practices --- --- title: "Лучшие практики в Capacitor SDK" description: "Справочные паттерны для интеграции Adapty SDK на Capacitor — порядок вызовов, обработка ошибок и другие правила для production-ready решений." --- <CustomDocCardList /> --- # File: capacitor-sdk-call-order --- --- title: "Порядок вызовов в Capacitor SDK" description: "Избегайте потери премиум-доступа, пропущенной атрибуции и периодических ошибок #2002, вызывая методы Adapty SDK в правильном порядке." --- `adapty.activate()` должен завершиться до вызова любого другого метода Adapty SDK. До его завершения у SDK нет состояния. Любой вызов, сделанный до или параллельно с `activate()`, завершится ошибкой [`#2002 notActivated`](capacitor-handle-errors#custom-network-codes). Если ваше приложение аутентифицирует пользователей и вы получаете customer user ID после запуска, вызовите `adapty.identify()` в этот момент. Не вызывайте методы, зависящие от пользователя, пока `identify` не завершится. Вызовы, которые выполняются параллельно с ним, либо завершатся с ошибкой [`#3006 profileWasChanged`](capacitor-handle-errors#custom-network-codes), либо применятся к анонимному профилю, созданному при активации. В таком случае атрибуция, MMP-идентификаторы вроде `appsflyer_id` и принадлежность установки не всегда переносятся на идентифицированный профиль. Если ваше приложение не аутентифицирует пользователей, пропустите `identify` и продолжайте работу с анонимным профилем. MMP и аналитические SDK (AppsFlyer, Adjust, Branch, PostHog) следуют тому же правилу. Сначала инициализируйте их и дождитесь коллбэков с UID, и только после этого вызывайте `adapty.activate`. Иначе MMP ID попадёт на временный анонимный профиль и не всегда переносится на идентифицированный. Подробнее об AppsFlyer см. в разделе [AppsFlyer](appsflyer). ## Правильный порядок \{#the-correct-order\} Ваш путь зависит от двух вещей: когда вы получаете customer user ID и используете ли вы MMP или аналитический SDK. - **Шаги 2 и 5**: Обязательны для каждого приложения. Активируйте SDK, затем вызовите методы SDK. - **Шаги 1 и 3**: Нужны только если вы интегрируете MMP или аналитический SDK (AppsFlyer, Adjust, Branch, PostHog). - **Шаг 4**: Нужен только если ваше приложение аутентифицирует пользователей и получает customer user ID после запуска. Если вы знаете customer user ID при запуске приложения, передайте его прямо в `activate()` (шаг 2a). В этом случае анонимный профиль не создаётся, поэтому шаг 4 не нужен. | Шаг | Вызов | Когда | Примечания | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | Инициализируйте MMP или SDK аналитики (AppsFlyer, Adjust, PostHog, Branch) | При запуске приложения, первым | Дождитесь callback с UID от MMP, например `getAppsFlyerUID`. | | 2a | `adapty.activate({ apiKey: '...', params: { customerUserId: '...' } })` | При запуске приложения, после шага 1, если у вас есть customer user ID | Рекомендуется. Анонимный профиль не создаётся. | | 2b | `adapty.activate({ apiKey: '...' })` без `customerUserId` | При запуске приложения, после шага 1, если у вас нет customer user ID (или вы его не собираете) | Adapty создаёт анонимный профиль. | | 3 | `adapty.setIntegrationIdentifier({ key: '...', value: '...' })` для каждого MMP | После шага 2, до любых вызовов, связанных с действиями пользователя | Обязательно, чтобы идентификаторы MMP попали в нужный профиль. | | 4 | `await adapty.identify({ customerUserId: 'YOUR_USER_ID' })` | После шага 3 (или шага 2, если нет MMP), перед шагом 5 — только по пути 2b с аутентификацией | Всегда используйте `await`. Одновременные вызовы во время `identify` приводят к ошибке `#3006 profileWasChanged`. | | 5 | `getPaywall` (`getFlow` в SDK v4), `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution`, `updateProfile` | После шага 4, если вызываете `identify`; иначе после шага 3 (или шага 2, если нет MMP) | Эти вызовы требуют стабильного профиля. | :::important Пропуск этих шагов приведёт к тому, что вернувшиеся пользователи потеряют премиум-доступ, в профилях будет отсутствовать `appsflyer_id`, а пейволы будут показываться не той аудитории. ::: ## Установки через web2app и веб-воронку \{#web2app-and-web-funnel-installs\} Если пользователь совершает покупку через веб-чекаут (Stripe, Paddle) и затем устанавливает нативное приложение, первый вызов `activate()` на устройстве создаёт новый анонимный профиль. Этот профиль не связан с веб-профилем. Если вам удаётся определить customer user ID до запуска приложения (из вашего auth-флоу или install referrer) — передайте его напрямую в `activate()`. В противном случае веб-покупка остаётся невидимой на устройстве, пока вы не вызовете `identify({ customerUserId: 'YOUR_USER_ID' })`, а затем `restorePurchases`. Какие метаданные передавать с каждым веб-чекаутом, смотрите здесь: - [Stripe](stripe) - [Paddle](paddle) --- # File: capacitor-optimize-paywall-fetching --- --- title: "Оптимизация загрузки пейвола в Capacitor SDK" description: "Надёжная загрузка пейволов Adapty: тайминг, кэширование и паттерны резервного отображения для Capacitor." --- Надёжная загрузка пейвола в Capacitor решает три задачи: быстрый рендеринг, возврат пейвола с учётом аудитории и корректное резервное отображение при медленной сети. Правила ниже охватывают тайминг, кэширование и паттерны резервного отображения. :::tip Предполагается, что `adapty.activate()` и `adapty.identify()` уже выполнены. См. [Порядок вызовов в Capacitor SDK](capacitor-sdk-call-order). ::: Рекомендации ниже используют названия методов v3. В SDK v4 метод `getPaywall` переименован в `getFlow` (см. [гайд по миграции](migration-to-capacitor-sdk-v4)) — все правила остаются в силе. ## Правила и подводные камни \{#rules-and-pitfalls\} | Делайте так | Не делайте так | Почему | |---|---|---| | Загружайте плейсмент непосредственно перед показом. | Предзагружайте все плейсменты одновременно при запуске. | Массовая предзагрузка блокирует главный поток и вызывает чёрный экран во время запроса. | | Вызывайте `getPaywall` после того, как атрибуция успеет разрешиться — например, через 1–2 секунды после `activate` или после срабатывания слушателя `onLatestProfileLoad`. | Вызывайте `getPaywall` при запуске приложения в `App.tsx`. | Атрибуция ещё не получена. Пейвол разрешается для аудитории по умолчанию и незаметно обходит сегменты и персонализацию ASA. | | Задайте `loadTimeoutMs` и настройте [резервный пейвол](fallback-paywalls) для каждого плейсмента. | Ждите ответа `getPaywall` без ограничения времени. | Без таймаута пользователи с плохим соединением видят пустой экран до тех пор, пока сеть не ответит — или закрывают приложение. | Подробнее о параметрах `fetchPolicy` и `loadTimeoutMs` см. в разделе [Получение пейволов и продуктов](fetch-paywalls-and-products-capacitor), а о выборе подходящего плейсмента — в разделе [Плейсменты](placements). ## Настройка для нестабильного соединения \{#tune-for-poor-connectivity\} Для рынков с постоянно плохим подключением (сельская местность, транспорт, регионы с проблемами маршрутизации): - Устанавливайте `fetchPolicy: 'return_cache_data_else_load'` для каждого запроса, кроме самого первого. - Настройте [резервный пейвол](fallback-paywalls) для каждого плейсмента в дашборде Adapty. - Задайте `loadTimeoutMs` в диапазоне 3000–5000 миллисекунд и используйте резервный пейвол при срабатывании таймаута. - Не ставьте отображение пейвола в зависимость от `adapty.getProfile()`. Вызывайте `getPaywall` независимо, чтобы медленная загрузка профиля не блокировала интерфейс. --- # File: capacitor-show-aa-targeted-paywall --- --- title: "Показ пейвола с таргетингом по Apple Ads при первом запуске в Capacitor SDK" description: "Показывайте пейвол сразу и обновляйте его для пользователей Apple Ads после применения атрибуции в Capacitor, используя AdaptyProfile.appliedAttributionSources." --- Атрибуция Apple Ads (AA) поступает асинхронно после вызова `adapty.activate()`. При первом запуске она, как правило, ещё не получена, поэтому `getFlow` разрешается для аудитории по умолчанию, и пользователи Apple Ads не попадают на пейвол, настроенный для сегмента AA. Вместо того чтобы откладывать показ пейвола до получения атрибуции, покажите его сразу, а затем обновите, когда атрибуция AA будет применена — так пользователи Apple Ads увидят целевой вариант, а все остальные не будут ждать. `AdaptyProfile.appliedAttributionSources` сообщает о том, что атрибуция AA применена. ## Перед началом работы \{#before-you-start\} Вам понадобится: - Adapty Capacitor SDK версии **3.17.1** или выше. - Настроенная интеграция Apple Ads для приложения в Adapty. См. [Apple Ads](apple-search-ads). ## Как это работает \{#how-it-works\} После `adapty.activate()` SDK в фоновом режиме запрашивает данные атрибуции Apple Ads у Apple и передаёт результат в бэкенд Adapty. Когда AA становится активным источником атрибуции для профиля, SDK доставляет обновлённый `AdaptyProfile` в ваш слушатель `onLatestProfileLoad`, в массиве `appliedAttributionSources` которого появляется `'apple_search_ads'`. Это позволяет загружать пейвол в два шага: 1. Вызовите `getFlow` сразу. Поскольку атрибуция ещё не применена, Adapty разрешает запрос по аудитории по умолчанию, и пользователь видит пейвол немедленно. 2. Когда появляется `'apple_search_ads'`, вызовите `getFlow` снова. Adapty теперь разрешает запрос по аудитории Apple Ads и возвращает целевой пейвол, который заменяет первый. `appliedAttributionSources` может быть пустым или отсутствовать. Это означает одно из двух: - атрибуция Apple Ads ещё не обработана для этого профиля, или - атрибуция не поступала вовсе. В любом случае шаг 1 безопасен — Adapty разрешает запрос по той аудитории, которая соответствует текущему состоянию профиля, как правило это дефолтная аудитория. Шаг 2 выполняется только после появления `'apple_search_ads'`. :::important При каждом последующем запуске кэшированный профиль уже содержит `'apple_search_ads'` в `appliedAttributionSources`, поэтому первый же `getFlow` возвращает пейвол, сегментированный по Apple Ads, — никакого второго запроса или видимых изменений не происходит. Двухшаговое флоу актуально только при первом запуске, пока атрибуция ещё не завершена. ::: ## Реализация \{#implementation\} Покажите пейвол сразу, а затем слушайте событие `'apple_search_ads'` и обновляйте пейвол при его получении. 1. **Активируйте SDK.** См. [Установка и настройка Capacitor SDK](sdk-installation-capacitor). 2. **Загрузите и покажите пейвол** с помощью `getFlow` как обычно — не блокируйте на атрибуции. 3. **Подпишитесь на обновления профиля** через `adapty.addListener('onLatestProfileLoad', …)` и отслеживайте появление `'apple_search_ads'`. Когда оно появится, снова запросите пейвол и покажите обновлённый. Если вы ещё не настроили слушатель, см. [Отслеживание обновлений подписки](capacitor-check-subscription-status#listen-to-subscription-updates): ```typescript const listener = await adapty.addListener('onLatestProfileLoad', async ({ profile }) => { if (!profile.appliedAttributionSources?.includes('apple_search_ads')) return; const targeted = await adapty.getFlow({ placementId }); // present the targeted flow in place of the first one }); // Call listener.remove() after the upgrade, or after a timeout (see below). ``` 4. **Прекращайте слушать по таймауту.** Большинство пользователей никогда не получают атрибуцию Apple Ads, поэтому удаляйте слушатель через некоторое время, а не держите его открытым на протяжении всей сессии. Настройте [резервный пейвол](capacitor-use-fallback-paywalls) для плейсмента, чтобы пользователь всегда что-то видел в случае ошибки запроса. ## Полный пример \{#complete-example\} `onAppleAdsAttribution` резолвится, когда атрибуция Apple Ads применена, или отклоняется по истечении `timeoutMs`. В примере ниже пейвол загружается сразу, а затем перезагружается, когда приходит атрибуция — пользователи Apple Ads видят целевой пейвол, а если атрибуция так и не пришла, остаётся первый пейвол: ```typescript const APPLE_ADS_SOURCE = 'apple_search_ads'; const placementId = 'YOUR_PLACEMENT_ID'; function hasAppleAdsAttribution(profile: AdaptyProfile): boolean { return profile.appliedAttributionSources?.includes(APPLE_ADS_SOURCE) ?? false; } /** * Resolves once Apple Ads attribution is applied to the profile. * Rejects with a timeout error if attribution never arrives within `timeoutMs`. * Call after `adapty.activate()`. */ export function onAppleAdsAttribution(timeoutMs: number): Promise<void> { return new Promise((resolve, reject) => { let timer: ReturnType<typeof setTimeout> | undefined; let handle: { remove: () => void } | undefined; const stop = () => { clearTimeout(timer); handle?.remove(); }; adapty .addListener('onLatestProfileLoad', ({ profile }) => { if (!hasAppleAdsAttribution(profile)) return; stop(); resolve(); }) .then(listener => { handle = listener; }); timer = setTimeout(() => { stop(); reject(new Error(`Apple Ads attribution timed out after ${timeoutMs}ms`)); }, timeoutMs); }); } let flow = await adapty.getFlow({ placementId }); onAppleAdsAttribution(30_000) .then(() => adapty.getFlow({ placementId })) .then(updated => { flow = updated; }) .catch(() => { console.log('Apple Ads attribution or loading failed'); }); ``` При первом запуске пользователь Apple Ads ненадолго видит пейвол по умолчанию, прежде чем он заменяется. Если вы показываете пейволы через Paywall Builder, решите, допустимо ли повторное отображение, или применяйте обновление до того, как пейвол будет показан. Настройте `timeoutMs` в зависимости от того, сколько вы готовы ждать — атрибуция, если она придёт, обычно поступает в течение нескольких секунд после запуска. Если ваше приложение уже слушает `onLatestProfileLoad` для других целей (например, [проверки статуса подписки](capacitor-check-subscription-status#listen-to-subscription-updates)), менять ничего не нужно. `adapty.addListener` поддерживает несколько независимых слушателей, поэтому этот добавляется самостоятельно, не затрагивая остальные. --- # File: capacitor-test --- --- title: "Тестирование и релиз в Capacitor SDK" description: "Узнайте, как тестировать и выпускать приложение на Capacitor с помощью Adapty SDK." --- Если вы уже интегрировали Adapty SDK в своё приложение на Capacitor, следующий шаг — убедиться, что всё настроено правильно и покупки работают корректно на iOS и Android. Это включает тестирование как интеграции SDK, так и самого процесса покупки в песочнице Apple и тестовой среде Google Play. ## Тестирование приложения \{#test-your-app\} Для полноценного тестирования встроенных покупок воспользуйтесь нашими платформенными гайдами: [гайд по тестированию на iOS](test-purchases-in-sandbox) и [гайд по тестированию на Android](testing-on-android). ## Подготовка к релизу \{#prepare-for-release\} Перед отправкой приложения в стор ознакомьтесь с [чеклистом для релиза](release-checklist) и убедитесь, что: - Подключение к стору и серверные уведомления настроены - Покупки завершаются и передаются в Adapty - Доступ корректно открывается и восстанавливается - Требования к конфиденциальности и модерации выполнены --- # File: kids-mode-capacitor --- --- title: "Режим «Для детей» в Capacitor SDK" description: "Легко включите режим «Для детей» для соответствия политикам Apple и Google. IDFA, GAID и рекламные данные не собираются в Capacitor SDK." --- Если ваше приложение на Capacitor предназначено для детей, вы обязаны соблюдать политики [Apple](https://developer.apple.com/kids/) и [Google](https://support.google.com/googleplay/android-developer/answer/9893335). Несколько простых шагов помогут настроить Adapty SDK в соответствии с этими требованиями и успешно пройти модерацию. ## Что нужно сделать? \{#whats-required\} Необходимо настроить SDK, чтобы отключить сбор: - [IDFA (Identifier for Advertisers)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) (iOS) - [Android Advertising ID (AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248) (Android) - [IP-адреса](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Кроме того, рекомендуем обращаться с customer user ID с осторожностью. User ID в формате `<FirstName.LastName>` однозначно расценивается как сбор персональных данных — так же, как и использование email. Для режима «Для детей» лучшая практика — использовать рандомизированные или анонимизированные идентификаторы (например, хэшированные ID или UUID, сгенерированные на устройстве), чтобы обеспечить соответствие требованиям. ## Включение режима «Для детей» \{#enabling-kids-mode\} ### Настройки в дашборде Adapty \{#updates-in-the-adapty-dashboard\} В дашборде Adapty необходимо отключить сбор IP-адресов. Для этого перейдите в [App settings](https://app.adapty.io/settings/general) и нажмите **Disable IP address collection** в разделе **Collect users' IP address**. ### Обновления в коде мобильного приложения \{#updates-in-your-mobile-app-code\} Чтобы соответствовать требованиям политик, отключите сбор IDFA, GAID и IP-адреса пользователя: ```typescript showLineNumbers try { await adapty.activate({ apiKey: 'YOUR_PUBLIC_SDK_KEY', params: { // Disable IP address collection ipAddressCollectionDisabled: true, // Disable IDFA collection on iOS ios: { idfaCollectionDisabled: true }, // Disable Google Advertising ID collection on Android android: { adIdCollectionDisabled: true } } }); console.log('Adapty activated with Kids Mode enabled'); } catch (error) { console.error('Failed to activate Adapty with Kids Mode:', error); } ``` ### Конфигурация для отдельных платформ \{#platform-specific-configurations\} #### iOS \{#ios\} <SDKv4> Даже если сбор IDFA отключён в коде (см. выше), ваш билд всё равно включает фреймворки `AdSupport` и `AppTrackingTransparency`. Категория Kids в App Store их не допускает. А поскольку SDK v4 устанавливает нативный iOS SDK через Swift Package Manager, убрать их через Podfile не получится. Чтобы соответствовать требованиям Apple, добавьте команду `adapty-kids-mode`, поставляемую вместе с SDK, в `postinstall` вашего приложения. Она включает трейт `KidsMode` в SDK, который исключает этот код из компиляции. Команда применяется заново при каждой установке: ```json showLineNumbers title="package.json" { "scripts": { "postinstall": "adapty-kids-mode" } } ``` Затем переустановите и пересоберите iOS-пакеты, после чего выполните сборку в **Xcode 26** или новее: ```sh showLineNumbers title="Shell" npm install npx cap sync ios ``` Чтобы отключить Kids Mode, выполните `adapty-kids-mode disable` и синхронизируйте проект снова. </SDKv4> <SDKv3> Если вы используете CocoaPods для iOS, Kids Mode можно также включить на нативном уровне: 1. Обновите Podfile: - Если у вас **нет** раздела `post_install`, добавьте весь блок кода ниже. - Если раздел `post_install` у вас **есть**, добавьте в него выделенные строки. ```ruby showLineNumbers title="Podfile" def adapty_enable_kids_mode(installer) installer.pods_project.targets.each do |target| next unless target.name == 'Adapty' target.build_configurations.each do |config| flags = config.build_settings['OTHER_SWIFT_FLAGS'] || '$(inherited)' flags = flags.join(' ') if flags.is_a?(Array) config.build_settings['OTHER_SWIFT_FLAGS'] = "#{flags} -DADAPTY_KIDS_MODE" end target.frameworks_build_phase.files.dup.each do |bf| target.frameworks_build_phase.remove_build_file(bf) if bf.display_name.to_s.include?('AdSupport') end end installer.pods_project.save Dir.glob(File.join(installer.sandbox.root, 'Target Support Files', '**', '*.xcconfig')).each do |xc| File.write(xc, File.read(xc).gsub(/\s*-framework\s+"?AdSupport"?/, '')) end end post_install do |installer| # ... keep your existing post_install body (Flutter adds one automatically) ... adapty_enable_kids_mode(installer) # <-- enable Adapty Kids Mode end ``` 2. Выполните следующую команду, чтобы применить изменения: ```sh showLineNumbers title="Shell" pod install ``` </SDKv3> #### Android: удаление разрешения на рекламный идентификатор \{#android-remove-the-advertising-id-permission\} Установка `adIdCollectionDisabled: true` (выше) останавливает сбор рекламного идентификатора в Adapty, однако SDK по-прежнему объявляет разрешение `AD_ID`. Если ваше приложение ориентировано **исключительно** на детей и компилируется под Android 13 (API 33) или выше, Google Play запрещает его запрашивать. Добавьте два элемента в тег `<manifest>`: 1. Объявите пространство имён `tools` (в манифесте Capacitor по умолчанию оно отсутствует). 2. Добавьте запись `<uses-permission>` для `AD_ID` с атрибутом `tools:node="remove"`, чтобы удалить его. ```xml showLineNumbers title="android/app/src/main/AndroidManifest.xml" <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove" /> </manifest> ``` ## Следующие шаги \{#next-steps\} После включения режима «Для детей» убедитесь, что: 1. Приложение тщательно протестировано и все функции работают корректно. 2. Политика конфиденциальности обновлена с учётом отключённого сбора данных. 3. При отправке приложения на проверку приложена документация, подтверждающая соответствие требованиям режима «Для детей». Дополнительные сведения о платформенных требованиях: - [Режим «Для детей» в iOS SDK](kids-mode) — подробности настройки для iOS - [Режим «Для детей» в Android SDK](kids-mode-android) — подробности настройки для Android --- # File: capacitor-reference --- --- title: "Справочник" description: "Справочная документация по Adapty Capacitor SDK." --- На этой странице собрана справочная документация по Adapty Capacitor SDK. Выберите нужный раздел: - **[Модели SDK](https://capacitor.adapty.io/)** — Модели данных и структуры, используемые SDK - **[Обработка ошибок](capacitor-handle-errors)** — Обработка ошибок и устранение неполадок --- # File: capacitor-handle-errors --- --- title: "Обработка ошибок в Capacitor SDK" description: "Обработка ошибок в Capacitor SDK." --- Каждая ошибка, возвращаемая SDK, является экземпляром `AdaptyError`. Пример: :::tip **Включите подробные логи перед отладкой.** Большинство `AdaptyError` оборачивают исходную ошибку StoreKit, Play Billing, сети или бэкенда. При включённых подробных логах (`adapty.setLogLevel({ logLevel: 'verbose' })` — см. [Логирование](sdk-installation-capacitor#logging)) обёрнутая ошибка выводится в консоль, что обычно сразу указывает на реальную причину. Свойство `detail` у `AdaptyError` заполняется вне зависимости от уровня логирования — подробные логи просто выводят его в консоль. ::: ```typescript showLineNumbers try { const result = await adapty.makePurchase({ product }); // Обработка результата покупки if (result.type === 'success') { console.log('Покупка успешна:', result.profile); } else if (result.type === 'user_cancelled') { console.log('Пользователь отменил покупку'); } else if (result.type === 'pending') { console.log('Покупка ожидает подтверждения'); } } catch (error) { if (error instanceof AdaptyError) { console.error('Ошибка Adapty:', error.adaptyCode, error.localizedDescription); // Обработка конкретных кодов ошибок switch (error.adaptyCode) { case ErrorCodeName.cantMakePayments: console.log('Встроенные покупки недоступны на этом устройстве'); break; case ErrorCodeName.notActivated: console.log('SDK Adapty не активирован'); break; case ErrorCodeName.productPurchaseFailed: console.log('Покупка не удалась:', error.detail); break; default: console.log('Произошла другая ошибка:', error.detail); } } else { console.error('Ошибка не связана с Adapty:', error); } } ``` ## Свойства ошибки \{#error-properties\} Класс `AdaptyError` предоставляет следующие свойства: | Свойство | Тип | Описание | |----------|------|-------------| | `adaptyCode` | `number` | Числовой код ошибки (например, `1003` для cantMakePayments) | | `localizedDescription` | `string` | Понятное пользователю сообщение об ошибке | | `detail` | `string \| undefined` | Дополнительная информация об ошибке (необязательно) | | `message` | `string` | Полное сообщение об ошибке с кодом и описанием | ## Коды ошибок \{#error-codes\} SDK экспортирует константы и утилиты для работы с кодами ошибок: ### Константа ErrorCodeName \{#errorcodename-constant\} Сопоставляет строковые идентификаторы с числовыми кодами: ```typescript ErrorCodeName.cantMakePayments // 1003 ErrorCodeName.notActivated // 2002 ErrorCodeName.networkFailed // 2005 ``` ### Константа ErrorCode \{#errorcode-constant\} Сопоставляет числовые коды со строковыми идентификаторами: ```typescript ErrorCode[1003] // 'cantMakePayments' ErrorCode[2002] // 'notActivated' ErrorCode[2005] // 'networkFailed' ``` ### Вспомогательные функции \{#helper-functions\} ```typescript // Get numeric code from string name: getErrorCode('cantMakePayments') // 1003 // Get string name from numeric code: getErrorPrompt(1003) // 'cantMakePayments' ``` ### Сравнение кодов ошибок \{#comparing-error-codes\} **Важно:** `error.adaptyCode` — это **число**, поэтому сравнивайте его напрямую с числовыми кодами: ```typescript // Option 1: Use ErrorCodeName constant (recommended) ✅ if (error.adaptyCode === ErrorCodeName.cantMakePayments) { console.log('Cannot make payments'); } // Option 2: Compare with numeric literal ✅ if (error.adaptyCode === 1003) { console.log('Cannot make payments'); } // NOT like this ❌ - compares number to string and will never match if (error.adaptyCode === ErrorCode[1003]) { } ``` ## Глобальный обработчик ошибок \{#global-error-handler\} Вы можете настроить глобальный обработчик ошибок для перехвата всех ошибок Adapty: ```typescript showLineNumbers // Set up global error handler AdaptyError.onError = (error: AdaptyError) => { console.error('Global Adapty error:', { code: error.adaptyCode, message: error.localizedDescription, detail: error.detail }); // Handle specific error types globally if (error.adaptyCode === ErrorCodeName.notActivated) { // SDK not activated - maybe retry activation console.log('SDK not activated, attempting to reactivate...'); } }; ``` ## Типовые паттерны обработки ошибок \{#common-error-handling-patterns\} ### Обработка ошибок при покупке \{#handle-purchase-errors\} ```typescript showLineNumbers async function handlePurchase(product: AdaptyPaywallProduct) { try { const result = await adapty.makePurchase({ product }); if (result.type === 'success') { console.log('Purchase successful:', result.profile); } else if (result.type === 'user_cancelled') { console.log('User cancelled the purchase'); } else if (result.type === 'pending') { console.log('Purchase is pending'); } } catch (error) { if (error instanceof AdaptyError) { switch (error.adaptyCode) { case ErrorCodeName.cantMakePayments: console.log('In-app purchases not allowed'); break; case ErrorCodeName.productPurchaseFailed: console.log('Purchase failed:', error.detail); break; default: console.error('Purchase error:', error.localizedDescription); } } } } ``` ### Обработка сетевых ошибок \{#handle-network-errors\} ```typescript showLineNumbers async function fetchFlow(placementId: string) { try { const flow = await adapty.getFlow({ placementId }); return flow; } catch (error) { if (error instanceof AdaptyError) { switch (error.adaptyCode) { case ErrorCodeName.networkFailed: console.log('Network error, retrying...'); // Implement retry logic break; case ErrorCodeName.serverError: console.log('Server error:', error.detail); break; case ErrorCodeName.notActivated: console.log('SDK not activated'); break; default: console.error('Paywall fetch error:', error.localizedDescription); } } throw error; } } ``` ## Системные коды StoreKit \{#system-storekit-codes\} | Ошибка | Код | Описание | |-----|----|-----------| | unknown | 0 | Неизвестная или непредвиденная ошибка. | | clientInvalid | 1 | Клиенту не разрешено выполнять запрошенное действие. | | paymentCancelled | 2 | <p>Пользователь отменил запрос на оплату.</p><p>Действий не требуется, но с точки зрения бизнес-логики можно предложить пользователю скидку или напомнить о покупке позже.</p> | | paymentInvalid | 3 | Один из параметров платежа не был распознан стором. | | paymentNotAllowed | 4 | <p>Пользователю не разрешено авторизовывать платежи. Возможные причины:</p><p></p><p>- Платежи не поддерживаются в стране пользователя.</p><p>- Пользователь является несовершеннолетним.</p> | | storeProductNotAvailable | 5 | Запрошенный продукт отсутствует в App Store. Убедитесь, что продукт доступен для нужной страны. | | cloudServicePermissionDenied | 6 | Пользователь не разрешил доступ к информации облачного сервиса. | | cloudServiceNetworkConnectionFailed | 7 | Устройству не удалось подключиться к сети. | | cloudServiceRevoked | 8 | Пользователь отозвал разрешение на использование облачного сервиса. | | privacyAcknowledgementRequired | 9 | Пользователь ещё не ознакомился с политикой конфиденциальности стора. | | unauthorizedRequestData | 10 | Запрос сформирован некорректно. | | invalidOfferIdentifier | 11 | <p>Идентификатор офера недействителен. Возможные причины:</p><p></p><p>- Офер с таким идентификатором не настроен в App Store.</p><p>- Офер был отозван.</p><p>- Идентификатор офера указан с опечаткой.</p> | | invalidSignature | 12 | Подпись в платёжной скидке недействительна. Убедитесь, что заполнено поле **In-app purchase Key ID** и загружен файл **In-App Purchase Private Key**. Подробнее — в разделе [Настройка интеграции с App Store](app-store-connection-configuration). | | missingOfferParams | 13 | <p>Проблема с интеграцией Adapty или с оферами.</p><p>Подробнее — в разделах [Настройка интеграции с App Store](app-store-connection-configuration) и [Оферы](offers).</p> | | invalidOfferPrice | 14 | Указанная в сторе цена больше не действительна. Оферы всегда должны предоставлять скидку относительно обычной цены. | ## Пользовательские коды Android \{#custom-android-codes\} | Ошибка | Код | Описание | |-----|----|-----------| | adaptyNotInitialized | 20 | Необходимо правильно настроить SDK Adapty с помощью метода `Adapty.activate`. Узнайте, как это сделать [для React Native](sdk-installation-reactnative). | | productNotFound | 22 | Запрошенный для покупки продукт недоступен в сторе. | | invalidJson | 23 | JSON пейвола недействителен. Исправьте его в дашборде Adapty. Подробнее — в разделе [Настройка пейвола с помощью Remote Config](customize-paywall-with-remote-config). | | currentSubscriptionToUpdateNotFoundInHistory | 24 | Исходная подписка, которую необходимо обновить, не найдена. | | pendingPurchase | 25 | Покупка находится в статусе ожидания, а не завершена. Подробнее — на странице [Обработка отложенных транзакций](https://developer.android.com/google/play/billing/integrate#pending) в документации Android Developer. | | billingServiceTimeout | 97 | Запрос превысил максимальное время ожидания до получения ответа от Google Play. Причиной может быть, например, задержка при выполнении действия, запрошенного вызовом Play Billing Library. | | featureNotSupported | 98 | Запрошенная функция не поддерживается Play Store на текущем устройстве. | | billingServiceDisconnected | 99 | Критическая ошибка: соединение клиентского приложения с сервисом Google Play Store через `BillingClient` разорвано. | | billingServiceUnavailable | 102 | Временная ошибка: сервис Google Play Billing сейчас недоступен. В большинстве случаев это означает проблему с сетевым подключением между клиентским устройством и сервисами Google Play Billing. | | billingUnavailable | 103 | <p>Ошибка выставления счёта пользователю в процессе покупки. Примеры ситуаций, в которых это может произойти:</p><p></p><p>1. Приложение Play Store на устройстве пользователя устарело.</p><p>2. Пользователь находится в неподдерживаемой стране.</p><p>3. Пользователь является корпоративным, и администратор отключил для него возможность совершать покупки.</p><p>4. Google Play не может списать средства с платёжного метода пользователя. Например, срок действия кредитной карты истёк.</p><p>5. Пользователь не авторизован в приложении Play Store.</p> | | developerError | 105 | Критическая ошибка: некорректное использование API. | | billingError | 106 | Критическая ошибка: внутренняя проблема самого Google Play. | | itemAlreadyOwned | 107 | Расходуемая покупка уже была приобретена. | | itemNotOwned | 108 | Запрошенное действие с элементом завершилось неудачей, так как он не принадлежит пользователю. | ## Пользовательские коды StoreKit \{#custom-storekit-codes\} | Ошибка | Код | Описание | |-----|----|-----------| | noProductIDsFound | 1000 | <p>Ни один из продуктов пейвола не доступен в сторе.</p><p>Если вы столкнулись с этой ошибкой, выполните следующие шаги для её устранения:</p><p></p><p>1. Убедитесь, что все продукты добавлены в дашборд Adapty.</p><p>2. Проверьте, что Bundle ID приложения совпадает с указанным в Apple Connect.</p><p>3. Убедитесь, что идентификаторы продуктов из стора совпадают с теми, что добавлены в дашборд. Обратите внимание: идентификаторы не должны содержать Bundle ID, если только он уже не включён в идентификатор в сторе.</p><p>4. Убедитесь, что статус оплаты приложения активен в налоговых настройках Apple. Проверьте актуальность налоговой информации и действительность сертификатов.</p><p>5. Проверьте, привязан ли к приложению банковский счёт — это необходимо для монетизации.</p><p>6. Проверьте доступность продуктов во всех регионах. Убедитесь, что продукты находятся в статусе **"Ready to Submit"**.</p> | | productRequestFailed | 1002 | <p>Не удаётся получить доступные продукты в данный момент. Возможная причина:</p><p></p><p>- Кэш ещё не создан, и одновременно отсутствует подключение к интернету.</p> | | cantMakePayments | 1003 | Встроенные покупки не разрешены на этом устройстве. | | noPurchasesToRestore | 1004 | Google Play не нашёл покупку для восстановления. | | cantReadReceipt | 1005 | <p>На устройстве нет действительного чека. Это может быть проблемой при тестировании в песочнице.</p><p>Действий не требуется, но с точки зрения бизнес-логики можно предложить пользователю скидку или напомнить о покупке позже.</p> | | productPurchaseFailed | 1006 | Не удалось выполнить покупку продукта. Эта ошибка оборачивает базовую ошибку StoreKit — прочитайте вложенную ошибку (или включите подробные логи для просмотра в консоли), чтобы узнать реальную причину. Вложенная ошибка, как правило, соответствует одному из кодов StoreKit 0–14 из таблицы выше — чаще всего `paymentCancelled`, `paymentInvalid`, `paymentNotAllowed` или `invalidOfferPrice`. Если определить конкретную причину не удаётся, попробуйте создать новый [профиль песочницы](test-purchases-in-sandbox); если проблема сохраняется, обратитесь в поддержку Apple. | | refreshReceiptFailed | 1010 | Чек не был получен. Применимо только к StoreKit 1. | | receiveRestoredTransactionsFailed | 1011 | Не удалось восстановить покупки. | ## Пользовательские сетевые коды \{#custom-network-codes\} | Ошибка | Код | Описание | | :------------------- | :--- | :----------------------------------------------------------- | | notActivated | 2002 | Необходимо правильно настроить SDK Adapty с помощью метода `Adapty.activate`. Узнайте, как это сделать [для React Native](sdk-installation-reactnative). | | badRequest | 2003 | Некорректный запрос. | | serverError | 2004 | Ошибка сервера. | | networkFailed | 2005 | Сетевой запрос не выполнен. | | decodingFailed | 2006 | Ошибка декодирования ответа. | | encodingFailed | 2009 | Ошибка кодирования запроса. | | analyticsDisabled | 3000 | Невозможно обработать аналитические события, так как вы отключили их сбор. Подробнее — в разделе [Интеграция аналитики](analytics-integration). | | wrongParam | 3001 | Один или несколько параметров некорректны: пустые там, где это недопустимо, или имеют неверный тип. | | activateOnceError | 3005 | Метод `.activate` нельзя вызывать более одного раза. | | profileWasChanged | 3006 | Профиль пользователя был изменён во время операции. | | fetchTimeoutError | 3101 | Пейвол не удалось загрузить в установленный срок. Чтобы избежать этой ситуации, [настройте локальные резервные пейволы](fetch-paywalls-and-products). | | operationInterrupted | 9000 | Операция была прервана системой. | --- # File: capacitor-sdk-migration-guides --- --- title: "Руководства по миграции Capacitor SDK" description: "Руководства по миграции для версий Adapty Capacitor SDK." --- На этой странице собраны все руководства по миграции для Adapty Capacitor SDK. Выберите версию, на которую хотите перейти, чтобы получить подробные инструкции: - **[Миграция на v4.0 (beta)](migration-to-capacitor-sdk-v4)** - [**Миграция на v3.16**](migration-to-capacitor-316) --- # File: migration-to-capacitor-sdk-v4 --- --- title: "Перенос Adapty Capacitor SDK на версию 4.0" description: "Перейдите на Adapty Capacitor SDK v4.0 (beta): замените paywall API на flow API, совместимые с Flow Builder и Paywall Builder." --- Adapty Capacitor SDK 4.0 (beta) вводит флоу и переименовывает paywall API соответствующим образом. Новые API работают как с новым Flow Builder, так и с существующим Paywall Builder — никаких изменений в настройках дашборда Adapty не требуется. ## Краткий справочник \{#quick-reference\} | v3 | v4 | |---|---| | `adapty.getPaywall({ placementId, locale?, params? })` | `adapty.getFlow({ placementId, params? })` | | `adapty.getPaywallForDefaultAudience({ placementId, locale?, params? })` | `adapty.getFlowForDefaultAudience({ placementId, params? })` | | `adapty.getPaywallProducts({ paywall })` | `adapty.getPaywallProducts({ flow })` | | `adapty.logShowPaywall({ paywall })` | `adapty.logShowFlow({ flow })` | | `AdaptyPaywall` (тип) | `AdaptyFlow` + `AdaptyFlowPaywall` | | `createPaywallView(paywall, params?)` | `createFlowView(flow, params?)` | | `PaywallViewController` | `FlowViewController` | | `EventHandlers` (тип) | `FlowEventHandlers` | | `CreatePaywallViewParamsInput` | `CreateFlowViewParamsInput` | | `onRenderingFailed` | `onError` | `AdaptyPaywallProduct` сохраняет своё название — продукты по-прежнему принадлежат флоу, и `getPaywallProducts` тоже сохраняет название, теперь принимая `AdaptyFlow`. Методы `getFlow` и `getFlowForDefaultAudience` больше не принимают параметр `locale`. API покупок и профиля (`makePurchase`, `restorePurchases`, `getProfile`, `identify`, `updateProfile`) и резервные пейволы через `setFallback` остались без изменений. Методы отображения `present`, `dismiss`, `setEventHandlers` и `showDialog`, а также обработчики событий `onCloseButtonPress`, `onUrlPress`, `onCustomAction`, `onProductSelected`, `onPurchaseStarted`, `onPurchaseCompleted`, `onPurchaseFailed`, `onRestoreStarted`, `onRestoreCompleted`, `onRestoreFailed`, `onLoadingProductsFailed`, `onWebPaymentNavigationFinished` и `onAndroidSystemBack` сохраняют те же названия, что и в v3. Методы онбординга по-прежнему работают, но объявлены устаревшими — см. [Устаревание Onboarding API](#onboarding-api-deprecation). Некоторые стандартные поведения изменились — см. [Изменения стандартного поведения](#default-behavior-changes). ## Минимальные версии \{#minimum-versions\} Требования к среде выполнения не изменились по сравнению с v3.16+: **iOS 15.0**, **Android minSdk 24** и **Capacitor 8**. Изменения deployment target не требуются. Появилось одно новое требование к сборке: **Xcode 26 или новее** — нативный Adapty iOS SDK 4.0.0-beta.2, входящий в этот релиз, использует Swift tools 6.2. v4 включает нативные Adapty SDK: iOS 4.0.0-beta.2 и Android BOM 4.0.0-beta.1. ## Установка \{#installation\} ### Обновление пакета \{#update-the-package\} v4.0 — предрелизная версия, поэтому укажите точную версию: npm не выбирает предрелизные версии через диапазоны с `^` или `~`: ```bash showLineNumbers npm install @adapty/capacitor@4.0.0-beta.2 ``` Затем синхронизируйте нативные проекты: ```bash showLineNumbers npx cap sync ``` ### iOS: только Swift Package Manager \{#ios-swift-package-manager-only\} [Репозиторий спецификаций CocoaPods станет доступен только для чтения в декабре 2026 года](https://blog.cocoapods.org/CocoaPods-Specs-Repo/), поэтому начиная с v4 файл `AdaptyCapacitor.podspec` удалён, и SDK устанавливается на iOS **только через Swift Package Manager (SPM)**. iOS-проект вашего приложения должен использовать интеграцию Capacitor с SPM: - Новые приложения: добавьте iOS-платформу с менеджером пакетов SPM: ```bash showLineNumbers npx cap add ios --packagemanager SPM ``` - Для существующих приложений на базе CocoaPods: перенесите iOS-проект, следуя [руководству Capacitor по использованию SPM в существующем проекте](https://capacitorjs.com/docs/ios/spm#using-spm-in-an-existing-capacitor-project). Подробнее об установке см. в [Установке Adapty SDK](sdk-installation-capacitor). ## Получение флоу \{#fetching-flows\} ### getPaywall → getFlow Возвращаемый тип изменяется с `AdaptyPaywall` на `AdaptyFlow`, а опция `locale` убрана — при рендеринге флоу локаль определяется автоматически; для кастомных пейволов все локали возвращаются в `flow.remoteConfigs`: ```diff showLineNumbers - const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en' }); + const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID' }); ``` `getPaywallForDefaultAudience` переименовывается аналогично: ```diff showLineNumbers - const paywall = await adapty.getPaywallForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID', locale: 'en' }); + const flow = await adapty.getFlowForDefaultAudience({ placementId: 'YOUR_PLACEMENT_ID' }); ``` ### getPaywallProducts(paywall) → getPaywallProducts(flow) `getPaywallProducts` сохраняет своё название, но теперь принимает `AdaptyFlow`: ```diff showLineNumbers - const products = await adapty.getPaywallProducts({ paywall }); + const products = await adapty.getPaywallProducts({ flow }); ``` ## Модель данных \{#data-model\} `getFlow` возвращает `AdaptyFlow` вместо `AdaptyPaywall`, и структура объекта изменилась: | Поле `AdaptyPaywall` (v3) | Поле `AdaptyFlow` (v4) | Действие | |---|---|---| | `remoteConfig?` (одно) | `remoteConfigs?: AdaptyRemoteConfig[]` (массив) | Флоу содержит один Remote Config на каждый настроенный язык. Получите нужный для пользователя: `flow.remoteConfigs?.find((c) => c.lang === 'en')`. | | `products` | `flow.paywalls[i].productIdentifiers` | Идентификаторы продуктов теперь хранятся в каждом варианте флоу, а не в самом флоу. | | `webPurchaseUrl?` | `flow.paywalls[i].webPurchaseUrl` | Перенесено из флоу в каждый вариант пейвола. | | `version?: number` | `flowVersionId?: string` | Переименовано, тип изменён с `number` на `string`. | | `hasViewConfiguration` | удалено | Удалите все проверки `hasViewConfiguration` из кода — теперь `createFlowView` выбрасывает исключение (см. [Отображение флоу](#displaying-flows)). | | `requestLocale` | удалено | Локаль больше не является частью модели. | | _(новое)_ | `paywalls: AdaptyFlowPaywall[]` | Каждый элемент — один вариант пейвола во флоу. | | _(новое)_ | `responseCreatedAt: number` | Временная метка ответа сервера в миллисекундах. | `hasViewConfiguration` и `requestLocale` остаются на `AdaptyOnboarding` — только модель флоу их убирает. Идентификаторы продуктов перенесены из флоу в каждый вариант: ```diff showLineNumbers - const ids = paywall.products; + const ids = flow.paywalls[0].productIdentifiers; ``` ## Методы веб-пейвола \{#web-paywall-methods\} `openWebPaywall` и `createWebPaywallUrl` сохраняют свои названия, но опция `paywallOrProduct` теперь принимает `AdaptyFlowPaywall` (вариант флоу) вместо `AdaptyPaywall`. По-прежнему можно передать `AdaptyPaywallProduct`. Перед обращением к первому элементу убедитесь, что `flow.paywalls` не пустой: ```diff showLineNumbers const flow = await adapty.getFlow({ placementId: 'YOUR_PLACEMENT_ID' }); - await adapty.openWebPaywall({ paywallOrProduct: paywall }); + await adapty.openWebPaywall({ paywallOrProduct: flow.paywalls[0] }); ``` ## Отслеживание просмотров флоу \{#tracking-flow-views\} ### logShowPaywall → logShowFlow `logShowPaywall` переименован в `logShowFlow` и теперь принимает `AdaptyFlow`. Событие по-прежнему фиксируется для той же вариации, поэтому метрики воронки и A/B-тестов продолжат работать без изменений в дашборде. ```diff showLineNumbers - await adapty.logShowPaywall({ paywall }); + await adapty.logShowFlow({ flow }); ``` Как и в v3, вызывать этот метод при отображении флоу или пейволов, созданных с помощью [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder), не нужно — Adapty отслеживает эти просмотры автоматически. ## Отображение флоу \{#displaying-flows\} ### createPaywallView → createFlowView Переименуйте фабричную функцию и передайте `AdaptyFlow`. Возвращаемый контроллер переименован с `PaywallViewController` на `FlowViewController`, но его методы (`present`, `dismiss`, `setEventHandlers`, `showDialog`) остались прежними. Тип параметров переименован с `CreatePaywallViewParamsInput` на `CreateFlowViewParamsInput`: ```diff showLineNumbers - import { createPaywallView } from '@adapty/capacitor'; + import { createFlowView } from '@adapty/capacitor'; - const view = await createPaywallView(paywall); + const view = await createFlowView(flow); await view.present(); ``` `createFlowView` выбрасывает `AdaptyError`, если для флоу не настроено представление — это заменяет проверку `hasViewConfiguration` из v3: ```diff showLineNumbers - if (paywall.hasViewConfiguration) { - const view = await createPaywallView(paywall); - await view.present(); - } + try { + const view = await createFlowView(flow); + await view.present(); + } catch (error) { + // the flow has no view configured, or view creation failed + } ``` :::note Представление флоу одноразовое: после вызова `dismiss()` оно уничтожается и обработчики событий очищаются — чтобы снова показать флоу, вызовите `createFlowView` заново. ::: ### Безопасные отступы Android \{#android-safe-area-paddings\} `CreateFlowViewParamsInput` добавляет один новый параметр: `enableSafeArea`, который управляет безопасными отступами Android во время выполнения. Он находится под ключом `android` и по умолчанию равен `true`: ```typescript showLineNumbers const view = await createFlowView(flow, { android: { enableSafeArea: true }, }); ``` ## Обработка событий \{#handling-events\} Интерфейс обработчика событий переименован с `EventHandlers` на `FlowEventHandlers`, а один из коллбэков тоже переименован. Тела существующих обработчиков менять не нужно — просто переименуйте: ```diff showLineNumbers - onRenderingFailed: (error) => { /* … */ }, + onError: (error) => { /* … */ }, ``` Все остальные обработчики событий сохраняют свои названия. Два из них также получают второй аргумент: `onPurchaseCompleted` теперь принимает `(purchaseResult, product)`, а `onPurchaseFailed` — `(error, product)`, где `product` — это задействованный `AdaptyPaywallProduct`. Полный список см. в [Обработка событий флоу и пейвола](capacitor-handling-events). В v4 также добавлено несколько возможностей, которые можно подключить по желанию: - Методы `adapty.openWebUrl({ url, openIn })` и `adapty.requestAppReview()` — они обеспечивают работу стандартных обработчиков `onUrlPress` и `onRequestAppReview`, поэтому URL-адреса и запросы на оценку приложения обрабатываются нативно «из коробки». Вызывайте их напрямую только если переопределяете эти обработчики. - Обработка покупок в режиме Observer внутри флоу через новые обработчики `onObserverPurchaseInitiated` / `onObserverRestoreInitiated`. См. [Показ флоу в режиме Observer](capacitor-present-flows-in-observer-mode). ## Изменения в поведении по умолчанию \{#default-behavior-changes\} Эти изменения не вызывают ошибок компиляции, поэтому проверяйте их во время выполнения: - **`onAndroidSystemBack`**: По умолчанию поведение изменилось: вместо закрытия вью теперь она остаётся открытой. Чтобы вернуть прежнее поведение, верните `true` из обработчика. - **`onPurchaseCompleted`**: По умолчанию поведение изменилось: вместо закрытия вью (если пользователь не отменил покупку) теперь она всегда остаётся открытой. Чтобы вернуть прежнее поведение, верните `purchaseResult.type !== 'user_cancelled'` из обработчика. - **`onRestoreCompleted`**: По умолчанию поведение изменилось: вместо закрытия вью после успешного восстановления она теперь остаётся открытой. Чтобы вернуть прежнее поведение, верните `true` из обработчика. - **`onUrlPress`**: Теперь по умолчанию URL открывается через нативный слой с учётом настройки встроенного или внешнего браузера из дашборда. Переопределите обработчик, чтобы открывать URL самостоятельно. - **Вью одноразовые**: после вызова `dismiss()` вью уничтожается. Чтобы снова показать флоу, вызовите `createFlowView` заново. ## Удалённые API \{#removed-apis\} ### Удалённые экспорты Эти символы больше не экспортируются из `@adapty/capacitor`. Удалите их из импортов: - **`AdaptyPaywall`**: Используйте `AdaptyFlow` и `AdaptyFlowPaywall` вместо них. - **`ProductReference`**: Используйте `AdaptyProductIdentifier`, доступный в `flow.paywalls[i].productIdentifiers`. - **`AdaptyPaywallBuilder`**: Удалён. Флоу и пейволы рендерятся нативно. - **`AdaptyAndroidSubscriptionUpdateParameters`**: Используйте вложенную структуру параметров покупки `android` (см. ниже). ### activate: lockMethodsUntilReady `lockMethodsUntilReady` (уже устаревший no-op в v3) удалён. Уберите его из вызова `activate` — с ним код больше не компилируется: ```diff showLineNumbers - await adapty.activate({ apiKey: 'PUBLIC_SDK_KEY', params: { lockMethodsUntilReady: true } }); + await adapty.activate({ apiKey: 'PUBLIC_SDK_KEY' }); ``` ### makePurchase: параметры Android \{#makepurchase-android-parameters\} Устаревший плоский Android-формат `MakePurchaseParamsInput` удалён — теперь используется только вложенная форма. Перенесите все параметры Android-покупки в `params: { android: { ... } }`. Полный пример см. в разделе [Совершение покупок](capacitor-making-purchases). ## Устаревший API онбординга \{#onboarding-api-deprecation\} Устаревший API онбординга объявлен устаревшим в v4.0 в пользу [Flow Builder](adapty-flow-builder). Он по-прежнему работает, но будет удалён в одном из будущих релизов — запланируйте перенос ваших онбордингов во Flow Builder. Устаревшие символы: `getOnboarding`, `getOnboardingForDefaultAudience`, `createOnboardingView` и `OnboardingViewController`. --- # File: migration-to-capacitor-316 --- --- title: "Миграция Adapty Capacitor SDK на v3.16" description: "Мигрируйте на Adapty Capacitor SDK v3.16 для повышения производительности и новых функций монетизации." --- Начиная с Adapty SDK v3.16.0, требуется Capacitor 8. Если вам нужен Capacitor 7, используйте Adapty SDK v3.15. Чтобы обновиться до Capacitor SDK v3.16, убедитесь, что ваш проект использует Capacitor 8. Если вы всё ещё используете Capacitor 7, у вас есть два варианта: 1. **Обновитесь до Capacitor 8**: следуйте [официальному руководству по миграции Capacitor](https://capacitorjs.com/docs/updating/8-0), чтобы обновить проект, затем установите Adapty SDK v3.16. 2. **Оставайтесь на Adapty SDK v3.15**: если обновление до Capacitor 8 нецелесообразно, продолжайте использовать Adapty SDK v3.15, который поддерживает Capacitor 7. --- # End of Documentation _Generated on: 2026-07-24T13:01:12.730Z_ _Successfully processed: 45/45 files_ # FLUTTER - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: ru Generated on: 2026-07-24T13:01:12.733Z Total files: 44 --- # File: sdk-installation-flutter --- --- title: "Установка и настройка Flutter SDK" description: "Пошаговое руководство по установке Adapty SDK на Flutter для приложений с подписками." --- SDK Adapty включает два ключевых модуля для бесшовной интеграции в ваше Flutter-приложение: - **Core Adapty**: Основной SDK, необходимый для работы Adapty в вашем приложении. - **AdaptyUI**: Этот модуль нужен, если вы используете [Adapty Paywall Builder](adapty-paywall-builder) — удобный no-code инструмент для создания кросс-платформенных пейволов. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Загляните в наше [демо-приложение](https://github.com/adaptyteam/AdaptySDK-Flutter/tree/master/example): оно демонстрирует полную настройку, включая отображение пейволов, совершение покупок и другие базовые функции. ::: ## Требования \{#requirements\} Adapty SDK поддерживает iOS 13.0+, но для корректной работы пейволов, созданных в Paywall Builder, требуется iOS 15.0+. Adapty Flutter SDK 4.0 — с поддержкой [Flow Builder](adapty-flow-builder) — повышает минимальные требования до **iOS 15.0+**, **Xcode 26+** и **Flutter 3.32.0+** (Dart 3.8.0+). Подробности об установке см. в разделе [Adapty SDK 4.0](#adapty-sdk-40-swift-package-manager) ниже. :::info Adapty совместима с Google Play Billing Library версий до 8.x включительно. По умолчанию Adapty работает с Google Play Billing Library v7.0.0, но если вам нужна более поздняя версия, вы можете вручную [добавить зависимость](https://developer.android.com/google/play/billing/integrate#dependency). ::: :::info Установка SDK — это шаг 5 настройки Adapty. Прежде чем покупки заработают в вашем приложении, вам также нужно подключить приложение к сторам, а затем создать продукты, пейвол и плейсмент в дашборде Adapty. [Гайд по быстрому старту](quickstart) описывает все необходимые шаги. ::: ## Установка Adapty SDK \{#install-adapty-sdk\} [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Flutter.svg?style=flat&logo=flutter)](https://github.com/adaptyteam/AdaptySDK-Flutter/releases) :::important Шаги ниже устанавливают последнюю стабильную версию SDK (3.x). Если вам нужна v4 — необходимая для [Flow Builder](adapty-flow-builder) и используемая в [быстром старте](flutter-quickstart-paywalls) — следуйте инструкции [Adapty SDK 4.0: Swift Package Manager](#adapty-sdk-40-swift-package-manager) ниже. ::: 1. Добавьте Adapty в файл `pubspec.yaml`: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter: ^<the latest SDK version> ``` 2. Выполните следующую команду для установки зависимостей: ```bash showLineNumbers title="Terminal" flutter pub get ``` 3. Импортируйте Adapty SDK в своё приложение: ```dart showLineNumbers title="main.dart" import 'package:adapty_flutter/adapty_flutter.dart'; ``` ### Adapty SDK 4.0: Swift Package Manager Добавьте Adapty Flutter SDK 4.0 — который добавляет поддержку [Flow Builder](adapty-flow-builder) — в ваш `pubspec.yaml`: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter: 4.0.0 ``` Начиная с v4, нативный iOS SDK больше не распространяется через CocoaPods — плагин подключает его только через **Swift Package Manager** ([репозиторий спецификаций CocoaPods переходит в режим только для чтения в декабре 2026 года](https://blog.cocoapods.org/CocoaPods-Specs-Repo/)). Если вы используете Flutter 3.32–3.43, включите поддержку Swift Package Manager один раз: ```bash showLineNumbers title="Terminal" flutter config --enable-swift-package-manager ``` Flutter 3.44 и более поздние версии включают Swift Package Manager по умолчанию, поэтому никаких дополнительных действий не требуется. Об изменениях API в v4 читайте в [руководстве по миграции](migration-to-flutter-sdk-v4). ## Активация модуля Adapty в SDK Adapty \{#activate-adapty-module-of-adapty-sdk\} Активируйте Adapty SDK в коде вашего приложения. :::note Adapty SDK достаточно активировать один раз в приложении. ::: Чтобы получить **Public SDK Key**: 1. Откройте дашборд Adapty и перейдите в [**App settings → General**](https://app.adapty.io/settings/general). 2. В разделе **Api keys** скопируйте **Public SDK Key** (НЕ Secret Key). 3. Замените `"YOUR_PUBLIC_SDK_KEY"` в коде. Или получите его программно с помощью [Adapty CLI](developer-cli): ``` npm install -g adapty adapty auth login adapty apps list ``` Или напрямую: ``` npx adapty auth login adapty apps list ``` - Убедитесь, что для инициализации Adapty вы используете **Public SDK key** — **Secret key** предназначен только для [серверного API](getting-started-with-server-side-api). - **SDK-ключи** уникальны для каждого приложения, поэтому если у вас несколько приложений, выберите нужный ключ. ```dart showLineNumbers title="main.dart" void main() { runApp(MyApp()); } class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { @override void initState() { _initializeAdapty(); super.initState(); } Future<void> _initializeAdapty() async { try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY'), ); } catch (e) { // handle the error } } Widget build(BuildContext context) { return Text("Hello"); } } ``` :::important Дождитесь завершения `activate` перед вызовом любых других методов Adapty SDK. Полная последовательность описана в [порядке вызовов в Flutter SDK](flutter-sdk-call-order). ::: Теперь настройте пейволы в вашем приложении: - Если вы используете [Adapty Paywall Builder](adapty-paywall-builder), сначала [активируйте модуль AdaptyUI](#activate-adaptyui-module-of-adapty-sdk) ниже, а затем следуйте [быстрому старту с Paywall Builder](flutter-quickstart-paywalls). - Если вы создаёте собственный UI пейвола, обратитесь к [быстрому старту для пользовательских пейволов](flutter-quickstart-manual). ## Активация модуля AdaptyUI в составе Adapty SDK \{#activate-adaptyui-module-of-adapty-sdk\} Если вы планируете использовать [Paywall Builder](adapty-paywall-builder) и уже [установили модуль AdaptyUI](sdk-installation-flutter#install-adapty-sdk), его также необходимо активировать: :::note Зависимости, связанные с AdaptyUI, подключаются к вашему приложению независимо от того, активирован ли AdaptyUI. ::: :::important В коде сначала необходимо активировать основной модуль Adapty, и только затем — AdaptyUI. ::: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withActivateUI(true), // This automatically activates AdaptyUI ); ``` ## Необязательная настройка \{#optional-setup\} ### Логирование \{#logging\} #### Настройка системы логирования \{#set-up-the-logging-system\} Adapty логирует ошибки и другую важную информацию, чтобы помочь вам разобраться в происходящем. Доступны следующие уровни логирования: | Уровень | Описание | | :----------------------- | :------------------------------------------------------------------------------------------------------------------------------- | | `AdaptyLogLevel.error` | Записываются только ошибки | | `AdaptyLogLevel.warn` | Записываются ошибки и сообщения от SDK, которые не вызывают критических ошибок, но заслуживают внимания. | | `AdaptyLogLevel.info` | Записываются ошибки, предупреждения и различные информационные сообщения. Значение по умолчанию | | `AdaptyLogLevel.verbose` | Записывается любая дополнительная информация, которая может быть полезна при отладке: вызовы функций, API-запросы и т. д. | | `AdaptyLogLevel.debug` | Записывается отладочная информация. | Вы можете задать уровень логирования в приложении до настройки Adapty: ```dart showLineNumbers title="main.dart" // Set log level before activation. // 'verbose' is recommended for development and the first production release await Adapty().setLogLevel(AdaptyLogLevel.verbose); // Or set it during configuration await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withLogLevel(AdaptyLogLevel.verbose), ); ``` ### Политика обработки данных \{#data-policies\} Adapty не хранит персональные данные пользователей, если вы не передаёте их явно. При этом вы можете настроить дополнительные политики безопасности данных для соответствия требованиям стора или законодательства конкретной страны. #### Отключение сбора и передачи IP-адресов \{#disable-ip-address-collection-and-sharing\} При активации модуля Adapty установите `ipAddressCollectionDisabled` в `true`, чтобы отключить сбор и передачу IP-адресов пользователей. Значение по умолчанию — `false`. Используйте этот параметр для защиты конфиденциальности пользователей, соблюдения региональных требований по защите данных (например, GDPR или CCPA), а также чтобы не собирать лишние данные, если функции на основе IP-адреса вашему приложению не нужны. ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withIpAddressCollectionDisabled(true), ); ``` #### Отключение сбора и передачи рекламного идентификатора \{#disable-advertising-id-collection-and-sharing\} При активации модуля Adapty установите `appleIdfaCollectionDisabled` (iOS) или `googleAdvertisingIdCollectionDisabled` (Android) в значение `true`, чтобы отключить сбор рекламных идентификаторов. Значение по умолчанию — `false`. Используйте этот параметр для соответствия политикам App Store/Play Store, чтобы не вызывать запрос App Tracking Transparency, или если ваше приложение не использует рекламную атрибуцию или аналитику на основе рекламных идентификаторов. ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withAppleIdfaCollectionDisabled(true) // iOS ..withGoogleAdvertisingIdCollectionDisabled(true), // Android ); ``` #### Настройка конфигурации медиакэша для AdaptyUI \{#set-up-media-cache-configuration-for-adaptyui\} Модуль активируется автоматически вместе с Adapty SDK. Если вы не используете Paywall Builder и хотите отключить модуль AdaptyUI, передайте `withActivateUI(false)` при активации. По умолчанию AdaptyUI кэширует медиафайлы (изображения и видео) для повышения производительности и снижения сетевой нагрузки. Вы можете настроить параметры кэша, передав собственную конфигурацию. Используйте `withMediaCacheConfiguration`, чтобы переопределить ограничения кэша по умолчанию. Это необязательно — если вы не вызываете этот метод, будут применяться значения по умолчанию (100 МБ на диске, неограниченное количество объектов в памяти). Однако если вы создаёте объект конфигурации, все его параметры обязательны. ```dart showLineNumbers title="main.dart" final mediaCacheConfig = AdaptyUIMediaCacheConfiguration( memoryStorageTotalCostLimit: 200 * 1024 * 1024, // 200 MB memoryStorageCountLimit: 2147483647, // max int value diskStorageSizeLimit: 200 * 1024 * 1024, // 200 MB ); await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withMediaCacheConfiguration(mediaCacheConfig), ); ``` **Параметры:** | Параметр | Наличие | Описание | |-------------------------|----------|-----------------------------------------------------------------------------| | memoryStorageTotalCostLimit | обязательный | Общий размер кэша в памяти в байтах. По умолчанию — 100 МБ. | | memoryStorageCountLimit | обязательный | Максимальное количество элементов в памяти. По умолчанию — максимальное значение int. | | diskStorageSizeLimit | обязательный | Ограничение размера файла на диске в байтах. По умолчанию — 100 МБ. | ### Включение локальных уровней доступа (Android) \{#enable-local-access-levels-android\} По умолчанию [локальные уровни доступа](local-access-levels) включены на iOS и отключены на Android. Чтобы включить их и на Android, передайте `withGoogleLocalAccessLevelAllowed` со значением `true`: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withGoogleLocalAccessLevelAllowed(true), ); ``` ### Очистка данных при восстановлении из резервной копии \{#clear-data-on-backup-restore\} Когда `appleClearDataOnBackup` установлен в `true`, SDK определяет, что приложение восстановлено из резервной копии iCloud, и удаляет все локально сохранённые данные SDK, включая кэшированную информацию профиля, детали продуктов и пейволы. После этого SDK инициализируется с чистого состояния. Значение по умолчанию — `false`. :::note Удаляется только локальный кэш SDK. История транзакций с Apple и данные пользователя на серверах Adapty остаются без изменений. ::: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withAppleClearDataOnBackup(true) // default – false ); ``` ## Устранение неполадок \{#troubleshooting\} #### Правила резервного копирования Android (настройка Auto Backup) \{#android-backup-rules-auto-backup-configuration\} Некоторые SDK (включая Adapty) поставляются с собственной конфигурацией Android Auto Backup. Если вы используете несколько SDK, каждый из которых определяет правила резервного копирования, слияние манифестов Android может завершиться ошибкой, связанной с `android:fullBackupContent`, `android:dataExtractionRules` или `android:allowBackup`. Типичные симптомы ошибки: `Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/your_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` :::note Эти изменения нужно вносить в директорию Android-платформы (обычно находится в папке `android/` вашего проекта). ::: Чтобы решить проблему, необходимо: - Указать механизму слияния манифестов использовать значения вашего приложения для атрибутов, связанных с резервным копированием. - Создать файлы правил резервного копирования, объединяющие правила Adapty с правилами других SDK. #### 1. Добавьте пространство имён `tools` в манифест В файле `AndroidManifest.xml` убедитесь, что корневой тег `<manifest>` включает tools: ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.app"> ... </manifest> ``` #### 2. Переопределите атрибуты резервного копирования в `<application>` В том же файле `AndroidManifest.xml` обновите тег `<application>`, чтобы ваше приложение предоставляло итоговые значения и указывало механизму слияния манифестов заменять значения библиотек: ```xml <application android:name=".App" android:allowBackup="true" android:fullBackupContent="@xml/sample_backup_rules" android:dataExtractionRules="@xml/sample_data_extraction_rules" tools:replace="android:fullBackupContent,android:dataExtractionRules"> ... </application> ``` Если какой-либо SDK также задаёт `android:allowBackup`, включите его в `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. Создайте объединённые файлы правил резервного копирования Создайте XML-файлы в директории `res/xml/` вашего Android-проекта, объединяющие правила Adapty с правилами других SDK. Android использует разные форматы правил резервного копирования в зависимости от версии ОС, поэтому создание обоих файлов обеспечивает совместимость со всеми версиями Android, которые поддерживает ваше приложение. :::note В примерах ниже в качестве стороннего SDK используется AppsFlyer. Замените или добавьте правила для других SDK, которые используются в вашем приложении. ::: **Для Android 12 и выше** (используется новый формат правил извлечения данных): ```xml title="sample_data_extraction_rules.xml" <?xml version="1.0" encoding="utf-8"?> <data-extraction-rules> <cloud-backup> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </cloud-backup> <device-transfer> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </device-transfer> </data-extraction-rules> ``` **Для Android 11 и ниже** (используется устаревший формат полного резервного копирования): ```xml title="sample_backup_rules.xml" <?xml version="1.0" encoding="utf-8"?> <full-backup-content> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> #### Покупки не выполняются после возврата из другого приложения на Android \{#purchases-fail-after-returning-from-another-app-in-android\} Если Activity, запускающий флоу покупки, использует нестандартный `launchMode`, Android может некорректно пересоздать или повторно использовать его при возврате пользователя из Google Play, банковского приложения или браузера. Это может привести к потере результата покупки или её трактовке как отменённой. Чтобы покупки работали корректно, используйте только режимы запуска `standard` или `singleTop` для Activity, который инициирует флоу покупки, и избегайте любых других режимов. В файле `AndroidManifest.xml` убедитесь, что Activity, запускающая флоу покупки, настроена на режим `standard` или `singleTop`: ```xml <activity android:name=".MainActivity" android:launchMode="standard" /> ``` #### Ошибки сборки Swift 6, вызванные переопределением SWIFT_VERSION в Podfile \{#swift-6-build-errors-caused-by-podfile-swift_version-override\} При сборке Flutter-приложения для iOS могут появляться ошибки компиляции Swift 6 в целевых объектах пода Adapty. Типичные симптомы: несоответствия `@Sendable` в `AdaptyUIBuilderLogic`, отсутствие соответствия `Sendable` у типов Adapty или ошибки изоляции акторов. Поды Adapty объявляют `s.swift_version = '6.0'` и требуют Swift 6 для сборки. Код вашего приложения может остаться на Swift 5 — только целевые поды Adapty (`Adapty`, `AdaptyUI`, `AdaptyUIBuilder`, `AdaptyLogger`, `AdaptyPlugin`) должны собираться с Swift 6. Наиболее частая причина — хук `post_install` в `ios/Podfile`, который перезаписывает `SWIFT_VERSION` для каждого целевого пода: ```ruby showLineNumbers title="ios/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` **Исправление**: Исключите pod-таргеты Adapty из переопределения: ```ruby showLineNumbers title="ios/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| next if %w[Adapty AdaptyUI AdaptyUIBuilder AdaptyLogger AdaptyPlugin].include?(target.name) target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` Затем выполните `pod install` из директории `ios/` и пересоберите проект. Для проверки откройте `ios/Pods/Pods.xcodeproj`, выберите таргет пода `Adapty` → **Build Settings** → **Swift Language Version**. Там должно быть указано **Swift 6**. --- # File: flutter-quickstart-paywalls --- --- title: "Включение покупок с помощью Flow Builder в Flutter SDK" description: "Быстрый старт по включению встроенных покупок с Adapty Flow Builder." --- Чтобы включить встроенные покупки, нужно разобраться в трёх ключевых концепциях: - [**Продукты**](product) – всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ) - [**Флоу**](adapty-flow-builder) – последовательности экранов, которые представляют продукты пользователям, созданные в no-code Flow Builder. SDK получает их через `getFlow`. Если вы предпочитаете строить UI в собственном коде, используйте пейвол — см. [Реализация пейволов вручную](flutter-quickstart-manual). - [**Плейсменты**](placements) – где и когда показывать флоу в приложении (например, `main`, `onboarding`, `settings`). Вы прикрепляете флоу к плейсментам в дашборде, а затем запрашиваете их по ID плейсмента в коде. Это упрощает проведение A/B-тестов и показ разных флоу разным пользователям. Adapty предлагает три способа подключить покупки в приложении. Выберите подходящий в зависимости от требований вашего приложения: | Реализация | Сложность | Когда использовать | |------------------------|------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Adapty Flow Builder | ✅ Легко | Вы [создаёте готовый к покупке флоу в no-code конструкторе](quickstart-paywalls). Adapty автоматически отрисовывает его и берёт на себя весь процесс покупки, валидацию чеков и управление подписками. | | Пейвол, созданный вручную | 🟡 Средне | Вы реализуете интерфейс пейвола в коде приложения, но всё равно получаете объект флоу из Adapty, сохраняя гибкость в управлении продуктами. См. [гайд](flutter-quickstart-manual). | | Observer mode | 🔴 Сложно | У вас уже есть собственная инфраструктура обработки покупок, и вы хотите продолжать её использовать. Обратите внимание, что observer mode имеет ряд ограничений в Adapty. См. [статью](observer-vs-full-mode). | :::important **Шаги ниже показывают, как реализовать флоу, созданный в Adapty Flow Builder.** Если вы предпочитаете строить UI пейвола самостоятельно, см. [Реализация пейволов вручную](flutter-quickstart-manual). ::: Чтобы отобразить флоу, созданный в Adapty Flow Builder, в коде приложения нужно сделать всего три вещи: 1. **Получить флоу**: запросить его из Adapty. 2. **Показать его — покупки Adapty обработает сам**: отобразить представление в приложении. 3. **Обработать действия кнопок**: связать взаимодействия пользователя с реакцией приложения на них. Например, открывать ссылки или закрывать флоу при нажатии кнопок. ## Перед началом работы \{#before-you-start\} Прежде чем приступить, выполните следующие шаги: 1. Подключите приложение к [App Store](initial_ios) и/или [Google Play](initial-android) в дашборде Adapty. 2. [Создайте продукты](create-product) в Adapty. 3. [Создайте флоу и добавьте в него продукты](create-paywall). 4. [Создайте плейсмент и добавьте в него флоу](create-placement). 5. [Установите и активируйте SDK](sdk-installation-flutter) в коде приложения. В этом гайде используются API Adapty Flutter SDK v4. :::tip Самый быстрый способ выполнить эти шаги — воспользоваться [гайдом по быстрому старту](quickstart) или создать пейволы и плейсменты с помощью [Developer CLI](developer-cli-quickstart). ::: ## 1. Получение флоу \{#1-get-the-flow\} Ваши флоу привязаны к плейсментам, настроенным в дашборде. Плейсменты позволяют показывать разные флоу для разных аудиторий или запускать [A/B-тесты](ab-tests). Чтобы получить флоу, созданное в Adapty Flow Builder, нужно: 1. Получить объект `flow` по ID [плейсмента](placements) с помощью метода `getFlow` и проверить, был ли он создан в билдере, используя свойство `hasViewConfiguration`. 2. Создать отображение флоу с помощью метода `createFlowView`. Отображение содержит элементы UI и стили, необходимые для показа флоу. :::important Чтобы получить конфигурацию вида, необходимо включить переключатель **Show on device** в билдере. В противном случае вы получите пустую конфигурацию вида, и флоу не отобразится. ::: ```dart showLineNumbers try { // the requested flow final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); final view = await AdaptyUI().createFlowView( flow: flow, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ## 2. Отобразите флоу \{#display-the-flow\} Теперь, когда у вас есть объект флоу, достаточно добавить несколько строк, чтобы его отобразить. Для отображения флоу вызовите метод `view.present()` на объекте `view`, созданном методом `createFlowView`. Каждый `view` можно показать только один раз: после закрытия он освобождается из памяти. Если нужно показать флоу снова, вызовите `createFlowView` ещё раз, чтобы создать новый экземпляр `view`. ```dart showLineNumbers title="Flutter" try { await view.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::tip Подробнее о том, как отобразить флоу, читайте в нашем [гайде](flutter-present-paywalls). ::: ## 3. Обработка действий кнопок \{#3-handle-button-actions\} Когда пользователи нажимают кнопки во флоу, Flutter SDK автоматически обрабатывает покупки, восстановление, закрытие экрана и открытие URL. Однако у других кнопок есть пользовательские или предустановленные ID, и обработку таких действий нужно реализовать в вашем коде. Чтобы управлять процессами на экране флоу или отслеживать их, реализуйте методы `AdaptyUIFlowsEventsObserver` и установите наблюдатель до показа любого экрана. Если пользователь выполнил какое-либо действие, будет вызван `flowViewDidPerformAction`, и ваше приложение должно отреагировать в зависимости от ID действия. Три метода наблюдателя **обязательны**: `flowViewDidFinishPurchase`, `flowViewDidFinishRestore` и `flowViewDidReceiveError` — без них класс не скомпилируется. :::tip Читайте наши гайды по обработке [действий](flutter-handle-paywall-actions) и [событий](flutter-handling-events) кнопок. ::: Реализуйте наблюдатель как отдельный долгоживущий объект, а не виджет. Поскольку во всём приложении используется единственный глобальный слот для наблюдателя, привязка его к `State` приведёт к утечке экрана (SDK хранит на него сильную ссылку) и молчаливой замене при регистрации следующего экрана. Использование `extends` также наследует поведение SDK по умолчанию, поэтому помимо трёх обязательных методов достаточно переопределить только нужные коллбэки. ```dart showLineNumbers title="Flutter" // A dedicated, long-lived handler for flow events. // It does NOT live inside a Widget/State, so it never leaks and is never // silently replaced when screens are pushed or popped. class FlowEventsHandler extends AdaptyUIFlowsEventsObserver { // A single, app-wide instance — same idiom as Adapty() and AdaptyUI(). static final FlowEventsHandler _instance = FlowEventsHandler._(); factory FlowEventsHandler() => _instance; FlowEventsHandler._(); // This method is called when user performs an action on the flow UI. // Overriding it replaces the default behavior (dismiss on close, open URLs), // so keep those cases if you want to preserve it. @override void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): // close the flow on the Android back button view.dismiss(); break; case OpenUrlAction(:final url, :final openIn): AdaptyUI().openUrl(url, openIn: openIn); break; default: break; } } // Required: decide what happens after a purchase finishes @override void flowViewDidFinishPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { if (purchaseResult is! AdaptyPurchaseResultUserCancelled) { view.dismiss(); } } // Required: dismiss the flow once a restore succeeds @override void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) { view.dismiss(); } // Required: handle rendering and other view errors @override void flowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) { print('Flow error: $error'); view.dismiss(); } } ``` Зарегистрируйте обработчик **один раз** при запуске приложения, до отображения любого флоу: ```dart showLineNumbers title="Flutter" AdaptyUI().setFlowsEventsObserver(FlowEventsHandler()); ``` ## Следующие шаги \{#next-steps\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Ваш пейвол готов к отображению в приложении. Протестируйте покупки в [песочнице App Store](test-purchases-in-sandbox) или [Google Play Store](testing-on-android), чтобы убедиться, что тестовая покупка через пейвол проходит успешно. Теперь нужно [проверить уровень доступа пользователей](flutter-check-subscription-status), чтобы показывать пейвол или открывать доступ к платным функциям только нужным пользователям. ## Полный пример \{#full-example\} Вот как все эти шаги можно объединить в вашем приложении. ```dart void main() { // Register a single, long-lived observer once, before any flow is shown. // It is intentionally a plain object (NOT a Widget/State): its lifetime is the // whole app, so it never leaks and is never silently replaced when screens are // pushed or popped. AdaptyUI().setFlowsEventsObserver(FlowEventsHandler()); runApp(MaterialApp(home: FlowScreen())); } /// A dedicated handler for AdaptyUI flow events. /// /// It `extends` [AdaptyUIFlowsEventsObserver] (rather than being implemented /// by a `State`), which gives you two things for free: /// * the SDK's sensible defaults for optional callbacks, so besides the three /// required methods you only override what you actually care about; /// * a lifecycle that is independent of the widget tree — there is no strong /// reference back into a `Widget`, so nothing leaks and there is nothing to /// unregister. /// /// Every callback receives the [AdaptyUIFlowView] it relates to, so handling /// flow actions never requires a `BuildContext` or widget state. class FlowEventsHandler extends AdaptyUIFlowsEventsObserver { // A single, app-wide instance — same idiom as Adapty() and AdaptyUI(). static final FlowEventsHandler _instance = FlowEventsHandler._(); factory FlowEventsHandler() => _instance; FlowEventsHandler._(); // Called when the user performs an action on the flow UI. @override void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): // close the flow on the Android back button view.dismiss(); break; case OpenUrlAction(:final url, :final openIn): // Open the URL natively, honoring the dashboard browser setting. AdaptyUI().openUrl(url, openIn: openIn); break; default: break; } } // Required: decide what happens after a purchase finishes. @override void flowViewDidFinishPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { if (purchaseResult is! AdaptyPurchaseResultUserCancelled) { view.dismiss(); } } // Required: dismiss the flow once a restore succeeds. @override void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) { view.dismiss(); } // Required: handle rendering and other view errors. @override void flowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) { print('Flow error: $error'); view.dismiss(); } } class FlowScreen extends StatefulWidget { const FlowScreen({super.key}); @override State<FlowScreen> createState() => _FlowScreenState(); } class _FlowScreenState extends State<FlowScreen> { @override void initState() { super.initState(); _showFlowIfNeeded(); } Future<void> _showFlowIfNeeded() async { try { final flow = await Adapty().getFlow( placementId: 'YOUR_PLACEMENT_ID', ); if (!flow.hasViewConfiguration) return; final view = await AdaptyUI().createFlowView(flow: flow); await view.present(); } catch (_) { // Handle any errors (network, SDK issues, etc.) } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Adapty Flow Example')), body: Center( // Add a button to re-trigger the flow for testing purposes. child: ElevatedButton( onPressed: _showFlowIfNeeded, child: const Text('Show Flow'), ), ), ); } } ``` --- # File: flutter-check-subscription-status --- --- title: "Проверка статуса подписки во Flutter SDK" description: "Узнайте, как проверить статус подписки в приложении на Flutter с помощью Adapty." --- Чтобы решить, может ли пользователь получить доступ к платному контенту или нужно показать ему пейвол, необходимо проверить его [уровень доступа](access-level) в профиле. В этой статье показано, как получить данные профиля и решить, что показать пользователю — пейвол или платный контент. ## Получение статуса подписки \{#get-subscription-status\} Когда нужно решить, показать пользователю пейвол или платный контент, вы проверяете его [уровень доступа](access-level) в профиле. Есть два варианта: - Вызвать `getProfile`, если нужны актуальные данные прямо сейчас (например, при запуске приложения) или требуется принудительное обновление. - Настроить **автоматическое обновление профиля**, чтобы хранить локальную копию, которая автоматически обновляется при изменении статуса подписки. ### Получение профиля \{#get-profile\} Самый простой способ узнать статус подписки — вызвать метод `getProfile`: ```dart showLineNumbers try { final profile = await Adapty().getProfile(); // check the access } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ### Отслеживание обновлений подписки \{#listen-to-subscription-updates\} Чтобы автоматически получать обновления профиля в приложении: 1. Используйте `Adapty().didUpdateProfileStream.listen()` для отслеживания изменений профиля — Adapty автоматически вызывает этот метод при изменении статуса подписки пользователя. 2. Сохраняйте обновлённые данные профиля при каждом вызове этого метода, чтобы использовать их в приложении без лишних сетевых запросов. ```dart class SubscriptionManager { AdaptyProfile? _currentProfile; SubscriptionManager() { // Listen for profile updates Adapty().didUpdateProfileStream.listen((profile) { _currentProfile = profile; // Update UI, unlock content, etc. }); } // Use stored profile instead of calling getProfile() bool hasAccess() { return _currentProfile?.accessLevels['premium']?.isActive ?? false; } } ``` :::note Adapty автоматически вызывает слушатель потока обновлений профиля при запуске приложения, предоставляя кешированные данные о подписке даже при отсутствии интернета. ::: ## Связь профиля с логикой пейвола \{#connect-profile-with-paywall-logic\} Когда нужно принимать мгновенные решения о показе пейволов или предоставлении доступа к платным функциям, можно напрямую проверить профиль пользователя. Это удобно, например, при запуске приложения, при переходе в премиум-разделы или перед показом определённого контента. ```dart Future<bool> _checkAccessLevel() async { try { final profile = await Adapty().getProfile(); return profile.accessLevels['YOUR_ACCESS_LEVEL']?.isActive ?? false; } catch (e) { print('Error checking access level: $e'); return false; // Show paywall if access check fails } } Future<void> _initializePaywall() async { await _loadPaywall(); final hasAccess = await _checkAccessLevel(); if (!hasAccess) { // Show paywall if no access } } ``` ## Следующие шаги \{#next-steps\} Теперь, когда вы знаете, как отслеживать статус подписки, узнайте, как [работать с профилями пользователей](flutter-quickstart-identify), чтобы они всегда имели доступ к тому, за что заплатили. --- # File: flutter-quickstart-identify --- --- title: "Идентификация пользователей в Flutter SDK" description: "Быстрый старт по настройке Adapty для управления встроенными покупками в Flutter." --- :::important Этот гайд для тех, у кого есть собственная система аутентификации. Здесь вы узнаете, как работать с профилями пользователей в Adapty, чтобы они соответствовали вашей системе аутентификации. ::: То, как вы управляете покупками пользователей, зависит от модели аутентификации в вашем приложении: - Если приложение не использует серверную аутентификацию и не хранит данные пользователей, см. [раздел об анонимных пользователях](#anonymous-users). - Если приложение использует (или будет использовать) серверную аутентификацию, см. [раздел об идентифицированных пользователях](#identified-users). **Ключевые понятия**: - **Профили** — это сущности, необходимые для работы SDK. Adapty создаёт их автоматически. - Они могут быть анонимными **(без customer user ID)** или идентифицированными **(с customer user ID)**. - Вы передаёте **customer user ID**, чтобы связать профили в Adapty с вашей внутренней системой аутентификации. Вот в чём разница между анонимными и идентифицированными пользователями: | | Анонимные пользователи | Идентифицированные пользователи | |-------------------------|-----------------------------------------------------------------|-------------------------------------------------------------------------------------------------------| | **Управление покупками** | Восстановление покупок на уровне стора | Сохранение истории покупок на всех устройствах через customer user ID | | **Управление профилем** | Новый профиль при каждой переустановке | Один и тот же профиль во всех сессиях и на всех устройствах | | **Хранение данных** | Данные анонимных пользователей привязаны к установке приложения | Данные идентифицированных пользователей сохраняются между установками приложения | ## Анонимные пользователи \{#anonymous-users\} Если у вас нет серверной аутентификации, **вам не нужно обрабатывать аутентификацию в коде приложения**: 1. Когда SDK активируется при первом запуске приложения, Adapty **создаёт новый профиль для пользователя**. 2. Когда пользователь совершает покупку в приложении, она **привязывается к его профилю в Adapty и его аккаунту в сторе**. 3. Когда пользователь **переустанавливает** приложение или устанавливает его на **новое устройство**, Adapty **создаёт новый анонимный профиль при активации**. 4. Если пользователь ранее совершал покупки в вашем приложении, по умолчанию они автоматически синхронизируются из App Store при активации SDK. Итак, при работе с анонимными пользователями новые профили создаются при каждой установке — но это не проблема, поскольку в аналитике Adapty можно [настроить, что считать новой установкой](general#4-installs-definition-for-analytics). Для анонимных пользователей нужно считать установки по **идентификаторам устройств**. В таком случае каждая установка приложения на устройство считается отдельной установкой, включая переустановки. ## Идентификация пользователей \{#identified-users\} Есть два способа идентифицировать пользователей в приложении: - [**При входе/регистрации:**](#during-loginsignup) Если пользователи входят уже после запуска приложения, вызовите `identify()` с customer user ID в момент аутентификации. - [**При активации SDK:**](#during-the-sdk-activation) Если customer user ID уже сохранён на момент запуска приложения, передайте его при вызове `activate()`. :::important По умолчанию, когда Adapty получает покупку от Customer User ID, который уже связан с другим Customer User ID, уровень доступа предоставляется обоим профилям — то есть оба получают платный доступ. Вы можете настроить это поведение так, чтобы платный доступ передавался от одного профиля к другому, или полностью отключить совместный доступ. Подробнее — в [статье](general#6-sharing-paid-access-between-user-accounts). ::: <img src="/assets/shared/img/identify-diagram.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### При входе/регистрации \{#during-loginsignup\} Если вы идентифицируете пользователей после запуска приложения (например, после входа в аккаунт или регистрации), используйте метод `identify`, чтобы задать их customer user ID. - Если **этот customer user ID ещё не использовался**, Adapty автоматически привяжет его к текущему профилю. - Если **этот customer user ID уже использовался для идентификации пользователя**, Adapty переключится на работу с профилем, связанным с этим customer user ID. :::important Идентификаторы пользователей (Customer User ID) должны быть уникальными для каждого пользователя. Если указать одно и то же значение, все пользователи будут считаться одним. ::: Всегда используйте `await` для `identify` перед вызовом других методов SDK. Параллельные вызовы приводят к ошибке `#3006 profileWasChanged` или работе с анонимным профилем. См. [Порядок вызовов во Flutter SDK](flutter-sdk-call-order). ```dart showLineNumbers try { await Adapty().identify(customerUserId); // Уникален для каждого пользователя } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ### При активации SDK \{#during-the-sdk-activation\} Если вы уже знаете customer user ID в момент активации SDK, передайте его прямо в метод `activate` — вызывать `identify` отдельно не нужно. Если вы знаете customer user ID, но передаёте его только после активации, это значит, что при активации Adapty создаст новый анонимный профиль и переключится на существующий лишь после вызова `identify`. Вы можете передать как существующий customer user ID (ранее уже использованный), так и новый. Если передать новый, то профиль, созданный при активации, будет автоматически привязан к этому customer user ID. :::note По умолчанию создание анонимных профилей не влияет на дашборды аналитики, так как установки считаются по идентификаторам устройств. Идентификатор устройства соответствует одной установке приложения из стора и пересоздаётся только после переустановки. Он не зависит от того, является ли установка первой или повторной, и от того, используется ли существующий пользовательский идентификатор. Создание профиля (при активации SDK или выходе из системы), вход в систему или обновление приложения без переустановки не генерируют дополнительные события установки. Если вы хотите считать установки по уникальным пользователям, а не по устройствам, перейдите в **App settings** и настройте [**Installs definition for analytics**](general#4-installs-definition-for-analytics). ::: ```dart showLineNumbers" try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID) // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. ); } catch (e) { // handle the error } ``` ### Выход пользователей из системы \{#log-users-out\} Если в вашем приложении есть кнопка выхода, используйте метод `logout`. :::important Выход из системы создаёт новый анонимный профиль для пользователя. ::: ```dart showLineNumbers try { await Adapty().logout(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle unknown error } ``` :::info Чтобы снова авторизовать пользователя в приложении, используйте метод `identify`. ::: ### Разрешить покупки без входа \{#allow-purchases-without-login\} Если ваши пользователи могут совершать покупки как до, так и после входа в приложение, необходимо убедиться, что после входа они сохранят доступ: 1. Когда неавторизованный пользователь совершает покупку, Adapty привязывает её к анонимному идентификатору профиля. 2. Когда пользователь входит в аккаунт, Adapty переключается на работу с идентифицированным профилем. - Если это новый customer user ID (например, покупка была совершена до регистрации), Adapty присваивает customer user ID текущему профилю, и вся история покупок сохраняется. - Если это существующий customer user ID (customer user ID уже привязан к профилю), после смены профиля нужно получить актуальный уровень доступа. Для этого можно либо вызвать [`getProfile`](flutter-check-subscription-status) сразу после идентификации, либо [подписаться на обновления профиля](flutter-check-subscription-status), чтобы данные синхронизировались автоматически. ## Дальнейшие шаги \{#next-steps\} Поздравляем! Вы реализовали логику встроенных покупок в своём приложении! Желаем вам успехов в монетизации! Чтобы получить от Adapty ещё больше пользы, изучите эти темы: - [**Тестирование**](troubleshooting-test-purchases): Убедитесь, что всё работает как ожидается - [**Онбординги**](flutter-onboardings): Вовлекайте пользователей с помощью онбордингов и повышайте удержание - [**Интеграции**](configuration): Интегрируйтесь с сервисами маркетинговой атрибуции и аналитики всего в одну строку кода - [**Настройка пользовательских атрибутов профиля**](flutter-setting-user-attributes): Добавляйте пользовательские атрибуты к профилям, создавайте сегменты и запускайте A/B-тесты или показывайте разные пейволы разным пользователям --- # File: adapty-sdk-integration-skill-flutter --- --- title: "Интеграция Adapty в Flutter-приложение с помощью навыка SDK integration" description: "Используйте навык adapty-sdk-integration для полноценной интеграции Adapty SDK в ваше Flutter-приложение с помощью AI-инструмента для написания кода." --- [Скилл adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) автоматизирует интеграцию Adapty от начала до конца: настройку дашборда, установку SDK, пейвол и проверку на каждом этапе. Он автоматически определяет вашу платформу и подгружает нужную документацию Adapty на каждом шаге. **Поддерживаемые инструменты**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Для установки выберите команду для своего инструмента. Полный список — в [README скилла](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex или любой другой инструмент** — используйте [skills CLI](https://skills.sh) (обратите внимание, что скиллы, установленные таким способом, не обновляются автоматически): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` Либо клонируйте репозиторий и скопируйте `skills/adapty-sdk-integration/` в директорию скиллов вашего инструмента. После установки запустите скилл в вашем проекте: ``` /adapty-sdk-integration ``` Скилл задаст несколько вопросов по настройке, а затем проведёт через настройку дашборда, установку SDK, пейвол и проверку. :::important Навык находится в бета-версии. Если он зависает или ведёт себя непредсказуемо, воспользуйтесь [пошаговым руководством по интеграции](adapty-cursor-flutter) — оно проведёт ваш AI-инструмент через каждый этап с нужной документацией. ::: [Скилл adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) автоматизирует интеграцию Adapty от начала до конца: настройку дашборда, установку SDK, пейвол и проверку на каждом этапе. Он автоматически определяет вашу платформу и подгружает нужную документацию Adapty на каждом шаге. **Поддерживаемые инструменты**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Для установки выберите команду для своего инструмента. Полный список — в [README скилла](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex или любой другой инструмент** — используйте [skills CLI](https://skills.sh) (обратите внимание, что скиллы, установленные таким способом, не обновляются автоматически): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` Либо клонируйте репозиторий и скопируйте `skills/adapty-sdk-integration/` в директорию скиллов вашего инструмента. После установки запустите скилл в вашем проекте: ``` /adapty-sdk-integration ``` Скилл задаст несколько вопросов по настройке, а затем проведёт через настройку дашборда, установку SDK, пейвол и проверку. --- # File: adapty-cursor-flutter --- --- title: "Интеграция Adapty во Flutter-приложение с помощью ИИ" description: "Пошаговое руководство по интеграции Adapty во Flutter-приложение с использованием Cursor, Context7, ChatGPT, Claude и других ИИ-инструментов." --- Этот гайд поможет шаг за шагом интегрировать Adapty в Flutter-приложение с помощью инструмента AI-кодинга — вы подаёте ему нужную документацию Adapty в нужном порядке. For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## Прежде чем начать: настройка дашборда \{#before-you-start-dashboard-setup\} Adapty требует некоторой настройки в дашборде до того, как вы напишете какой-либо код SDK. Это можно сделать с помощью интерактивного LLM-навыка или вручную через дашборд. ### Подход через skill (рекомендуется) \{#skill-approach-recommended\} Skill Adapty CLI позволяет LLM настроить приложение, продукты, уровни доступа, пейволы и плейсменты напрямую — без открытия дашборда на каждом шаге. Нужно только [подключить сторы](integrate-payments) в дашборде. ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` После добавления skill запустите `/adapty-cli` в агенте. Он проведёт вас через каждый шаг — в том числе подскажет, когда нужно открыть дашборд для подключения сторов. ### Подход через дашборд Если вы предпочитаете настраивать всё вручную, вот что нужно сделать до написания кода. Ваша LLM не может самостоятельно найти значения в дашборде — вам придётся предоставить их самостоятельно. 1. **Подключите сторы**: В дашборде Adapty перейдите в **App settings → General**. Подключите App Store и Google Play, если ваше Flutter-приложение поддерживает обе платформы. Это обязательное условие для работы покупок. [Подключить сторы](integrate-payments) 2. **Скопируйте публичный SDK-ключ**: В дашборде Adapty перейдите в **App settings → General** и найдите раздел **API keys**. В коде это строка, которую вы передаёте в конфигурацию Adapty. 3. **Создайте хотя бы один продукт**: В дашборде Adapty перейдите на страницу **Products**. В коде продукты не указываются напрямую — Adapty передаёт их через пейволы. [Добавить продукты](quickstart-products) 4. **Создайте пейвол и плейсмент**: в дашборде Adapty создайте пейвол на странице **Paywalls**, затем назначьте его на плейсмент на странице **Placements**. В коде идентификатор плейсмента — это строка, которую вы передаёте в `Adapty().getPaywall()`. [Создать пейвол](quickstart-paywalls) 5. **Настройте уровни доступа**: В дашборде Adapty настройте каждый продукт на странице **Products**. В коде проверяйте строку `profile.accessLevels['premium']?.isActive`. Уровень доступа `premium` по умолчанию подходит для большинства приложений. Если платящие пользователи получают доступ к разным функциям в зависимости от продукта (например, план `basic` и план `pro`), [создайте дополнительные уровни доступа](assigning-access-level-to-a-product) до начала разработки. :::tip Когда у вас есть все пять, можно писать код. Скажите своему LLM: «Мой публичный SDK-ключ — X, идентификатор плейсмента — Y», чтобы он сгенерировал корректный код инициализации и получения пейвола. ::: ### Настройте по мере готовности \{#set-up-when-ready\} Это не обязательно для начала разработки, но пригодится по мере развития интеграции: - **A/B-тесты**: настраиваются на странице **Placements**. Изменения в коде не нужны. [A/B-тесты](ab-tests) - **Дополнительные пейволы и плейсменты**: добавьте больше вызовов `getPaywall` с разными идентификаторами плейсментов. - **Интеграции аналитики**: настраиваются на странице **Integrations**. Процесс настройки зависит от конкретной интеграции. См. [интеграции аналитики](analytics-integration) и [интеграции атрибуции](attribution-integration). ## Передайте документацию Adapty своей LLM \{#feed-adapty-docs-to-your-llm\} ### Используйте Context7 (рекомендуется) \{#use-context7-recommended\} [Context7](https://context7.com) — это MCP-сервер, который даёт вашей LLM прямой доступ к актуальной документации Adapty. LLM автоматически загружает нужные документы исходя из вашего запроса — никаких ручных вставок ссылок. Context7 работает с **Cursor**, **Claude Code**, **Windsurf** и другими MCP-совместимыми инструментами. Чтобы настроить, запустите: ``` npx ctx7 setup ``` Команда определит ваш редактор и настроит сервер Context7. Для ручной настройки см. [репозиторий Context7 на GitHub](https://github.com/upstash/context7). После настройки ссылайтесь на библиотеку Adapty в своих запросах: ``` Use the adaptyteam/adapty-docs library to look up how to install the Flutter SDK ``` :::warning Несмотря на то что Context7 избавляет от необходимости вставлять ссылки на документацию вручную, порядок реализации важен. Следуйте [пошаговому руководству](#implementation-walkthrough) ниже, выполняя шаги строго по порядку. ::: ### Используйте документацию в формате обычного текста \{#use-plain-text-docs\} Любую документацию Adapty можно открыть как обычный текст в формате Markdown. Добавьте `.md` в конец URL или нажмите **Copy for LLM** под заголовком статьи. Например: [adapty-cursor-flutter.md](https://adapty.io/docs/ru/adapty-cursor-flutter.md). Каждый шаг [пошагового руководства по интеграции](#implementation-walkthrough) ниже содержит блок «Отправьте это своему LLM» со ссылками `.md` для вставки. Чтобы получить сразу несколько документов, смотрите [индексные файлы и платформенные подборки](#plain-text-doc-index-files) ниже. ## Пошаговое руководство по интеграции \{#implementation-walkthrough\} В этом гайде мы разберём интеграцию Adapty в порядке реализации. Для каждого этапа указаны нужные документы для передачи LLM, ожидаемый результат и типичные проблемы. ### Планирование интеграции \{#plan-your-integration\} Прежде чем переходить к коду, попросите LLM проанализировать ваш проект и составить план реализации. Если ваш AI-инструмент поддерживает режим планирования (например, план-режим в Cursor или Claude Code), используйте его — так LLM сможет изучить и структуру вашего проекта, и документацию Adapty до того, как начнёт писать код. Сообщите LLM, какой подход к покупкам вы используете — это определяет, какими гайдами он должен руководствоваться: - [**Adapty Paywall Builder**](adapty-paywall-builder): вы создаёте пейволы в no-code редакторе Adapty, а SDK отображает их автоматически. - [**Пейволы, созданные вручную**](flutter-making-purchases): вы строите собственный интерфейс пейвола в коде, но всё равно используете Adapty для получения продуктов и обработки покупок. - [**Режим Observer**](observer-vs-full-mode): вы сохраняете существующую инфраструктуру покупок и используете Adapty только для аналитики и интеграций. Не знаете, что выбрать? Прочитайте [сравнительную таблицу в разделе быстрого старта](flutter-quickstart-paywalls). ### Установка и настройка SDK \{#install-and-configure-the-sdk\} Добавьте зависимость Adapty SDK с помощью `flutter pub add` и активируйте её с вашим публичным ключом SDK. Это основа — без неё ничего не работает. **Гайд:** [Установка и настройка Adapty SDK](sdk-installation-flutter) Отправьте это в ваш LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/sdk-installation-flutter.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** Приложение собирается и запускается на iOS и Android. В консоли отладки есть лог активации Adapty. - **Частая проблема:** «Public API key is missing» → проверьте, что вы заменили плейсхолдер на реальный ключ из **App settings**. ::: ### Показ пейволов и обработка покупок \{#show-paywalls-and-handle-purchases\} Получите пейвол по ID плейсмента, отобразите его и обработайте события покупок. Нужные гайды зависят от того, как вы обрабатываете покупки. Тестируйте каждую покупку в песочнице по мере работы — не откладывайте на конец. Инструкции по настройке см. в разделе [Тестирование покупок в песочнице](test-purchases-in-sandbox). <Tabs groupId="paywall-approach"> <TabItem value="builder" label="Paywall Builder" default> **Гайды:** - [Включить покупки через пейволы (быстрый старт)](flutter-quickstart-paywalls) - [Получить пейволы Paywall Builder и их конфигурацию](flutter-get-pb-paywalls) - [Отобразить пейволы](flutter-present-paywalls) - [Обработать события пейвола](flutter-handling-events) - [Реагировать на действия кнопок](flutter-handle-paywall-actions) Отправь это своему LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/flutter-quickstart-paywalls.md - https://adapty.io/docs/ru/flutter-get-pb-paywalls.md - https://adapty.io/docs/ru/flutter-present-paywalls.md - https://adapty.io/docs/ru/flutter-handling-events.md - https://adapty.io/docs/ru/flutter-handle-paywall-actions.md ``` :::tip[Checkpoint] - **Ожидается:** Пейвол отображается с настроенными продуктами. Нажатие на продукт запускает диалог покупки в песочнице. - **Частая ошибка:** Пустой пейвол или ошибка `getPaywall` → проверьте, что ID плейсмента точно совпадает с указанным в дашборде, и что плейсменту назначена аудитория. ::: </TabItem> <TabItem value="manual" label="Ручные пейволы"> **Гайды:** - [Включить покупки в вашем кастомном пейволе (быстрый старт)](flutter-quickstart-manual) - [Получить пейволы и продукты](fetch-paywalls-and-products-flutter) - [Отобразить пейвол на основе Remote Config](present-remote-config-paywalls-flutter) - [Совершать покупки](flutter-making-purchases) - [Восстановить покупки](flutter-restore-purchase) Read these Adapty docs before writing code: - https://adapty.io/docs/ru/flutter-quickstart-manual.md - https://adapty.io/docs/ru/fetch-paywalls-and-products-flutter.md - https://adapty.io/docs/ru/present-remote-config-paywalls-flutter.md - https://adapty.io/docs/ru/flutter-making-purchases.md - https://adapty.io/docs/ru/flutter-restore-purchase.md :::tip[Checkpoint] - **Ожидаемый результат:** Ваш пользовательский пейвол отображает продукты, полученные из Adapty. Нажатие на продукт открывает диалог покупки в песочнице. - **Частая проблема:** Пустой массив продуктов → убедитесь, что в дашборде пейволу назначены продукты, а у плейсмента есть аудитория. ::: </TabItem> <TabItem value="observer" label="Observer mode"> **Гайды:** - [Обзор Observer mode](observer-vs-full-mode) - [Реализация Observer mode](implement-observer-mode-flutter) - [Отправка транзакций в Observer mode](report-transactions-observer-mode-flutter) Отправьте это своему LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/observer-vs-full-mode.md - https://adapty.io/docs/ru/implement-observer-mode-flutter.md - https://adapty.io/docs/ru/report-transactions-observer-mode-flutter.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После покупки в песочнице через ваш существующий флоу покупки транзакция появляется в **Event Feed** дашборда Adapty. - **Подводный камень:** Если событий нет — убедитесь, что вы передаёте транзакции в Adapty и что серверные уведомления настроены для обоих сторов. ::: </TabItem> </Tabs> ### Проверка статуса подписки \{#check-subscription-status\} После покупки проверьте профиль пользователя на наличие активного уровня доступа, чтобы ограничить доступ к премиум-контенту. **Гайд:** [Проверка статуса подписки](flutter-check-subscription-status) Отправьте это своему LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/flutter-check-subscription-status.md ``` :::tip[Контрольная точка] - **Ожидаемый результат:** После покупки в песочнице `profile.accessLevels['premium']?.isActive` возвращает `true`. - **Частая проблема:** Пустой `accessLevels` после покупки → проверьте, что продукту назначен уровень доступа в дашборде. ::: ### Идентификация пользователей \{#identify-users\} Свяжите аккаунты пользователей вашего приложения с профилями Adapty, чтобы покупки сохранялись на всех устройствах. :::important Пропустите этот шаг, если в вашем приложении нет авторизации. ::: **Гайд:** [Идентификация пользователей](flutter-quickstart-identify) Отправьте это в ваш LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/flutter-quickstart-identify.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После вызова `Adapty().identify()` в разделе **Profiles** дашборда отображается ваш пользовательский ID. - **Важно:** Вызывайте `identify` после активации, но до загрузки пейволов — иначе события могут привязаться к анонимному профилю. ::: ### Подготовка к релизу Когда интеграция заработает в песочнице, пройдитесь по чеклисту релиза и убедитесь, что всё готово к продакшену. **Гайд:** [Чеклист релиза](release-checklist) Отправьте это в свой LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/ru/release-checklist.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** Все пункты чеклиста подтверждены: подключение сторов, серверные уведомления, флоу покупки, проверки уровня доступа и требования конфиденциальности. - **Возможная проблема:** Отсутствуют серверные уведомления → настройте App Store Server Notifications в **App settings → iOS SDK** и Google Play Real-Time Developer Notifications в **App settings → Android SDK**. ::: ## Индексные файлы простого текста \{#plain-text-doc-index-files\} Если вам нужно дать вашему LLM более широкий контекст, выходящий за рамки отдельных страниц, мы размещаем индексные файлы, которые перечисляют или объединяют всю документацию Adapty: - [`llms.txt`](https://adapty.io/docs/ru/llms.txt): Список всех страниц со ссылками в формате `.md`. [Формирующийся стандарт](https://llmstxt.org/) для обеспечения доступности сайтов для LLM. Обратите внимание, что для некоторых AI-агентов (например, ChatGPT) нужно скачать `llms.txt` и загрузить файл в чат. - [`llms-full.txt`](https://adapty.io/docs/ru/llms-full.txt): Вся документация Adapty, объединённая в один файл. Очень большой — используйте только когда нужна полная картина. - Flutter-специфичные [`flutter-llms.txt`](https://adapty.io/docs/ru/flutter-llms.txt) и [`flutter-llms-full.txt`](https://adapty.io/docs/ru/flutter-llms-full.txt): Подмножества документации для конкретной платформы, позволяющие сэкономить токены по сравнению с полным сайтом. --- # File: flutter-get-pb-paywalls --- --- title: "Получение флоу и пейволов — Flutter" description: "Получите флоу и пейволы из Adapty в вашем Flutter-приложении." --- <SDKv4> <MethodPromo method="getFlow" /> После того как вы [разработали флоу или пейвол в Paywall Builder](adapty-paywall-builder), его можно отобразить в мобильном приложении. Первый шаг — получить флоу или пейвол, связанный с плейсментом, вместе с конфигурацией его отображения, как описано ниже. Обратите внимание, что этот раздел посвящён флоу и пейволам, созданным в Paywall Builder. Если вы реализуете пейволы вручную, обратитесь к разделу [Получение пейволов и продуктов для пейволов с Remote Config в мобильном приложении](fetch-paywalls-and-products-flutter). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Прежде чем начать отображать флоу и пейволы в вашем мобильном приложении (нажмите, чтобы раскрыть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте флоу/пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них флоу/пейвол](create-placement) в дашборде Adapty. 4. Установите [Adapty SDK](sdk-installation-flutter) в своём мобильном приложении. </details> ## Получение флоу/пейвола \{#fetch-flowpaywall\} Если вы создали флоу или пейвол с помощью Flow Builder или Paywall Builder, вам не нужно беспокоиться о его отображении в коде мобильного приложения. Такой флоу или пейвол содержит всё необходимое: и то, что должно отображаться, и то, как это должно выглядеть. Тем не менее вам нужно получить его ID через плейсмент, настроить конфигурацию отображения и затем показать его в мобильном приложении. Загрузите флоу или пейвол и создайте его [представление](flutter-get-pb-paywalls#fetch-the-view-configuration) как можно раньше — желательно задолго до его отображения. Метод `createFlowView` загружает конфигурацию представления и начинает скачивать и кешировать изображения в фоне. Чем раньше вы его вызовете, тем больше времени останется на завершение загрузок. К моменту показа флоу или пейвола его конфигурация и изображения уже могут быть закешированы и готовы к отображению. Чтобы получить флоу или пейвол, используйте метод `getFlow`: ```dart showLineNumbers try { final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); // запрошенный флоу/пейвол } on AdaptyError catch (adaptyError) { // обработка ошибки } catch (e) { // обработка ошибки } ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кэшированные данные в случае ошибки. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают самые актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `.returnCacheDataElseLoad`, чтобы возвращать кэшированные данные, если они существуют. В этом случае пользователи могут не получать самые последние данные, но загрузка будет быстрее вне зависимости от качества соединения. Кэш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание: кэш сохраняется при перезапуске приложения и очищается только при его переустановке или ручной очистке.</p><p></p><p>Adapty SDK хранит пейволы локально в двух слоях: регулярно обновляемый кэш, описанный выше, и [резервные пейволы](fallback-paywalls). Для более быстрой загрузки пейволов также используется CDN и отдельный резервный сервер на случай недоступности CDN. Такая система гарантирует, что вы всегда получаете актуальную версию пейволов, обеспечивая надёжность даже при слабом интернет-соединении.</p> | | **loadTimeout** | по умолчанию: 5 сек | <p>`Duration`, ограничивающий таймаут этого метода. При достижении таймаута будут возвращены кэшированные данные или локальный резервный вариант.</p><p>Обратите внимание: в редких случаях метод может завершиться с задержкой относительно значения, указанного в `loadTimeout`, поскольку операция может включать несколько запросов под капотом.</p> | ## Параметры ответа \{#response-parameters\} | Параметр | Описание | | :-------- |:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Flow | Объект `AdaptyFlow` с идентификаторами флоу (`instanceIdentity`, `variationId`), именем, плейсментом, вариантами пейволов (`paywalls`) и Remote Config (`remoteConfigs`). | ## Получение конфигурации экрана \{#fetch-the-view-configuration\} :::important Убедитесь, что в конструкторе включён переключатель **Show on device**. Если он не активирован, конфигурация экрана не будет доступна для получения. ::: Если плейсмент был создан в **Flow Builder** или **Paywall Builder**, Adapty самостоятельно отрисовывает UI — свойство `hasViewConfiguration` полученного флоу равно `true`. Создайте представление с помощью `createFlowView`, затем [отобразите флоу или пейвол](flutter-present-paywalls). Если плейсмент — это кастомный пейвол без UI Builder (`hasViewConfiguration` равно `false`), [обработайте его как пейвол с Remote Config](present-remote-config-paywalls-flutter). :::warning Результат метода `createFlowView` можно использовать для отображения только один раз. Если нужно показать его снова, вызовите метод `createFlowView` заново. ::: ```dart showLineNumbers try { final view = await AdaptyUI().createFlowView(flow: flow); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` Параметры: | Параметр | Наличие | Описание | | :------------------- | :------- | :----------------------------------------------------------- | | **flow** | обязательный | Объект `AdaptyFlow` для получения представления нужного флоу/пейвола. | | **customTags** | необязательный | Задайте карту пользовательских тегов и их значений. Пользовательские теги служат плейсхолдерами в контенте и динамически заменяются конкретными строками для персонализации контента во флоу/пейволе. Подробнее см. в разделе [Пользовательские теги в Paywall Builder](custom-tags-in-paywall-builder). | | **preloadProducts** | необязательный | Включите, чтобы оптимизировать время отображения продуктов на экране. При значении `true` AdaptyUI автоматически загрузит необходимые продукты. По умолчанию: `false`. | | **loadTimeout** | необязательный | Значение типа `Duration`, ограничивающее время загрузки конфигурации представления. При истечении таймаута будут использованы кешированные данные или локальный резервный вариант. | :::note Если вы используете несколько языков, узнайте, как добавить [локализацию флоу](add-paywall-locale-in-adapty-paywall-builder) и как правильно использовать коды локалей [здесь](flutter-localizations-and-locale-codes). ::: После того как вы получили представление, [отобразите флоу/пейвол](flutter-present-paywalls). ## Получение флоу или пейвола для аудитории по умолчанию ради ускорения загрузки \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} Как правило, флоу и пейволы загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и плейсментов, а у пользователей слабое интернет-соединение, загрузка флоу или пейвола может занять больше времени, чем хотелось бы. В таких ситуациях имеет смысл показывать флоу или пейвол по умолчанию — это обеспечит плавный пользовательский опыт вместо пустого экрана. Чтобы решить эту проблему, используйте метод `getFlowForDefaultAudience`, который получает флоу или пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать флоу или пейвол через метод `getFlow`, как описано в разделе [Получение флоу/пейвола](#fetch-flowpaywall) выше. :::warning Почему мы рекомендуем использовать `getFlow` Метод `getFlowForDefaultAudience` имеет ряд существенных недостатков: - **Потенциальные проблемы обратной совместимости**: если вам нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать пейволы с поддержкой текущей (устаревшей) версии, либо мириться с тем, что пользователи этой версии могут столкнуться с нерендеренными пейволами. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, настроенный для аудитории **All Users**, — это означает потерю персонализированного таргетинга (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вы готовы принять эти недостатки ради более быстрой загрузки флоу или пейвола, используйте метод `getFlowForDefaultAudience` следующим образом. В противном случае используйте `getFlow`, описанный [выше](#fetch-flowpaywall). ::: ```dart showLineNumbers try { final flow = await Adapty().getFlowForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); // the requested flow/paywall } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кэшированные данные в случае неудачи. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают самые актуальные данные.</p><p></p><p>Однако если вы считаете, что у ваших пользователей нестабильный интернет, рассмотрите вариант `.returnCacheDataElseLoad` — он возвращает кэшированные данные, если они есть. В этом случае пользователи могут не получить самые последние данные, зато загрузка будет быстрее, независимо от качества соединения. Кэш регулярно обновляется, поэтому его безопасно использовать в течение сессии для сокращения числа сетевых запросов.</p><p></p><p>Обратите внимание: кэш сохраняется при перезапуске приложения и очищается только при его переустановке или при ручной очистке.</p> | ## Настройка ассетов \{#customize-assets\} Чтобы настроить изображения и видео в своём флоу/пейволе, используйте кастомные ассеты. У изображений-героев и видео-героев есть предопределённые ID: `hero_image` и `hero_video`. В бандле кастомных ассетов вы обращаетесь к этим элементам по их ID и настраиваете их поведение. Для остальных изображений и видео необходимо [задать кастомный ID](custom-media) в дашборде Adapty. Например, вы можете: - Показывать разное изображение или видео отдельным пользователям. - Показывать локальное превью-изображение, пока загружается основное удалённое изображение. - Показывать превью-изображение перед воспроизведением видео. Вот пример того, как можно передать пользовательские ресурсы через простой словарь: ```dart final customAssets = { // Show a local image using a custom ID 'custom_image': AdaptyCustomAsset.localImageAsset( assetId: 'assets/images/image_name.png', ), // Show a local video with a preview image 'hero_video': AdaptyCustomAsset.localVideoAsset( assetId: 'assets/videos/custom_video.mp4', ), }; try { final view = await AdaptyUI().createFlowView( flow: flow, customAssets: customAssets, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::note Если ресурс не найден, флоу/пейвол вернётся к внешнему виду по умолчанию. ::: ## Настройка таймеров, определяемых разработчиком \{#set-up-developer-defined-timers\} Чтобы использовать кастомные таймеры в мобильном приложении, передайте карту `customTimers` в метод `createFlowView`. Каждый ключ карты — это идентификатор таймера, а значение — объект `DateTime`, определяющий момент окончания таймера. Пример: ```dart showLineNumbers try { final view = await AdaptyUI().createFlowView( flow: flow, customTimers: { 'CUSTOM_TIMER_6H': DateTime.now().add(const Duration(seconds: 3600 * 6)), 'CUSTOM_TIMER_NY': DateTime(2027, 1, 1), // New Year 2027 }, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` В этом примере `CUSTOM_TIMER_NY` и `CUSTOM_TIMER_6H` — это **Timer ID** таймеров, заданных разработчиком в дашборде Adapty. Словарь `customTimers` обеспечивает динамическое обновление каждого таймера с нужным значением. Например: - `CUSTOM_TIMER_NY`: время, оставшееся до конца отсчёта таймера, например до Нового года. - `CUSTOM_TIMER_6H`: время, оставшееся в 6-часовом периоде, который начался, когда пользователь открыл флоу. </SDKv4> <SDKv3> После того как вы [создали визуальную часть пейвола](adapty-paywall-builder) в новом Paywall Builder на дашборде Adapty, вы можете отобразить его в мобильном приложении. Первый шаг — получить пейвол, связанный с плейсментом, и его конфигурацию отображения, как описано ниже. :::warning Новый Paywall Builder работает с Flutter SDK версии 3.3.0 и выше. ::: Обратите внимание, что этот раздел посвящён пейволам, настроенным с помощью Paywall Builder. Если вы реализуете пейволы вручную, обратитесь к разделу [Получение пейволов и продуктов для Remote Config пейволов в вашем мобильном приложении](fetch-paywalls-and-products-flutter). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Прежде чем начать отображать пейволы в вашем мобильном приложении (нажмите, чтобы развернуть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них пейвол](create-placement) в дашборде Adapty. 4. Установите [Adapty SDK](sdk-installation-flutter) в своё мобильное приложение. </details> ## Получение пейвола, созданного в Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\} Если вы [создали пейвол с помощью Paywall Builder](adapty-paywall-builder), вам не нужно беспокоиться о его отображении в коде мобильного приложения. Такой пейвол содержит как то, что должно быть показано, так и то, как именно это должно быть показано. Тем не менее, вам нужно получить его ID через плейсмент, конфигурацию отображения, а затем показать пейвол в вашем мобильном приложении. Для обеспечения оптимальной производительности крайне важно получать пейвол и его [конфигурацию отображения](flutter-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) как можно раньше, чтобы изображения успели загрузиться до того, как пользователь увидит пейвол. Для получения пейвола используйте метод `getPaywall`: ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en"); // the requested paywall } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Параметры: | Параметр | Обязательность | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы задаёте при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-paywall-locale-in-adapty-paywall-builder). Ожидается языковой код, состоящий из одного или двух подтегов, разделённых символом минус (**-**). Первый подтег указывает язык, второй — регион.</p><p></p><p>Пример: `en` означает английский, `pt-br` — бразильский португальский.</p><p>Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](flutter-localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное соединение, рассмотрите использование `.returnCacheDataElseLoad` — оно возвращает кешированные данные при их наличии. В этом случае пользователи могут не получить самые свежие данные, но загрузка будет заметно быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в рамках сессии для сокращения числа сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при переустановке или ручной очистке.</p><p></p><p>Adapty SDK хранит пейволы локально в двух слоях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](fallback-paywalls). Для ускорения загрузки используется CDN, а при его недоступности — отдельный резервный сервер. Такая система обеспечивает получение актуальных пейволов и надёжность даже при нестабильном интернете.</p> | | **loadTimeout** | по умолчанию: 5 сек | <p>Ограничивает таймаут для этого метода. При истечении таймаута возвращаются кешированные данные или локальный резервный вариант.</p><p>Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения `loadTimeout`, так как операция может включать несколько запросов под капотом.</p><p>Для Android: `TimeInterval` можно создать с помощью функций-расширений (например, `5.seconds`, где `.seconds` импортируется из `import com.adapty.utils.seconds`) или через `TimeInterval.seconds(5)`. Чтобы убрать ограничение, используйте `TimeInterval.INFINITE`.</p> | Параметры ответа: | Параметр | Описание | | :-------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Объект [`AdaptyPaywall`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html) со списком идентификаторов продуктов, идентификатором пейвола, Remote Config и рядом других свойств. | ## Получение конфигурации отображения пейвола, созданного в Paywall Builder \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important Убедитесь, что в Paywall Builder включён переключатель **Show on device**. Если эта опция не активирована, конфигурация отображения не будет доступна для получения. ::: После получения пейвола проверьте, содержит ли он `ViewConfiguration` — это означает, что пейвол был создан с помощью Paywall Builder. Это подскажет вам, как отображать пейвол. Если `ViewConfiguration` присутствует, обрабатывайте его как пейвол Paywall Builder; если нет, [обрабатывайте его как пейвол с Remote Config](present-remote-config-paywalls-flutter). ```dart showLineNumbers try { final view = await AdaptyUI().createPaywallView( paywall: paywall, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` Когда представление готово, [покажите пейвол](flutter-present-paywalls). ## Получение пейвола для аудитории по умолчанию для более быстрой загрузки \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} Как правило, пейволы загружаются практически мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и пейволов, а интернет-соединение у пользователей нестабильное, загрузка пейвола может занять больше времени, чем хотелось бы. В таких случаях имеет смысл показывать пейвол для аудитории по умолчанию — это обеспечит плавный пользовательский опыт вместо ситуации, когда пейвол не отображается вовсе. Чтобы решить эту задачу, вы можете использовать метод `getPaywallForDefaultAudience`, который загружает пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — загружать пейвол методом `getPaywall`, как описано в разделе [Получение информации о пейволе](flutter-get-pb-paywalls#fetch-paywall-designed-with-paywall-builder) выше. :::warning Почему мы рекомендуем использовать `getPaywall` Метод `getPaywallForDefaultAudience` имеет ряд существенных недостатков: - **Возможные проблемы обратной совместимости**: если вам нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть трудности. Придётся либо создавать пейволы, совместимые с текущей (устаревшей) версией, либо смириться с тем, что пользователи на текущей (устаревшей) версии могут столкнуться с нерендерящимися пейволами. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, созданный для аудитории **All Users**, — то есть вы теряете персонализированный таргетинг (включая таргетинг по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вы готовы принять эти ограничения ради более быстрой загрузки пейвола, используйте метод `getPaywallForDefaultAudience` следующим образом. В противном случае придерживайтесь метода `getPaywall`, описанного [выше](#fetch-paywall-designed-with-paywall-builder). ::: ```dart showLineNumbers try { final paywall = await Adapty().getPaywallForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` :::note Метод `getPaywallForDefaultAudience` доступен начиная с Flutter SDK версии 3.2.0. ::: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение, которое вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается, что этот параметр будет языковым кодом, состоящим из одного или нескольких подтегов, разделённых символом минуса (**-**). Первый подтег — язык, второй — регион.</p><p></p><p>Пример: `en` означает английский, `pt-br` — бразильский португальский.</p><p></p><p>Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кэшированные данные в случае ошибки. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `.returnCacheDataElseLoad` — оно вернёт кэшированные данные, если они есть. В этом случае пользователи могут не получить самые последние данные, но загрузка будет быстрее независимо от качества соединения. Кэш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание, что кэш сохраняется при перезапуске приложения и очищается только при переустановке приложения или вручную.</p> | ## Настройка ресурсов \{#customize-assets\} Чтобы настроить изображения и видео на пейволе, реализуйте пользовательские ресурсы. Для изображений-заголовков и видео-заголовков есть предопределённые идентификаторы: `hero_image` и `hero_video`. В пакете пользовательских ресурсов вы обращаетесь к этим элементам по их идентификаторам и настраиваете их поведение. Для других изображений и видео нужно [задать пользовательский идентификатор](custom-media) в дашборде Adapty. Например, вы можете: - Показывать разные изображения или видео разным пользователям. - Показывать локальное превью, пока загружается основное удалённое изображение. - Показывать превью перед запуском видео. :::important Чтобы использовать эту функцию, обновите Flutter SDK Adapty до версии 3.8.0 или выше. ::: Вот пример того, как можно передавать пользовательские ресурсы через простой словарь: ```dart final customAssets = { // Show a local image using a custom ID 'custom_image': AdaptyCustomAsset.localImageAsset( assetId: 'assets/images/image_name.png', ), // Show a local video with a preview image 'hero_video': AdaptyCustomAsset.localVideoAsset( assetId: 'assets/videos/custom_video.mp4', ), }; try { final view = await AdaptyUI().createPaywallView( paywall: paywall, customAssets: customAssets, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::note Если ресурс не найден, пейвол вернётся к внешнему виду по умолчанию. ::: ## Настройка таймеров, определяемых разработчиком \{#set-up-developer-defined-timers\} Чтобы использовать пользовательские таймеры в мобильном приложении, передайте словарь `customTimers` в метод `createPaywallView`. Каждый ключ словаря — это идентификатор таймера, а значение — объект `DateTime`, определяющий момент окончания таймера. Пример: ```dart showLineNumbers try { final view = await AdaptyUI().createPaywallView( paywall: paywall, customTimers: { 'CUSTOM_TIMER_6H': DateTime.now().add(const Duration(seconds: 3600 * 6)), 'CUSTOM_TIMER_NY': DateTime(2025, 1, 1), // New Year 2025 }, ); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` В этом примере `CUSTOM_TIMER_NY` и `CUSTOM_TIMER_6H` — это **Timer ID** пользовательских таймеров, которые вы задали в дашборде Adapty. Словарь `customTimers` гарантирует, что приложение динамически обновит каждый таймер с нужным значением. Например: - `CUSTOM_TIMER_NY`: время до окончания таймера, например до Нового года. - `CUSTOM_TIMER_6H`: оставшееся время в 6-часовом периоде, который начался, когда пользователь открыл пейвол. </SDKv3> --- # File: flutter-present-paywalls --- --- title: "Отображение флоу и пейволов — Flutter" description: "Отображайте флоу и пейволы в Flutter-приложениях с помощью функций монетизации Adapty." --- <SDKv4> Если вы создали флоу или пейвол с помощью Flow Builder или Paywall Builder, вам не нужно беспокоиться о том, как отрисовать его в коде мобильного приложения для отображения пользователю. Такой флоу или пейвол содержит и то, что должно быть показано, и то, как это должно выглядеть. :::warning Этот гайд предназначен для флоу и пейволов, созданных в Paywall Builder. Информацию о представлении **пейволов на основе Remote Config** см. в разделе [Отрисовка пейвола, созданного через Remote Config](present-remote-config-paywalls-flutter). ::: Adapty Flutter SDK предоставляет два способа отображения флоу и пейволов: - **Отдельный экран** - **Встроенный виджет** ## Отображение как отдельного экрана \{#present-as-standalone-screen\} Чтобы отобразить флоу или пейвол как отдельный экран, вызовите метод `view.present()` на объекте `view`, созданном методом [`createFlowView`](flutter-get-pb-paywalls#fetch-the-view-configuration). Каждый `view` можно показать только один раз: после закрытия он освобождается из памяти. Если нужно показать флоу или пейвол снова, вызовите `createFlowView` ещё раз, чтобы создать новый экземпляр `view`. ```dart showLineNumbers title="Flutter" try { await view.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Закрытие флоу или пейвола \{#dismiss-the-flow-or-paywall\} Чтобы программно закрыть флоу или пейвол, используйте метод `dismiss()`: ```dart showLineNumbers title="Flutter" try { await view.dismiss(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::note После закрытия вью освобождается из памяти — повторно отобразить его невозможно. Создайте новый с помощью `createFlowView`. ::: ### Показать диалог \{#show-dialog\} Используйте этот метод вместо стандартных диалоговых окон, когда на Android отображается флоу или пейвол. На Android обычные алерты появляются позади вью, из-за чего пользователи их не видят. Этот метод гарантирует корректное отображение диалога поверх флоу или пейвола на всех платформах. ```dart showLineNumbers title="Flutter" try { final action = await view.showDialog( title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', ); if (action == AdaptyUIDialogActionType.secondary) { // User confirmed - close the paywall await view.dismiss(); } // If primary - do nothing, user stays } catch (e) { // handle error } ``` ### Настройка стиля представления для iOS \{#configure-ios-presentation-style\} Настройте способ отображения флоу или пейвола на iOS, передав параметр `iosPresentationStyle` в метод `present()`. Параметр принимает значения `AdaptyUIIOSPresentationStyle.fullScreen` (по умолчанию) или `AdaptyUIIOSPresentationStyle.pageSheet`. ```dart showLineNumbers try { await view.present(iosPresentationStyle: AdaptyUIIOSPresentationStyle.pageSheet); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ## Встраивание в иерархию виджетов \{#embed-in-widget-hierarchy\} Чтобы встроить флоу или пейвол в существующее дерево виджетов, используйте виджет `AdaptyUIFlowPlatformView` напрямую в иерархии виджетов Flutter. ```dart showLineNumbers title="Flutter" AdaptyUIFlowPlatformView( flow: flow, // The flow object you fetched onDidAppear: (view) { }, onDidDisappear: (view) { }, onDidPerformAction: (view, action) { }, onDidSelectProduct: (view, productId) { }, onDidStartPurchase: (view, product) { }, onDidFinishPurchase: (view, product, purchaseResult) { }, onDidFailPurchase: (view, product, error) { }, onDidStartRestore: (view) { }, onDidFinishRestore: (view, profile) { }, onDidFailRestore: (view, error) { }, onDidReceiveError: (view, error) { }, onDidFailLoadingProducts: (view, error) { }, onDidFinishWebPaymentNavigation: (view, product, error) { }, ) ``` :::note Чтобы платформенное представление Android работало корректно, убедитесь, что ваш `MainActivity` расширяет `FlutterFragmentActivity`: ```kotlin showLineNumbers title="Kotlin" class MainActivity : FlutterFragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } ``` ::: </SDKv4> <SDKv3> Если вы настроили пейвол с помощью Paywall Builder, вам не нужно беспокоиться о его отрисовке в коде мобильного приложения для показа пользователю. Такой пейвол содержит как то, что должно отображаться, так и то, как именно это должно отображаться. :::warning Этот гайд предназначен только для **пейволов на основе нового Paywall Builder**, которые требуют SDK v3.2.0 или выше. Процесс отображения пейволов различается в зависимости от версии Paywall Builder и Remote Config пейволов. - Для отображения **Remote Config пейволов** см. [Отображение пейвола, созданного с помощью Remote Config](present-remote-config-paywalls-flutter). ::: Adapty Flutter SDK предоставляет два способа отображения пейволов: - **Отдельный экран** - **Встроенный виджет** ## Отображение как отдельного экрана \{#present-as-standalone-screen\} Чтобы отобразить пейвол как отдельный экран, вызовите метод `view.present()` на объекте `view`, созданном методом [`createPaywallView`](flutter-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). Каждый `view` можно использовать только один раз. Если нужно показать пейвол повторно, снова вызовите `createPaywallView`, чтобы создать новый экземпляр `view`. :::warning Повторное использование одного и того же `view` без его пересоздания может привести к ошибке `AdaptyUIError.viewAlreadyPresented`. ::: ```dart showLineNumbers title="Flutter" try { await view.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Закрытие пейвола \{#dismiss-the-paywall\} Чтобы программно закрыть пейвол, используйте метод `dismiss()`: ```dart showLineNumbers title="Flutter" try { await view.dismiss(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### Показ диалога \{#show-dialog\} Используйте этот метод вместо стандартных диалогов на Android, когда отображается пейвол. На Android обычные алёрты появляются за пейволом и становятся невидимыми для пользователей. Этот метод обеспечивает корректное отображение диалога поверх пейвола на всех платформах. ```dart showLineNumbers title="Flutter" try { final action = await view.showDialog( title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', ); if (action == AdaptyUIDialogActionType.secondary) { // User confirmed - close the paywall await view.dismiss(); } // If primary - do nothing, user stays } catch (e) { // handle error } ``` ### Настройка стиля отображения на iOS \{#configure-ios-presentation-style\} Настройте способ отображения пейвола на iOS, передав параметр `iosPresentationStyle` в метод `present()`. Параметр принимает значения `AdaptyUIIOSPresentationStyle.fullScreen` (по умолчанию) или `AdaptyUIIOSPresentationStyle.pageSheet`. ```dart showLineNumbers try { await view.present(iosPresentationStyle: AdaptyUIIOSPresentationStyle.pageSheet); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ## Встраивание в иерархию виджетов \{#embed-in-widget-hierarchy\} Чтобы встроить пейвол в существующее дерево виджетов, используйте виджет `AdaptyUIPaywallPlatformView` напрямую в иерархии виджетов Flutter. ```dart showLineNumbers title="Flutter" AdaptyUIPaywallPlatformView( paywall: paywall, // The paywall object you fetched onDidAppear: (view) { }, onDidDisappear: (view) { }, onDidPerformAction: (view, action) { }, onDidSelectProduct: (view, productId) { }, onDidStartPurchase: (view, product) { }, onDidFinishPurchase: (view, product, purchaseResult) { }, onDidFailPurchase: (view, product, error) { }, onDidStartRestore: (view) { }, onDidFinishRestore: (view, profile) { }, onDidFailRestore: (view, error) { }, onDidFailRendering: (view, error) { }, onDidFailLoadingProducts: (view, error) { }, onDidFinishWebPaymentNavigation: (view, product, error) { }, ) ``` :::note Чтобы Android platform view работал корректно, убедитесь, что ваш `MainActivity` расширяет `FlutterFragmentActivity`: ```kotlin showLineNumbers title="Kotlin" class MainActivity : FlutterFragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } ``` ::: </SDKv3> --- # File: flutter-handle-paywall-actions --- --- title: "Реакция на действия кнопок в Flutter SDK" description: "Обработка действий кнопок пейвола во Flutter с помощью Adapty для улучшения монетизации приложения." --- <SDKv4> Если вы создаёте флоу или пейволы с помощью конструктора Adapty, важно правильно настроить кнопки: 1. Добавьте [кнопку в Builder](paywall-buttons) и назначьте ей существующее действие или создайте пользовательский идентификатор действия. 2. Напишите код в приложении для обработки каждого назначенного действия. В этом гайде показано, как обрабатывать пользовательские и встроенные действия в коде. :::warning **Закрытие экрана и открытие URL обрабатываются автоматически** стандартной реализацией `flowViewDidPerformAction`, а SDK самостоятельно обрабатывает покупки и восстановления. Все остальные действия кнопок, такие как вход в систему или открытие другого флоу, требуют реализации соответствующей логики в коде приложения. Обратите внимание, что реагирование на *завершённые* покупки и восстановления происходит в обязательных коллбэках наблюдателя — см. [Обработка событий флоу и пейвола](flutter-handling-events). ::: ## Закрытие флоу и пейволов \{#close-flows-and-paywalls\} Чтобы добавить кнопку закрытия флоу или пейвола, в билдере добавьте кнопку и назначьте ей действие **Close**. Код писать не нужно: стандартная реализация `flowViewDidPerformAction` автоматически закрывает экран при получении `CloseAction`. :::info Системная кнопка **Back** на Android больше не закрывает экран по умолчанию. Она передаётся в `flowViewDidPerformAction` как `AndroidSystemBackAction` — обработайте её самостоятельно, если хотите, чтобы кнопка «Назад» закрывала флоу или пейвол. ::: Переопределите `flowViewDidPerformAction`, если вам нужна кастомная логика — например, чтобы закрывать экран также по системной кнопке «Назад» на Android, как в v3: ```dart void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): view.dismiss(); break; case OpenUrlAction(:final url, :final openIn): AdaptyUI().openUrl(url, openIn: openIn); break; default: break; } } ``` :::warning Переопределение `flowViewDidPerformAction` полностью заменяет реализацию по умолчанию — сохраните обработку `CloseAction` и `OpenUrlAction`, если хотите сохранить стандартное поведение закрытия и открытия URL. ::: ## Открытие URL из флоу и пейволов \{#open-urls-from-flows-and-paywalls\} :::tip Если вы хотите добавить группу ссылок (например, условия использования и восстановление покупок), добавьте элемент **Link** в конструкторе и обработайте его так же, как кнопки с действием **Open URL**. ::: Чтобы добавить кнопку, открывающую ссылку (например, **Terms of use** или **Privacy policy**), добавьте кнопку в конструкторе, назначьте ей действие **Open URL** и укажите нужный URL. Никакой дополнительной реализации не требуется: стандартная реализация `flowViewDidPerformAction` открывает URL нативно через `AdaptyUI().openUrl`, учитывая настройку внутреннего или внешнего браузера из дашборда. Стандартного поведения достаточно в большинстве случаев. Если вы всё же хотите открывать URL самостоятельно, переопределите обработчик: ```dart void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): view.dismiss(); break; case OpenUrlAction(url: final url): // Open the URL in whatever way fits your app break; default: break; } } ``` ## Вход в приложение \{#log-into-the-app\} Чтобы добавить кнопку для входа пользователей в приложение: 1. В билдере добавьте кнопку и назначьте ей действие **Login**. 2. В коде приложения реализуйте обработчик для действия `login`, который идентифицирует пользователя. ```dart void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case CustomAction(action: 'login'): // Navigate to your login screen in whatever way fits your app break; default: break; } } ``` ## Обработка пользовательских действий \{#handle-custom-actions\} Чтобы добавить кнопку с произвольным действием: 1. В конструкторе добавьте кнопку, назначьте ей действие **Custom** и задайте ID. 2. В коде приложения реализуйте обработчик для этого ID действия. Например, если у вас есть другой набор предложений по подпискам или разовые покупки, можно добавить кнопку, которая откроет другой флоу или пейвол: ```dart void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { switch (action) { case CustomAction(action: 'openNewPaywall'): // Display another flow or paywall break; default: break; } } ``` </SDKv4> <SDKv3> Если вы создаёте пейволы с помощью Adapty Paywall Builder, важно правильно настроить кнопки: 1. Добавьте [кнопку в Paywall Builder](paywall-buttons) и назначьте ей существующее действие или создайте пользовательский ID действия. 2. Напишите код в приложении для обработки каждого назначенного действия. В этом гайде показано, как обрабатывать пользовательские и встроенные действия в вашем коде. :::warning **Только покупки и восстановления обрабатываются автоматически.** Все остальные действия кнопок, например закрытие пейволов или открытие ссылок, требуют реализации соответствующей обработки в коде приложения. ::: ## Закрытие пейвола \{#close-paywalls\} Чтобы добавить кнопку закрытия пейвола: 1. В Paywall Builder добавьте кнопку и назначьте ей действие **Close**. 2. В коде приложения реализуйте обработчик для действий `CloseAction` и `AndroidSystemBackAction`. :::info В Flutter SDK действия `CloseAction` и `AndroidSystemBackAction` по умолчанию закрывают пейвол. Однако при необходимости вы можете переопределить это поведение в коде. Например, закрытие одного пейвола может запускать открытие другого. ::: ```dart void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case const CloseAction(): case const AndroidSystemBackAction(): view.dismiss(); break; default: break; } } ``` ## Открытие URL с пейволов \{#open-urls-from-paywalls\} :::tip Если вы хотите добавить группу ссылок (например, условия использования и восстановление покупок), добавьте элемент **Link** в Paywall Builder и обработайте его так же, как кнопки с действием **Open URL**. ::: Чтобы добавить кнопку, открывающую ссылку с вашего пейвола (например, **Terms of use** или **Privacy policy**): 1. В Paywall Builder добавьте кнопку, назначьте ей действие **Open URL** и введите URL, который нужно открыть. 2. В коде вашего приложения реализуйте обработчик действия `openUrl`, который открывает полученный URL в браузере. ```dart // You have to install url_launcher plugin in order to handle urls: // https://pub.dev/packages/url_launcher void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case OpenUrlAction(url: final url): final Uri uri = Uri.parse(url); launchUrl(uri, mode: LaunchMode.inAppBrowserView); break; default: break; } } ``` ## Вход в приложение \{#log-into-the-app\} Чтобы добавить кнопку для входа пользователей в приложение: 1. В Paywall Builder добавьте кнопку и назначьте ей действие **Login**. 2. В коде приложения реализуйте обработчик действия `login`, который идентифицирует пользователя. ```dart void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case CustomAction(action: 'login'): // Navigate to your login screen in whatever way fits your app break; default: break; } } ``` ## Обработка пользовательских действий \{#handle-custom-actions\} Чтобы добавить кнопку, обрабатывающую любые другие действия: 1. В Paywall Builder добавьте кнопку, назначьте ей действие **Custom** и задайте идентификатор. 2. В коде приложения реализуйте обработчик для созданного идентификатора действия. Например, если у вас есть другой набор предложений подписок или разовых покупок, можно добавить кнопку, которая будет открывать другой пейвол: ```dart void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { switch (action) { case CustomAction(action: 'openNewPaywall'): // Показать другой пейвол break; default: break; } } ``` </SDKv3> --- # File: flutter-handling-events --- --- title: "Flutter - Обработка событий флоу и пейвола" description: "Узнайте, как обрабатывать события, связанные с подпиской, во Flutter с помощью Adapty для эффективного отслеживания взаимодействий пользователей." --- <SDKv4> :::important Это руководство охватывает обработку событий покупок, восстановлений, выбора продукта и рендеринга. Закрытие экрана и открытие ссылок обрабатываются реализацией `flowViewDidPerformAction` по умолчанию — см. наш [гайд по обработке действий кнопок](flutter-handle-paywall-actions), чтобы переопределить их или обработать пользовательские действия кнопок. ::: Флоу и пейволы, настроенные с помощью билдера, не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют события, на которые ваше приложение может реагировать. К таким событиям относятся нажатия кнопок (кнопки закрытия, URL, выбор продуктов и т. д.), а также уведомления о действиях, связанных с покупками, выполненных во флоу или пейволе. Ниже описано, как обрабатывать эти события. Чтобы управлять процессами, происходящими на экране флоу или пейвола в вашем мобильном приложении, или отслеживать их, реализуйте методы `AdaptyUIFlowsEventsObserver` и установите наблюдатель перед отображением любого экрана: ```dart showLineNumbers title="Flutter" AdaptyUI().setFlowsEventsObserver(this); ``` Три метода наблюдателя **обязательны** — без них класс не скомпилируется: `flowViewDidFinishPurchase`, `flowViewDidFinishRestore` и `flowViewDidReceiveError`. Остальные методы опциональны. Чтобы отвязать ранее установленный наблюдатель, передайте `null` в `setFlowsEventsObserver`. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: В примерах событий ниже показаны свойства, доступные для каждого объекта, с поясняющими значениями в комментариях. ### События, генерируемые пользователем \{#user-generated-events\} #### Отображение вью \{#view-appeared\} Этот метод вызывается, когда флоу или вью пейвола появляется на экране. :::note На iOS также вызывается, когда пользователь нажимает [кнопку веб-пейвола](web-paywall#step-2a-add-a-web-purchase-button) внутри пейвола и веб-пейвол открывается во встроенном браузере. ::: ```dart showLineNumbers title="Flutter" void flowViewDidAppear(AdaptyUIFlowView view) { } ``` #### Скрытие вью \{#view-disappeared\} Этот метод вызывается, когда флоу или вью пейвола убирается с экрана. :::note На iOS также вызывается, когда [веб-пейвол](web-paywall#step-2a-add-a-web-purchase-button), открытый из пейвола во встроенном браузере, исчезает с экрана. ::: ```dart showLineNumbers title="Flutter" void flowViewDidDisappear(AdaptyUIFlowView view) { } ``` #### Выбор продукта \{#product-selection\} Если продукт выбран для покупки (пользователем или системой), будет вызван следующий метод: ```dart showLineNumbers title="Flutter" void flowViewDidSelectProduct(AdaptyUIFlowView view, String productId) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```dart void flowViewDidSelectProduct(AdaptyUIFlowView view, String productId) { // productId is a String: productId; // 'premium_monthly' } ``` </Details> #### Начало покупки \{#started-purchase\} Если пользователь инициирует процесс покупки, будет вызван этот метод: ```dart showLineNumbers title="Flutter" void flowViewDidStartPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```dart void flowViewDidStartPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' product.localizedTitle; // 'Premium Monthly' product.localizedDescription; // 'Premium subscription for 1 month' product.price.amount; // 9.99 (double) product.price.currencyCode; // 'USD' product.price.localizedString; // '$9.99' } ``` </Details> #### Завершённая покупка \{#finished-purchase\} Этот метод **обязателен**. Он вызывается при успешной покупке, отмене покупки пользователем или если покупка находится в состоянии ожидания: ```dart showLineNumbers title="Flutter" void flowViewDidFinishPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // successful purchase break; case AdaptyPurchaseResultPending(): // purchase is pending break; case AdaptyPurchaseResultUserCancelled(): // user cancelled the purchase break; default: break; } } ``` <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```dart void flowViewDidFinishPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // profile — AdaptyProfile: profile.accessLevels['premium']?.isActive; // true profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) break; case AdaptyPurchaseResultPending(): // no additional data break; case AdaptyPurchaseResultUserCancelled(): // no additional data break; } } ``` </Details> :::info В отличие от v3, у этого метода нет поведения по умолчанию — экран больше не закрывается автоматически после успешной покупки. Решите самостоятельно, что происходит дальше: продолжить флоу или вызвать `view.dismiss()`. Подробнее об управлении закрытием экрана — в разделе [Реагирование на действия кнопок](flutter-handle-paywall-actions). ::: #### Завершение навигации в веб-платёжке \{#finished-web-payment-navigation\} Этот метод вызывается после попытки открыть [веб-пейвол](web-paywall) для конкретного продукта. Это касается как успешных, так и неудачных попыток навигации: ```dart showLineNumbers title="Flutter" void flowViewDidFinishWebPaymentNavigation(AdaptyUIFlowView view, AdaptyPaywallProduct? product, AdaptyError? error) { } ``` **Параметры:** | Параметр | Описание | |:------------|:--------------------------------------------------------------------------------------------------------------------------------| | **product** | Объект `AdaptyPaywallProduct`, для которого был открыт веб-пейвол. Может быть `null`. | | **error** | Объект `AdaptyError`, если навигация по веб-пейволу завершилась ошибкой; `null`, если навигация прошла успешно. | #### Неудачная покупка \{#failed-purchase\} Этот метод вызывается при неудачной попытке покупки (например, из-за проблем с оплатой или сетевых ошибок). Он **не** срабатывает при отмене пользователем или ожидающих транзакциях — они обрабатываются через `flowViewDidFinishPurchase`: ```dart showLineNumbers title="Flutter" void flowViewDidFailPurchase(AdaptyUIFlowView view, AdaptyPaywallProduct product, AdaptyError error) { } ``` #### Восстановление начато \{#started-restore\} Если пользователь инициирует процесс восстановления покупок, этот метод будет вызван: ```dart showLineNumbers title="Flutter" void flowViewDidStartRestore(AdaptyUIFlowView view) { } ``` #### Успешное восстановление \{#successful-restore\} Этот метод **обязательный**. Если восстановление покупки прошло успешно, он будет вызван: ```dart showLineNumbers title="Flutter" void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```dart void flowViewDidFinishRestore(AdaptyUIFlowView view, AdaptyProfile profile) { // profile — AdaptyProfile: profile.accessLevels['premium']?.isActive; // true profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) profile.subscriptions['premium_monthly']?.isActive; // true profile.subscriptions['premium_monthly']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) } ``` </Details> Мы рекомендуем закрывать экран, если у пользователя есть необходимый `accessLevel`. Обратитесь к разделу [Статус подписки](flutter-listen-subscription-changes), чтобы узнать, как его проверить, и к разделу [Обработка действий кнопок](flutter-handle-paywall-actions), чтобы узнать, как закрыть экран. #### Неудачное восстановление \{#failed-restore\} Если восстановление покупки завершается с ошибкой, будет вызван этот метод: ```dart showLineNumbers title="Flutter" void flowViewDidFailRestore(AdaptyUIFlowView view, AdaptyError error) { } ``` ### Загрузка данных и отображение \{#data-fetching-and-rendering\} #### Ошибки загрузки продуктов \{#product-loading-errors\} Если вы не передаёте массив продуктов при инициализации, AdaptyUI самостоятельно запросит необходимые объекты с сервера. Если эта операция завершится ошибкой, AdaptyUI сообщит об этом, вызвав следующий метод: ```dart showLineNumbers title="Flutter" void flowViewDidFailLoadingProducts(AdaptyUIFlowView view, AdaptyError error) { } ``` #### Ошибки отображения \{#view-errors\} Этот метод **обязателен**. Он заменяет метод `paywallViewDidFailRendering` из v3: ошибки, возникающие при рендеринге интерфейса, а также другие ошибки представления, передаются через него. После реализации метода решение о закрытии остаётся за вами — мы рекомендуем закрывать представление при таких ошибках; именно так ведёт себя встроенная логика SDK по умолчанию, когда наблюдатель не задан: ```dart showLineNumbers title="Flutter" void flowViewDidReceiveError(AdaptyUIFlowView view, AdaptyError error) { // log the error and dismiss the broken view view.dismiss(); } ``` В нормальной ситуации ошибки рендеринга возникать не должны, поэтому если вы с ними столкнётесь — пожалуйста, сообщите нам. ### Аналитические события \{#analytics-events\} Опциональный метод `flowViewDidReceiveAnalyticEvent` предназначен для получения пользовательских аналитических событий из флоу. Флоу пока не отправляет такие события в ваш код, поэтому реализовывать этот метод не нужно. ### Обработка покупок в режиме наблюдателя \{#handle-purchases-in-observer-mode\} Если вы активировали SDK в [режиме наблюдателя](implement-observer-mode-flutter) и отображаете флоу или пейвол, отрисованный Adapty, SDK не совершает покупки самостоятельно. Когда пользователь нажимает кнопку покупки или восстановления, SDK вызывает ваш `AdaptyUIObserverModeResolver`. Подробнее о настройке читайте в разделе [Отображение флоу в режиме наблюдателя](flutter-present-flows-in-observer-mode). ### Обработка системных запросов \{#handle-system-requests\} `AdaptyUISystemRequestsHandler` (регистрируется через `AdaptyUI().setSystemRequestsHandler(...)`) предназначен для системных запросов из флоу: запросы разрешений ОС (например, push-уведомления или доступ к камере) и запросы на оценку в App Store. Флоу пока не инициируют такие запросы, поэтому регистрировать обработчик не нужно. Если вы регистрируете обработчик, обратите внимание: `handlePermission` — это обязательный метод класса; запросите разрешение своим кодом, затем верните `AdaptyUIPermissionResult.granted()` или `AdaptyUIPermissionResult.denied()`; `handleAppReviewRequest` — необязательный. </SDKv4> <SDKv3> :::important Этот гайд охватывает обработку событий покупок, восстановления, выбора продуктов и отображения пейвола. Также необходимо реализовать обработку кнопок (закрытие пейвола, открытие ссылок и т. д.). Подробнее см. в нашем [гайде по обработке действий кнопок](flutter-handle-paywall-actions). ::: Пейволы, созданные в [Paywall Builder](adapty-paywall-builder), не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют события, на которые ваше приложение может реагировать. Среди них — нажатия кнопок (кнопки закрытия, URL, выбор продукта и т. д.), а также уведомления о действиях, связанных с покупками, выполненных на пейволе. Ниже описано, как обрабатывать эти события. :::warning Это руководство предназначено **только для пейволов, созданных в новом Paywall Builder**, для работы с которыми требуется Adapty SDK v3.0 или более поздней версии. ::: Чтобы контролировать или отслеживать процессы, происходящие на экране пейвола в вашем мобильном приложении, реализуйте методы `AdaptyUIPaywallsEventsObserver` и установите наблюдатель до отображения любого экрана: ```dart showLineNumbers title="Flutter" AdaptyUI().setPaywallsEventsObserver(this); ``` :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: В примерах событий ниже показаны свойства, доступные для каждого объекта, с иллюстративными значениями в комментариях. ### События, генерируемые пользователем \{#user-generated-events\} #### Пейвол появился \{#paywall-appeared\} Этот метод вызывается, когда представление пейвола отображается на экране. :::note На iOS также вызывается, когда пользователь нажимает на [кнопку веб-пейвола](web-paywall#step-2a-add-a-web-purchase-button) внутри пейвола, и веб-пейвол открывается во встроенном браузере. ::: ```dart showLineNumbers title="Flutter" void paywallViewDidAppear(AdaptyUIPaywallView view) { } ``` #### Пейвол исчез \{#paywall-disappeared\} Этот метод вызывается, когда представление пейвола убирается с экрана. :::note На iOS также вызывается, когда [веб-пейвол](web-paywall#step-2a-add-a-web-purchase-button), открытый из пейвола во встроенном браузере, исчезает с экрана. ::: ```dart showLineNumbers title="Flutter" void paywallViewDidDisappear(AdaptyUIPaywallView view) { } ``` #### Выбор продукта \{#product-selection\} Если продукт выбран для покупки (пользователем или системой), этот метод будет вызван: ```dart showLineNumbers title="Flutter" void paywallViewDidSelectProduct(AdaptyUIPaywallView view, String productId) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```dart void paywallViewDidSelectProduct(AdaptyUIPaywallView view, String productId) { // productId is a String: productId; // 'premium_monthly' } ``` </Details> #### Начало покупки \{#started-purchase\} Если пользователь инициирует процесс покупки, будет вызван этот метод: ```dart showLineNumbers title="Flutter" void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```dart void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' product.localizedTitle; // 'Premium Monthly' product.localizedDescription; // 'Premium subscription for 1 month' product.price.amount; // 9.99 (double) product.price.currencyCode; // 'USD' product.price.localizedString; // '$9.99' } ``` </Details> #### Завершённая покупка \{#finished-purchase\} Этот метод вызывается, когда покупка завершается успешно, пользователь отменяет покупку или покупка оказывается в ожидании: ```dart showLineNumbers title="Flutter" void paywallViewDidFinishPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // successful purchase break; case AdaptyPurchaseResultPending(): // purchase is pending break; case AdaptyPurchaseResultUserCancelled(): // user cancelled the purchase break; default: break; } } ``` <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```dart void paywallViewDidFinishPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchaseResult) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // profile — AdaptyProfile: profile.accessLevels['premium']?.isActive; // true profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) break; case AdaptyPurchaseResultPending(): // no additional data break; case AdaptyPurchaseResultUserCancelled(): // no additional data break; } } ``` </Details> Мы рекомендуем закрывать экран в этом случае. Подробнее о закрытии экрана пейвола см. в разделе [Реакция на действия кнопок](flutter-handle-paywall-actions). #### Завершение навигации веб-платежа \{#finished-web-payment-navigation\} Этот метод вызывается после попытки открыть [веб-пейвол](web-paywall) для конкретного продукта. Это включает как успешные, так и неудачные попытки навигации: ```dart showLineNumbers title="Flutter" void paywallViewDidFinishWebPaymentNavigation(AdaptyUIPaywallView view, AdaptyPaywallProduct? product, AdaptyError? error) { } ``` **Параметры:** | Параметр | Описание | |:------------|:-----------------------------------------------------------------------------------------------------------------| | **product** | `AdaptyPaywallProduct` — продукт, для которого открыт веб-пейвол. Может быть `null`. | | **error** | Объект `AdaptyError`, если навигация по веб-пейволу завершилась с ошибкой; `null`, если навигация прошла успешно. | <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```dart void paywallViewDidFinishWebPaymentNavigation(AdaptyUIPaywallView view, AdaptyPaywallProduct? product, AdaptyError? error) { // product — AdaptyPaywallProduct?: product?.vendorProductId; // 'premium_monthly' if (error == null) { // navigation succeeded } else { // error — AdaptyError: error.code; // AdaptyErrorCode.networkFailed (2005) error.message; // 'Network request failed' error.detail; // platform-specific underlying error, or null } } ``` </Details> #### Неудачная покупка \{#failed-purchase\} Этот метод вызывается, когда покупка завершается ошибкой (например, из-за проблем с оплатой или сетевых ошибок). Он **не** срабатывает при отмене пользователем или незавершённых транзакциях — они обрабатываются через `paywallViewDidFinishPurchase`: ```dart showLineNumbers title="Flutter" void paywallViewDidFailPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```dart void paywallViewDidFailPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) { // product — AdaptyPaywallProduct: product.vendorProductId; // 'premium_monthly' // error — AdaptyError: error.code; // AdaptyErrorCode.productPurchaseFailed (1006) error.message; // 'Product purchase failed.' error.detail; // platform-specific underlying error, or null } ``` </Details> #### Восстановление начато \{#started-restore\} Когда пользователь инициирует процесс восстановления покупок, вызывается этот метод: ```dart showLineNumbers title="Flutter" void paywallViewDidStartRestore(AdaptyUIPaywallView view) { } ``` #### Успешное восстановление \{#successful-restore\} Если восстановление покупки прошло успешно, будет вызван этот метод: ```dart showLineNumbers title="Flutter" void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```dart void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) { // profile — AdaptyProfile: profile.accessLevels['premium']?.isActive; // true profile.accessLevels['premium']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) profile.subscriptions['premium_monthly']?.isActive; // true profile.subscriptions['premium_monthly']?.expiresAt; // DateTime(2027, 2, 15, 10, 30) } ``` </Details> Мы рекомендуем закрывать экран, если у пользователя есть нужный `accessLevel`. Обратитесь к разделу [Статус подписки](flutter-listen-subscription-changes), чтобы узнать, как его проверить, и к разделу [Реагирование на действия кнопок](flutter-handle-paywall-actions), чтобы узнать, как закрыть экран пейвола. #### Ошибка восстановления \{#failed-restore\} Если восстановление покупки завершится с ошибкой, будет вызван следующий метод: ```dart showLineNumbers title="Flutter" void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```dart void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) { // error — AdaptyError: error.code; // AdaptyErrorCode.receiveRestoredTransactionsFailed (1011) error.message; // 'Error occurred in the process of restoring purchases.' error.detail; // platform-specific underlying error, or null } ``` </Details> ### Получение данных и рендеринг \{#data-fetching-and-rendering\} #### Ошибки загрузки продуктов \{#product-loading-errors\} Если вы не передаёте массив продуктов при инициализации, AdaptyUI самостоятельно получит необходимые объекты с сервера. Если эта операция завершится ошибкой, AdaptyUI сообщит о ней, вызвав следующий метод: ```dart showLineNumbers title="Flutter" void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyError error) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```dart void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyError error) { // error — AdaptyError: error.code; // AdaptyErrorCode.productRequestFailed (1002) error.message; // 'Unable to fetch available In-App Purchase products at the moment.' error.detail; // platform-specific underlying error, or null } ``` </Details> #### Ошибки рендеринга \{#rendering-errors\} Если во время отображения интерфейса возникает ошибка, она сообщается путём вызова этого метода. По умолчанию (начиная с v3.15.2) пейвол автоматически закрывается при ошибке рендеринга, но при необходимости это поведение можно переопределить. ```dart showLineNumbers title="Flutter" void paywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) { // Default behavior: view.dismiss() // Override with custom logic if needed, for example: // - Log the error // - Show an error message to the user } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```dart void paywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) { // error — AdaptyError: error.code; // AdaptyErrorCode.jsException (4105) error.message; // 'An exception was thrown from JS during AdaptyUI flow execution.' error.detail; // platform-specific underlying error, or null // Default behavior: view.dismiss() } ``` </Details> В нормальной ситуации такие ошибки возникать не должны, поэтому если вы столкнулись с одной из них, пожалуйста, сообщите нам. </SDKv3> --- # File: flutter-use-fallback-paywalls --- --- title: "Flutter - Использование резервных пейволов" description: "Обработка случаев, когда пользователи офлайн или серверы Adapty недоступны" --- :::warning Резервные пейволы поддерживаются Flutter SDK v2.11 и более поздними версиями. ::: Чтобы поддерживать бесперебойный пользовательский опыт, важно настроить [резервные пейволы](/fallback-paywalls) для флоу, [пейволов](paywalls) и [онбордингов](onboardings). Это позволит приложению продолжить работу при частичной или полной потере интернет-соединения. * **Если приложение не может обратиться к серверам Adapty:** Оно сможет отобразить резервный флоу или пейвол, а также использовать локальную конфигурацию онбординга. * **Если приложение не может подключиться к интернету:** Оно сможет отобразить резервный флоу или пейвол. Онбординги содержат удалённый контент и требуют интернет-соединения для работы. :::important Прежде чем следовать шагам этого гайда, [скачайте](/local-fallback-paywalls) файлы резервной конфигурации из Adapty. ::: ## Настройка \{#configuration\} 1. Добавьте файлы резервной конфигурации в директорию `assets` приложения в корне проекта. 2. Вызовите метод `.setFallback` **до** того, как запросите целевой пейвол или онбординг. ```dart showLineNumbers title="Flutter" final assetId = Platform.isIOS ? 'assets/ios_fallback.json' : 'assets/android_fallback.json'; try { await Adapty().setFallback(assetId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Параметры: | Parameter | Description | | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **assetId** | Путь к файлу резервной конфигурации. | :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: --- # File: flutter-localizations-and-locale-codes --- --- title: "Использование локализаций и кодов локали во Flutter SDK" description: "Управляйте локализациями приложения и кодами локали для охвата глобальной аудитории." --- <SDKv4> ## Почему это важно \{#why-this-is-important\} Коды локалей используются, когда Adapty выбирает локализацию для флоу и когда вы читаете Remote Config для кастомного пейвола. Коды локалей — штука непростая: они отличаются от платформы к платформе, поэтому Adapty использует единый внутренний стандарт для всех поддерживаемых платформ. Понимание этого стандарта поможет вам предсказать, какую локализацию получит пользователь. ## Стандарт кодов локали в Adapty \{#locale-code-standard-at-adapty\} Для кодов локали Adapty использует слегка модифицированный стандарт [BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): каждый код состоит из подтегов в нижнем регистре, разделённых дефисами. Примеры: `en` (английский), `pt-br` (португальский (Бразилия)), `zh` (упрощённый китайский), `zh-hant` (традиционный китайский). ## Сопоставление кодов локали \{#locale-code-matching\} Когда Adapty ищет локализацию, соответствующую языковому стандарту пользователя, происходит следующее: 1. Строка локали преобразуется в нижний регистр, все символы подчёркивания (`_`) заменяются дефисами (`-`) 2. Adapty ищет локализацию с полностью совпадающим кодом локали 3. Если совпадение не найдено, Adapty берёт подстроку до первого дефиса (`pt` для `pt-br`) и ищет соответствующую локализацию 4. Если совпадение снова не найдено, Adapty возвращает локализацию по умолчанию — `en` Таким образом, `'pt_BR'`, `pt-BR` и `pt-br` — все они указывают на одну и ту же локализацию. ## Реализация локализаций \{#implementing-localizations\} В SDK v4 передавать код локали при получении флоу не нужно. - **Пейволы из Flow Builder и Paywall Builder**: Adapty автоматически определяет локализацию на основе настроек устройства и локализаций, заданных в билдере. Отображайте флоу через `createFlowView` — код локали не требуется. - **Кастомные пейволы (Remote Config)**: `getFlow` возвращает все настроенные локализации в `flow.remoteConfigs`. Каждая запись содержит код локали `locale` и содержимое конфига (строка `data` или разобранный `dictionary`). Выберите нужную запись по настройкам пользователя, задав собственный фолбэк: ```dart showLineNumbers final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); final config = flow.remoteConfigs.firstWhereOrNull((c) => c.locale == 'en') ?? flow.remoteConfig; // the first remote config, if present // read your values from config?.dictionary ``` Правила сопоставления кодов локали, описанные выше, объясняют, как Adapty нормализует коды `locale`, хранящиеся в каждом Remote Config. </SDKv4> <SDKv3> ## Почему это важно \{#why-this-is-important\} Есть несколько сценариев, в которых коды локалей играют ключевую роль — например, когда нужно получить правильный пейвол для текущей локализации вашего приложения. Поскольку коды локалей бывают сложными и могут различаться в зависимости от платформы, мы используем внутренний стандарт для всех поддерживаемых платформ. Тем не менее именно из-за этой сложности важно чётко понимать, что именно вы отправляете на наш сервер для получения нужной локализации и что происходит дальше — чтобы вы всегда получали именно то, что ожидаете. ## Стандарт кодов локалей в Adapty \{#locale-code-standard-at-adapty\} Для кодов локалей Adapty использует немного изменённый [стандарт BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): каждый код состоит из подтегов в нижнем регистре, разделённых дефисами. Примеры: `en` (английский), `pt-br` (португальский (Бразилия)), `zh` (упрощённый китайский), `zh-hant` (традиционный китайский). ## Сопоставление кода локали \{#locale-code-matching\} Когда Adapty получает вызов от клиентского SDK с кодом локали и начинает искать соответствующую локализацию пейвола, происходит следующее: 1. Входящая строка локали приводится к нижнему регистру, а все символы подчёркивания (`_`) заменяются дефисами (`-`) 2. Затем выполняется поиск локализации с полностью совпадающим кодом локали 3. Если совпадение не найдено, берётся подстрока до первого дефиса (`pt` для `pt-br`) и выполняется поиск подходящей локализации 4. Если совпадение снова не найдено, возвращается локализация по умолчанию — `en` Таким образом, устройство iOS, отправившее `'pt_BR'`, устройство Android, отправившее `pt-BR`, и другое устройство, отправившее `pt-br`, получат одинаковый результат. ## Реализация локализаций: рекомендуемый подход \{#implementing-localizations-recommended-way\} Если вы задаётесь вопросом о локализациях, скорее всего, вы уже работаете с файлами локализованных строк в своём проекте. В таком случае мы рекомендуем добавить в каждый файл пару ключ-значение с нужным кодом локали Adapty для соответствующей локализации. Затем при вызове SDK извлекайте значение по этому ключу, как показано ниже: ```dart showLineNumbers // 1. Modify your app_en.arb, app_es.arb, app_pt_br.arb files /* app_en.arb */ "adapty_paywalls_locale": "en", /* app_es.arb */ "adapty_paywalls_locale": "es", /* app_pt_br.arb */ "adapty_paywalls_locale": "pt-br", // 2. Extract and use the locale code final locale = AppLocalizations.of(context)!.adapty_paywalls_locale; // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` Таким образом вы полностью контролируете, какая локализация будет загружена для каждого пользователя вашего приложения. ## Реализация локализаций: альтернативный способ \{#implementing-localizations-the-other-way\} Похожего (но не идентичного) результата можно добиться без явного указания кодов локали для каждой локализации. Это означает извлечение кода локали из других объектов, предоставляемых вашей платформой, например: ```dart showLineNumbers final locale = Localizations.localeOf(context).languageCode; // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` Обратите внимание, что мы не рекомендуем этот подход по нескольким причинам: 1. На iOS предпочтительные языки и текущая локаль — не одно и то же. Чтобы локализация подбиралась корректно, придётся либо опираться на логику Apple (которая работает из коробки при рекомендованном подходе с локализованными строковыми файлами), либо реализовывать её самостоятельно. 2. Сложно предсказать, что именно получит сервер Adapty. Например, на iOS устройство может вернуть локаль вида `ar_OM@numbers='latn'`, которая будет отправлена на сервер. В ответ вы получите не локализацию `ar-om`, которую ожидали, а `ar` — что, скорее всего, не то, что нужно. Should you decide to use this approach anyway — make sure you've covered all the relevant use cases. </SDKv3> --- # File: flutter-web-paywall --- --- title: "Реализация веб-пейволов в Flutter SDK" description: "Настройте веб-пейвол для приёма платежей без комиссий и проверок App Store." --- :::important Прежде чем начать, убедитесь, что вы [настроили веб-пейвол в дашборде](web-paywall) и установили Adapty SDK версии 3.6.1 или выше. ::: Если вы работаете с пейволом собственной разработки, для обработки веб-пейволов нужно использовать метод SDK. Метод `.openWebPaywall`: 1. Генерирует уникальный URL, который позволяет Adapty связать конкретный пейвол, показанный определённому пользователю, с веб-страницей, на которую он перенаправляется. 2. Отслеживает возвращение пользователя в приложение и затем с короткими интервалами вызывает `.getProfile`, чтобы определить, обновились ли права доступа профиля. Таким образом, если оплата прошла успешно и права доступа обновились, подписка активируется в приложении практически мгновенно. ```dart showLineNumbers title="Flutter" try { await Adapty().openWebPaywall(product: <YOUR_PRODUCT>); // The web paywall will be opened } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle other errors } ``` :::note Метод `openWebPaywall` существует в двух вариантах: 1. `openWebPaywall(product)` — генерирует URL по пейволу и добавляет данные продукта в URL. 2. `openWebPaywall(paywall)` — генерирует URL по пейволу без добавления данных продукта в URL. Используйте его, если продукты в пейволе Adapty отличаются от продуктов в веб-пейволе. В SDK v4 параметр `paywall` принимает `AdaptyFlowPaywall` — вариацию пейвола из полученного флоу. Убедитесь, что `flow.paywalls` не пустой, прежде чем обращаться к его элементам, например `flow.paywalls[0]`. ::: #### Обработка ошибок \{#handle-errors\} | Ошибка | Описание | Рекомендуемое действие | |-----------------------------------------|-------------------------------------------------------------------|-------------------------------------------------------------------------------------| | AdaptyError.paywallWithoutPurchaseUrl | У пейвола не настроен URL для веб-покупки | Проверьте, правильно ли настроен пейвол в дашборде Adapty | | AdaptyError.productWithoutPurchaseUrl | У продукта отсутствует URL для веб-покупки | Проверьте настройки продукта в дашборде Adapty | | AdaptyError.failedOpeningWebPaywallUrl | Не удалось открыть URL в браузере | Проверьте настройки устройства или предложите альтернативный способ оплаты | | AdaptyError.failedDecodingWebPaywallUrl | Не удалось корректно закодировать параметры в URL | Убедитесь, что параметры URL корректны и имеют правильный формат | ## Открытие веб-пейволов во встроенном браузере \{#open-web-paywalls-in-an-in-app-browser\} :::important Открытие веб-пейволов во встроенном браузере поддерживается начиная с Adapty SDK v3.15. ::: По умолчанию веб-пейволы открываются во внешнем браузере. Чтобы обеспечить бесшовный пользовательский опыт, можно открывать веб-пейволы во встроенном браузере. Это позволяет отображать страницу веб-покупки прямо внутри приложения, и пользователи смогут завершить транзакцию, не переключаясь между приложениями. Чтобы включить это, задайте для параметра `in` значение `.inAppBrowser`: ```dart showLineNumbers try { await Adapty().openWebPaywall( product: <YOUR_PRODUCT>, openIn: AdaptyWebPresentation.inAppBrowser, ); // The web paywall will be opened in the in-app browser } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle other errors } ``` --- # File: flutter-troubleshoot-paywall-builder --- --- title: "Устранение неполадок Paywall Builder во Flutter SDK" description: "Устранение неполадок Paywall Builder во Flutter SDK" --- Этот гайд поможет вам устранить распространённые проблемы при использовании пейволов, созданных в Adapty Paywall Builder, во Flutter SDK. ## Получение конфигурации пейвола завершается ошибкой \{#getting-a-paywall-configuration-fails\} **Проблема**: Метод `createPaywallView` не может получить конфигурацию пейвола. **Причина**: Пейвол не включён для отображения на устройстве в Paywall Builder. **Решение**: Включите переключатель **Show on device** в Paywall Builder. <img src="/assets/shared/img/show-on-device.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Число просмотров пейвола слишком велико \{#the-paywall-view-number-is-too-big\} **Проблема**: Счётчик просмотров пейвола показывает число вдвое больше ожидаемого. **Причина**: Возможно, вы вызываете `logShowFlow` (Flutter SDK v4+) / `logShowPaywall` в своём коде, что дублирует счётчик просмотров, если вы используете Paywall Builder или Flow Builder. Для флоу и пейволов, созданных с помощью этих инструментов, аналитика отслеживается автоматически, поэтому использовать этот метод не нужно. **Решение**: Убедитесь, что вы не вызываете `logShowFlow` (Flutter SDK v4+) / `logShowPaywall` в своём коде, если используете Paywall Builder или Flow Builder. ## Другие проблемы \{#other-issues\} **Проблема**: У вас возникают другие проблемы, связанные с Paywall Builder, которые не описаны выше. **Решение**: При необходимости обновите SDK до последней версии, следуя [гайдам по миграции](flutter-sdk-migration-guides). Многие проблемы уже исправлены в новых версиях SDK. --- # File: flutter-present-flows-in-observer-mode --- --- title: "Показ флоу в режиме Observer в Flutter SDK" description: "Показывайте флоу и пейволы Paywall Builder в режиме Observer в вашем Flutter-приложении, обрабатывая покупки собственным кодом." --- Если вы настроили флоу или пейвол с помощью билдера, вам не нужно беспокоиться об их рендеринге в коде мобильного приложения для отображения пользователю. Такой флоу или пейвол уже содержит и то, что нужно показать, и то, как именно это должно выглядеть. :::warning Этот раздел относится только к [режиму Observer](observer-vs-full-mode). Если вы не работаете в режиме Observer, обратитесь к разделу [Отображение флоу и пейволов](flutter-present-paywalls). ::: :::info Эта функция доступна начиная с Adapty Flutter SDK 4.0 — ранее она была доступна только в нативных SDK для iOS и Android. Ознакомьтесь с [руководством по миграции](migration-to-flutter-sdk-v4), чтобы выполнить обновление. ::: <details> <summary>Прежде чем начать показывать флоу (нажмите, чтобы развернуть)</summary> 1. Настройте начальную интеграцию Adapty [с App Store](initial_ios) и [с Google Play](initial-android). 2. Установите и настройте Adapty SDK. Обязательно задайте параметр `observerMode` равным `true`. Обратитесь к [гайду по установке Flutter SDK](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk). 3. [Создайте продукты](create-product) в дашборде Adapty. 4. [Настройте флоу или пейволы в билдерах](create-paywall) и привяжите к ним продукты. 5. [Создайте плейсменты и назначьте им флоу или пейволы](create-placement). 6. [Получите флоу и их конфигурацию](flutter-get-pb-paywalls) в коде мобильного приложения. </details> В режиме Observer SDK не выполняет покупки самостоятельно. Когда пользователь нажимает кнопку покупки или восстановления во флоу или пейволе, отрисованном Adapty, SDK вызывает ваш `AdaptyUIObserverModeResolver` — выполните покупку или восстановление там с помощью собственного кода. 1. Реализуйте `AdaptyUIObserverModeResolver`: ```dart showLineNumbers title="Flutter" class MyObserverModeResolver extends AdaptyUIObserverModeResolver { @override void observerModeDidInitiatePurchase( AdaptyUIFlowView view, AdaptyPaywallProduct product, void Function() onStartPurchase, void Function() onFinishPurchase, ) { onStartPurchase(); // the view shows its loading indicator // make the purchase with your own code, then: onFinishPurchase(); // the view hides the loading indicator } @override void observerModeDidInitiateRestore( AdaptyUIFlowView view, void Function() onStartRestore, void Function() onFinishRestore, ) { onStartRestore(); // restore purchases with your own code, then: onFinishRestore(); } } ``` Метод `observerModeDidInitiatePurchase` сообщает вам о том, что пользователь инициировал покупку, а `observerModeDidInitiateRestore` — что пользователь инициировал восстановление. В ответ на эти события запустите своё кастомное флоу покупки или восстановления. Также не забудьте вызвать следующие коллбэки, чтобы уведомить AdaptyUI о процессе покупки или восстановления. Это необходимо для корректного поведения флоу — например, для отображения лоадера: | Callback | Description | | :----------------- | :----------------------------------------------------------------------------------------------- | | onStartPurchase() | Коллбэк должен вызываться, чтобы уведомить AdaptyUI о начале покупки. | | onFinishPurchase() | Коллбэк должен вызываться, чтобы уведомить AdaptyUI о завершении покупки. | | onStartRestore() | Коллбэк должен вызываться, чтобы уведомить AdaptyUI о начале восстановления. | | onFinishRestore() | Коллбэк должен вызываться, чтобы уведомить AdaptyUI о завершении восстановления. | 2. Зарегистрируйте резолвер до отображения любого экрана: ```dart showLineNumbers title="Flutter" AdaptyUI().setObserverModeResolver(MyObserverModeResolver()); ``` 3. Создайте и отобразите флоу как обычно: [получите флоу и создайте его представление](flutter-get-pb-paywalls), затем [отобразите его](flutter-present-paywalls). Никаких дополнительных параметров не требуется — после регистрации резолвера все покупки и восстановления в пейволах и флоу Adapty будут проходить через него. :::warning Не забудьте [сообщить о транзакции и связать её с пейволом](report-transactions-observer-mode-flutter). В противном случае Adapty не распознает транзакцию и не определит, с какого пейвола была совершена покупка. ::: --- # File: flutter-quickstart-manual --- --- title: "Подключение покупок в кастомном пейволе во Flutter SDK" description: "Интегрируйте Adapty SDK в ваши кастомные пейволы Flutter для включения встроенных покупок." --- Это руководство описывает, как интегрировать Adapty в кастомные пейволы. Сохраняйте полный контроль над реализацией пейвола, пока SDK Adapty получает продукты, обрабатывает новые покупки и восстанавливает предыдущие. Руководство использует API Adapty Flutter SDK v4 — если вы используете v3, см. [гайд по миграции](migration-to-flutter-sdk-v4) с соответствующими именами методов. :::important **Это руководство предназначено для разработчиков, реализующих кастомные пейволы.** Если вы хотите подключить покупки максимально просто, используйте [Adapty Paywall Builder](flutter-quickstart-paywalls). С Paywall Builder вы создаёте пейволы в визуальном редакторе без кода, Adapty берёт на себя всю логику покупок, а тестировать разные дизайны можно без переpublikации приложения. ::: ## Прежде чем начать \{#before-you-start\} ### Настройка продуктов \{#set-up-products\} Чтобы подключить встроенные покупки, нужно разобраться с тремя ключевыми понятиями: - [**Продукты**](product) – всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ) - [**Пейволы**](paywalls) – конфигурации, определяющие, какие продукты предлагать. В Adapty пейволы — единственный способ получить продукты, но такой подход позволяет изменять продукты, цены и офферы без обновления кода приложения. В SDK v4 варианты пейвола для плейсмента передаются через объект **flow** — вы получаете флоу и запрашиваете из него продукты. - [**Плейсменты**](placements) – где и когда показывать пейволы в приложении (например, `main`, `onboarding`, `settings`). Вы настраиваете пейволы для плейсментов в дашборде, а затем запрашиваете их по ID плейсмента в коде. Это упрощает проведение A/B-тестов и показ разных пейволов разным пользователям. Убедитесь, что вы понимаете эти концепции, даже если используете собственный пейвол. По сути, это просто способ управлять продуктами, которые вы продаёте в приложении. Чтобы реализовать собственный пейвол, нужно создать **пейвол** и добавить его в **плейсмент**. Такая настройка позволяет получать ваши продукты. Чтобы разобраться, что нужно сделать в дашборде, воспользуйтесь гайдом по быстрому старту [здесь](quickstart). ### Управление пользователями \{#manage-users\} Вы можете работать как с серверной аутентификацией, так и без неё. При этом Adapty SDK по-разному обрабатывает анонимных и идентифицированных пользователей. Прочитайте [гайд по быстрому старту с идентификацией](flutter-quickstart-identify), чтобы разобраться в деталях и убедиться, что вы правильно работаете с пользователями. ## Шаг 1. Получение продуктов \{#step-1-get-products\} Чтобы получить продукты для вашего кастомного пейвола, нужно: 1. Получить объект `flow`, передав ID [плейсмента](placements) в метод `getFlow`. 2. Получить массив продуктов для этого флоу с помощью метода `getPaywallProducts`. ```dart showLineNumbers Future<void> loadPaywall() async { try { final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); final products = await Adapty().getPaywallProducts(flow: flow); // Use products to build your custom paywall UI } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } } ``` ## Шаг 2. Принятие покупок \{#step-2-accept-purchases\} Когда пользователь нажимает на продукт в вашем кастомном пейволе, вызовите метод `makePurchase` с выбранным продуктом. Это запустит флоу покупки и вернёт обновлённый профиль. ```dart showLineNumbers Future<void> purchaseProduct(AdaptyPaywallProduct product) async { try { final purchaseResult = await Adapty().makePurchase(product: product); switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): // Purchase successful, profile updated break; case AdaptyPurchaseResultUserCancelled(): // User canceled the purchase break; case AdaptyPurchaseResultPending(): // Purchase is pending (e.g., user will pay offline with cash) break; } } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } } ``` ## Шаг 3. Восстановление покупок \{#step-3-restore-purchases\} Сторы требуют, чтобы все приложения с подписками предоставляли пользователям возможность восстановить покупки. Вызывайте метод `restorePurchases`, когда пользователь нажимает кнопку восстановления. Это синхронизирует историю покупок с Adapty и вернёт обновлённый профиль. ```dart showLineNumbers Future<void> restorePurchases() async { try { final profile = await Adapty().restorePurchases(); // Restore successful, profile updated } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } } ``` ## Шаг 4. Проверьте статус подписки \{#step-4-check-the-subscription-status\} После покупки или восстановления проверьте [уровень доступа](access-level) пользователя, чтобы решить, показывать пейвол или открывать платные функции. Методы `makePurchase` и `restorePurchases` уже возвращают обновлённый профиль; когда нужно получить текущий статус в другом месте приложения, используйте метод `getProfile`: ```dart showLineNumbers Future<bool> hasPremiumAccess() async { try { final profile = await Adapty().getProfile(); return profile.accessLevels['premium']?.isActive ?? false; } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } return false; } ``` Другие способы проверки и отслеживания статуса подписки, включая получение обновлений в реальном времени, описаны в разделе [Проверка статуса подписки](flutter-check-subscription-status). ## Следующие шаги \{#next-steps\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Ваш пейвол готов к отображению в приложении. Протестируйте покупки в [песочнице App Store](test-purchases-in-sandbox) или в [Google Play Store](testing-on-android), чтобы убедиться, что вы можете совершить тестовую покупку через пейвол. Чтобы увидеть, как это работает в production-реализации, ознакомьтесь с [PurchasesObserver](https://github.com/adaptyteam/AdaptySDK-Flutter/blob/master/example/lib/purchase_observer.dart) в нашем примере приложения — там показана обработка покупок с корректной обработкой ошибок, наблюдателями UI и полноценной интеграцией SDK. --- # File: fetch-paywalls-and-products-flutter --- --- title: "Получение пейволов и продуктов для пейволов с Remote Config в Flutter SDK" description: "Получайте пейволы и продукты в Adapty Flutter SDK для улучшения монетизации пользователей." --- <SDKv4> Прежде чем отображать Remote Config и кастомные пейволы, необходимо получить информацию о них. Обратите внимание: этот раздел посвящён Remote Config и кастомным пейволам. Если вам нужна информация о получении флоу и пейволов, созданных в Paywall Builder, обратитесь к разделу [Получение флоу и пейволов](flutter-get-pb-paywalls). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Перед тем как начать получать пейволы и продукты в мобильном приложении (нажмите, чтобы развернуть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте пейвол и добавьте продукты в него](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте пейвол в плейсмент](create-placement) в дашборде Adapty. 4. [Установите SDK Adapty](sdk-installation-flutter) в своё мобильное приложение. </details> ## Получение информации о флоу \{#fetch-flow-information\} В Adapty [продукт](product) объединяет продукты из App Store и Google Play. Эти кросс-платформенные продукты интегрируются в пейволы, позволяя показывать их в конкретных плейсментах мобильного приложения. Чтобы отобразить продукты, нужно получить `AdaptyFlow` из одного из ваших [плейсментов](placements) с помощью метода `getFlow`. :::important **Не вшивайте ID продуктов в код.** Единственный ID, который нужно хардкодить, — это ID плейсмента. Пейволы настраиваются удалённо, поэтому количество продуктов и доступных офферов может меняться в любой момент. Ваше приложение должно обрабатывать эти изменения динамически: если сегодня пейвол возвращает два продукта, а завтра три — показывайте все без изменений в коде. ::: ```dart showLineNumbers try { final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); // the requested flow } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если вы считаете, что у ваших пользователей нестабильное интернет-соединение, рассмотрите вариант `.returnCacheDataElseLoad`, который возвращает кешированные данные, если они существуют. В этом случае пользователи могут не получать самые последние данные, но время загрузки будет меньше независимо от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание, что кеш сохраняется при перезапуске приложения и очищается только при его переустановке или ручной очистке.</p><p></p><p>Adapty SDK хранит пейволы в двух слоях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](flutter-use-fallback-paywalls). Также используется CDN для более быстрой загрузки пейволов и отдельный резервный сервер на случай недоступности CDN. Эта система обеспечивает актуальность пейволов и их доступность даже при нестабильном интернет-соединении.</p> | | **loadTimeout** | по умолчанию: 5 сек | <p>Это значение ограничивает таймаут данного метода. При истечении таймаута будут возвращены кешированные данные или локальный резервный вариант.</p><p></p><p>Обратите внимание, что в редких случаях метод может завершиться с небольшой задержкой относительно значения, указанного в `loadTimeout`, поскольку операция может включать несколько запросов.</p> | :::note В v4 метод `getFlow` не принимает параметр `locale`. Для кастомных пейволов все доступные локализации возвращаются в Remote Config флоу (`flow.remoteConfigs`) — выберите ту, которая соответствует языку устройства или настройкам приложения. См. [Локализации и коды локалей](flutter-localizations-and-locale-codes). ::: Параметры ответа: | Parameter | Description | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | Объект `AdaptyFlow` с идентификаторами флоу (`instanceIdentity`, `variationId`), именем, плейсментом, вариантами пейволов (`paywalls`) и Remote Config-ами (`remoteConfigs`). | ## Получение продуктов \{#fetch-products\} Получив флоу, вы можете запросить массив продуктов, соответствующих ему: ```dart showLineNumbers try { final products = await Adapty().getPaywallProducts(flow: flow); // the requested products array } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` Параметры ответа: | Параметр | Описание | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Список объектов [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html) с: идентификатором продукта, названием продукта, ценой, валютой, длительностью подписки и рядом других свойств. | При реализации собственного дизайна пейвола вам, скорее всего, понадобится доступ к свойствам объекта [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html). Ниже приведены наиболее часто используемые свойства; полный список доступных свойств см. в документации по ссылке. | Свойство | Описание | |-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Чтобы отобразить название продукта, используйте `product.localizedTitle`. Обратите внимание, что локализация основана на стране стора, выбранной пользователем, а не на локали самого устройства. | | **Price** | Чтобы отобразить локализованную цену, используйте `product.price.localizedString`. Эта локализация основана на данных локали устройства. Цену в числовом виде можно получить через `product.price.amount`. Значение будет указано в местной валюте. Чтобы получить соответствующий символ валюты, используйте `product.price.currencySymbol`. | | **Subscription Period** | Чтобы отобразить период (например, неделя, месяц, год и т.д.), используйте `product.subscription?.localizedPeriod`. Эта локализация основана на локали устройства. Чтобы получить период подписки программно, используйте `product.subscription?.period`. Оттуда можно обратиться к перечислению `unit`, чтобы получить длину периода (day, week, month, year или unknown). Значение `numberOfUnits` возвращает количество единиц периода. Например, для квартальной подписки в свойстве unit будет `AdaptyPeriodUnit.month`, а в numberOfUnits — `3`. | | **Introductory Offer** | Чтобы отобразить значок или другой индикатор наличия introductory offer у подписки, проверьте свойство `product.subscription?.offer?.phases`. Это список, который может содержать до двух фаз скидки: фазу бесплатного пробного периода и фазу вводной цены. Каждый объект фазы содержит следующие полезные свойства:<br/>• `paymentMode`: перечисление со значениями `AdaptyPaymentMode.freeTrial`, `AdaptyPaymentMode.payAsYouGo`, `AdaptyPaymentMode.payUpFront` и `AdaptyPaymentMode.unknown`. Бесплатные пробные периоды имеют тип `AdaptyPaymentMode.freeTrial`.<br/>• `price`: цена со скидкой в числовом виде. Для бесплатных пробных периодов здесь будет `0`.<br/>• `localizedNumberOfPeriods`: строка, локализованная с учётом локали устройства, описывающая длительность предложения. Например, для трёхдневного пробного периода в этом поле будет `3 days`.<br/>• `subscriptionPeriod`: альтернативно можно получить отдельные детали периода предложения с помощью этого свойства. Оно работает так же, как описано в предыдущем разделе.<br/>• `localizedSubscriptionPeriod`: форматированный период подписки для скидки с учётом локали пользователя. | ## Ускорьте загрузку флоу с помощью флоу аудитории по умолчанию \{#speed-up-flow-fetching-with-default-audience-flow\} Как правило, флоу загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и плейсментов, а пользователи работают с медленным интернетом, загрузка флоу может занять больше времени, чем хотелось бы. В таких случаях имеет смысл показывать флоу по умолчанию — это обеспечит плавный пользовательский опыт вместо пустого экрана. Чтобы решить эту проблему, можно воспользоваться методом `getFlowForDefaultAudience`, который получает флоу указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать флоу с помощью метода `getFlow`, как описано в разделе [Получение информации о флоу](fetch-paywalls-and-products-flutter#fetch-flow-information) выше. :::warning Почему мы рекомендуем использовать `getFlow` Метод `getFlowForDefaultAudience` имеет ряд существенных недостатков: - **Потенциальные проблемы с обратной совместимостью**: если нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать пейволы с поддержкой текущей (устаревшей) версии, либо смириться с тем, что пользователи на текущей (устаревшей) версии могут столкнуться с проблемами при отображении пейволов. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, настроенный для аудитории **All Users**, — это означает отказ от персонализированного таргетинга (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вы готовы принять эти ограничения ради более быстрой загрузки флоу, используйте метод `getFlowForDefaultAudience` следующим образом. В противном случае придерживайтесь метода `getFlow`, описанного [выше](fetch-paywalls-and-products-flutter#fetch-flow-information). ::: ```dart showLineNumbers try { final flow = await Adapty().getFlowForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); // the requested flow } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` | Параметр | Обязательность | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение, которое вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильный интернет, рассмотрите вариант `.returnCacheDataElseLoad` — он возвращает кешированные данные, если они есть. В этом случае пользователи могут не получить самые последние данные, зато контент будет загружаться быстро вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому использовать его в течение сессии для сокращения сетевых запросов вполне безопасно.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.</p> | </SDKv4> <SDKv3> Прежде чем отображать Remote Config и кастомные пейволы, необходимо получить информацию о них. Обратите внимание, что этот раздел посвящён Remote Config и кастомным пейволам. Если вам нужны инструкции по получению пейволов, созданных с помощью Paywall Builder, обратитесь к статье [Получение пейволов Paywall Builder и их конфигурации](flutter-get-pb-paywalls). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Прежде чем начать получать пейволы и продукты в мобильном приложении (нажмите, чтобы развернуть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте пейвол и добавьте продукты в него](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте пейвол в плейсмент](create-placement) в дашборде Adapty. 4. [Установите SDK Adapty](sdk-installation-flutter) в своём мобильном приложении. </details> ## Получение данных о пейволе \{#fetch-paywall-information\} В Adapty [продукт](product) объединяет продукты из App Store и Google Play. Эти кросс-платформенные продукты добавляются в пейволы, позволяя отображать их в нужных плейсментах мобильного приложения. Чтобы показать продукты, нужно получить [пейвол](paywalls) из одного из ваших [плейсментов](placements) с помощью метода `getPaywall`. :::important **Не указывайте ID продуктов в коде явно.** Единственный ID, который нужно прописать в коде — это ID плейсмента. Пейволы настраиваются удалённо, поэтому количество продуктов и доступных офферов может меняться в любой момент. Приложение должно обрабатывать эти изменения динамически — если сегодня пейвол возвращает два продукта, а завтра три, отображайте все без изменений в коде. ::: ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en"); // the requested paywall } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается, что этот параметр будет языковым кодом, состоящим из одного или нескольких подтегов, разделённых символом минус (**-**). Первый подтег обозначает язык, второй — регион.</p><p></p><p>Например: `en` означает английский, `pt-br` — бразильский португальский.</p><p></p><p>Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](flutter-localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `.returnCacheDataElseLoad` — он возвращает кешированные данные, если они есть. В этом случае пользователи могут не получать самые свежие данные, зато время загрузки будет минимальным вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в рамках сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.</p><p></p><p>Adapty SDK хранит пейволы в двух слоях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](flutter-use-fallback-paywalls). Также используется CDN для ускорения загрузки пейволов и отдельный резервный сервер на случай недоступности CDN. Эта система обеспечивает актуальность пейволов и надёжность даже при нестабильном интернет-соединении.</p> | | **loadTimeout** | по умолчанию: 5 сек | <p>Это значение ограничивает тайм-аут для данного метода. По истечении тайм-аута будут возвращены кешированные данные или локальный резервный пейвол.</p><p></p><p>Обратите внимание: в редких случаях метод может завершиться с тайм-аутом чуть позже указанного в `loadTimeout` значения, поскольку операция может состоять из нескольких запросов под капотом.</p> | Не хардкодьте идентификаторы продуктов! Поскольку пейволы настраиваются удалённо, набор доступных продуктов, их количество и специальные предложения (например, бесплатные пробные периоды) могут меняться со временем. Убедитесь, что ваш код корректно обрабатывает подобные сценарии. Например, если изначально вы получаете 2 продукта, приложение должно отображать именно 2 продукта. Но если позже вы получите 3 продукта, приложение должно показать все 3 без каких-либо изменений в коде. Единственное, что нужно захардкодить, — это идентификатор плейсмента. Параметры ответа: | Параметр | Описание | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Объект [`AdaptyPaywall`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html) со списком идентификаторов продуктов, идентификатором пейвола, Remote Config и рядом других свойств. | ## Получение продуктов \{#fetch-products\} Получив пейвол, вы можете запросить массив продуктов, соответствующих ему: ```dart showLineNumbers try { final products = await Adapty().getPaywallProducts(paywall: paywall); // the requested products array } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Параметры ответа: | Параметр | Описание | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Список объектов [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html) со следующими свойствами: идентификатор продукта, название продукта, цена, валюта, длительность подписки и ряд других параметров. | При реализации собственного дизайна пейвола вам, скорее всего, потребуется доступ к свойствам объекта [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html). Ниже приведены наиболее часто используемые свойства; полный список доступных свойств см. в документации по ссылке. | Свойство | Описание | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Чтобы отобразить название продукта, используйте `product.localizedTitle`. Локализация основана на выбранной пользователем стране в сторе, а не на локали устройства. | | **Price** | Чтобы отобразить цену в локализованном формате, используйте `product.price.localizedString`. Локализация основана на локали устройства. Также можно получить цену как число через `product.price.amount` — значение будет в местной валюте. Чтобы получить символ валюты, используйте `product.price.currencySymbol`. | | **Subscription Period** | Чтобы отобразить период (например, неделю, месяц, год и т. д.), используйте `product.subscription?.localizedPeriod`. Локализация основана на локали устройства. Чтобы получить период подписки программно, используйте `product.subscription?.period`. Оттуда можно обратиться к enum `unit`, чтобы получить единицу длительности (день, неделя, месяц, год или unknown). Значение `numberOfUnits` вернёт количество единиц периода. Например, для квартальной подписки в свойстве unit будет `AdaptyPeriodUnit.month`, а в numberOfUnits — `3`. | | **Introductory Offer** | Чтобы отобразить бейдж или другой индикатор наличия introductory offer у подписки, проверьте свойство `product.subscription?.offer?.phases`. Это список, который может содержать до двух фаз скидки: фазу бесплатного пробного периода и фазу вводной цены. Каждый объект фазы содержит следующие полезные свойства:<br/>• `paymentMode`: enum со значениями `AdaptyPaymentMode.freeTrial`, `AdaptyPaymentMode.payAsYouGo`, `AdaptyPaymentMode.payUpFront` и `AdaptyPaymentMode.unknown`. Бесплатные пробные периоды имеют тип `AdaptyPaymentMode.freeTrial`.<br/>• `price`: скидочная цена в виде числа. Для бесплатных пробных периодов здесь будет `0`.<br/>• `localizedNumberOfPeriods`: строка, локализованная с учётом локали устройства и описывающая длительность предложения. Например, для трёхдневного пробного периода в этом поле будет `3 days`.<br/>• `subscriptionPeriod`: альтернативно можно получить отдельные детали периода предложения через это свойство — оно работает так же, как описано в предыдущем разделе.<br/>• `localizedSubscriptionPeriod`: форматированный период подписки скидки для локали пользователя. | ## Ускорьте загрузку пейвола с помощью пейвола для аудитории по умолчанию \{#speed-up-paywall-fetching-with-default-audience-paywall\} Как правило, пейволы загружаются практически мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и пейволов, а пользователи находятся в зоне слабого интернета, загрузка пейвола может занять больше времени, чем хотелось бы. В таких ситуациях имеет смысл показывать пейвол по умолчанию, чтобы пользователь не остался без пейвола вовсе. Чтобы решить эту проблему, можно использовать метод `getPaywallForDefaultAudience`, который получает пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать пейвол методом `getPaywall`, как описано в разделе [Получение информации о пейволе](fetch-paywalls-and-products-flutter#fetch-paywall-information) выше. :::warning Почему мы рекомендуем использовать `getPaywall` Метод `getPaywallForDefaultAudience` имеет ряд существенных недостатков: - **Возможные проблемы с обратной совместимостью**: Если вам нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать пейволы с поддержкой текущей (устаревшей) версии, либо смириться с тем, что пользователи на этой версии могут столкнуться с нерендерящимися пейволами. - **Потеря таргетинга**: Все пользователи будут видеть один и тот же пейвол, настроенный для аудитории **All Users**, — а значит, вы теряете персонализированный таргетинг (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вас устраивают эти ограничения ради более быстрой загрузки пейвола, используйте метод `getPaywallForDefaultAudience` следующим образом. В противном случае используйте `getPaywall`, описанный [выше](fetch-paywalls-and-products-flutter#fetch-paywall-information). ::: ```dart showLineNumbers try { final paywall = await Adapty().getPaywallForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` :::note Метод `getPaywallForDefaultAudience` доступен начиная с версии Flutter SDK 3.2.0. ::: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>необязательный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается, что этот параметр будет представлять собой языковой код, состоящий из одного или нескольких субтегов, разделённых символом минус (**-**). Первый субтег обозначает язык, второй — регион.</p><p></p><p>Например: `en` означает английский язык, `pt-br` — бразильский португальский.</p><p></p><p>Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](flutter-localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кэшированные данные в случае ошибки. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если вы считаете, что у ваших пользователей нестабильное интернет-соединение, попробуйте использовать `.returnCacheDataElseLoad` — он возвращает кэшированные данные, если они есть. В таком случае пользователи могут не получать самые свежие данные, но зато загрузка будет быстрой вне зависимости от качества соединения. Кэш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание, что кэш сохраняется при перезапуске приложения и очищается только при его переустановке или при ручной очистке.</p> | </SDKv3> --- # File: present-remote-config-paywalls-flutter --- --- title: "Отображение пейвола на основе Remote Config в Flutter SDK" description: "Узнайте, как отображать пейволы с Remote Config в Adapty Flutter SDK для персонализации пользовательского опыта." --- <SDKv4> Если вы настроили пейвол с помощью Remote Config, вам нужно реализовать его отображение в коде мобильного приложения. Поскольку Remote Config гибко адаптируется под ваши задачи, вы сами решаете, что включить и как будет выглядеть пейвол. Мы предоставляем метод для получения Remote Config, а дальше вы сами управляете отображением пейвола. ## Получение Remote Config пейвола и его отображение \{#get-paywall-remote-config-and-present-it\} В v4 флоу содержит список `remoteConfigs` — по одному Remote Config на каждую настроенную локализацию. Выберите запись, соответствующую локали пользователя, и извлеките нужные значения. Подробнее о выборе подходящей локализации — в разделе [Локализации и коды локалей](flutter-localizations-and-locale-codes). ```dart showLineNumbers try { final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); // one entry per configured localization; fall back to the first one final config = flow.remoteConfigs.firstWhereOrNull((c) => c.locale == 'en') ?? flow.remoteConfig; final String? headerText = config?.dictionary?['header_text'] as String?; } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` На этом этапе, получив все необходимые значения, можно приступить к рендерингу и сборке визуально привлекательного экрана. Убедитесь, что дизайн адаптирован для различных размеров экранов и ориентаций мобильных телефонов, обеспечивая удобный и бесперебойный пользовательский опыт на разных устройствах. :::warning Обязательно фиксируйте событие просмотра пейвола, как описано ниже, чтобы аналитика Adapty могла собирать данные для воронок и A/B-тестов. ::: После отображения пейвола переходите к настройке процесса покупки. Когда пользователь совершает покупку, просто вызовите `.makePurchase()` с продуктом из вашего пейвола. Подробнее о методе `.makePurchase()` читайте в разделе [Совершение покупок](flutter-making-purchases). Рекомендуем [создать резервный пейвол](flutter-use-fallback-paywalls). Он будет отображаться пользователю при отсутствии интернет-соединения или кэша, обеспечивая бесперебойную работу даже в таких ситуациях. ## Отслеживание событий просмотра пейвола \{#track-paywall-view-events\} Adapty помогает измерять эффективность ваших пейволов. Данные о покупках собираются автоматически, но события просмотра пейвола нужно логировать вручную — только вы знаете, когда пользователь видит пейвол. Чтобы залогировать событие просмотра пейвола, вызовите `.logShowFlow(flow: flow)` — это отразится в метриках пейвола в воронках и A/B-тестах. :::important Вызывать `.logShowFlow(flow: flow)` не нужно, если вы отображаете флоу или пейволы, созданные в [конструкторе](adapty-paywall-builder). ::: ```dart showLineNumbers try { await Adapty().logShowFlow(flow: flow); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` Параметры запроса: | Параметр | Наличие | Описание | | :---------- | :------- |:----------------------------------------------------------------------| | **flow** | обязательный | Объект `AdaptyFlow`. | </SDKv4> <SDKv3> Если вы настроили пейвол с помощью Remote Config, вам нужно реализовать его отображение в коде мобильного приложения. Поскольку Remote Config предоставляет гибкость под ваши нужды, вы полностью контролируете содержимое и внешний вид пейвола. Мы предоставляем метод для получения Remote Config, чтобы вы могли самостоятельно отобразить пейвол, настроенный через него. ## Получение Remote Config пейвола и его отображение \{#get-paywall-remote-config-and-present-it\} Чтобы получить Remote Config пейвола, обратитесь к свойству `remoteConfig` и извлеките нужные значения. ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(placementId: "YOUR_PLACEMENT_ID"); final String? headerText = paywall.remoteConfig?.dictionary?['header_text'] as String?; } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` На этом этапе, получив все необходимые значения, можно переходить к рендерингу и сборке визуально привлекательного экрана. Убедитесь, что дизайн адаптирован под различные экраны и ориентации мобильных телефонов, обеспечивая удобный пользовательский опыт на всех устройствах. :::warning Обязательно зафиксируйте событие просмотра пейвола, как описано ниже, — это позволит аналитике Adapty собирать данные для воронок и A/B-тестов. ::: После того как пейвол отображён, переходите к настройке процесса покупки. Когда пользователь совершает покупку, просто вызовите `.makePurchase()` с продуктом из вашего пейвола. Подробнее о методе `.makePurchase()` читайте в разделе [Совершение покупок](flutter-making-purchases). Рекомендуем [создать резервный пейвол](flutter-use-fallback-paywalls). Он будет показываться пользователю при отсутствии интернета или кэша, обеспечивая бесперебойную работу в таких ситуациях. ## Отслеживание событий просмотра пейвола \{#track-paywall-view-events\} Adapty помогает измерять эффективность ваших пейволов. Данные о покупках собираются автоматически, но логирование просмотров пейвола требует вашего участия — только вы знаете, когда пользователь видит пейвол. Чтобы залогировать событие просмотра пейвола, вызовите `.logShowPaywall(paywall)` — это отразится в метриках пейвола в воронках и A/B-тестах. :::important Вызывать `.logShowPaywall(paywall)` не нужно, если вы отображаете пейволы, созданные в [Paywall Builder](adapty-paywall-builder). ::: ```dart showLineNumbers try { final result = await Adapty().logShowPaywall(paywall: paywall); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Параметры запроса: | Параметр | Наличие | Описание | | :---------- | :------- |:----------------------------------------------------------------------| | **paywall** | обязательный | Объект [`AdaptyPaywall`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html). | </SDKv3> --- # File: flutter-making-purchases --- --- title: "Совершение покупок в мобильном приложении с Flutter SDK" description: "Руководство по обработке встроенных покупок и подписок с помощью Adapty." --- Отображение пейволов в мобильном приложении — важный шаг для предоставления пользователям доступа к премиум-контенту или сервисам. Однако просто показать пейвол достаточно для поддержки покупок только в том случае, если вы используете [Paywall Builder](adapty-paywall-builder) для их настройки. Если вы не используете Paywall Builder, для завершения покупки и открытия нужного контента необходимо использовать отдельный метод `.makePurchase()`. Этот метод служит точкой входа для пользователей при взаимодействии с пейволами и совершении транзакций. Если для продукта, который пользователь хочет купить, активен promotional offer, Adapty автоматически применит его в момент покупки. :::warning Обратите внимание: introductory offer применяется автоматически только при использовании пейволов, созданных в Paywall Builder. В других случаях вам нужно [проверить право пользователя на introductory offer на iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios). Пропуск этого шага может привести к отклонению приложения при публикации. Кроме того, пользователи, имеющие право на introductory offer, могут быть списана полная стоимость. ::: Убедитесь, что вы выполнили [начальную настройку](quickstart), не пропустив ни одного шага. Без неё мы не сможем валидировать покупки. ## Совершение покупки \{#make-purchase\} :::note **Используете [Paywall Builder](adapty-paywall-builder)?** Покупки обрабатываются автоматически — этот шаг можно пропустить. **Нужны пошаговые инструкции?** Ознакомьтесь с [гайдом по быстрому старту](flutter-implement-paywalls-manually) — там есть полное руководство по реализации с подробным контекстом. ::: ```dart showLineNumbers try { final purchaseResult = await Adapty().makePurchase(product: product); switch (purchaseResult) { case AdaptyPurchaseResultSuccess(profile: final profile): if (profile.accessLevels['premium']?.isActive ?? false) { // Grant access to the paid features } break; case AdaptyPurchaseResultPending(): break; case AdaptyPurchaseResultUserCancelled(): break; default: break; } } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } ``` Параметры запроса: | Параметр | Наличие | Описание | | :---------- | :------- | :-------------------------------------------------------------------------------------------------- | | **Product** | обязательный | Объект [`AdaptyPaywallProduct`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywallProduct-class.html), полученный из пейвола. | Параметры ответа: | Параметр | Описание | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>Если запрос выполнен успешно, ответ содержит этот объект. Объект [AdaptyProfile](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html) предоставляет исчерпывающую информацию об уровнях доступа пользователя, подписках и разовых покупках в приложении.</p><p>Проверьте статус уровня доступа, чтобы убедиться, что пользователь имеет необходимый доступ к приложению.</p> | :::warning **Примечание:** если вы используете Apple StoreKit версии ниже v2.0 и Adapty SDK версии ниже v2.9.0, вам нужно указать [общий секрет Apple App Store](app-store-connection-configuration#step-5-enter-app-store-shared-secret). Этот метод в настоящее время устарел и не рекомендуется Apple. ::: ## Смена подписки при совершении покупки \{#change-subscription-when-making-a-purchase\} Когда пользователь выбирает новую подписку вместо продления текущей, поведение зависит от стора: - В App Store подписка обновляется автоматически в рамках группы подписок. Если пользователь покупает подписку из одной группы, уже имея активную из другой, обе подписки будут активны одновременно. - В Google Play подписка не обновляется автоматически. Переключение нужно реализовать в коде вашего приложения, как описано ниже. Чтобы заменить подписку на другую в Android, вызовите метод `.makePurchase()` с дополнительным параметром: ```dart showLineNumbers try { final subscriptionUpdateParams = AdaptyAndroidSubscriptionUpdateParameters( 'OLD_PRODUCT_ID', AdaptyAndroidSubscriptionUpdateReplacementMode.immediateWithTimeProration, ); final result = await Adapty().makePurchase( product: product, parameters: AdaptyPurchaseParameters( subscriptionUpdateParams: subscriptionUpdateParams, ), ); // successful cross-grade } on AdaptyError catch (adaptyError) { // Handle the error } catch (e) { // Handle the error } ``` Дополнительный параметр запроса: | Параметр | Наличие | Описание | | :--------------------------- | :------- |:--------------------------------------------------------------------------------------------------------| | **parameters** | обязательный | объект `AdaptyPurchaseParameters` с полем `subscriptionUpdateParams`, установленным в объект [`AdaptyAndroidSubscriptionUpdateParameters`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyAndroidSubscriptionUpdateParameters-class.html). | Подробнее о подписках и режимах замены можно прочитать в документации Google для разработчиков: - [О режимах замены](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Рекомендации Google по режимам замены](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - Режим замены [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE()). Примечание: этот метод доступен только для повышения уровня подписки. Понижение уровня не поддерживается. - Режим замены [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED()). Примечание: реальное изменение подписки произойдёт только по окончании текущего расчётного периода. ## Активация промокодов в iOS \{#redeem-offer-codes-in-ios\} <Details> <summary>Об офферных кодах</summary> Офферные коды позволяют предоставлять скидки или бесплатные пробные периоды конкретным пользователям. В отличие от обычных офферов, которые применяются автоматически, офферные коды распространяются за пределами приложения — через email-рассылки, социальные сети или печатные материалы. Пользователи активируют их, вводя код в App Store, переходя по ссылке для активации или через диалог внутри приложения. Чтобы настроить офферные коды, откройте подписку в App Store Connect и перейдите в раздел **Offer Codes**. Вы можете создать [три вида](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes) офферных кодов: - **Free** — подписка бесплатна на заданный период, следующее продление — по полной цене. - **Pay as you go** — пользователь платит сниженную цену в каждом расчётном периоде на протяжении заданного срока, после чего подписка продлевается по полной цене. - **Pay up front** — пользователь единовременно платит сниженную цену за весь срок оффера, после чего подписка продлевается по полной цене. Добавлять офферные коды в Adapty не нужно. Apple помечает каждую транзакцию в период действия оффера категорией офферного кода. Это касается как первоначальной активации, так и всех последующих продлений со скидкой. Adapty обнаруживает метку и записывает каждую транзакцию с категорией оффера `offer_code`. Как только период оффера заканчивается и подписка продлевается по полной цене, метка исчезает. Вы можете фильтровать аналитику по типу оффера **Offer Code** в [дашборде Adapty](controls-filters-grouping-compare-proceeds). #### Устранение расхождений в выручке \{#revenue-discrepancy-troubleshooting\} Если транзакция по офферному коду отображается в Adapty по полной цене продукта вместо сниженной цены оффера, проверьте следующее в App Store Connect: - Для офферного кода настроены корректные цены для всех регионов, где пользователи могут его активировать. - Цена оффера задана для конкретной страны или региона пользователя. Apple передаёт региональную цену в транзакции. Если для оффера не настроена региональная цена, Apple может передать полную цену продукта. Вы можете фильтровать и проверять транзакции по офферным кодам в [дашборде Adapty](controls-filters-grouping-compare-proceeds) по фильтрам типа оффера **Offer Code** и **Offer Discount Type**. #### Устаревшие промокоды (deprecated) \{#legacy-promo-codes-deprecated\} :::warning Apple прекратила поддержку промокодов для встроенных покупок в марте 2026 года. Офферные коды заменяют их с расширенными возможностями: настраиваемые условия применения, сроки действия и до 1 миллиона кодов в квартал. Если вы ранее использовали промокоды для встроенных покупок, перейдите на офферные коды в App Store Connect. ::: Устаревшие промокоды (не более 100 на приложение на версию) предоставляли бесплатный доступ к подписке. В отличие от офферных кодов, Apple не включала информацию о скидке в транзакции по промокодам — в чеке указывалась полная цена продукта. В результате Adapty записывал эти транзакции по полной цене, что приводило к расхождениям в выручке между аналитикой Adapty и App Store Connect. Если вы видите исторические транзакции по полной цене, которые должны были быть бесплатными, скорее всего, они связаны с устаревшими промокодами. Поскольку эти коды больше не поддерживаются, перейдите на офферные коды для точного учёта выручки. </Details> Чтобы показать экран активации кода в приложении: ```dart showLineNumbers try { await Adapty().presentCodeRedemptionSheet(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` :::danger По нашим наблюдениям, экран активации промокода в некоторых приложениях работает ненадёжно. Рекомендуем перенаправлять пользователя напрямую в App Store. Для этого нужно открыть URL следующего формата: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: ### Управление предоплаченными планами (Android) \{#manage-prepaid-plans-android\} Если пользователи вашего приложения могут приобретать [предоплаченные планы](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (например, невозобновляемую подписку на несколько месяцев), вы можете включить [отложенные транзакции](https://developer.android.com/google/play/billing/subscriptions#pending) для таких планов. ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withGoogleEnablePendingPrepaidPlans(true), ); ``` --- # File: flutter-restore-purchase --- --- title: "Восстановление покупок в мобильном приложении с Flutter SDK" description: "Узнайте, как восстановить покупки в Adapty для обеспечения бесперебойного пользовательского опыта." --- Восстановление покупок в iOS и Android позволяет пользователям восстановить доступ к ранее купленному контенту — подпискам или встроенным покупкам — без повторного списания средств. Это особенно удобно для тех, кто переустановил приложение или перешёл на новое устройство и хочет снова получить доступ к оплаченному контенту. :::note В пейволах, созданных с помощью [Paywall Builder](adapty-paywall-builder), покупки восстанавливаются автоматически — дополнительный код писать не нужно. Если это ваш случай — этот шаг можно пропустить. ::: Чтобы восстановить покупку, если вы не используете [Paywall Builder](adapty-paywall-builder) для настройки пейвола, вызовите метод `.restorePurchases()`: ```dart showLineNumbers try { final profile = await Adapty().restorePurchases(); if (profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive ?? false) { // successful access restore } } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Параметры ответа: | Параметр | Описание | |---------|-----------| | **Profile** | <p>Объект [`AdaptyProfile`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html). Эта модель содержит информацию об уровнях доступа, подписках и разовых покупках.</p><p>Проверьте **статус уровня доступа**, чтобы определить, есть ли у пользователя доступ к приложению.</p> | :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: --- # File: implement-observer-mode-flutter --- --- title: "Реализация Observer mode во Flutter SDK" description: "Реализуйте Observer mode в Adapty для отслеживания событий подписки пользователей во Flutter SDK." --- Если у вас уже есть собственная инфраструктура покупок и вы не готовы полностью переходить на Adapty, можно воспользоваться [Observer mode](observer-vs-full-mode). В базовом варианте Observer Mode предоставляет расширенную аналитику и бесшовную интеграцию с системами атрибуции и аналитики. Если это подходит вам, нужно лишь: 1. Включить этот режим при настройке SDK, установив параметр `observerMode` в значение `true`. Следуйте инструкциям по настройке для [Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk). 2. [Передавать транзакции](report-transactions-observer-mode-flutter) из вашей существующей инфраструктуры покупок в Adapty. ## Настройка режима Observer \{#observer-mode-setup\} Включите режим Observer, если вы самостоятельно обрабатываете покупки и статус подписки, а Adapty используете только для отправки событий подписки и аналитики. :::important В режиме Observer SDK Adapty не закрывает транзакции самостоятельно — позаботьтесь об этом в своём коде. ::: ```dart showLineNumbers title="main.dart" await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') ..withObserverMode(true) // Enable observer mode ..withLogLevel(AdaptyLogLevel.verbose), ); ``` Параметры: | Параметр | Описание | | --------------------------- | ------------------------------------------------------------ | | observerMode | Булево значение, которое управляет [Observer mode](observer-vs-full-mode). Значение по умолчанию — `false`. | ## Использование пейволов Adapty в Observer Mode \{#using-adapty-paywalls-in-observer-mode\} Если вы также хотите использовать пейволы и A/B-тесты Adapty, это возможно — но в режиме Observer Mode потребуется дополнительная настройка. Вот что нужно сделать помимо шагов выше: 1. Отображайте пейволы как обычно для [пейволов на Remote Config](present-remote-config-paywalls-flutter). 3. [Свяжите пейволы](report-transactions-observer-mode-flutter) с транзакциями покупок. :::tip В SDK v4 вы также можете отображать флоу и пейволы, отрисованные Adapty, в режиме Observer: зарегистрируйте `AdaptyUIObserverModeResolver`, чтобы выполнять покупку или восстановление с помощью вашего собственного кода, когда пользователь нажимает соответствующую кнопку. См. [Отображение флоу в режиме Observer](flutter-present-flows-in-observer-mode). ::: --- # File: report-transactions-observer-mode-flutter --- --- title: "Отчёт о транзакциях в Observer Mode в Flutter SDK" description: "Отправляйте информацию о транзакциях покупок в Adapty Observer Mode для аналитики пользователей и отслеживания дохода во Flutter SDK." --- <Tabs groupId="sdk-version" queryString> <TabItem value="current" label="Adapty SDK v3.4+ (current)" default> В режиме Observer Adapty SDK не может самостоятельно отслеживать покупки, сделанные через вашу существующую систему. Вам нужно передавать транзакции из стора вручную. Важно настроить это **до** публикации приложения, чтобы избежать ошибок в аналитике. Используйте `reportTransaction`, чтобы явно сообщать Adapty о каждой транзакции. :::warning **Не пропускайте отчёт о транзакции!** Если вы не вызовете `reportTransaction`, Adapty не распознает транзакцию, она не появится в аналитике и не будет отправлена в интеграции. ::: Если вы используете пейволы Adapty, передавайте `variationId` при отчёте о транзакции. Это свяжет покупку с пейволом, который её инициировал, и обеспечит корректную аналитику пейволов. ```dart showLineNumbers try { // every time when calling transaction.finish() await Adapty().reportTransaction( "YOUR_TRANSACTION_ID", variationId: "PAYWALL_VARIATION_ID", // optional ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` Параметры: | Параметр | Наличие | Описание | | ------------- | -------- | ------------------------------------------------------------ | | transactionId | обязательный | <ul><li> Для iOS: идентификатор транзакции.</li><li> Для Android: строковый идентификатор `purchase.getOrderId` покупки, где покупка — это экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) библиотеки биллинга.</li></ul> | | variationId | необязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html). | </TabItem> <TabItem value="old" label="Adapty SDK 3.3.x (legacy)" default> В режиме Observer, SDK не может самостоятельно отслеживать покупки, сделанные через вашу существующую систему покупок. Вам нужно передавать транзакции из вашего стора или восстанавливать их. Важно настроить это **до** выпуска приложения, чтобы избежать ошибок в аналитике. Используйте `reportTransaction` на обеих платформах для явной передачи каждой транзакции, а также `restorePurchases` на Android как дополнительный шаг, чтобы Adapty её распознала. :::warning **Не пропускайте отчётность о транзакции и восстановление покупки!** Если не вызвать эти методы, Adapty не распознает транзакцию — она не появится в аналитике и не будет отправлена в интеграции. ::: Если вы используете пейволы Adapty, передавайте `variationId` при отчёте о транзакции. Это свяжет покупку с пейволом, который её инициировал, и обеспечит точную аналитику пейвола. ```dart showLineNumbers // every time when calling transaction.finish() if (Platform.isAndroid) { try { await Adapty().restorePurchases(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } } try { // every time when calling transaction.finish() await Adapty().reportTransaction( "YOUR_TRANSACTION_ID", variationId: "PAYWALL_VARIATION_ID", // optional ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` Параметры: | Параметр | Наличие | Описание | | ------------- | -------- | ------------------------------------------------------------ | | transactionId | обязательный | <ul><li> Для iOS, StoreKit 1: объект [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction).</li><li> Для iOS, StoreKit 2: объект [Transaction](https://developer.apple.com/documentation/storekit/transaction).</li><li> Для Android: строковый идентификатор (purchase.getOrderId покупки, где purchase — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) из библиотеки биллинга).</li></ul> | | variationId | необязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyPaywall-class.html). | </TabItem> <TabItem value="old2" label="Adapty SDK до 3.2.x (устаревшая версия)" default> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> **Передача транзакций** - Версии до 3.1.x автоматически отслеживают транзакции в App Store, поэтому передавать их вручную не нужно. - Версия 3.2 не поддерживает Observer Mode. </TabItem> <TabItem value="kotlin" label="Android and Android-based cross-platforms" default> **Передача транзакций** Используйте `restorePurchases`, чтобы передать транзакцию в Adapty в режиме Observer Mode, как описано на странице [Восстановление покупок в коде приложения](flutter-restore-purchase). :::warning **Не пропускайте передачу транзакций!** Если вы не вызовете `restorePurchases`, Adapty не распознает транзакцию: она не появится в аналитике и не будет отправлена в интеграции. ::: </TabItem> </Tabs> **Привязка пейволов к транзакциям** SDK Adapty не может самостоятельно определить источник покупок, поскольку вы сами их обрабатываете. Поэтому, если вы планируете использовать пейволы и/или A/B-тесты в режиме Observer, вам необходимо в коде мобильного приложения связать транзакцию из стора с соответствующим пейволом. Это важно сделать правильно до релиза приложения, иначе это приведёт к ошибкам в аналитике. ```dart final transactionId = transaction.transactionIdentifier final variationId = paywall.variationId try { await Adapty().setVariationId('transactionId', variationId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` </TabItem> </Tabs> --- # File: flutter-troubleshoot-purchases --- --- title: "Устранение неполадок с покупками в Flutter SDK" description: "Устранение неполадок с покупками в Flutter SDK" --- Этот гайд поможет вам решить распространённые проблемы при реализации покупок вручную в Flutter SDK. ## makePurchase вызывается успешно, но профиль не обновляется \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\} **Проблема**: Метод `makePurchase` выполняется успешно, но профиль пользователя и статус подписки не обновляются в Adapty. **Причина**: Как правило, это указывает на неполную настройку Google Play Store или проблемы с конфигурацией. **Решение**: Убедитесь, что вы выполнили все [шаги по настройке Google Play](initial-android). ## makePurchase вызывается дважды \{#makepurchase-is-invoked-twice\} **Проблема**: Метод `makePurchase` вызывается несколько раз для одной и той же покупки. **Причина**: Обычно это происходит, когда процесс покупки запускается несколько раз из-за проблем с управлением состоянием UI или быстрых действий пользователя. **Решение**: Убедитесь, что вы выполнили все [шаги по настройке Google Play](initial-android). ## AdaptyError.cantMakePayments в режиме наблюдателя \{#adaptyerrorcantmakepayments-in-observer-mode\} **Проблема**: При использовании `makePurchase` в режиме наблюдателя возникает ошибка `AdaptyError.cantMakePayments`. **Причина**: В режиме наблюдателя покупки нужно обрабатывать на вашей стороне, а не использовать метод `makePurchase` из Adapty. **Решение**: Если вы используете `makePurchase` для покупок, отключите режим наблюдателя. Нужно либо использовать `makePurchase`, либо обрабатывать покупки самостоятельно в режиме наблюдателя. Подробнее см. в разделе [Реализация режима наблюдателя](implement-observer-mode-flutter). ## Ошибка Adapty: (code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\} **Проблема**: Вы получаете ошибку недоступности биллинга от Google Play Store. **Причина**: Эта ошибка не связана с Adapty. Это ошибка библиотеки Google Play Billing, означающая, что биллинг недоступен на устройстве. **Решение**: Эта ошибка не связана с Adapty. Подробнее о ней можно узнать в документации Play Store: [Handle BillingResult response codes](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers. ## Not found makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\} **Проблема**: Возникают проблемы с тем, что `makePurchasesCompletionHandlers` не найден. **Причина**: Как правило, это связано с проблемами при тестировании в песочнице. **Решение**: Создайте нового пользователя в песочнице и повторите попытку. Это часто решает проблемы с обработчиком завершения покупки в песочнице. ## Другие проблемы \{#other-issues\} **Проблема**: У вас возникают другие проблемы с покупками, не описанные выше. **Решение**: При необходимости обновите SDK до последней версии с помощью [гайдов по миграции](flutter-sdk-migration-guides). Многие проблемы устранены в новых версиях SDK. --- # File: flutter-identifying-users --- --- title: "Идентификация пользователей в Flutter SDK" description: "Идентифицируйте пользователей в Adapty для улучшения персонализированного опыта подписок." --- Adapty создаёт внутренний ID профиля для каждого пользователя. Однако если у вас есть собственная система аутентификации, вы можете задать свой Customer User ID. Пользователей можно искать по Customer User ID в разделе [Профили](profiles-crm), а также использовать его в [серверном API](getting-started-with-server-side-api) — он будет передаваться во все интеграции. ### Передача пользовательского идентификатора при инициализации \{#setting-customer-user-id-on-configuration\} Если у вас есть идентификатор пользователя на момент инициализации, просто передайте его в качестве параметра `customerUserId` в метод `.activate()`: ```dart showLineNumbers title="Dart" try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID) ); } catch (e) { // handle the error } ``` :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Установка идентификатора пользователя после инициализации \{#setting-customer-user-id-after-configuration\} Если при инициализации SDK у вас не было идентификатора пользователя, его можно задать в любой момент позже с помощью метода `.identify()`. Чаще всего этот метод используют после регистрации или авторизации — когда анонимный пользователь становится аутентифицированным. ```dart showLineNumbers try { await Adapty().identify(customerUserId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Параметры запроса: - **Customer User ID** (обязательный): строковый идентификатор пользователя. :::warning Повторная отправка важных данных пользователя В некоторых случаях, например когда пользователь снова входит в свой аккаунт, серверы Adapty уже располагают информацией об этом пользователе. В таких сценариях SDK автоматически переключится на работу с новым пользователем. Если вы передавали какие-либо данные анонимному пользователю — например, пользовательские атрибуты или атрибуцию из сторонних сетей — эти данные необходимо отправить повторно для идентифицированного пользователя. Также важно помнить, что после идентификации пользователя нужно заново запросить все пейволы и продукты, поскольку данные нового пользователя могут отличаться. ::: ### Выход и вход \{#logging-out-and-logging-in\} Вы можете выйти из аккаунта пользователя в любое время, вызвав метод `.logout()`: ```dart showLineNumbers try { await Adapty().logout(); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle unknown error } ``` После этого можно авторизовать пользователя с помощью метода `.identify()`. ## Назначение `appAccountToken` (iOS) [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) — это **UUID**, который позволяет связывать транзакции App Store с вашим внутренним идентификатором пользователя. StoreKit привязывает этот токен к каждой транзакции, поэтому ваш бэкенд может сопоставлять данные App Store с конкретными пользователями. Используйте стабильный UUID, генерируемый один раз для каждого пользователя, и применяйте его для одного и того же аккаунта на всех устройствах. Это гарантирует, что покупки и уведомления App Store будут корректно привязаны к нужному пользователю. Токен можно задать двумя способами — при активации SDK или при идентификации пользователя. :::important Всегда передавайте `appAccountToken` вместе с `customerUserId`. Если передать только токен, он не будет включён в транзакцию. ::: ```dart showLineNumbers // Во время конфигурации: try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID, iosAppAccountToken: "YOUR_APP_ACCOUNT_TOKEN") ); } catch (e) { // обработайте ошибку } // Или при идентификации пользователей try { await Adapty().identify(customerUserId, iosAppAccountToken: "YOUR_APP_ACCOUNT_TOKEN"); } on AdaptyError catch (adaptyError) { // обработайте ошибку } catch (e) { } ``` ### Установка обфусцированных идентификаторов аккаунта (Android) \{#set-obfuscated-account-ids-android\} Google Play требует обфусцированные идентификаторы аккаунта для определённых сценариев использования — с целью защиты конфиденциальности и безопасности пользователей. Эти идентификаторы позволяют Google Play отслеживать покупки, сохраняя анонимность пользователей, что особенно важно для предотвращения мошенничества и аналитики. Задавать эти идентификаторы может потребоваться, если приложение работает с чувствительными пользовательскими данными или если вы обязаны соблюдать определённые требования по защите персональных данных. Обфусцированные идентификаторы позволяют Google Play отслеживать покупки, не раскрывая реальные пользовательские данные. ```dart showLineNumbers // Во время настройки: try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') ..withCustomerUserId(YOUR_CUSTOMER_USER_ID, androidObfuscatedAccountId: "OBFUSCATED_ACCOUNT_ID") ); } catch (e) { // обработка ошибки } // Или при идентификации пользователей try { await Adapty().identify(customerUserId, androidObfuscatedAccountId: "OBFUSCATED_ACCOUNT_ID"); } on AdaptyError catch (adaptyError) { // обработка ошибки } catch (e) { } ``` ## Определение пользователей на разных устройствах \{#detect-users-across-devices\} При активации SDK он автоматически считывает существующие права пользователя из StoreKit (iOS) или Google Play Billing (Android) и синхронизирует их с бэкендом Adapty. Активная подписка появляется в профиле Adapty без вызова `restorePurchases` со стороны приложения. Что **не** происходит автоматически — так это распознавание того, что профиль на новом устройстве принадлежит тому же пользователю, что и профиль на исходном устройстве. Adapty сопоставляет профили по Customer User ID, поэтому непрерывность идентификации зависит от того, что вы используете в качестве CUID. **Что Adapty может определить между устройствами** | Ваша настройка | Что Adapty определяет | Что нужно сделать | | --- | --- | --- | | Customer User ID = `device_id` (без входа в аккаунт) | Новое устройство получает другой CUID и, следовательно, другой профиль. Подписка синхронизируется с новым профилем через событие **Access level updated**, но `subscription_started` не срабатывает — новый профиль считается наследником исходной покупки. Аналитика, основанная на `subscription_started`, будет занижать количество возвращающихся пользователей. | Используйте стабильный идентификатор аккаунта в качестве Customer User ID, чтобы вернувшийся пользователь соответствовал существующему профилю на разных устройствах. | | Customer User ID = стабильный идентификатор аккаунта (вход на каждом устройстве) | SDK автоматически синхронизирует подписку при `activate()`, а `identify()` сопоставляет существующий профиль по CUID. | Никаких дополнительных действий не требуется — и идентификация, и подписка разрешаются автоматически. | | Наследник Apple Family Sharing | Член семьи получает подписку только через событие **Access level updated** — `subscription_started` не срабатывает. | Отслеживайте событие **Access level updated**. Полную матрицу событий см. в разделе [Apple Family Sharing](apple-family-sharing). | | Один аккаунт Apple/Google, разные пользователи внутри приложения | Первый профиль, зафиксировавший покупку, становится родительским. Последующие профили видят подписку через цепочку наследования с одним событием **Access level updated**. | Требуйте входа в аккаунт, затем выберите [режим совместного использования](sharing-paid-access-between-user-accounts), подходящий для вашей модели. | **Восстановление покупок на новом устройстве** Добавьте кнопку «Восстановить покупки», инициируемую пользователем, на свой пейвол. Apple App Review (руководство 3.1.1) её требует, и она служит запасным вариантом на случай, если автоматическая синхронизация пропустит граничный случай. Кнопка должна вызывать `restorePurchases` в вашем SDK. Программный вызов `restorePurchases` при первом запуске не нужен для обычного использования — SDK уже выполняет аналогичную операцию при `activate()`. Программные вызовы стоит использовать только для принудительной проверки чека, например при отладке отсутствующего доступа после завершения `activate()`. --- # File: flutter-setting-user-attributes --- --- title: "Задание атрибутов пользователя в Flutter SDK" description: "Узнайте, как задавать атрибуты пользователя в Adapty для более точной сегментации аудитории." --- Вы можете задавать пользователям вашего приложения дополнительные атрибуты: email, номер телефона и другие. Атрибуты можно использовать для создания [сегментов](segments) пользователей или просматривать их в CRM. ### Настройка атрибутов пользователя \{#setting-user-attributes\} Чтобы задать атрибуты пользователя, вызовите метод `.updateProfile()`: ```dart showLineNumbers final builder = AdaptyProfileParametersBuilder() ..setEmail("email@email.com") ..setPhoneNumber("+18888888888") ..setFirstName('John') ..setLastName('Appleseed') ..setGender(AdaptyProfileGender.other) ..setBirthday(DateTime(1970, 1, 3)); try { await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Обратите внимание, что атрибуты, которые вы ранее задали с помощью метода `updateProfile`, сбрасываться не будут. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Список допустимых ключей \{#the-allowed-keys-list\} Допустимые ключи `<Key>` объекта `AdaptyProfileParameters.Builder` и соответствующие значения `<Value>` приведены ниже: | Ключ | Значение | |---|-----| | <p>email</p><p>phoneNumber</p><p>firstName</p><p>lastName</p> | String | | gender | Enum, допустимые значения: `female`, `male`, `other` | | birthday | Date | ### Пользовательские атрибуты \{#custom-user-attributes\} Вы можете задавать собственные пользовательские атрибуты — как правило, они связаны с использованием вашего приложения. Например, в фитнес-приложениях это может быть количество тренировок в неделю, в приложениях для изучения языков — уровень знаний пользователя и так далее. Атрибуты можно использовать в сегментах для создания таргетированных пейволов и предложений, а также в аналитике, чтобы понять, какие продуктовые метрики сильнее всего влияют на выручку. ```dart showLineNumbers try { final builder = AdaptyProfileParametersBuilder() ..setCustomStringAttribute('value1', 'key1') ..setCustomDoubleAttribute(1.0, 'key2'); await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Чтобы удалить существующий ключ, используйте метод `.withRemoved(customAttributeForKey:)`: ```dart showLineNumbers try { final builder = AdaptyProfileParametersBuilder() ..removeCustomAttribute('key1') ..removeCustomAttribute('key2'); await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Иногда нужно узнать, какие пользовательские атрибуты уже установлены. Для этого используйте поле `customAttributes` объекта `AdaptyProfile`. :::warning Помните, что значение `customAttributes` может быть устаревшим: атрибуты пользователя могут отправляться с разных устройств в любое время, поэтому данные на сервере могут измениться после последней синхронизации. ::: ### Ограничения \{#limits\} - До 30 пользовательских атрибутов на одного пользователя - Длина имени ключа — не более 30 символов. Допускаются буквенно-цифровые символы, а также: `_` `-` `.` - Значение может быть строкой или числом с плавающей точкой длиной не более 50 символов. --- # File: flutter-listen-subscription-changes --- --- title: "Проверка статуса подписки в Flutter SDK" description: "Отслеживайте и управляйте статусом подписки пользователей в Adapty для повышения удержания клиентов в вашем Flutter-приложении." --- С Adapty отслеживать статус подписки очень просто. Вам не нужно вручную прописывать идентификаторы продуктов в коде. Вместо этого достаточно проверить наличие активного [уровня доступа](access-level), чтобы убедиться, что у пользователя есть подписка. <details> <summary>Перед тем как проверять статус подписки (нажмите, чтобы развернуть)</summary> - Для iOS настройте [App Store Server Notifications](enable-app-store-server-notifications) - Для Android настройте [Real-time Developer Notifications (RTDN)](enable-real-time-developer-notifications-rtdn) </details> ## Уровень доступа и объект AdaptyProfile \{#access-level-and-the-adaptyprofile-object\} Уровни доступа — это свойства объекта [AdaptyProfile](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html). Рекомендуем получать профиль при запуске приложения — например, когда вы [идентифицируете пользователя](flutter-identifying-users#setting-customer-user-id-on-configuration) — и обновлять его при каждом изменении. Так вы сможете использовать объект профиля без лишних запросов. Чтобы получать уведомления об обновлениях профиля, подпишитесь на изменения профиля, как описано в разделе [Отслеживание изменений профиля, включая уровни доступа](flutter-listen-subscription-changes) ниже. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ## Получение уровня доступа с сервера \{#retrieving-the-access-level-from-the-server\} Чтобы получить уровень доступа с сервера, используйте метод `.getProfile()`: ```dart showLineNumbers try { final profile = await Adapty().getProfile(); // check the access } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Параметры ответа: | Параметр | Описание | | --------- | ------------------------------------------------------------ | | Profile | <p>Объект [AdaptyProfile](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyProfile-class.html). Как правило, достаточно проверить статус уровня доступа профиля, чтобы определить, есть ли у пользователя премиум-доступ к приложению.</p><p></p><p>Метод `.getProfile` возвращает максимально актуальные данные, поскольку всегда обращается к API. Если по какой-то причине (например, при отсутствии интернета) SDK не может получить информацию с сервера, возвращаются данные из кэша. Важно также отметить, что SDK регулярно обновляет кэш `AdaptyProfile`, чтобы поддерживать эту информацию в актуальном состоянии.</p> | Метод `.getProfile()` возвращает профиль пользователя, из которого можно получить статус уровня доступа. В приложении может быть несколько уровней доступа. Например, если у вас газетное приложение и вы продаёте подписки на разные темы независимо друг от друга, можно создать уровни доступа «sports» и «science». Но в большинстве случаев достаточно одного уровня доступа — тогда можно просто использовать уровень доступа по умолчанию «premium». Вот пример проверки уровня доступа «premium» по умолчанию: ```dart showLineNumbers try { final profile = await Adapty().getProfile(); if (profile?.accessLevels['premium']?.isActive ?? false) { // grant access to premium features } } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` ### Отслеживание обновлений статуса подписки \{#listening-for-subscription-status-updates\} Adapty отправляет событие каждый раз, когда подписка пользователя изменяется. Чтобы получать сообщения от Adapty, выполните дополнительную настройку: ```dart showLineNumbers Adapty().didUpdateProfileStream.listen((profile) { // handle any changes to subscription state }); ``` Adapty также отправляет событие при запуске приложения — в этом случае передаётся кэшированный статус подписки. ### Кэш статуса подписки \{#subscription-status-cache\} Кэш в Adapty SDK хранит статус подписки профиля. Это значит, что даже при недоступности сервера можно получить кэшированные данные о статусе подписки профиля. Однако важно учитывать, что прямые запросы данных из кэша невозможны. SDK периодически обращается к серверу каждую минуту, чтобы проверить наличие обновлений или изменений, связанных с профилем. Если появятся какие-либо изменения — например, новые транзакции или другие обновления — они будут отправлены в кэшированные данные, чтобы поддерживать их синхронизацию с сервером. --- # File: flutter-deal-with-att --- --- title: "Работа с ATT во Flutter SDK" description: "Начните работу с Adapty на Flutter для упрощения настройки подписок и управления ими." --- Если ваше приложение использует фреймворк AppTrackingTransparency и запрашивает у пользователя разрешение на отслеживание, необходимо передать [статус авторизации](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/) в Adapty. ```dart showLineNumbers final builder = AdaptyProfileParametersBuilder() ..setAppTrackingTransparencyStatus(AdaptyIOSAppTrackingTransparencyStatus.authorized); try { await Adapty().updateProfile(builder.build()); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle unknown error } ``` :::warning Настоятельно рекомендуем отправлять это значение как можно раньше при каждом его изменении — только в этом случае данные будут своевременно переданы в настроенные вами интеграции. ::: --- # File: kids-mode-flutter --- --- title: "Режим Kids Mode во Flutter SDK" description: "Легко включите Kids Mode для соответствия политикам Apple и Google. IDFA, GAID и рекламные данные не собираются во Flutter SDK." --- Если ваше Flutter-приложение предназначено для детей, необходимо соблюдать политики [Apple](https://developer.apple.com/kids/) и [Google](https://support.google.com/googleplay/android-developer/answer/9893335). Если вы используете Adapty SDK, несколько простых шагов помогут настроить его в соответствии с этими требованиями и успешно пройти проверку в стор. ## Что нужно настроить? \{#whats-required\} Необходимо настроить SDK, чтобы отключить сбор следующих данных: - [IDFA (Identifier for Advertisers)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) (iOS) - [Android Advertising ID (AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248) (Android) - [IP-адрес](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Кроме того, рекомендуем осторожно подходить к выбору customer user ID. Идентификатор в формате `<FirstName.LastName>` однозначно будет расценён как сбор персональных данных — так же, как и использование email. Для Kids Mode лучшей практикой является использование случайных или анонимизированных идентификаторов (например, хешированных ID или UUID, сгенерированных на устройстве) для обеспечения соответствия требованиям. ## Включение Kids Mode \{#enabling-kids-mode\} ### Настройки в дашборде Adapty \{#updates-in-the-adapty-dashboard\} В дашборде Adapty необходимо отключить сбор IP-адресов. Для этого перейдите в [App settings](https://app.adapty.io/settings/general) и нажмите **Disable IP address collection** в разделе **Collect users' IP address**. ### Обновления в коде вашего мобильного приложения \{#updates-in-your-mobile-app-code\} В соответствии с требованиями политик отключите сбор IDFA пользователя (для iOS), GAID/AAID (для Android) и IP-адреса. **Android: Обновите конфигурацию SDK** ```dart showLineNumbers title="Dart" try { await Adapty().activate( configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') // highlight-start ..withGoogleAdvertisingIdCollectionDisabled(true) // set to `true` ..withIpAddressCollectionDisabled(true), // set to `true` // highlight-end ); } catch (e) { // handle the error } ``` **iOS: включение Kids Mode в SDK v4** :::important В SDK v4 нативный iOS SDK устанавливается через Swift Package Manager, а Kids Mode включается через трейт `KidsMode` Swift Package, который исключает из компиляции весь код IDFA, AdSupport и AppTrackingTransparency. Для этого требуется **Xcode 26** или выше. ::: В SDK v4 используйте пакет `adapty_flutter_kids` вместо `adapty_flutter` в файле `pubspec.yaml`. Это вариант плагина с Kids Mode — с тем же публичным API и той же версией; единственное отличие в том, что его нативный iOS SDK собран с трейтом `KidsMode`: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter_kids: 4.0.0 ``` Ваш код на Dart остаётся прежним — нужно лишь обновить импорт на новое имя пакета: ```dart showLineNumbers title="Dart" ``` **iOS: включение Kids Mode через CocoaPods (SDK v3)** 1. Обновите Podfile: - Если у вас **нет** секции `post_install` — добавьте весь блок кода ниже целиком. - Если секция `post_install` **есть** — добавьте в неё выделенные строки. ```ruby showLineNumbers title="Podfile" def adapty_enable_kids_mode(installer) installer.pods_project.targets.each do |target| next unless target.name == 'Adapty' target.build_configurations.each do |config| flags = config.build_settings['OTHER_SWIFT_FLAGS'] || '$(inherited)' flags = flags.join(' ') if flags.is_a?(Array) config.build_settings['OTHER_SWIFT_FLAGS'] = "#{flags} -DADAPTY_KIDS_MODE" end target.frameworks_build_phase.files.dup.each do |bf| target.frameworks_build_phase.remove_build_file(bf) if bf.display_name.to_s.include?('AdSupport') end end installer.pods_project.save Dir.glob(File.join(installer.sandbox.root, 'Target Support Files', '**', '*.xcconfig')).each do |xc| File.write(xc, File.read(xc).gsub(/\s*-framework\s+"?AdSupport"?/, '')) end end post_install do |installer| # ... keep your existing post_install body (Flutter adds one automatically) ... adapty_enable_kids_mode(installer) # <-- enable Adapty Kids Mode end ``` 2. Примените изменения, выполнив команду ```sh showLineNumbers title="Shell" pod install ``` --- # File: flutter-get-onboardings --- --- title: "Получение онбордингов в Flutter SDK" description: "Узнайте, как получать онбординги в Adapty для Flutter." --- :::warning **Онбординги устарели в SDK v4 и будут удалены в одном из следующих релизов.** Они больше не получают исправлений или улучшений. Используйте [флоу](flutter-get-pb-paywalls) вместо них: в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — обеспечивая более плавную анимацию, единый нативный внешний вид, быструю загрузку и отсутствие зависимости от WebView. Подробнее в разделах [Получение флоу и пейволов](flutter-get-pb-paywalls) и [Отображение флоу и пейволов](flutter-present-paywalls). ::: После того как вы [оформили визуальную часть онбординга](design-onboarding) в Paywall Builder на дашборде Adapty, его можно отобразить в Flutter-приложении. Первый шаг — получить онбординг, связанный с плейсментом, и его конфигурацию отображения, как описано ниже. Перед началом убедитесь, что: 1. Установлен [Adapty Flutter SDK](sdk-installation-flutter) версии 3.8.0 или выше. 2. Вы [создали онбординг](create-onboarding). 3. Онбординг добавлен в [плейсмент](placements). ## Загрузка онбординга \{#fetch-onboarding\} Когда вы создаёте [онбординг](onboardings) в нашем no-code конструкторе, он сохраняется в виде контейнера с конфигурацией, которую приложение должно загрузить и отобразить. Этот контейнер управляет всем процессом: какой контент показывать, как его представлять и как обрабатывать действия пользователя (например, ответы на вопросы викторины или данные из форм). Контейнер также автоматически отслеживает события аналитики, поэтому отдельно реализовывать отслеживание просмотров не нужно. Для лучшей производительности загружайте конфигурацию онбординга заранее — чтобы изображения успели скачаться до того, как пользователь увидит онбординг. Чтобы получить онбординг, используйте метод `getOnboarding`: ```dart showLineNumbers try { final onboarding = await Adapty().getOnboarding(placementId: "YOUR_PLACEMENT_ID"); } on AdaptyError catch (e) { //handle error } catch (e) { //handle error } ``` Затем вызовите метод `createOnboardingView`, чтобы получить отображение, которое вы будете показывать. :::warning Результат метода `createOnboardingView` можно использовать только один раз. Если вам нужно использовать его повторно, вызовите метод `createOnboardingView` заново. Повторный вызов без пересоздания может привести к ошибке `AdaptyUIError.viewAlreadyPresented`. ::: ```dart showLineNumbers try { final onboardingView = await Adapty().createOnboardingView(onboarding: onboarding); } on AdaptyError catch (e) { //handle error } catch (e) { //handle error } ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>необязательный</p><p>по умолчанию: `en`</p> | <p>Идентификатор локализации онбординга. Ожидается код языка, состоящий из одного или двух подтегов, разделённых символом минус (**-**). Первый подтег обозначает язык, второй — регион.</p><p></p><p>Например: `en` — английский, `pt-br` — бразильский португальский.</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное соединение, рассмотрите вариант `.returnCacheDataElseLoad`: он возвращает кешированные данные, если они есть. В этом случае пользователи могут получать не самые свежие данные, зато загрузка будет быстрее независимо от качества связи. Кеш обновляется регулярно, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.</p><p></p><p>Adapty SDK хранит онбординги локально на двух уровнях: регулярно обновляемый кеш, описанный выше, и резервные онбординги. Для более быстрой загрузки мы также используем CDN и отдельный резервный сервер на случай недоступности CDN. Такая система гарантирует, что вы всегда получаете актуальную версию онбордингов, сохраняя надёжность даже при нестабильном интернете.</p> | | **loadTimeout** | по умолчанию: 5 сек | <p>Это значение ограничивает таймаут для данного метода. По истечении таймаута возвращаются кешированные данные или локальный резервный вариант.</p><p>Обратите внимание, что в редких случаях метод может отработать чуть позже указанного в `loadTimeout` времени, поскольку операция может включать несколько запросов под капотом.</p> | Параметры ответа: | Параметр | Описание | |:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Объект [`AdaptyOnboarding`](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyOnboarding-class.html), содержащий: идентификатор и конфигурацию онбординга, Remote Config и ряд других свойств. | ## Ускорьте загрузку онбординга с помощью онбординга аудитории по умолчанию \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} Как правило, онбординги загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и онбордингов, а у пользователей слабый интернет, загрузка онбординга может занять больше времени, чем хотелось бы. В таких случаях удобно показывать онбординг по умолчанию — чтобы пользователь не видел пустой экран, а получал полноценный опыт. Чтобы решить эту задачу, используйте метод `getOnboardingForDefaultAudience`, который получает онбординг для указанного плейсмента из аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать онбординг через метод `getOnboarding`, как описано в разделе [Получение онбординга](#fetch-onboarding) выше. :::warning Рекомендуется использовать `getOnboarding` вместо `getOnboardingForDefaultAudience`, поскольку последний имеет важные ограничения: - **Проблемы совместимости**: могут возникнуть сложности при поддержке нескольких версий приложения — придётся либо делать обратно совместимый дизайн, либо мириться с тем, что старые версии будут отображать онбординг некорректно. - **Нет персонализации**: отображается только контент для аудитории «Все пользователи», без таргетинга по стране, атрибуции или пользовательским атрибутам. Если для вашего случая скорость загрузки важнее этих недостатков, используйте `getOnboardingForDefaultAudience`, как показано ниже. В противном случае используйте `getOnboarding`, как описано [выше](#fetch-onboarding). ::: ```dart showLineNumbers try { final onboarding = await Adapty().getOnboardingForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle unknown error } ``` Параметры: | Параметр | Наличие | Описание | |-----------------|-----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>необязательный</p><p>по умолчанию: `en`</p> | <p>Идентификатор локализации онбординга. Ожидается языковой код из одного или двух подтегов, разделённых знаком минус (**-**). Первый подтег — язык, второй — регион.</p><p></p><p>Например: `en` — английский, `pt-br` — бразильский португальский.</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если вы считаете, что у ваших пользователей нестабильный интернет, рассмотрите `.returnCacheDataElseLoad` — он возвращает кешированные данные, если они есть. В этом случае пользователи могут получить не самые свежие данные, зато загрузка будет быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в рамках сессии, чтобы сократить количество сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется после перезапуска приложения и очищается только при переустановке или вручную.</p><p></p><p>Adapty SDK хранит онбординги локально в двух слоях: регулярно обновляемый кеш, описанный выше, и резервные онбординги. Для более быстрой загрузки мы используем CDN, а также отдельный резервный сервер на случай недоступности CDN. Эта система гарантирует, что вы всегда получаете актуальную версию онбордингов, даже при нестабильном интернет-соединении.</p> | --- # File: flutter-present-onboardings --- --- title: "Отображение онбординга во Flutter SDK" description: "Узнайте, как эффективно отображать онбординги для повышения конверсии." --- :::warning **Онбординги устарели в SDK v4 и будут удалены в одном из следующих релизов.** Они больше не получают исправлений и улучшений. Используйте [флоу](flutter-get-pb-paywalls): в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это даёт более плавные анимации, единый нативный внешний вид, быструю загрузку и отсутствие зависимости от WebView. Подробнее: [Получение флоу и пейволов](flutter-get-pb-paywalls) и [Отображение флоу и пейволов](flutter-present-paywalls). ::: Если вы настроили онбординг с помощью билдера, вам не нужно беспокоиться о его рендеринге в коде Flutter-приложения — всё отображение уже описано внутри самого онбординга. Он содержит как то, что нужно показать, так и то, как это должно выглядеть. Перед началом убедитесь, что: 1. Вы установили [Adapty Flutter SDK](sdk-installation-flutter) версии 3.8.0 или новее. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). Adapty Flutter SDK предоставляет два способа отображения онбордингов: - **Отдельный экран (Standalone screen)** - **Встроенный виджет (Embedded widget)** ## Отображение как отдельный экран \{#present-as-standalone-screen\} Чтобы отобразить онбординг как отдельный экран, вызовите метод `onboardingView.present()` на объекте `onboardingView`, созданном методом `createOnboardingView`. Каждый `view` можно использовать только один раз. Если нужно снова показать онбординг, вызовите `createOnboardingView` ещё раз, чтобы создать новый экземпляр `onboardingView`. :::warning Повторное использование того же `onboardingView` без его пересоздания может привести к ошибке `AdaptyUIError.viewAlreadyPresented`. ::: ```dart showLineNumbers title="Flutter" try { await onboardingView.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### Закрытие онбординга \{#dismiss-the-onboarding\} Чтобы программно закрыть онбординг, используйте метод `dismiss()`: ```dart showLineNumbers title="Flutter" try { await onboardingView.dismiss(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### Настройка стиля отображения на iOS \{#configure-ios-presentation-style\} Настройте способ отображения онбординга на iOS, передав параметр `iosPresentationStyle` в метод `present()`. Параметр принимает значения `AdaptyUIIOSPresentationStyle.fullScreen` (по умолчанию) или `AdaptyUIIOSPresentationStyle.pageSheet`. ```dart showLineNumbers try { await onboardingView.present(iosPresentationStyle: AdaptyUIIOSPresentationStyle.pageSheet); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ## Встраивание в иерархию виджетов \{#embed-in-widget-hierarchy\} Чтобы встроить онбординг в существующее дерево виджетов, используйте виджет `AdaptyUIOnboardingPlatformView` напрямую в иерархии виджетов Flutter. ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, // The onboarding object you fetched onDidFinishLoading: (meta) { }, onDidFailWithError: (error) { }, onCloseAction: (meta, actionId) { }, onPaywallAction: (meta, actionId) { }, onCustomAction: (meta, actionId) { }, onStateUpdatedAction: (meta, elementId, params) { }, onAnalyticsEvent: (meta, event) { }, ) ``` :::note Чтобы платформенный виджет на Android работал корректно, убедитесь, что ваш `MainActivity` наследует `FlutterFragmentActivity`: ```kotlin showLineNumbers title="Kotlin" class MainActivity : FlutterFragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } ``` ::: ## Загрузка во время онбординга \{#loader-during-onboarding\} При отображении онбординга между сплэш-экраном и самим онбордингом может появиться короткий экран загрузки — пока инициализируется базовое представление. Это можно обработать по-разному в зависимости от ваших потребностей. #### Управление сплэш-экраном через onDidFinishLoading \{#control-splash-screen-using-ondidfinishloading\} :::note Этот подход доступен только при встраивании онбординга как виджета. Для отображения в виде отдельного экрана он недоступен. ::: Рекомендуемый кросс-платформенный подход — держать сплэш-экран или собственный оверлей видимым до тех пор, пока онбординг полностью не загрузится, а затем скрыть его вручную. При использовании встроенного виджета разместите свой виджет поверх него и скройте оверлей, когда сработает `onDidFinishLoading`: ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, onDidFinishLoading: (meta) { // Hide your custom splash screen or overlay here }, // ... other callbacks ) ``` ### Кастомизация нативного загрузчика \{#customize-native-loader\} :::important Этот подход зависит от платформы и требует поддержки нативного UI-кода. Не рекомендуется, если вы не поддерживаете отдельные нативные слои в своём приложении. ::: Если нужно кастомизировать сам загрузчик по умолчанию, его можно заменить платформо-специфичными макетами. Этот подход требует отдельных реализаций для Android и iOS: - **iOS**: добавьте `AdaptyOnboardingPlaceholderView.xib` в ваш Xcode-проект - **Android**: создайте `adapty_onboarding_placeholder_view.xml` в `res/layout` и определите там заглушку ## Настройка открытия ссылок в онбордингах \{#customize-how-links-open-in-onboardings\} :::important Настройка открытия ссылок в онбордингах поддерживается начиная с Adapty SDK v3.15.1. ::: По умолчанию ссылки в онбордингах открываются во встроенном браузере. Это обеспечивает бесшовный пользовательский опыт: веб-страницы отображаются прямо внутри приложения, и пользователю не нужно переключаться между приложениями. Если вы хотите открывать ссылки во внешнем браузере, вы можете изменить это поведение, задав параметру `externalUrlsPresentation` значение `AdaptyWebPresentation.externalBrowser`: <Tabs> <TabItem value="standalone" label="Отдельный экран" default> ```dart showLineNumbers title="Flutter" final onboardingView = await AdaptyUI().createOnboardingView( onboarding: onboarding, externalUrlsPresentation: AdaptyWebPresentation.externalBrowser, // default – AdaptyWebPresentation.inAppBrowser ); try { await onboardingView.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="embedded" label="Встроенный виджет"> ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, externalUrlsPresentation: AdaptyWebPresentation.externalBrowser, // default – AdaptyWebPresentation.inAppBrowser onDidFinishLoading: (meta) { }, onDidFailWithError: (error) { }, onCloseAction: (meta, actionId) { }, onPaywallAction: (meta, actionId) { }, onCustomAction: (meta, actionId) { }, onStateUpdatedAction: (meta, elementId, params) { }, onAnalyticsEvent: (meta, event) { }, ) ``` </TabItem> </Tabs> ## Отключение отступов для безопасной зоны (Android) \{#disable-safe-area-paddings-android\} По умолчанию на Android-устройствах экран онбординга автоматически добавляет отступы для безопасной зоны, чтобы не перекрывать системные элементы интерфейса — строку состояния и навигационную панель. Если вы хотите отключить это поведение и самостоятельно управлять разметкой, добавьте булев ресурс в ваше приложение: 1. Перейдите в `android/app/src/main/res/values`. Если файл `bools.xml` отсутствует, создайте его. 2. Добавьте следующий ресурс: ```xml <resources> <bool name="adapty_onboarding_enable_safe_area_paddings">false</bool> </resources> ``` Обратите внимание, что изменения применяются глобально для всех онбордингов в вашем приложении. --- # File: flutter-handling-onboarding-events --- --- title: "Обработка событий онбординга в Flutter SDK" description: "Обработка событий онбординга во Flutter с помощью Adapty." --- :::warning **Онбординги устарели в SDK v4 и будут удалены в одном из будущих релизов.** Они больше не получают исправлений и улучшений. Используйте [флоу](flutter-get-pb-paywalls) вместо них: в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает плавные анимации, единообразный нативный внешний вид, быструю загрузку и отсутствие зависимости от WebView. Смотрите [Получение флоу и пейволов](flutter-get-pb-paywalls) и [Отображение флоу и пейволов](flutter-present-paywalls), чтобы начать работу. ::: Онбординги, настроенные с помощью билдера, генерируют события, на которые может реагировать ваше приложение. Способ обработки этих событий зависит от выбранного подхода к отображению: - **Полноэкранное отображение**: требует настройки глобального наблюдателя событий, который обрабатывает события для всех представлений онбординга - **Встроенный виджет**: обрабатывает события через параметры обратного вызова прямо в виджете Прежде чем начать, убедитесь, что: 1. Вы установили [Adapty Flutter SDK](sdk-installation-flutter) версии 3.8.0 или выше. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). ## События полноэкранного отображения \{#full-screen-presentation-events\} ### Настройка наблюдателя событий \{#set-up-event-observer\} Чтобы обрабатывать события для полноэкранных онбордингов, реализуйте `AdaptyUIOnboardingsEventsObserver` и установите его перед отображением: ```dart showLineNumbers title="Flutter" AdaptyUI().setOnboardingsEventsObserver(this); try { await onboardingView.present(); } on AdaptyError catch (e) { // handle the error } catch (e) { // handle the error } ``` ### Обработка событий \{#handle-events\} Реализуйте следующие методы в вашем обработчике: ```dart showLineNumbers title="Flutter" void onboardingViewDidFinishLoading( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, ) { // Onboarding finished loading } void onboardingViewDidFailWithError( AdaptyUIOnboardingView view, AdaptyError error, ) { // Handle loading errors } void onboardingViewOnCloseAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Handle close action view.dismiss(); } void onboardingViewOnPaywallAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Dismiss onboarding before presenting paywall view.dismiss().then((_) { _openPaywall(actionId); }); } void onboardingViewOnCustomAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Handle custom actions } void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Handle user input updates } void onboardingViewOnAnalyticsEvent( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, AdaptyOnboardingsAnalyticsEvent event, ) { // Track analytics events } ``` ## События встроенного виджета \{#embedded-widget-events\} При использовании `AdaptyUIOnboardingPlatformView` вы можете обрабатывать события через встроенные параметры обратного вызова непосредственно в виджете. Обратите внимание, что события отправляются как в колбэки виджета, так и в глобальный наблюдатель (если он настроен), однако глобальный наблюдатель необязателен: ```dart showLineNumbers title="Flutter" AdaptyUIOnboardingPlatformView( onboarding: onboarding, onDidFinishLoading: (meta) { // Onboarding finished loading }, onDidFailWithError: (error) { // Handle loading errors }, onCloseAction: (meta, actionId) { // Handle close action }, onPaywallAction: (meta, actionId) { _openPaywall(actionId); }, onCustomAction: (meta, actionId) { // Handle custom actions }, onStateUpdatedAction: (meta, elementId, params) { // Handle user input updates }, onAnalyticsEvent: (meta, event) { // Track analytics events }, ) ``` ## Типы событий \{#event-types\} В следующих разделах описаны различные типы событий, которые можно обрабатывать независимо от выбранного подхода к отображению. ### Обработка пользовательских действий \{#handle-custom-actions\} В конструкторе вы можете добавить **пользовательское** действие к кнопке и назначить ему идентификатор. <img src="/assets/shared/img/ios-events-1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Затем этот ID можно использовать в коде и обрабатывать как пользовательское действие. Например, если пользователь нажимает кастомную кнопку — **Login** или **Allow notifications** — сработает метод делегата `onboardingController` с кейсом `.custom(id:)`, а параметр `actionId` будет равен **Action ID** из билдера. Вы можете создавать собственные ID, например `"allowNotifications"`. ```dart // Full-screen presentation void onboardingViewOnCustomAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { switch (actionId) { case 'login': _login(); break; case 'allow_notifications': _allowNotifications(); break; } } // Embedded widget onCustomAction: (meta, actionId) { _handleCustomAction(actionId); } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ``` </Details> ### Завершение загрузки онбординга \{#finishing-loading-onboarding\} Когда онбординг заканчивает загрузку, срабатывает это событие: ```dart showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewDidFinishLoading( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, ) { print('Onboarding loaded: ${meta.onboardingId}'); } // Embedded widget onDidFinishLoading: (meta) { print('Onboarding loaded: ${meta.onboardingId}'); } ``` <Details> <summary>Пример события (нажмите, чтобы раскрыть)</summary> ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ``` </Details> ### Закрытие онбординга \{#closing-onboarding\} Онбординг считается закрытым, когда пользователь нажимает кнопку с назначенным действием **Close**. <img src="/assets/shared/img/ios-events-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important Обратите внимание: вам нужно самостоятельно управлять тем, что происходит при закрытии онбординга пользователем. Например, нужно прекратить отображение самого онбординга. ::: ```dart showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewOnCloseAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { await view.dismiss(); } // Embedded widget onCloseAction: (meta, actionId) { Navigator.of(context).pop(); } ``` <Details> <summary>Пример события (нажмите, чтобы раскрыть)</summary> ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> ### Открытие пейвола \{#opening-a-paywall\} :::tip Обрабатывайте это событие, чтобы открыть пейвол внутри онбординга. Если вы хотите открыть пейвол после его закрытия, есть более простой способ — обработать действие закрытия и открыть пейвол, не полагаясь на данные события. ::: Самый удобный подход при работе с пейволами в онбординге — сделать ID действия равным ID плейсмента пейвола: Обратите внимание: на iOS одновременно на экране может отображаться только один экран (пейвол или онбординг). Если вы показываете пейвол поверх онбординга, вы не можете программно управлять онбордингом в фоне. Попытка закрыть онбординг закроет пейвол, оставив онбординг видимым. Чтобы этого избежать, всегда закрывайте экран онбординга перед показом пейвола. ```dart showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewOnPaywallAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String actionId, ) { // Dismiss onboarding before presenting paywall view.dismiss().then((_) { _openPaywall(actionId); }); } Future<void> _openPaywall(String actionId) async { // Implement your paywall opening logic here } // Embedded widget onPaywallAction: (meta, actionId) { _openPaywall(actionId); } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ``` </Details> ### Отслеживание навигации \{#tracking-navigation\} Аналитическое событие поступает при различных навигационных событиях в онбординге: ```dart showLineNumbers title="Flutter" // Full-screen presentation void onboardingViewOnAnalyticsEvent( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, AdaptyOnboardingsAnalyticsEvent event, ) { trackEvent(event.type, meta.onboardingId); } // Embedded widget onAnalyticsEvent: (meta, event) { trackEvent(event.type, meta.onboardingId); } ``` Объект `event` может быть одного из следующих типов: | Тип | Описание | |------------|-------------| | `onboardingStarted` | Когда онбординг загружен | | `screenPresented` | Когда показан любой экран | | `screenCompleted` | Когда экран завершён. Включает необязательный `elementId` (идентификатор завершённого элемента) и необязательный `reply` (ответ пользователя). Срабатывает, когда пользователь выполняет любое действие для выхода с экрана. | | `secondScreenPresented` | Когда показан второй экран | | `userEmailCollected` | Срабатывает, когда email пользователя собран через поле ввода | | `onboardingCompleted` | Срабатывает, когда пользователь достигает экрана с идентификатором `final`. Если вам нужно это событие, [назначьте идентификатор `final` последнему экрану](design-onboarding). | | `unknown` | Для любого нераспознанного типа события. Включает `name` (название неизвестного события) и `meta` (дополнительные метаданные) | Каждое событие содержит `meta`-информацию: | Поле | Описание | |------------|-------------| | `onboardingId` | Уникальный идентификатор флоу онбординга | | `screenClientId` | Идентификатор текущего экрана | | `screenIndex` | Позиция текущего экрана в флоу | | `screensTotal` | Общее количество экранов во флоу | <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```javascript // onboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // screenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // screenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // secondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // userEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // onboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> --- # File: flutter-onboarding-input --- --- title: "Обработка данных онбординга во Flutter SDK" description: "Сохраняйте и используйте данные онбординга в Flutter-приложении с помощью Adapty SDK." --- :::warning **Онбординги объявлены устаревшими в SDK v4 и будут удалены в одном из будущих релизов.** Они больше не получают исправлений и улучшений. Используйте [флоу](flutter-get-pb-paywalls) вместо них: в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает более плавные анимации, единый нативный внешний вид, быструю загрузку и отсутствие зависимости от WebView. Подробнее см. в разделах [Получение флоу и пейволов](flutter-get-pb-paywalls) и [Отображение флоу и пейволов](flutter-present-paywalls). ::: Когда пользователи отвечают на вопрос викторины или вводят данные в поле ввода, вызывается метод `onStateUpdatedAction`. Вы можете сохранить или обработать тип поля в своём коде. Например: ```dart // Full-screen presentation void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Process data } // Embedded widget onStateUpdatedAction: (meta, elementId, params) { // Process data } ``` Узнайте о формате action [здесь](https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/AdaptyUIOnboardingPlatformView/onStateUpdatedAction.html). <Details> <summary>Форма свойств для каждого типа params (нажмите, чтобы развернуть)</summary> ```dart void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // elementId — это String: elementId; // 'preference_selector' // meta — AdaptyUIOnboardingMeta: meta.onboardingId; // 'onboarding_123' meta.screenClientId; // 'preferences_screen' meta.screenIndex; // 1 meta.screensTotal; // 3 // params — один из подклассов AdaptyOnboardingsStateUpdatedParams: switch (params) { case AdaptyOnboardingsSelectParams(:final id, :final value, :final label): // одна выбранная опция id; // 'option_1' value; // 'premium' label; // 'Premium Plan' break; case AdaptyOnboardingsMultiSelectParams(:final params): // список выбранных опций, каждая из которых является AdaptyOnboardingsSelectParams params; // [(id: 'interest_1', value: 'sports', label: 'Sports'), (id: 'interest_2', value: 'music', label: 'Music')] break; case AdaptyOnboardingsInputParams(:final input): switch (input) { case AdaptyOnboardingsTextInput(:final value): value; // 'John Doe' break; case AdaptyOnboardingsEmailInput(:final value): value; // 'user@example.com' break; case AdaptyOnboardingsNumberInput(:final value): value; // 25.0 (double) break; } break; case AdaptyOnboardingsDatePickerParams(:final day, :final month, :final year): day; // 15 month; // 6 year; // 1990 break; } } ``` </Details> ## Сценарии использования \{#use-cases\} ### Обогащение профилей пользователей данными \{#enrich-user-profiles-with-data\} Если вы хотите сразу связать введённые данные с профилем пользователя и не спрашивать одно и то же дважды, нужно [обновить профиль пользователя](flutter-setting-user-attributes) с этими данными при обработке действия. Например, вы просите пользователей ввести имя в текстовое поле с ID `name` и хотите сохранить это значение как имя пользователя. Также вы просите ввести email в поле `email`. В коде приложения это может выглядеть так: ```dart showLineNumbers // Full-screen presentation void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Store user preferences or responses if (params is AdaptyOnboardingsInputParams) { final builder = AdaptyProfileParametersBuilder(); // Map elementId to appropriate profile field switch (elementId) { case 'name': if (params.input is AdaptyOnboardingsTextInput) { builder.setFirstName((params.input as AdaptyOnboardingsTextInput).value); } break; case 'email': if (params.input is AdaptyOnboardingsEmailInput) { builder.setEmail((params.input as AdaptyOnboardingsEmailInput).value); } break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } // Embedded widget onStateUpdatedAction: (meta, elementId, params) { // Store user preferences or responses if (params is AdaptyOnboardingsInputParams) { final builder = AdaptyProfileParametersBuilder(); // Map elementId to appropriate profile field switch (elementId) { case 'name': if (params.input is AdaptyOnboardingsTextInput) { builder.setFirstName((params.input as AdaptyOnboardingsTextInput).value); } break; case 'email': if (params.input is AdaptyOnboardingsEmailInput) { builder.setEmail((params.input as AdaptyOnboardingsEmailInput).value); } break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } ``` ### Настройте пейволы на основе ответов \{#customize-paywalls-based-on-answers\} С помощью квизов в онбординге вы можете настраивать пейволы, которые показываете пользователям после завершения онбординга. Например, можно спросить пользователей об их опыте в спорте и показывать разные CTA и продукты разным группам пользователей. 1. [Добавьте квиз](onboarding-quizzes) в конструкторе онбординга и назначьте понятные идентификаторы его вариантам ответов. 2. Обработайте ответы квиза по их идентификаторам и [задайте пользовательские атрибуты](flutter-setting-user-attributes) для пользователей. ```dart showLineNumbers // Full-screen presentation void onboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, String elementId, AdaptyOnboardingsStateUpdatedParams params, ) { // Handle quiz responses and set custom attributes if (params is AdaptyOnboardingsSelectParams) { final builder = AdaptyProfileParametersBuilder(); // Map quiz responses to custom attributes switch (elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.setCustomStringAttribute(params.value, 'experience'); break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } // Embedded widget onStateUpdatedAction: (meta, elementId, params) { // Handle quiz responses and set custom attributes if (params is AdaptyOnboardingsSelectParams) { final builder = AdaptyProfileParametersBuilder(); // Map quiz responses to custom attributes switch (elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.setCustomStringAttribute(params.value, 'experience'); break; } // Update profile Adapty().updateProfile(builder.build()).catchError((error) { // handle the error }); } } ``` 3. [Создайте сегменты](segments) для каждого значения пользовательского атрибута. 4. Создайте [плейсмент](placements) и добавьте [аудитории](audience) для каждого созданного сегмента. 5. [Отобразите пейвол](flutter-paywalls) для плейсмента в коде приложения. Если в онбординге есть кнопка, открывающая пейвол, реализуйте код пейвола как [реакцию на действие этой кнопки](flutter-handling-onboarding-events#opening-a-paywall). --- # File: flutter-sdk-call-order --- --- title: "Порядок вызовов в Flutter SDK" description: "Избегайте потери платного доступа, пропущенной атрибуции и случайных ошибок #2002, вызывая методы Adapty SDK в правильном порядке." --- `Adapty().activate()` должен завершиться до вызова любых других методов Adapty SDK. До его завершения SDK не имеет состояния. Любой вызов, сделанный до или параллельно с `activate()`, завершится ошибкой [`#2002 notActivated`](error-handling-on-flutter-react-native-unity#custom-network-codes). Если ваше приложение аутентифицирует пользователей и вы получаете customer user ID после запуска, вызовите `Adapty().identify()` в этот момент. Не вызывайте методы, связанные с действиями пользователя, пока `identify` не завершится. Вызовы, которые выполняются параллельно с ним, либо завершатся с ошибкой [`#3006 profileWasChanged`](error-handling-on-flutter-react-native-unity#custom-network-codes), либо применятся к анонимному профилю, созданному при активации. В этом случае атрибуция, MMP ID вроде `appsflyer_id` и принадлежность установки не всегда переносятся на идентифицированный профиль. Если ваше приложение не аутентифицирует пользователей, пропустите `identify` и продолжайте работать с анонимным профилем. MMP и аналитические SDK (AppsFlyer, Adjust, Branch, PostHog) следуют тому же правилу. Инициализируйте их первыми и дождитесь колбэков с UID, прежде чем вызывать `Adapty().activate`. Иначе MMP ID попадёт на краткосрочный анонимный профиль и не всегда переносится на идентифицированный. Подробности для AppsFlyer см. в разделе [AppsFlyer](appsflyer). ## Правильный порядок \{#the-correct-order\} Ваш путь зависит от двух вещей: когда вы узнаёте customer user ID и используете ли вы MMP или аналитический SDK. - **Шаги 2 и 5**: обязательны для каждого приложения. Активируйте SDK, затем вызывайте методы SDK. - **Шаги 1 и 3**: нужны только при интеграции MMP или аналитического SDK (AppsFlyer, Adjust, Branch, PostHog). - **Шаг 4**: нужен только если ваше приложение аутентифицирует пользователей и получает customer user ID после запуска. Если вы знаете customer user ID в момент запуска приложения, передайте его напрямую в `activate()` (шаг 2a). В этом случае анонимный профиль не создаётся, поэтому шаг 4 не нужен. | Шаг | Вызов | Когда | Примечания | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | Инициализируйте SDK вашего MMP или аналитики (AppsFlyer, Adjust, PostHog, Branch) | При запуске приложения, первым делом | Дождитесь callback с UID от MMP, например `getAppsFlyerUID`. | | 2a | `Adapty().activate(configuration: ...)` с `withCustomerUserId`, заданным в конфигурации | При запуске приложения, после шага 1, если customer user ID известен | Рекомендуется. Анонимный профиль не создаётся. | | 2b | `Adapty().activate(configuration: ...)` без `withCustomerUserId` | При запуске приложения, после шага 1, если customer user ID неизвестен (или не используется) | Adapty создаёт анонимный профиль. | | 3 | `Adapty().setIntegrationIdentifier(key: ..., value: ...)` для каждого MMP | После шага 2, до любого вызова, инициированного действием пользователя | Необходимо, чтобы идентификаторы MMP попали в правильный профиль. | | 4 | `await Adapty().identify(customerUserId)` | После шага 3 (или шага 2, если нет MMP), до шага 5 — только на пути 2b с аутентификацией | Всегда используйте `await`. Параллельные вызовы во время `identify` приводят к ошибке `#3006 profileWasChanged`. | | 5 | `getPaywall` (`getFlow` в SDK v4), `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution`, `updateProfile` | После шага 4, если вызывается `identify`; иначе после шага 3 (или шага 2, если нет MMP) | Эти вызовы требуют стабильного профиля. | :::important Пропуск этих шагов приведёт к потере уровня доступа у вернувшихся пользователей, отсутствию `appsflyer_id` в профилях и к тому, что пейволы будут показываться не той аудитории. ::: ## Установки через web2app и веб-воронки \{#web2app-and-web-funnel-installs\} Если пользователи совершают покупку через веб-чекаут (Stripe, Paddle), а затем устанавливают нативное приложение, первый вызов `activate()` на устройстве создаёт новый анонимный профиль. Этот профиль не связан с веб-профилем. Если вы можете определить customer user ID до запуска приложения (через авторизацию или install referrer), передайте его напрямую в `activate()`. В противном случае веб-покупка будет недоступна на устройстве, пока вы не вызовете `identify("YOUR_USER_ID")`, а затем `restorePurchases`. Сведения о метаданных, которые нужно передавать при каждом веб-чекауте, смотрите здесь: - [Stripe](stripe) - [Paddle](paddle) --- # File: flutter-optimize-paywall-fetching --- --- title: "Оптимизация загрузки пейволов в Flutter SDK" description: "Надёжная загрузка пейволов Adapty: тайминг, кэширование и резервные паттерны для Flutter." --- Надёжная загрузка пейвола во Flutter решает три задачи: быстрый рендер, возврат пейвола с нужной аудиторией и корректный фолбэк при медленной сети. Правила ниже охватывают тайминг, кэширование и резервные паттерны для достижения этого. :::tip Предполагается, что `Adapty().activate()` и `Adapty().identify()` уже выполнены. См. [Порядок вызовов в Flutter SDK](flutter-sdk-call-order). ::: Советы ниже используют имена методов v3. В SDK v4 `getPaywall` переименован в `getFlow`, а тип политики получения — в `AdaptyFlowFetchPolicy` — все правила применяются без изменений. ## Правила и подводные камни \{#rules-and-pitfalls\} | Делайте так | Не делайте так | Почему | |---|---|---| | Загружайте только тот плейсмент, который собираетесь показать. | Предзагружайте все плейсменты параллельно при запуске. | Массовая предзагрузка блокирует главный поток и вызывает чёрный экран во время всплеска нагрузки. | | Вызывайте `getPaywall` после того, как атрибуция успеет разрешиться — например, через 1–2 секунды после `activate` или после срабатывания `didUpdateProfileStream`. | Вызывайте `getPaywall` в `main()` до `runApp`. | Атрибуция ещё не получена. Пейвол разрешается по дефолтной аудитории и молча обходит сегменты и персонализацию ASA. | | Задайте `loadTimeout` и настройте [резервный пейвол](fallback-paywalls) для каждого плейсмента. | Ожидайте `getPaywall` бесконечно. | Без таймаута пользователи с плохим соединением видят пустой экран до тех пор, пока сеть не ответит — или закрывают приложение. | См. [Получение пейволов и продуктов](fetch-paywalls-and-products-flutter) для справки по параметрам `fetchPolicy` и `loadTimeout`, а также [Плейсменты](placements) для выбора подходящего плейсмента. ## Настройте приложение для работы при слабом соединении \{#tune-for-poor-connectivity\} Для рынков с устойчиво плохим качеством связи (сельские районы, транспортные узлы, регионы с проблемной маршрутизацией): - Используйте `fetchPolicy: AdaptyPaywallFetchPolicy.returnCacheDataElseLoad` при каждом запросе, кроме самого первого. - Настройте [резервный пейвол](fallback-paywalls) для каждого плейсмента в дашборде Adapty. - Установите `loadTimeout` в 3–5 секунд и принимайте резервный пейвол при срабатывании таймаута. - Не блокируйте отображение пейвола вызовом `getProfile()`. Вызывайте `getPaywall` независимо, чтобы медленная загрузка профиля не задерживала интерфейс. --- # File: flutter-show-aa-targeted-paywall --- --- title: "Показ пейвола с таргетингом Apple Ads при первом запуске во Flutter SDK" description: "Ненадолго подождите атрибуцию Apple Ads перед показом пейвола при первом запуске во Flutter, возвращаясь к аудитории по умолчанию при истечении таймаута. Использует AdaptyProfile.appliedAttributionSources." --- Атрибуция Apple Ads (AA) поступает асинхронно после вызова `Adapty().activate()`. При первом запуске она, как правило, ещё не получена, поэтому если сразу вызвать `getPaywall`, Adapty обработает запрос по аудитории по умолчанию, и пользователи Apple Ads не увидят пейвол, настроенный для AA-сегмента. Вместо того чтобы показывать один пейвол, а затем заменять его другим, подождите немного, пока не придёт атрибуция AA: если она поступит в течение короткого таймаута — покажите целевой пейвол, если нет — пейвол аудитории по умолчанию. `AdaptyProfile.appliedAttributionSources` сообщает, когда атрибуция AA была применена. ## Прежде чем начать \{#before-you-start\} Вам понадобится: - Adapty Flutter SDK **3.17.0** или новее. - Apple Ads, настроенный для приложения в Adapty. См. [Apple Ads](apple-search-ads). ## Как это работает \{#how-it-works\} После `Adapty().activate()` SDK в фоне запрашивает у Apple данные атрибуции Apple Ads и передаёт результат на сервер Adapty. Когда AA становится активным источником атрибуции для профиля, SDK доставляет обновлённый `AdaptyProfile` в слушатель `didUpdateProfileStream`, а в списке `appliedAttributionSources` появляется `AdaptyAttributionSource.appleAds`. При первом запуске возможны два исхода: 1. **Атрибуция поступает в рамках таймаута.** Вызовите `getPaywall` — Adapty обработает запрос с учётом аудитории Apple Ads и вернёт целевой пейвол. 2. **Таймаут истекает первым.** В этом случае покажите пейвол для аудитории по умолчанию — пользователи без атрибуции Apple Ads не будут ждать. `getPaywallForDefaultAudience` вернёт его без ожидания сегментации. `appliedAttributionSources` может быть пустым. Это означает одно из двух: - атрибуция Apple Ads ещё не обработана для этого профиля, или - атрибуция не поступила вовсе. В любом случае вызов `getPaywallForDefaultAudience` безопасен — он возвращает пейвол для аудитории по умолчанию вне зависимости от состояния профиля. :::important Ожидание актуально только при первом запуске. После того как атрибуция Apple Ads записана, она постоянно хранится в профиле. При каждом последующем запуске кешированный профиль уже содержит `AdaptyAttributionSource.appleAds` в `appliedAttributionSources`, поэтому путь атрибуции разрешается немедленно и `getPaywall` возвращает пейвол для сегмента Apple Ads без какой-либо задержки. ::: ## Реализация \{#implementation\} При первом запуске дождитесь `AdaptyAttributionSource.appleAds` и установите жёсткий таймаут — если атрибуция Apple Ads так и не пришла, эти пользователи всё равно должны увидеть пейвол. 1. **Активируйте SDK.** См. [Установка и настройка Flutter SDK](sdk-installation-flutter). 2. **Подпишитесь на обновления профиля** с помощью `Adapty().didUpdateProfileStream.listen(…)`. Если вы ещё не настроили слушатель, см. [Отслеживание обновлений подписки](flutter-check-subscription-status#listen-to-subscription-updates). 3. **Отслеживайте `AdaptyAttributionSource.appleAds` в `appliedAttributionSources`.** Когда он появится, загрузите пейвол с помощью `getPaywall` — Adapty вернёт вариант, сегментированный по AA: ```dart final subscription = Adapty().didUpdateProfileStream.listen((profile) async { if (!profile.appliedAttributionSources.contains(AdaptyAttributionSource.appleAds)) return; final paywall = await Adapty().getPaywall(placementId: placementId); // present the segmented paywall, then cancel the subscription and the timer }); ``` `didUpdateProfileStream` — это широковещательный поток без повтора событий, поэтому также проверяйте текущий профиль через `getProfile()`. При повторных запусках приложения сохранённая атрибуция уже применена и повторно не отправляется. 4. **Запустите таймер на 3–5 секунд параллельно с подпиской.** Если таймер сработает раньше, чем появится `AdaptyAttributionSource.appleAds`, загрузите пейвол для аудитории по умолчанию с помощью `getPaywallForDefaultAudience`. Отобразите тот пейвол, который разрешится первым, и отмените второй запрос, чтобы пейвол не загружался дважды. Настройте [резервный пейвол](flutter-use-fallback-paywalls) для плейсмента, чтобы пользователь не остался ни с чем при сбое сетевого запроса. ## Полный пример \{#complete-example\} Реализация ниже запускает гонку между получением атрибуции и таймаутом, параллельно подгружает пейвол для аудитории по умолчанию и возвращает нужный пейвол. Вызывающий код ждёт одну функцию — никаких слушателей и флагов состояния на стороне вызова: - Если атрибуция приходит раньше `timeout`, возвращается сегментированный пейвол через `getPaywall`. - Если первым срабатывает `timeout`, возвращается заранее загруженный пейвол для аудитории по умолчанию через `getPaywallForDefaultAudience`. ```dart title="apple_ads_paywall.dart" /// Returns the Apple Ads-segmented paywall if attribution is applied within /// [timeout], otherwise the default-audience paywall. Call after Adapty().activate(). Future<AdaptyPaywall> getPaywallOrDefault({ required String placementId, required Duration timeout, }) { // Prefetch the default-audience paywall right away so the timeout path resolves // without an extra network round-trip. `getPaywallForDefaultAudience` skips the // wait for segmentation data. `..ignore()` keeps an unused prefetch from surfacing // as an unhandled error; the error still reaches the caller if this paywall wins. final defaultPaywall = Adapty().getPaywallForDefaultAudience(placementId: placementId)..ignore(); final completer = Completer<AdaptyPaywall>(); late final StreamSubscription<AdaptyProfile> subscription; late final Timer timer; void resolve(Future<AdaptyPaywall> paywall) { if (completer.isCompleted) return; timer.cancel(); subscription.cancel(); completer.complete(paywall); } void onProfile(AdaptyProfile profile) { if (profile.appliedAttributionSources.contains(AdaptyAttributionSource.appleAds)) { resolve(Adapty().getPaywall(placementId: placementId)); } } // Attribution path: react to profile updates as attribution is applied. subscription = Adapty().didUpdateProfileStream.listen(onProfile); // The stream is a broadcast stream and doesn't replay, so check the current // profile too — on relaunches attribution is already stored and won't re-emit. Adapty().getProfile().then(onProfile).ignore(); // Timeout path: fall back to the prefetched default-audience paywall. timer = Timer(timeout, () => resolve(defaultPaywall)); return completer.future; } ``` Вызывайте этот метод с экрана-заставки, а затем отображайте пейвол после получения результата: ```dart try { final paywall = await getPaywallOrDefault( placementId: 'YOUR_PLACEMENT_ID', timeout: const Duration(seconds: 5), ); // present the paywall } on AdaptyError catch (adaptyError) { // handle the error or show a fallback paywall } catch (e) { // handle the error } ``` Настройте `timeout` под то, сколько времени вы готовы заставлять пользователей ждать перед показом пейвола. У большинства пользователей нет атрибуции Apple Ads, поэтому они ждут всё отведённое время — 3–5 секунд — разумный баланс. Атрибуция, если она приходит, обычно поступает в течение нескольких секунд после запуска. Если ваше приложение уже слушает `didUpdateProfileStream` для других целей (например, [проверки статуса подписки](flutter-check-subscription-status#listen-to-subscription-updates)), менять ничего не нужно. `didUpdateProfileStream` — это широковещательный поток (broadcast stream), поэтому он поддерживает несколько независимых слушателей, не мешая друг другу. --- # File: flutter-test --- --- title: "Тестирование и релиз в Flutter SDK" description: "Узнайте, как проверить статус подписки в Flutter-приложении с помощью Adapty." --- Если вы уже интегрировали Adapty SDK в своё Flutter-приложение, вам нужно убедиться, что всё настроено правильно и покупки работают корректно на платформах iOS и Android. Это включает тестирование как интеграции SDK, так и самого процесса покупки в песочнице Apple и тестовой среде Google Play. ## Тестирование приложения \{#test-your-app\} Подробное руководство по тестированию встроенных покупок доступно в гайдах для каждой платформы: [гайд по тестированию на iOS](test-purchases-in-sandbox) и [гайд по тестированию на Android](testing-on-android). ## Подготовка к релизу \{#prepare-for-release\} Перед отправкой приложения в стор пройдите по [чеклисту для релиза](release-checklist) и проверьте: - Настроены ли подключение к стору и серверные уведомления - Завершаются ли покупки и передаются ли данные в Adapty - Корректно ли открывается и восстанавливается доступ - Выполнены ли требования к конфиденциальности и ревью --- # File: InvalidProductIdentifiers-flutter --- --- title: "Исправление ошибки Code-1000 noProductIDsFound в Flutter SDK" description: "Устраните ошибку с недопустимым идентификатором продукта при управлении подписками в Adapty." --- Ошибка с кодом 1000 — `noProductIDsFound` — означает, что ни один из продуктов, запрошенных на пейволе, недоступен для покупки в App Store, хотя они там перечислены. Иногда эта ошибка сопровождается предупреждением `InvalidProductIdentifiers`. Если предупреждение появляется без ошибки, можно его проигнорировать. Если вы столкнулись с ошибкой `noProductIDsFound`, выполните следующие шаги для её устранения: ## Шаг 1. Проверьте Bundle ID \{#step-2-check-bundle-id\} 1. Откройте [App Store Connect](https://appstoreconnect.apple.com/apps). Выберите своё приложение и перейдите в раздел **General** → **App Information**. 2. Скопируйте **Bundle ID** в подразделе **General Information**. <Zoom> <img src="/docs/img/afd5012-bundle_id_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. Откройте вкладку [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) в верхнем меню Adapty и вставьте скопированное значение в поле **Bundle ID**. <Zoom> <img src="/docs/img/2d64163-bundle_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 4. Вернитесь на страницу **App information** в App Store Connect и скопируйте **Apple ID**. 5. На странице [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) в дашборде Adapty вставьте этот ID в поле **Apple app ID**. ## Шаг 2. Проверьте продукты \{#step-3-check-products\} 1. Откройте **App Store Connect** и перейдите в раздел [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions) в левом меню. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите на название группы подписок. Ваши продукты будут перечислены в разделе **Subscriptions**. 3. Убедитесь, что тестируемый продукт имеет статус **Ready to Submit**. <img src="/assets/shared/img/ready-to-submit.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Сравните ID продукта из таблицы с тем, что указан на вкладке [**Products**](https://app.adapty.io/products) в дашборде Adapty. Если ID не совпадают, скопируйте ID продукта из таблицы и [создайте продукт](create-product) с этим ID в дашборде Adapty. <img src="/assets/shared/img/product-id-copy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 3. Проверьте доступность продукта \{#step-4-check-product-availability\} 1. Вернитесь в **App Store Connect** и откройте тот же раздел **Subscriptions**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите на название группы подписок, чтобы просмотреть продукты. 3. Выберите тестируемый продукт. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Прокрутите до раздела **Availability** и убедитесь, что все необходимые страны и регионы указаны. <img src="/assets/shared/img/product-availability.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 4. Проверьте цены продукта \{#step-5-check-product-prices\} 1. Снова перейдите в раздел **Monetization** → **Subscriptions** в **App Store Connect**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите на название группы подписок. 3. Выберите тестируемый продукт. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Прокрутите вниз до раздела **Subscription Pricing** и раскройте секцию **Current Pricing for New Subscribers**. <img src="/assets/shared/img/check-prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Убедитесь, что все необходимые цены указаны. <img src="/assets/shared/img/product-pricing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 5. Убедитесь, что статус платного приложения, банковский счёт и налоговые формы активны \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\} 1. На главной странице [**App Store Connect**](https://appstoreconnect.apple.com/) нажмите **Business**. <img src="/assets/shared/img/business.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Выберите название своей компании. <img src="/assets/shared/img/business-name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Прокрутите вниз и убедитесь, что **Paid Apps Agreement**, **Bank Account** и **Tax forms** имеют статус **Active**. <img src="/assets/shared/img/appstore-connect-status.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Выполнив эти шаги, вы должны устранить предупреждение `InvalidProductIdentifiers` и сделать продукты доступными в сторе. ## Шаг 6. Пересоздайте продукт, если он завис \{#step-6-recreate-the-product-if-its-stuck\} Шаги 1–5 могут пройти успешно — статус `Approved`, совпадающий Bundle ID, действительный API-ключ — а SDK всё равно возвращает `1000 noProductIDsFound`. В таком случае продукт может быть завис в реестре Apple. Иногда реестр продуктов Apple входит в состояние, при котором продукт существует в интерфейсе App Store Connect, но недоступен через путь поиска StoreKit. Удалите продукт в App Store Connect и пересоздайте его с тем же ID. После пересоздания подождите до 24 часов для распространения изменений. --- # File: cantMakePayments-flutter --- --- title: "Исправление ошибки Code-1003 cantMakePayment в Flutter SDK" description: "Решение ошибки при проведении платежей при управлении подписками в Adapty." --- Ошибка 1003, `cantMakePayments`, означает, что на этом устройстве нельзя совершать встроенные покупки. Если вы столкнулись с ошибкой `cantMakePayments`, обычно это происходит по одной из следующих причин: - Ограничения устройства: ошибка не связана с Adapty. Способы решения описаны ниже. - Настройка Observer mode: метод `makePurchase` и Observer mode нельзя использовать одновременно. Подробнее — в соответствующем разделе ниже. ## Проблема: ограничения устройства \{#issue-device-restrictions\} | Проблема | Решение | |---------------------------------|-------------------------------------------------------------------------------------------------------------------| | Ограничения Screen Time | Отключите ограничения встроенных покупок в [Screen Time](https://support.apple.com/en-us/102470) | | Аккаунт заблокирован | Обратитесь в службу поддержки Apple для решения проблем с аккаунтом | | Региональные ограничения | Используйте аккаунт App Store из поддерживаемого региона | ## Проблема: одновременное использование Observer mode и makePurchase \{#issue-using-both-observer-mode-and-makepurchase\} Если вы используете `makePurchase` для обработки покупок, Observer mode не нужен. [Observer mode](observer-vs-full-mode) требуется только в том случае, если логику покупок вы реализуете самостоятельно. Таким образом, если вы используете `makePurchase`, можно смело убрать активацию Observer mode из кода инициализации SDK. --- # File: migration-to-flutter-sdk-v4 --- --- title: "Миграция Adapty Flutter SDK на версию 4.0" description: "Перейдите на Adapty Flutter SDK v4.0, заменив paywall API на flow API, совместимые как с Flow Builder, так и с Paywall Builder." --- Adapty Flutter SDK 4.0 вводит флоу и соответственно переименовывает paywall API. Новые API работают как с новым Flow Builder, так и с существующим Paywall Builder — никаких изменений в настройках дашборда Adapty не требуется. ## Краткий справочник \{#quick-reference\} | v3 | v4 | |---|---| | `Adapty().getPaywall(placementId: id)` | `Adapty().getFlow(placementId: id)` | | `Adapty().getPaywallForDefaultAudience(placementId: id)` | `Adapty().getFlowForDefaultAudience(placementId: id)` | | `Adapty().getPaywallProducts(paywall: paywall)` | `Adapty().getPaywallProducts(flow: flow)` | | `Adapty().logShowPaywall(paywall: paywall)` | `Adapty().logShowFlow(flow: flow)` | | `AdaptyPaywall` (тип) | `AdaptyFlow` | | `AdaptyPaywallFetchPolicy` (тип) | `AdaptyFlowFetchPolicy` | | `AdaptyUI().createPaywallView(paywall: paywall)` | `AdaptyUI().createFlowView(flow: flow)` | | `AdaptyUIPaywallView` (тип) | `AdaptyUIFlowView` | | `AdaptyUIPaywallPlatformView` (виджет) | `AdaptyUIFlowPlatformView` | | `AdaptyUI().presentPaywallView(view)` / `dismissPaywallView(view)` | `AdaptyUI().presentFlowView(view)` / `dismissFlowView(view)` | | `AdaptyUIPaywallsEventsObserver` | `AdaptyUIFlowsEventsObserver` | | `AdaptyUI().setPaywallsEventsObserver(observer)` | `AdaptyUI().setFlowsEventsObserver(observer)` | | Колбэки `paywallViewDid*` | Колбэки `flowViewDid*` | | `paywallViewDidFailRendering` | `flowViewDidReceiveError` | `AdaptyPaywallProduct` сохраняет своё название — продукты по-прежнему принадлежат флоу, а `getPaywallProducts` теперь принимает `AdaptyFlow`. При получении флоу больше не нужно передавать `locale`. API для покупок и профиля (`makePurchase`, `restorePurchases`, `getProfile`, `identify` и т. д.) остался без изменений, как и методы работы с представлениями `present`, `dismiss` и `showDialog`. Часть поведения по умолчанию изменилась — см. [Изменения поведения по умолчанию](#default-behavior-changes). ## Минимальные требования \{#minimum-versions\} Adapty Flutter SDK 4.0 повышает минимальные требования: - **iOS 15.0** — минимальная цель развёртывания iOS, повышена с iOS 13.0. - **Xcode 26** или новее — нативный iOS SDK использует Swift tools 6.2. - **Flutter 3.32.0** (Dart 3.8.0) или новее. ## Установка \{#installation\} ### Обновите пакет \{#update-the-package\} Какой пакет устанавливать, зависит от того, используется ли в вашем приложении Kids Mode. Для большинства приложений обновите `adapty_flutter` до версии 4.0 в файле `pubspec.yaml`: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter: 4.0.0 ``` Если ваше приложение использует Kids Mode, укажите вместо него `adapty_flutter_kids`: ```yaml showLineNumbers title="pubspec.yaml" dependencies: adapty_flutter_kids: 4.0.0 ``` Этот **автономный** пакет удаляет IDFA и код отслеживания рекламы для соответствия требованиям App Store. Обновите путь импорта Dart на `package:adapty_flutter_kids/adapty_flutter.dart`. В остальном миграция полностью идентична обычному пакету. Kids Mode также требует отключения сбора IP-адресов в дашборде Adapty — полную инструкцию по настройке см. в разделе [Kids Mode](kids-mode-flutter). ### iOS: нативные SDK теперь поставляются через Swift Package Manager \{#ios-native-sdks-now-come-through-swift-package-manager\} [Репозиторий спецификаций CocoaPods становится доступным только для чтения в декабре 2026 года](https://blog.cocoapods.org/CocoaPods-Specs-Repo/), поэтому начиная с v4 нативный iOS SDK **больше не распространяется через CocoaPods** — плагин получает его только через **Swift Package Manager**. Если вы используете Flutter 3.32–3.43, один раз включите поддержку Swift Package Manager: ```bash flutter config --enable-swift-package-manager ``` В Flutter 3.44 и выше Swift Package Manager включён по умолчанию, так что никаких дополнительных действий не требуется. ## Получение флоу \{#fetching-flows\} ### getPaywall → getFlow Возвращаемый тип меняется с `AdaptyPaywall` на `AdaptyFlow`, и теперь не нужно передавать `locale` — при отображении флоу локализация определяется автоматически; для кастомных пейволов все настроенные локали возвращаются в `flow.remoteConfigs`: ```diff showLineNumbers - final paywall = await Adapty().getPaywall(placementId: 'YOUR_PLACEMENT_ID', locale: 'en'); + final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); ``` `getPaywallForDefaultAudience` переименовывается аналогично: ```diff showLineNumbers - final paywall = await Adapty().getPaywallForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID', locale: 'en'); + final flow = await Adapty().getFlowForDefaultAudience(placementId: 'YOUR_PLACEMENT_ID'); ``` Тип политики загрузки переименован с `AdaptyPaywallFetchPolicy` на `AdaptyFlowFetchPolicy`; его варианты (`reloadRevalidatingCacheData`, `returnCacheDataElseLoad`, `returnCacheDataIfNotExpiredElseLoad`) остались без изменений. ### getPaywallProducts(paywall) → getPaywallProducts(flow) `getPaywallProducts` сохраняет своё название, но теперь принимает `AdaptyFlow` через параметр `flow`: ```diff showLineNumbers - final products = await Adapty().getPaywallProducts(paywall: paywall); + final products = await Adapty().getPaywallProducts(flow: flow); ``` ## Модель данных \{#data-model\} `getFlow` возвращает `AdaptyFlow` вместо `AdaptyPaywall`, и структура объекта изменилась: | Член `AdaptyPaywall` v3 | Член `AdaptyFlow` v4 | Действие | |---|---|---| | `remoteConfig` (один, nullable) | `remoteConfigs` (список) | Флоу хранит один Remote Config на каждый настроенный язык. Геттер `remoteConfig` по-прежнему существует и возвращает первую запись; чтобы выбрать конкретный язык, найдите нужную запись в `remoteConfigs` по полю `locale`. | | `productIdentifiers` | `productIdentifiers` | Сохранён, но теперь объединяет идентификаторы из всех вариаций пейволов флоу. Идентификаторы для каждой вариации доступны через `flow.paywalls[i].productIdentifiers`. | | `hasViewConfiguration` | `hasViewConfiguration` | Без изменений. | | `placementId` (устарело) | удалён | Используйте `flow.placement.id`. | | `revision` (устарело) | удалён | Используйте `flow.placement.revision`. | | `vendorProductIds` (устарело) | удалён | Используйте `productIdentifiers`. | | _(новое)_ | `paywalls` (список `AdaptyFlowPaywall`) | Каждый элемент — одна вариация пейвола во флоу со своими `name`, `variationId` и `productIdentifiers`. | `AdaptyPaywallViewConfiguration` больше не предоставляется публично — конфигурация представления теперь непрозрачна. Удалите все ссылки на этот тип. ## Методы веб-пейвола \{#web-paywall-methods\} `openWebPaywall` и `createWebPaywallUrl` сохраняют свои названия, но параметр `paywall` теперь принимает `AdaptyFlowPaywall` (вариант флоу) вместо `AdaptyPaywall`. По-прежнему можно передать `AdaptyPaywallProduct`. ```diff showLineNumbers final flow = await Adapty().getFlow(placementId: 'YOUR_PLACEMENT_ID'); - await Adapty().openWebPaywall(paywall: paywall); + if (flow.paywalls.isNotEmpty) { + await Adapty().openWebPaywall(paywall: flow.paywalls[0]); + } ``` ## Отслеживание просмотров флоу \{#tracking-flow-views\} ### logShowPaywall → logShowFlow `logShowPaywall` переименован в `logShowFlow` и теперь принимает `AdaptyFlow`. Событие по-прежнему фиксируется для той же вариации, поэтому существующие метрики воронки и A/B-тестов продолжают работать без изменений в дашборде. ```diff showLineNumbers - await Adapty().logShowPaywall(paywall: paywall); + await Adapty().logShowFlow(flow: flow); ``` Как и в v3, вызывать этот метод при отображении флоу или пейволов, отрисованных с помощью [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder), не нужно — Adapty отслеживает эти просмотры автоматически. ## Отображение флоу \{#displaying-flows\} ### createPaywallView → createFlowView Переименуйте метод и передайте `AdaptyFlow` через параметр `flow`. Остальные параметры (`loadTimeout`, `preloadProducts`, `customTags`, `customTimers`, `customAssets`, `productPurchaseParams`) остаются без изменений, как и методы представления `present`, `dismiss` и `showDialog`: ```diff showLineNumbers - final view = await AdaptyUI().createPaywallView(paywall: paywall); + final view = await AdaptyUI().createFlowView(flow: flow); await view.present(); ``` ### AdaptyUIPaywallView → AdaptyUIFlowView Тип представления переименован. Устаревшее свойство `paywallVariationId` удалено — используйте `variationId`: ```diff showLineNumbers - void flowViewDidAppear(AdaptyUIPaywallView view) { + void flowViewDidAppear(AdaptyUIFlowView view) { ``` ### AdaptyUIPaywallPlatformView → AdaptyUIFlowPlatformView Если вы встраиваете представление как виджет в дерево виджетов, переименуйте его и передайте параметр `flow`. Колбэки событий (`onDidAppear`, `onDidFinishPurchase` и так далее) сохраняют свои названия: ```diff showLineNumbers - AdaptyUIPaywallPlatformView( - paywall: paywall, + AdaptyUIFlowPlatformView( + flow: flow, onDidFinishPurchase: (view, product, purchaseResult) { /* … */ }, ) ``` :::note Представление флоу, созданное с помощью `createFlowView`, одноразовое: после вызова `dismiss()` оно освобождается из памяти и не может быть показано повторно — вызовите `createFlowView` снова, чтобы отобразить флоу ещё раз. ::: ## Обработка событий \{#handling-events\} Класс-наблюдатель переименован с `AdaptyUIPaywallsEventsObserver` на `AdaptyUIFlowsEventsObserver`, метод его регистрации — с `setPaywallsEventsObserver` на `setFlowsEventsObserver`, а все колбэки `paywallViewDid*` — на `flowViewDid*`: ```diff showLineNumbers - class MyObserver extends AdaptyUIPaywallsEventsObserver { + class MyObserver extends AdaptyUIFlowsEventsObserver { @override - void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) { + void flowViewDidPerformAction(AdaptyUIFlowView view, AdaptyUIAction action) { // … } } - AdaptyUI().setPaywallsEventsObserver(this); + AdaptyUI().setFlowsEventsObserver(this); ``` Три коллбэка теперь **обязательны** — без них наблюдатель не скомпилируется: - **`flowViewDidFinishPurchase`**: В v3 был опциональным — по умолчанию закрывал вью после покупки. Теперь вы сами решаете, что произойдёт: продолжить флоу или вызвать `view.dismiss()`. - **`flowViewDidFinishRestore`**: Обязательный, как и в v3. - **`flowViewDidReceiveError`**: Заменяет `paywallViewDidFailRendering` и теперь также получает другие ошибки вью. Два небольших изменения: - `setFlowsEventsObserver` (и `setOnboardingsEventsObserver`) теперь принимают `null`, чтобы отвязать ранее установленный наблюдатель — SDK больше не удерживает его. - Новый необязательный коллбэк `flowViewDidReceiveAnalyticEvent` зарезервирован для пользовательских аналитических событий из флоу. Флоу пока не отправляют их в ваш код, поэтому реализовывать его не нужно. В v4 также появились возможности, которые можно подключить по желанию: - `AdaptyUI().setObserverModeResolver(...)` с `AdaptyUIObserverModeResolver` — управляет покупками и восстановлениями, инициированными из флоу, когда SDK работает в [режиме Observer](implement-observer-mode-flutter). Ранее это было доступно только в нативных SDK для iOS и Android. См. [Показ флоу в режиме Observer](flutter-present-flows-in-observer-mode). - `AdaptyUI().setSystemRequestsHandler(...)` с `AdaptyUISystemRequestsHandler` — зарезервировано для системных запросов из флоу (запросы разрешений ОС и запросы на отзыв в App Store). Флоу пока не инициируют такие запросы, поэтому регистрировать обработчик не нужно. ## Удалённые API \{#removed-apis\} Эти символы были помечены как устаревшие в версии 3.x и удалены в v4: ### setFallbackPaywalls → setFallback ```diff showLineNumbers - await Adapty().setFallbackPaywalls(assetId); + await Adapty().setFallback(assetId); ``` ### withIdfaCollectionDisabled → withAppleIdfaCollectionDisabled ```diff showLineNumbers configuration: AdaptyConfiguration(apiKey: 'YOUR_PUBLIC_SDK_KEY') - ..withIdfaCollectionDisabled(true), + ..withAppleIdfaCollectionDisabled(true), ``` ### Другие удалённые элементы - **`AdaptyPurchaseResultSuccess.jwsTransaction`**: Используйте `appleJwsTransaction`. - **`AdaptyUIFlowView.paywallVariationId`**: Используйте `variationId`. - **`AdaptyUIObserver` и `AdaptyUI().setObserver(...)`**: Используйте `AdaptyUIFlowsEventsObserver` и `setFlowsEventsObserver(...)`. ## Изменения поведения по умолчанию \{#default-behavior-changes\} Эти изменения не вызывают ошибок компиляции, поэтому проверяйте их во время выполнения: - **Успешная покупка**: В v3 дефолтный `paywallViewDidFinishPurchase` закрывал экран. В v4 `flowViewDidFinishPurchase` обязателен и не имеет реализации по умолчанию — закрывайте экран самостоятельно, если хотите такого поведения. - **Системная кнопка «Назад» на Android**: Она больше не закрывает флоу по умолчанию. Действие передаётся в `flowViewDidPerformAction` как `AndroidSystemBackAction` — обработайте его там, если хотите, чтобы кнопка «Назад» закрывала флоу. - **Открытие URL**: Дефолтный `flowViewDidPerformAction` теперь обрабатывает `OpenUrlAction`, открывая URL нативно (с учётом настройки встроенного или внешнего браузера из дашборда), а также закрывает экран по `CloseAction`. Переопределите коллбэк, чтобы обрабатывать URL самостоятельно. - **Ошибки экрана**: `flowViewDidReceiveError` обязателен, и закрытие экрана зависит от вашей реализации. Если в v3 ваша интеграция рассчитывала на автоматическое закрытие при ошибках рендеринга, вызывайте `view.dismiss()` в этом коллбэке. - **Жизненный цикл экрана**: Закрытие флоу или онбординга освобождает его из памяти. Закрытый экран нельзя показать повторно — создайте новый. ## Устаревший API онбординга \{#onboarding-api-deprecation\} Устаревший API онбординга объявлен устаревшим в v4.0 в пользу [Flow Builder](adapty-flow-builder). Он по-прежнему работает, а IDE помечает устаревшие символы через аннотации `@Deprecated` — никаких предупреждений во время выполнения нет. Эти символы будут удалены в одном из следующих релизов, поэтому планируйте переход ваших онбордингов на Flow Builder. Устаревшие символы: `getOnboarding`, `getOnboardingForDefaultAudience`, `createOnboardingView`, `presentOnboardingView`, `dismissOnboardingView`, `setOnboardingsEventsObserver`, `AdaptyOnboarding`, `AdaptyUIOnboardingView`, `AdaptyUIOnboardingPlatformView`, `AdaptyUIOnboardingsEventsObserver`, а также модели состояния, ввода и аналитики онбординга. --- # File: flutter-migration-guide-310 --- --- title: "Руководство по миграции на Flutter Adapty SDK 3.10.0" description: "" --- Adapty SDK 3.10.0 — это крупный релиз с рядом улучшений, которые могут потребовать миграции: 1. Обновите метод `makePurchase`, чтобы использовать `AdaptyPurchaseParameters` вместо отдельных параметров. 2. Замените `vendorProductIds` на `productIdentifiers` в модели `AdaptyPaywall`. ## Обновление метода makePurchase \{#update-makepurchase-method\} Метод `makePurchase` теперь принимает `AdaptyPurchaseParameters` вместо отдельных аргументов `subscriptionUpdateParams` и `isOfferPersonalized`. Это обеспечивает более строгую типизацию и упрощает добавление новых параметров покупки в будущем. ```diff showLineNumbers - final purchaseResult = await adapty.makePurchase( - product: product, - subscriptionUpdateParams: subscriptionUpdateParams, - isOfferPersonalized: true, - ); + final parameters = AdaptyPurchaseParametersBuilder() + ..setSubscriptionUpdateParams(subscriptionUpdateParams) + ..setIsOfferPersonalized(true) + ..setObfuscatedAccountId('your-account-id') + ..setObfuscatedProfileId('your-profile-id'); + final purchaseResult = await adapty.makePurchase( + product: product, + parameters: parameters.build(), + ); ``` Если дополнительные параметры не нужны, достаточно написать: ```dart showLineNumbers final purchaseResult = await adapty.makePurchase( product: product, ); ``` ## Обновление использования модели AdaptyPaywall \{#update-adaptypaywall-model-usage\} Свойство `vendorProductIds` устарело и заменено на `productIdentifiers`. Новое свойство возвращает объекты `AdaptyProductIdentifier` вместо обычных строк, что обеспечивает более структурированную информацию о продуктах. ```diff showLineNumbers - paywall.vendorProductIds.map((vendorId) => - ListTextTile(title: vendorId) - ).toList() + paywall.productIdentifiers.map((productId) => + ListTextTile(title: productId.vendorProductId) + ).toList() ``` Объект `AdaptyProductIdentifier` предоставляет доступ к идентификатору продукта через свойство `vendorProductId`, сохраняя прежнюю функциональность и обеспечивая лучшую структуру для будущих улучшений. ## Обратная совместимость \{#backward-compatibility\} Оба изменения обратно совместимы: - Старые параметры в `makePurchase` устарели, но продолжают работать - Свойство `vendorProductIds` устарело, но по-прежнему доступно - Существующий код продолжит работать, однако будут отображаться предупреждения об устаревании Рекомендуем перейти на новые API, чтобы обеспечить совместимость в будущем и воспользоваться преимуществами улучшенной типизации и расширяемости. --- # File: flutter-migration-guide-38 --- --- title: "Миграция Adapty Flutter SDK на v. 3.8" description: "Перейдите на Adapty Flutter SDK v3.8 для улучшения производительности и новых возможностей монетизации." --- Adapty SDK 3.8.0 — это мажорный релиз, который принёс ряд улучшений, требующих выполнения нескольких шагов миграции. 1. Обновите названия класса и методов наблюдателя. 2. Обновите название метода для резервных пейволов. 3. Обновите название класса представления в методах обработки событий. ## Обновление класса и методов наблюдателя \{#update-observer-class-and-method-names\} Класс наблюдателя и метод его регистрации были переименованы: ```diff showLineNumbers - class MyObserver extends AdaptyUIObserver { + class MyObserver extends AdaptyUIPaywallsEventsObserver { @override void paywallViewDidPerformAction(AdaptyUIView view, AdaptyUIAction action) { // Handle action } } // Register observer - AdaptyUI().setObserver(this); + AdaptyUI().setPaywallsEventsObserver(this); ``` ## Обновление названия метода для резервных пейволов \{#update-fallback-paywalls-method-name\} Метод установки резервных пейволов был упрощён: ```diff showLineNumbers try { - await Adapty.setFallbackPaywalls(assetId); + await Adapty.setFallback(assetId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ## Обновление имени класса представления в методах обработки событий \{#update-view-class-name-in-event-handling-methods\} Все методы обработки событий теперь используют новый класс `AdaptyUIPaywallView` вместо `AdaptyUIView`: ```diff showLineNumbers - void paywallViewDidPerformAction(AdaptyUIView view, AdaptyUIAction action) + void paywallViewDidPerformAction(AdaptyUIPaywallView view, AdaptyUIAction action) - void paywallViewDidSelectProduct(AdaptyUIView view, AdaptyPaywallProduct product) + void paywallViewDidSelectProduct(AdaptyUIPaywallView view, AdaptyPaywallProduct product) - void paywallViewDidStartPurchase(AdaptyUIView view, AdaptyPaywallProduct product) + void paywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) - void paywallViewDidFinishPurchase(AdaptyUIView view, AdaptyPaywallProduct product, AdaptyProfile profile) + void paywallViewDidFinishPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyProfile profile) - void paywallViewDidFailPurchase(AdaptyUIView view, AdaptyPaywallProduct product, AdaptyError error) + void paywallViewDidFailPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) - void paywallViewDidFinishRestore(AdaptyUIView view, AdaptyProfile profile) + void paywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) - void paywallViewDidFailRestore(AdaptyUIView view, AdaptyError error) + void paywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) - void paywallViewDidFailLoadingProducts(AdaptyUIView view, AdaptyIOSProductsFetchPolicy? fetchPolicy, AdaptyError error) + void paywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyIOSProductsFetchPolicy? fetchPolicy, AdaptyError error) - void paywallViewDidFailRendering(AdaptyUIView view, AdaptyError error) + void paywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) ``` --- # File: migration-to-flutter-sdk-34 --- --- title: "Миграция Adapty Flutter SDK на v. 3.4" description: "Мигрируйте на Adapty Flutter SDK v3.4 для повышения производительности и доступа к новым функциям монетизации." --- Adapty SDK 3.4.0 — это мажорный релиз, который вносит изменения, требующие миграции с вашей стороны. ## Обновите файлы резервных пейволов \{#update-fallback-paywall-files\} Обновите файлы резервных пейволов, чтобы обеспечить совместимость с новой версией SDK: 1. [Скачайте обновлённые файлы резервных пейволов](fallback-paywalls) из дашборда Adapty. 2. [Замените существующие резервные пейволы в своём мобильном приложении](flutter-use-fallback-paywalls) новыми файлами. ## Обновите реализацию Observer Mode \{#update-implementation-of-observer-mode\} Если вы используете Observer Mode, обновите его реализацию. Раньше для передачи транзакций в Adapty использовались разные методы. В новой версии метод `reportTransaction` следует использовать единообразно как на Android, так и на iOS. Этот метод явно передаёт каждую транзакцию в Adapty, гарантируя её распознавание. Если использовался пейвол, передайте variation ID, чтобы привязать транзакцию к нему. :::warning **Не пропускайте передачу информации о транзакции!** Если вы не вызовете `reportTransaction`, Adapty не распознает транзакцию, она не появится в аналитике и не будет отправлена в интеграции. ::: ```diff showLineNumbers - // every time when calling transaction.finish() - if (Platform.isAndroid) { - try { - await Adapty().restorePurchases(); - } on AdaptyError catch (adaptyError) { - // handle the error - } catch (e) { - } - } try { // every time when calling transaction.finish() await Adapty().reportTransaction( "YOUR_TRANSACTION_ID", variationId: "PAYWALL_VARIATION_ID", // optional ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` --- # File: migration-to-flutter330 --- --- title: "Миграция Adapty Flutter SDK на v3.3" description: "Перейдите на Adapty Flutter SDK v3.3 для улучшения производительности и новых функций монетизации." --- Adapty SDK 3.3.0 — это крупный релиз, который принёс ряд улучшений, однако для перехода на него могут потребоваться некоторые шаги миграции. 1. Обновите метод предоставления резервных пейволов. 2. Удалите метод `getProductsIntroductoryOfferEligibility`. 3. Обновите настройки интеграций для Adjust, AirBridge, Amplitude, AppMetrica, Appsflyer, Branch, Facebook Ads, Firebase и Google Analytics, Mixpanel, OneSignal, Pushwoosh. 4. Обновите реализацию режима Observer. ## Обновление метода для предоставления резервных пейволов \{#update-method-for-providing-fallback-paywalls\} Раньше метод принимал резервный пейвол в виде JSON-строки (`jsonString`), теперь вместо этого он принимает путь к локальному файлу резервного пейвола (`assetId`). ```diff showLineNumbers import 'dart:async' show Future; import 'dart:io' show Platform; -import 'package:flutter/services.dart' show rootBundle; -final filePath = Platform.isIOS ? 'assets/ios_fallback.json' : 'assets/android_fallback.json'; -final jsonString = await rootBundle.loadString(filePath); +final assetId = Platform.isIOS ? 'assets/ios_fallback.json' : 'assets/android_fallback.json'; try { - await adapty.setFallbackPaywalls(jsonString); + await adapty.setFallbackPaywalls(assetId); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { } ``` Полный пример кода смотрите на странице [Использование резервных пейволов](flutter-use-fallback-paywalls). ## Удаление метода `getProductsIntroductoryOfferEligibility` \{#remove-getproductsintroductoryoffereligibility-method\} До Adapty iOS SDK 3.3.0 объект продукта всегда включал офферы вне зависимости от того, подходит ли для них пользователь. Вам нужно было проверять eligibility вручную перед использованием оффера. Теперь объект продукта включает оффер только в том случае, если пользователь на него подходит. Это значит, что проверять eligibility больше не нужно — если оффер присутствует, пользователь подходит для него. ## Обновление конфигурации SDK сторонних интеграций \{#update-third-party-integration-sdk-configuration\} Чтобы интеграции корректно работали с Adapty Flutter SDK 3.3.0 и новее, обновите конфигурации SDK для следующих интеграций согласно инструкциям ниже. ### Adjust Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с Adjust](adjust#connect-your-app-to-adjust). ```diff showLineNumbers import 'package:adjust_sdk/adjust.dart'; import 'package:adjust_sdk/adjust_config.dart'; try { final adid = await Adjust.getAdid(); if (adid == null) { // handle the error } + await Adapty().setIntegrationIdentifier( + key: "adjust_device_id", + value: adid, + ); final attributionData = await Adjust.getAttribution(); var attribution = Map<String, String>(); if (attributionData.trackerToken != null) attribution['trackerToken'] = attributionData.trackerToken!; if (attributionData.trackerName != null) attribution['trackerName'] = attributionData.trackerName!; if (attributionData.network != null) attribution['network'] = attributionData.network!; if (attributionData.adgroup != null) attribution['adgroup'] = attributionData.adgroup!; if (attributionData.creative != null) attribution['creative'] = attributionData.creative!; if (attributionData.clickLabel != null) attribution['clickLabel'] = attributionData.clickLabel!; if (attributionData.costType != null) attribution['costType'] = attributionData.costType!; if (attributionData.costAmount != null) attribution['costAmount'] = attributionData.costAmount!.toString(); if (attributionData.costCurrency != null) attribution['costCurrency'] = attributionData.costCurrency!; if (attributionData.fbInstallReferrer != null) attribution['fbInstallReferrer'] = attributionData.fbInstallReferrer!; - Adapty().updateAttribution( - attribution, - source: AdaptyAttributionSource.adjust, - networkUserId: adid, - ); + await Adapty().updateAttribution(attribution, source: "adjust"); } catch (e) { // handle the error } on AdaptyError catch (adaptyError) { // handle the error } ``` ### AirBridge Обновите код мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [настройка SDK для интеграции с AirBridge](airbridge#connect-your-app-to-airbridge). ```diff showLineNumbers import 'package:airbridge_flutter_sdk/airbridge_flutter_sdk.dart'; final deviceUUID = await Airbridge.state.deviceUUID; try { - final builder = AdaptyProfileParametersBuilder() - ..setAirbridgeDeviceId(deviceUUID); - await Adapty().updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "airbridge_device_id", + value: deviceUUID, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### Amplitude Обновите код мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [настройка SDK для интеграции с Amplitude](amplitude#sdk-configuration). ```diff showLineNumbers import 'package:amplitude_flutter/amplitude.dart'; final Amplitude amplitude = Amplitude.getInstance(instanceName: "YOUR_INSTANCE_NAME"); final deviceId = await amplitude.getDeviceId(); final userId = await amplitude.getUserId(); try { - final builder = AdaptyProfileParametersBuilder() - ..setAmplitudeDeviceId(deviceId) - ..setAmplitudeUserId(userId); - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "amplitude_user_id", + value: userId, + ); + await Adapty().setIntegrationIdentifier( + key: "amplitude_device_id", + value: deviceId, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### AppMetrica Обновите код мобильного приложения, как показано ниже. Полный пример кода см. в разделе [Настройка SDK для интеграции с AppMetrica](appmetrica#sdk-configuration). ```diff showLineNumbers import 'package:appmetrica_plugin/appmetrica_plugin.dart'; final deviceId = await AppMetrica.deviceId; if (deviceId != null) { try { - final builder = AdaptyProfileParametersBuilder() - ..setAppmetricaDeviceId(deviceId) - ..setAppmetricaProfileId("YOUR_ADAPTY_CUSTOMER_USER_ID"); - - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "appmetrica_device_id", + value: deviceId, + ); + await Adapty().setIntegrationIdentifier( + key: "appmetrica_profile_id", + value: "YOUR_ADAPTY_CUSTOMER_USER_ID", + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } } ``` ### AppsFlyer Обновите код мобильного приложения, как показано ниже. Полный пример кода см. в разделе [Настройка SDK для интеграции с AppsFlyer](appsflyer#connect-your-app-to-appsflyer). ```diff showLineNumbers import 'package:appsflyer_sdk/appsflyer_sdk.dart'; AppsflyerSdk appsflyerSdk = AppsflyerSdk(<YOUR_OPTIONS>); appsflyerSdk.onInstallConversionData((data) async { try { final appsFlyerUID = await appsFlyerSdk.getAppsFlyerUID(); - await Adapty().updateAttribution( - data, - source: AdaptyAttributionSource.appsflyer, - networkUserId: appsFlyerUID, - ); + await Adapty().setIntegrationIdentifier( + key: "appsflyer_id", + value: appsFlyerUID, + ); + + await Adapty().updateAttribution(data, source: "appsflyer"); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } }); appsflyerSdk.initSdk( registerConversionDataCallback: true, registerOnAppOpenAttributionCallback: true, registerOnDeepLinkingCallback: true, ); ``` ### Branch Обновите код вашего мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [настройка SDK для интеграции с Branch](branch#connect-your-app-to-branch). ```diff showLineNumbers FlutterBranchSdk.initSession().listen((data) async { try { + await Adapty().setIntegrationIdentifier( + key: "branch_id", + value: <BRANCH_IDENTITY_ID>, + ); - await Adapty().updateAttribution(data, source: AdaptyAttributionSource.branch); + await Adapty().updateAttribution(data, source: "branch"); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ); ``` ### Firebase и Google Analytics \{#firebase-and-google-analytics\} Обновите код мобильного приложения, как показано ниже. Полный пример кода см. в разделе [Настройка SDK для интеграции с Firebase и Google Analytics](firebase-and-google-analytics). ```diff showLineNumbers final appInstanceId = await FirebaseAnalytics.instance.appInstanceId; try { - final builder = AdaptyProfileParametersBuilder() - ..setFirebaseAppInstanceId(appInstanceId); - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "firebase_app_instance_id", + value: appInstanceId, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### Mixpanel Обновите код мобильного приложения, как показано ниже. Полный пример кода см. в разделе [настройка SDK для интеграции с Mixpanel](mixpanel#sdk-configuration). ```diff showLineNumbers final mixpanel = await Mixpanel.init("Your Token", trackAutomaticEvents: true); final distinctId = await mixpanel.getDistinctId(); try { - final builder = AdaptyProfileParametersBuilder() - ..setMixpanelUserId(distinctId); - await Adapty().updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "mixpanel_user_id", + value: distinctId, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ### OneSignal Обновите код мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [настройка SDK для интеграции с OneSignal](onesignal#sdk-configuration). ```diff showLineNumbers OneSignal.shared.setSubscriptionObserver((changes) { final playerId = changes.to.userId; if (playerId != null) { - final builder = - AdaptyProfileParametersBuilder() - ..setOneSignalPlayerId(playerId); - // ..setOneSignalSubscriptionId(playerId); try { - Adapty().updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "one_signal_player_id", + value: playerId, + ); } on AdaptyError catch (adaptyError) { // handle error } catch (e) { // handle error } } }); ``` ### Pushwoosh Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [настройка SDK для интеграции с Pushwoosh](pushwoosh#sdk-configuration). ```diff showLineNumbers final hwid = await Pushwoosh.getInstance.getHWID; - final builder = AdaptyProfileParametersBuilder() - ..setPushwooshHWID(hwid); try { - await adapty.updateProfile(builder.build()); + await Adapty().setIntegrationIdentifier( + key: "pushwoosh_hwid", + value: hwid, + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` ## Обновите реализацию режима Observer \{#update-observer-mode-implementation\} Обновите способ привязки пейволов к транзакциям. Раньше для назначения `variationId` использовался метод `setVariationId`. Теперь можно передавать `variationId` напрямую при записи транзакции с помощью нового метода `reportTransaction`. Итоговый пример кода приведён в разделе [Привязка пейволов к транзакциям покупок в режиме Observer](report-transactions-observer-mode-flutter). :::warning Не забудьте зафиксировать транзакцию с помощью метода `reportTransaction`. Если пропустить этот шаг, Adapty не распознает транзакцию, не предоставит уровни доступа, не включит её в аналитику и не передаст в интеграции. Этот шаг обязателен! ::: ```diff showLineNumbers try { - await Adapty().setVariationId("YOUR_TRANSACTION_ID", "PAYWALL_VARIATION_ID"); + // every time when calling transaction.finish() + await Adapty().reportTransaction( + "YOUR_TRANSACTION_ID", + variationId: "PAYWALL_VARIATION_ID", // optional + ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` --- # File: migration-to-flutter-sdk-v3 --- --- title: "Миграция Adapty Flutter SDK на v3.0" description: "Мигрируйте на Adapty Flutter SDK v3.0 для улучшенной производительности и новых функций монетизации." --- Adapty SDK v3.0 добавляет поддержку нового [Adapty Paywall Builder](adapty-paywall-builder) — обновлённой версии no-code инструмента для создания пейволов. Благодаря максимальной гибкости и широким возможностям дизайна ваши пейволы станут ещё эффективнее и прибыльнее. :::info Обратите внимание: библиотека AdaptyUI устарела и теперь включена в состав AdaptySDK. ::: ## Удаление AdaptyUI SDK \{#remove-adaptyi-sdk\} 1. AdaptyUI теперь является модулем Adapty SDK, поэтому удалите `adapty_ui_flutter` из файла `pubspec.yaml`: ```diff showLineNumbers dependencies: + adapty_flutter: ^3.2.1 - adapty_flutter: ^2.10.3 - adapty_ui_flutter: ^2.1.3 ``` 2. Выполните команду: ```bash showLineNumbers title="Bash" flutter pub get ``` ## Настройка Adapty SDK \{#configure-adapty-sdks\} Ранее для настройки Adapty SDK требовалось использовать файлы `Adapty-Info.plist` и `AndroidManifest.xml`. Теперь дополнительные файлы не нужны — все необходимые параметры передаются при активации. Настройку Adapty SDK достаточно выполнить один раз, как правило, при запуске приложения. ### Активация модуля Adapty SDK \{#activate-adapty-module-of-adapty-sdk\} 1. Удалите импорт AdaptyUI SDK из вашего приложения: ```diff showLineNumbers import 'package:adapty_flutter/adapty_flutter.dart'; - import 'package:adapty_ui_flutter/adapty_ui_flutter.dart'; ``` 2. Обновите активацию Adapty SDK следующим образом: ```diff showLineNumbers try { - Adapty().activate(); + await Adapty().activate( + configuration: AdaptyConfiguration(apiKey: 'YOUR_API_KEY') + ..withLogLevel(AdaptyLogLevel.debug) + ..withObserverMode(false) + ..withCustomerUserId(null) + ..withIpAddressCollectionDisabled(false) + ..withIdfaCollectionDisabled(false), + ); } catch (e) { // handle the error } ``` Параметры: | Параметр | Обязательность | Описание | | ----------------------------------- | -------------- | ------------------------------------------------------------ | | **PUBLIC_SDK_KEY** | обязательный | Ключ, который можно найти в поле **Public SDK key** в настройках приложения в Adapty: [**App settings**-> **General** tab -> **API keys** subsection](https://app.adapty.io/settings/general) | | **withLogLevel** | опциональный | Adapty записывает ошибки и другую важную информацию для анализа работы вашего приложения. Доступны следующие уровни логирования:<ul><li>error: регистрируются только ошибки.</li><li>warn: регистрируются ошибки и сообщения от SDK, которые не вызывают критических ошибок, но заслуживают внимания.</li><li>info: регистрируются ошибки, предупреждения и важные информационные сообщения, например о жизненном цикле различных модулей.</li><li>verbose: регистрируется любая дополнительная информация, которая может быть полезна при отладке: вызовы функций, API-запросы и т. д.</li></ul> | | **withObserverMode** | опциональный | <p>Булево значение, управляющее [режимом Observer](observer-vs-full-mode). Включите его, если вы самостоятельно обрабатываете покупки и статус подписки, а Adapty используете только для отправки событий подписки и аналитики.</p><p>Значение по умолчанию: `false`.</p><p></p><p>🚧 В режиме Observer Adapty SDK не закрывает транзакции — убедитесь, что вы обрабатываете их самостоятельно.</p> | | **withCustomerUserId** | опциональный | Идентификатор пользователя в вашей системе. Мы передаём его в событиях подписки и аналитики, чтобы привязать события к нужному профилю. Вы также можете искать пользователей по `customerUserId` в меню [**Profiles and Segments**](https://app.adapty.io/profiles/users). | | **withIdfaCollectionDisabled** | опциональный | <p>Установите значение `true`, чтобы отключить сбор и передачу IDFA.</p><p>а также передачу IP-адреса пользователя.</p><p>Значение по умолчанию: `false`.</p><p>Подробнее о сборе IDFA см. в разделе [Интеграция с аналитикой](analytics-integration#disable-collection-of-advertising-identifiers).</p> | | **withIpAddressCollectionDisabled** | опциональный | <p>Установите значение `true`, чтобы отключить сбор и передачу IP-адреса пользователя.</p><p>Значение по умолчанию: `false`.</p> | ### Активация модуля AdaptyUI в SDK Adapty \{#activate-adaptyui-module-of-adapty-sdk\} Настраивать модуль AdaptyUI нужно только в том случае, если вы планируете использовать [Paywall Builder](adapty-paywall-builder): ```dart showLineNumbers title="Dart" try { final mediaCache = AdaptyUIMediaCacheConfiguration( memoryStorageTotalCostLimit: 100 * 1024 * 1024, // 100MB memoryStorageCountLimit: 2147483647, // 2^31 - 1, max int value in Dart diskStorageSizeLimit: 100 * 1024 * 1024, // 100MB ); await AdaptyUI().activate( configuration: AdaptyUIConfiguration(mediaCache: mediaCache), observer: <AdaptyUIObserver Implementation>, ); } catch (e) { // handle the error } ``` Обратите внимание, что конфигурация AdaptyUI необязательна — модуль AdaptyUI можно активировать без неё. Однако если вы используете конфигурацию, все её параметры обязательны. Параметры: | Параметр | Наличие | Описание | | :------------------------------ | :------- | :----------------------------------------------------------- | | **memoryStorageTotalCostLimit** | обязательный | Общий лимит стоимости хранилища в байтах. | | **memoryStorageCountLimit** | обязательный | Лимит количества элементов в памяти. | | **diskStorageSizeLimit** | обязательный | Лимит размера файла на диске в байтах. 0 означает отсутствие ограничений. | --- # End of Documentation _Generated on: 2026-07-24T13:01:12.756Z_ _Successfully processed: 44/44 files_ # IOS - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: ru Generated on: 2026-07-24T13:01:12.759Z Total files: 44 --- # File: sdk-installation-ios --- --- title: "Установка и настройка iOS SDK" description: "Пошаговое руководство по установке Adapty SDK на iOS для приложений с подписками." --- SDK Adapty включает два ключевых модуля для бесшовной интеграции в ваше мобильное приложение: - **Core Adapty**: Основной SDK, необходимый для работы Adapty в вашем приложении. - **AdaptyUI**: Опциональный модуль, нужный если вы используете [Adapty Paywall Builder](adapty-paywall-builder) — удобный no-code инструмент для создания кросс-платформенных пейволов. :::tip Хотите посмотреть на реальный пример интеграции Adapty SDK в мобильное приложение? Загляните в наши [примеры приложений](https://github.com/adaptyteam/AdaptySDK-iOS/tree/master/Examples) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другую базовую функциональность. ::: Для полного пошагового руководства по реализации вы также можете посмотреть видео: <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="iOS (SwiftUI)" default> <div style={{ textAlign: 'center' }}> <iframe width="560" height="315" src="https://www.youtube.com/embed/cSChHc8k2zA?si=KhNFhqXccIzYwTcm" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> </div> </TabItem> <TabItem value="uikit" label="iOS (UIKit)" default> <div style={{ textAlign: 'center' }}> <iframe width="560" height="315" src="https://www.youtube.com/embed/WEUnlaAjSI0?si=sjXKVVb56tEHDKzJ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> </div> </TabItem> </Tabs> ## Требования \{#requirements\} Хотя SDK технически поддерживает iOS 13.0+ для базового модуля, на практике требуется iOS 15.0+, поскольку: - Все функции StoreKit 2 требуют iOS 15.0+ - Модуль AdaptyUI поддерживает только iOS 15.0+ :::important Adapty SDK 3.15.7+ обязателен при сборке с Xcode 26.4 или более поздней версией. ::: :::info Установка SDK — это шаг 5 настройки Adapty. Прежде чем покупки заработают в вашем приложении, вам также нужно подключить приложение к сторам, а затем создать продукты, пейвол и плейсмент в дашборде Adapty. [Гайд по быстрому старту](quickstart) описывает все необходимые шаги. ::: ## Установка Adapty SDK \{#install-adapty-sdk\} [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-iOS.svg?style=flat&logo=apple)](https://github.com/adaptyteam/AdaptySDK-iOS/releases) Adapty SDK устанавливается через Swift Package Manager. В Xcode перейдите в **File** -> **Add Package Dependency...**. Обратите внимание, что шаги по добавлению зависимостей могут отличаться в разных версиях Xcode — при необходимости обратитесь к документации Xcode. 1. Введите URL репозитория: ``` https://github.com/adaptyteam/AdaptySDK-iOS.git ``` 2. Выберите версию (рекомендуется последняя стабильная) и нажмите **Add Package**. 3. В окне **Choose Package Products** выберите нужные модули: - **Adapty** (основной модуль) - **AdaptyUI** (опционально — только если планируете использовать Paywall Builder) :::note Примечание: - Чтобы включить [Kids Mode](kids-mode) в SDK 3.x, выберите **Adapty_KidsMode** вместо **Adapty**. В SDK 4.0 и выше выбирайте обычные модули — Kids Mode включается через трейт пакета `KidsMode`. - Не выбирайте другие пакеты из списка — они вам не понадобятся. ::: 4. Нажмите **Add Package**, чтобы завершить установку. 5. **Проверьте установку:** в навигаторе проекта должны появиться «Adapty» (и «AdaptyUI», если был выбран) в разделе **Package Dependencies**. :::important Adapty iOS SDK 4.0 — это pre-release версия. Swift Package Manager не разрешает бета-версии по правилу **Up to Next Major Version** (`from:`), поэтому необходимо указать точную версию. В Xcode установите **Dependency Rule** в значение **Exact Version** и введите `4.0.0-beta.2`. В `Package.swift` используйте `.exact("4.0.0-beta.2")`. См. [Миграция Adapty iOS SDK на v4](migration-to-ios-sdk-v4). ::: ## Активация модуля Adapty в SDK Adapty \{#activate-adapty-module-of-adapty-sdk\} Активируйте Adapty SDK в коде вашего приложения. :::note Adapty SDK достаточно активировать один раз в приложении. ::: Чтобы получить **Public SDK Key**: 1. Откройте дашборд Adapty и перейдите в [**App settings → General**](https://app.adapty.io/settings/general). 2. В разделе **Api keys** скопируйте **Public SDK Key** (НЕ Secret Key). 3. Замените `"YOUR_PUBLIC_SDK_KEY"` в коде. Или получите его программно с помощью [Adapty CLI](developer-cli): ``` npm install -g adapty adapty auth login adapty apps list ``` Или напрямую: ``` npx adapty auth login adapty apps list ``` - Убедитесь, что для инициализации Adapty вы используете **Public SDK key** — **Secret key** предназначен только для [серверного API](getting-started-with-server-side-api). - **SDK-ключи** уникальны для каждого приложения, поэтому если у вас несколько приложений, выберите нужный ключ. <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI"> ```swift showLineNumbers @main struct YourApp: App { init() { // Configure Adapty SDK let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") // Get from Adapty dashboard Adapty.logLevel = .verbose // recommended for development and the first production release let config = configurationBuilder.build() // Activate Adapty SDK asynchronously Task { do { try await Adapty.activate(with: config) } catch { // Handle error appropriately for your app print("Adapty activation failed: ", error) } } var body: some Scene { WindowGroup { // Your content view } } } } ``` </TabItem> <TabItem value="swift" label="UIKit" default> ```swift showLineNumbers // In your AppDelegate class: // If you only use an AppDelegate, place the following code in the // application(_:didFinishLaunchingWithOptions:) method. // If you use a SceneDelegate, place the following code in the // scene(_:willConnectTo:options:) method. Task { do { let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") // Get from Adapty dashboard .with(logLevel: .verbose) // recommended for development and the first production release let config = configurationBuilder.build() try await Adapty.activate(with: config) } catch { // Handle error appropriately for your app print("Adapty activation failed: ", error) } } ``` </TabItem> </Tabs> :::important Дождитесь завершения `activate` перед вызовом любых других методов Adapty SDK. Полная последовательность описана в [Порядок вызовов в iOS SDK](ios-sdk-call-order). ::: Теперь настройте пейволы в вашем приложении: - Если вы используете [Adapty Paywall Builder](adapty-paywall-builder), сначала [активируйте модуль AdaptyUI](#activate-adaptyui-module-of-adapty-sdk) ниже, а затем следуйте [быстрому старту с Paywall Builder](ios-quickstart-paywalls). - Если вы создаёте собственный UI пейвола, воспользуйтесь [быстрым стартом для кастомных пейволов](ios-quickstart-manual). ## Активация модуля AdaptyUI в Adapty SDK \{#activate-adaptyui-module-of-adapty-sdk\} Если вы планируете использовать [Paywall Builder](adapty-paywall-builder) и [установили модуль AdaptyUI](sdk-installation-ios#install-adapty-sdk), его также необходимо активировать. :::important В коде необходимо активировать основной модуль Adapty до активации AdaptyUI. ::: <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI"> ```swift showLineNumbers title="Swift" @main struct YourApp: App { init() { // ...ConfigurationBuilder steps // Activate Adapty SDK asynchronously Task { do { try await Adapty.activate(with: config) try await AdaptyUI.activate() } catch { // Handle error appropriately for your app print("Adapty activation failed: ", error) } } // main body... } } ``` </TabItem> <TabItem value="uikit" label="UIKit" default> ```swift showLineNumbers title="UIKit" // If you only use an AppDelegate, place the following code in the // application(_:didFinishLaunchingWithOptions:) method. // If you use a SceneDelegate, place the following code in the // scene(_:willConnectTo:options:) method. Task { do { let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") // Get from Adapty dashboard .with(logLevel: .verbose) // recommended for development let config = configurationBuilder.build() try await Adapty.activate(with: config) try await AdaptyUI.activate() } catch { // Handle error appropriately for your app print("Adapty activation failed: ", error) } } ``` </TabItem> </Tabs> :::tip При желании, активируя AdaptyUI, вы можете [переопределить настройки кэширования пейволов по умолчанию](#set-up-media-cache-configuration-for-adaptyui). ::: ## Дополнительная настройка \{#optional-setup\} ### Логирование \{#logging\} #### Настройка системы логирования \{#set-up-the-logging-system\} Adapty записывает ошибки и другую важную информацию, чтобы вы понимали, что происходит. Доступны следующие уровни логирования: | Level | Description | | ---------- | ------------------------------------------------------------ | | `error` | Будут логироваться только ошибки | | `warn` | Будут логироваться ошибки и сообщения от SDK, которые не вызывают критических ошибок, но заслуживают внимания | | `info` | Будут логироваться ошибки, предупреждения и различные информационные сообщения | | `verbose` | Будет логироваться любая дополнительная информация, которая может пригодиться при отладке: вызовы функций, API-запросы и т. д. | ```swift showLineNumbers let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(logLevel: .verbose) // recommended for development ``` #### Перенаправление сообщений системы логирования \{#redirect-the-logging-system-messages\} Если вам нужно отправлять лог-сообщения Adapty в вашу систему или сохранять их в файл, используйте метод `setLogHandler` и реализуйте в нём свою логику логирования. Этот обработчик получает записи логов, содержащие текст сообщения и уровень серьёзности. ```swift showLineNumbers title="Swift" Adapty.setLogHandler { record in writeToLocalFile("Adapty \(record.level): \(record.message)") } ``` ### Политики обработки данных \{#data-policies\} Adapty не хранит персональные данные пользователей, если вы явно их не передаёте, но вы можете настроить дополнительные политики безопасности данных для соответствия требованиям стора или законодательства конкретной страны. #### Отключение сбора и передачи IDFA \{#disable-idfa-collection-and-sharing\} При активации модуля Adapty установите `idfaCollectionDisabled` в значение `true`, чтобы отключить сбор и передачу IDFA. Используйте этот параметр, чтобы соответствовать требованиям App Store Review Guidelines или избежать появления запроса App Tracking Transparency, если IDFA не нужен вашему приложению. Значение по умолчанию — `false`. Подробнее о сборе IDFA читайте в разделе [Интеграция аналитики](analytics-integration#disable-collection-of-advertising-identifiers). ```swift showLineNumbers let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(idfaCollectionDisabled: true) ``` #### Отключение сбора и передачи IP-адресов \{#disable-ip-collection-and-sharing\} При активации модуля Adapty установите `ipAddressCollectionDisabled` в `true`, чтобы отключить сбор и передачу IP-адресов пользователей. Значение по умолчанию — `false`. Используйте этот параметр для защиты конфиденциальности пользователей, соблюдения региональных требований по защите данных (например, GDPR или CCPA) или сокращения лишнего сбора данных, если функции на основе IP не нужны вашему приложению. ```swift showLineNumbers let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(ipAddressCollectionDisabled: true) ``` #### Конфигурация кэша медиа для пейволов в AdaptyUI Обратите внимание, что конфигурация AdaptyUI является необязательной. Вы можете активировать модуль AdaptyUI без конфига. Однако если вы используете конфиг, все параметры обязательны. ```swift showLineNumbers title="Swift" // Configure AdaptyUI let adaptyUIConfiguration = AdaptyUI.Configuration( mediaCacheConfiguration: .init( memoryStorageTotalCostLimit: 100 * 1024 * 1024, memoryStorageCountLimit: .max, diskStorageSizeLimit: 100 * 1024 * 1024 ) ) // Activate AdaptyUI AdaptyUI.activate(configuration: adaptyUIConfiguration) ``` Параметры: | Параметр | Наличие | Описание | | :-------------------------- | :------- | :----------------------------------------------------------- | | memoryStorageTotalCostLimit | required | Общий лимит стоимости хранилища в байтах. | | memoryStorageCountLimit | required | Лимит количества элементов в памяти. | | diskStorageSizeLimit | required | Лимит размера файлов на диске в байтах. 0 означает отсутствие лимита. | ### Поведение завершения транзакций \{#transaction-finishing-behavior\} :::info Эта функция доступна начиная с версии SDK 3.12.0. ::: По умолчанию Adapty автоматически завершает транзакции после успешной валидации. Однако если вам нужна расширенная валидация транзакций (например, валидация чека на стороне сервера, защита от мошенничества или пользовательская бизнес-логика), вы можете настроить SDK для ручного завершения транзакций. ```swift showLineNumbers title="Swift" let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(transactionsFinishBehavior: .manual) // .auto is the default ``` Подробнее о завершении транзакций читайте в [гайде](ios-transaction-management). ### Очистка данных при восстановлении из резервной копии \{#clear-data-on-backup-restore\} Если `clearDataOnBackup` установлен в `true`, SDK обнаруживает восстановление приложения из резервной копии iCloud и удаляет все локально сохранённые данные SDK, включая кэшированную информацию профиля, данные продуктов и пейволы. После этого SDK инициализируется с чистым состоянием. Значение по умолчанию — `false`. :::note Удаляется только локальный кэш SDK. История транзакций в Apple и пользовательские данные на серверах Adapty остаются без изменений. ::: ```swift showLineNumbers let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") .with(clearDataOnBackup: true) // default – false ``` ## Устранение неполадок \{#troubleshooting\} #### Ошибка конкурентности Swift 6 при использовании Tuist \{#swift-6-concurrency-error-with-tuist\} При сборке с помощью [Tuist](https://tuist.dev/) могут возникать ошибки строгой конкурентности Swift 6. Типичные симптомы: несоответствие атрибута `@Sendable` в `AdaptyUIBuilderLogic` или аналогичные ошибки Sendability между модулями. Это происходит потому, что Tuist генерирует проекты Xcode из SPM-пакетов, но не сохраняет настройку `swift-tools-version: 6.0`. В результате некоторые таргеты Adapty (`Adapty`, `AdaptyUI`, `AdaptyUIBuilder`) компилируются по правилам Swift 5, тогда как другие используют Swift 6, что приводит к несовместимости `@Sendable` между модулями. **Исправление**: обновитесь до Adapty SDK **3.15.5** или более поздней версии — это решает проблему независимо от смешанных версий Swift. **Обходное решение**: если обновление невозможно, явно укажите Swift 6 для всех трёх таргетов Adapty в конфигурации Tuist: ```swift showLineNumbers targetSettings: [ "Adapty": .init().swiftVersion("6"), "AdaptyUI": .init().swiftVersion("6"), "AdaptyUIBuilder": .init().swiftVersion("6"), ] ``` --- # File: ios-quickstart-paywalls --- --- title: "Включение покупок с Flow Builder в iOS SDK" description: "Быстрый старт по включению встроенных покупок с Adapty Flow Builder." --- Чтобы включить встроенные покупки, нужно разобраться в трёх ключевых концепциях: - [**Продукты**](product) – всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ) - [**Флоу**](adapty-flow-builder) – последовательности экранов, которые показывают продукты пользователям, созданные в no-code Flow Builder. SDK получает их через `getFlow`. Если вы предпочитаете строить UI в собственном коде, используйте пейвол — см. [Реализация пейволов вручную](ios-quickstart-manual). - [**Плейсменты**](placements) – где и когда в приложении показываются флоу (например, `main`, `onboarding`, `settings`). Вы привязываете флоу к плейсментам в дашборде, а затем запрашиваете их по ID плейсмента в коде. Это упрощает проведение A/B-тестов и показ разных флоу разным пользователям. Adapty предлагает три способа подключить покупки в вашем приложении. Выберите подходящий в зависимости от требований: | Реализация | Сложность | Когда использовать | |---|---|---| | Adapty Flow Builder | ✅ Легко | Вы [создаёте полноценный флоу, готовый к покупкам, в no-code конструкторе](quickstart-paywalls). Adapty автоматически его отображает и берёт на себя весь процесс покупки, валидацию чеков и управление подписками. | | Пейволы, созданные вручную | 🟡 Средне | Вы реализуете UI пейвола в коде приложения, но всё равно получаете объект флоу от Adapty, сохраняя гибкость в управлении продуктами. См. [гайд](ios-quickstart-manual). | | Observer mode | 🔴 Сложно | У вас уже есть собственная инфраструктура обработки покупок, и вы хотите продолжать ею пользоваться. Обратите внимание, что observer mode имеет ряд ограничений в Adapty. См. [статью](observer-vs-full-mode). | :::important **Шаги ниже показывают, как реализовать флоу, созданный в Adapty Flow Builder.** Если вы предпочитаете создавать UI пейвола самостоятельно, см. [Реализация пейволов вручную](ios-quickstart-manual). ::: Чтобы отобразить флоу, созданный в Adapty Flow Builder, в коде вашего приложения нужно всего лишь: 1. **Получить флоу**: Запросить его из Adapty. 2. **Отобразить его — Adapty сам обработает покупки**: Показать представление в вашем приложении. 3. **Обработать действия кнопок**: Связать взаимодействия пользователя с реакцией приложения на них. Например, открывать ссылки или закрывать флоу при нажатии кнопок. ## Прежде чем начать \{#before-you-start\} Прежде чем начать, выполните следующие шаги: 1. [Подключите приложение к App Store](initial_ios) в дашборде Adapty. 2. [Создайте продукты](create-product) в Adapty. 3. [Создайте флоу и добавьте в него продукты](create-paywall). 4. [Создайте плейсмент и добавьте в него флоу](create-placement). 5. [Установите и активируйте SDK](sdk-installation-ios) в коде приложения. В этом гайде используются API Adapty iOS SDK v4. ## 1. Получите флоу \{#1-get-the-flow\} Ваши флоу привязаны к плейсментам, настроенным в дашборде. Плейсменты позволяют показывать разные флоу разным аудиториям или запускать [A/B-тесты](ab-tests). Чтобы получить флоу, созданный в Adapty Flow Builder, нужно: 1. Получить объект `flow` по ID [плейсмента](placements) с помощью метода `getFlow` и проверить, есть ли в нём конфигурация представления. 2. Получить конфигурацию представления с помощью метода `getFlowConfiguration`. Она содержит элементы UI и стили, необходимые для отображения флоу. ```swift func loadFlow() async { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") guard flow.hasViewConfiguration else { print("Flow doesn't have a view configuration") return } flowConfiguration = try await AdaptyUI.getFlowConfiguration(forFlow: flow) } ``` ## 2. Отобразите флоу \{#2-display-the-flow\} Теперь, когда у вас есть конфигурация флоу, достаточно добавить несколько строк, чтобы отобразить его. <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI" default> В SwiftUI при отображении флоу также нужно обрабатывать события. `didFinishPurchase`, `didFailPurchase`, `didFinishRestore`, `didFailRestore` и `didReceiveError` обязательны. При тестировании можно просто скопировать код из фрагмента ниже, чтобы логировать эти события. :::tip Флоу не закрывается автоматически после успешной покупки. В `didFinishPurchase` установите привязку отображения в `false`, чтобы закрыть его, или ничего не делайте, чтобы флоу перешёл к следующим экранам. ::: ```swift .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didFinishPurchase: { product, purchaseResult in print("Purchase finished successfully") flowPresented = false // or do nothing to let the flow continue }, didFailPurchase: { product, error in print("Purchase failed: \(error)") }, didFinishRestore: { profile in print("Restore finished successfully") }, didFailRestore: { error in print("Restore failed: \(error)") }, didReceiveError: { error in flowPresented = false print("Flow error: \(error)") } ) ``` </TabItem> <TabItem value="uikit" label="UIKit" default> ```swift func presentFlow(with config: AdaptyUI.FlowConfiguration) { let flowController = try AdaptyUI.flowController( with: config, delegate: self ) present(flowController, animated: true) } ``` Реализуйте `AdaptyFlowControllerDelegate` для обработки событий. Как минимум, реализуйте четыре метода, у которых нет реализации по умолчанию. Обратите внимание: контроллер не закрывается сам после успешной покупки — закройте его в `didFinishPurchase` или ничего не делайте, чтобы флоу перешёл к следующим экранам: ```swift extension YourViewController: AdaptyFlowControllerDelegate { func flowController(_ controller: AdaptyFlowController, didFinishPurchase product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult) { if !purchaseResult.isPurchaseCancelled { controller.dismiss(animated: true) // or do nothing to let the flow continue } } func flowController(_ controller: AdaptyFlowController, didFailPurchase product: AdaptyPaywallProduct, error: AdaptyError) { print("Purchase failed: \(error)") } func flowController(_ controller: AdaptyFlowController, didFinishRestoreWith profile: AdaptyProfile) { print("Restore finished successfully") } func flowController(_ controller: AdaptyFlowController, didFailRestoreWith error: AdaptyError) { print("Restore failed: \(error)") } } ``` </TabItem> </Tabs> :::info Подробнее о том, как отобразить флоу, см. наш [гайд](ios-present-paywalls). ::: ## 3. Обработка действий кнопок \{#handle-button-actions\} Когда пользователи нажимают кнопки, iOS SDK автоматически обрабатывает покупки, восстановление, закрытие флоу и переходы по ссылкам. Однако у других кнопок есть пользовательские или заранее заданные идентификаторы, и обработку их действий нужно реализовывать в коде. Кроме того, вы можете переопределить их поведение по умолчанию. Например, вот как обрабатывать кнопку закрытия. В UIKit SDK автоматически закрывает контроллер, когда срабатывает `.close` — переопределяйте это только если нужно нестандартное поведение. В SwiftUI вам нужно самостоятельно установить привязку `isPresented` в `false`. :::tip Читайте наши гайды о том, как обрабатывать [действия](handle-paywall-actions) кнопок и [события](ios-handling-events). ::: <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI" default> ```swift .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case .close: flowPresented = false // dismiss the flow when the user taps close default: break } }, didFinishPurchase: { product, purchaseResult in flowPresented = false }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false } ) ``` </TabItem> <TabItem value="uikit" label="UIKit" default> ```swift extension YourViewController: AdaptyFlowControllerDelegate { func flowController(_ controller: AdaptyFlowController, didPerform action: AdaptyUI.Action) { switch action { case .close: controller.dismiss(animated: true) // default behavior — override only if needed default: break } } } ``` </TabItem> </Tabs> ## Следующие шаги \{#next-steps\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Ваш пейвол готов к отображению в приложении. [Протестируйте покупки в режиме песочницы](test-purchases-in-sandbox), чтобы убедиться, что тестовая покупка через пейвол проходит успешно. Теперь нужно [проверить уровень доступа пользователей](ios-check-subscription-status), чтобы показывать пейвол или предоставлять доступ к платным функциям только нужным пользователям. ## Полный пример \{#full-example\} Вот как все шаги из этого гайда можно объединить в вашем приложении. <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI" default> ```swift struct ContentView: View { @State private var flowPresented = false @State private var flowConfiguration: AdaptyUI.FlowConfiguration? @State private var isLoading = false @State private var hasInitialized = false var body: some View { VStack { if isLoading { ProgressView("Loading...") } else { Text("Your App Content") } } .task { guard !hasInitialized else { return } await initializeFlow() hasInitialized = true } .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case .close: flowPresented = false default: break } }, didFinishPurchase: { product, purchaseResult in print("Purchase finished successfully") flowPresented = false // or do nothing to let the flow continue }, didFailPurchase: { product, error in print("Purchase failed: \(error)") }, didFinishRestore: { profile in print("Restore finished successfully") }, didFailRestore: { error in print("Restore failed: \(error)") }, didReceiveError: { error in print("Flow error: \(error)") flowPresented = false } ) } private func initializeFlow() async { isLoading = true defer { isLoading = false } await loadFlow() flowPresented = true } private func loadFlow() async { do { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") guard flow.hasViewConfiguration else { print("Flow doesn't have a view configuration") return } flowConfiguration = try await AdaptyUI.getFlowConfiguration(forFlow: flow) } catch { print("Failed to load: \(error)") } } } ``` </TabItem> <TabItem value="uikit" label="UIKit" default> ```swift class ViewController: UIViewController { private var flowConfiguration: AdaptyUI.FlowConfiguration? override func viewDidLoad() { super.viewDidLoad() Task { await initializeFlow() } } private func initializeFlow() async { do { flowConfiguration = try await loadFlow() if let flowConfiguration { await MainActor.run { presentFlow(with: flowConfiguration) } } } catch { print("Error initializing: \(error)") } } private func loadFlow() async throws -> AdaptyUI.FlowConfiguration? { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") guard flow.hasViewConfiguration else { print("Flow doesn't have a view configuration") return nil } return try await AdaptyUI.getFlowConfiguration(forFlow: flow) } private func presentFlow(with config: AdaptyUI.FlowConfiguration) { guard let flowController = try? AdaptyUI.flowController( with: config, delegate: self ) else { return } present(flowController, animated: true) } } extension ViewController: AdaptyFlowControllerDelegate { func flowController(_ controller: AdaptyFlowController, didFinishPurchase product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult) { if !purchaseResult.isPurchaseCancelled { controller.dismiss(animated: true) // or do nothing to let the flow continue } } func flowController(_ controller: AdaptyFlowController, didFailPurchase product: AdaptyPaywallProduct, error: AdaptyError) { print("Purchase failed for \(product.vendorProductId): \(error)") guard error.adaptyErrorCode != .paymentCancelled else { return } let message = switch error.adaptyErrorCode { case .paymentNotAllowed: "Purchases are not allowed on this device." default: "Purchase failed. Please try again." } let alert = UIAlertController(title: "Purchase Error", message: message, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default)) present(alert, animated: true) } func flowController(_ controller: AdaptyFlowController, didFinishRestoreWith profile: AdaptyProfile) { print("Restore finished successfully") controller.dismiss(animated: true) } func flowController(_ controller: AdaptyFlowController, didFailRestoreWith error: AdaptyError) { print("Restore failed: \(error)") } func flowController(_ controller: AdaptyFlowController, didReceiveError error: AdaptyUIError) { print("Flow error: \(error)") controller.dismiss(animated: true) } } ``` </TabItem> </Tabs> --- # File: ios-check-subscription-status --- --- title: "Проверка статуса подписки в iOS SDK" description: "Узнайте, как проверять статус подписки в iOS-приложении с помощью Adapty." --- Чтобы решить, открыть ли пользователям доступ к платному контенту или показать пейвол, нужно проверить их [уровень доступа](access-level) в профиле. В этой статье показано, как читать состояние профиля и принимать решение: показывать пейвол или давать доступ к платным функциям. ## Получение статуса подписки \{#get-subscription-status\} Когда нужно решить, показать пейвол или платный контент, проверяется [уровень доступа](access-level) в профиле пользователя. Есть два варианта: - Вызвать `getProfile`, если нужны актуальные данные прямо сейчас (например, при запуске приложения) или принудительно обновить профиль. - Настроить **автоматические обновления профиля**, чтобы хранить локальную копию, которая автоматически обновляется при изменении статуса подписки. :::important По умолчанию в Adapty уже существует уровень доступа `premium`. Если вам не нужно больше одного уровня доступа, можно просто использовать `premium`. ::: ### Получить профиль \{#get-profile\} Самый простой способ получить статус подписки — вызвать метод `getProfile`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let profile = try await Adapty.getProfile() if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.getProfile { result in if let profile = try? result.get() { // check the access profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } } ``` </TabItem> </Tabs> ### Отслеживание обновлений подписки \{#listen-to-subscription-updates\} Чтобы автоматически получать обновления профиля в приложении: 1. Реализуйте протокол `AdaptyDelegate` в удобном для вас типе и добавьте метод `didLoadLatestProfile` — Adapty будет автоматически вызывать его при каждом изменении статуса подписки пользователя. В примере ниже используется тип `SubscriptionManager`, который берёт на себя работу с подписками и профилем пользователя. Его можно внедрить как зависимость, создать как синглтон в UIKit-приложении или добавить в окружение SwiftUI из главной структуры приложения. 2. Сохраняйте обновлённые данные профиля при каждом вызове этого метода, чтобы использовать их в приложении без лишних сетевых запросов. ```swift class SubscriptionManager: AdaptyDelegate { nonisolated func didLoadLatestProfile(_ profile: AdaptyProfile) { let hasAccess = profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false // Update UI, unlock content, etc. } } // Set delegate after Adapty activation Adapty.delegate = subscriptionManager ``` :::note Adapty автоматически вызывает `didLoadLatestProfile` при запуске приложения, предоставляя кешированные данные о подписке даже при отсутствии интернета. ::: ## Связь профиля с логикой пейвола \{#connect-profile-with-paywall-logic\} Когда нужно сразу принять решение — показать пейвол или открыть доступ к платным функциям, — можно напрямую проверить профиль пользователя. Этот подход удобен при запуске приложения, входе в разделы с премиум-контентом или перед отображением определённого контента. <Tabs> <TabItem value="swiftui" label="SwiftUI" default> ```swift private func checkAccessLevel() async -> Bool { do { let profile = try await Adapty.getProfile() return profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false } catch { print("Error checking access level: \(error)") return false } } // In your initialization logic: let hasAccess = await checkAccessLevel() if !hasAccess { paywallPresented = true // Show paywall if no access } ``` </TabItem> <TabItem value="uikit" label="UIKit"> ```swift private func checkAccessLevel() async throws -> Bool { let profile = try await Adapty.getProfile() return profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false } // In your initialization logic: let hasAccess = try await checkAccessLevel() if !hasAccess { presentPaywall(with: paywallConfiguration) } ``` </TabItem> </Tabs> ## Следующие шаги \{#next-steps\} Теперь, когда вы знаете, как отслеживать статус подписки, [изучите работу с профилями пользователей](ios-quickstart-identify), чтобы правильно связать их с вашей системой аутентификации и настройками совместного доступа к платным функциям. Если у вас нет собственной системы аутентификации — ничего страшного, Adapty сам управляет пользователями. Но вы всё равно можете прочитать [гайд](ios-quickstart-identify), чтобы узнать, как Adapty работает с анонимными пользователями. --- # File: ios-quickstart-identify --- --- title: "Идентификация пользователей в iOS SDK" description: "Быстрый старт по настройке Adapty для управления встроенными покупками." --- :::important Этот гайд для вас, если у вас есть собственная система аутентификации. Здесь вы узнаете, как работать с профилями пользователей в Adapty, чтобы они соответствовали вашей существующей системе аутентификации. ::: То, как вы управляете покупками пользователей, зависит от модели аутентификации в вашем приложении: - Если приложение не использует бэкенд-аутентификацию и не хранит данные пользователей, смотрите [раздел об анонимных пользователях](#anonymous-users). - Если в приложении есть (или планируется) бэкенд-аутентификация, смотрите [раздел об идентифицированных пользователях](#identified-users). **Ключевые понятия**: - **Профили** — сущности, необходимые для работы SDK. Adapty создаёт их автоматически. - Они могут быть анонимными **(без customer user ID)** или идентифицированными **(с customer user ID)**. - Вы передаёте **customer user ID**, чтобы связать профили в Adapty с вашей внутренней системой авторизации. Вот чем отличаются анонимные и идентифицированные пользователи: | | Анонимные пользователи | Идентифицированные пользователи | |-------------------------|----------------------------------------------------------------|------------------------------------------------------------------------------------------------------| | **Управление покупками** | Восстановление покупок на уровне стора | История покупок сохраняется на всех устройствах через customer user ID | | **Управление профилем** | Новый профиль при каждой переустановке | Один и тот же профиль во всех сессиях и на всех устройствах | | **Хранение данных** | Данные анонимных пользователей привязаны к установке приложения | Данные идентифицированных пользователей сохраняются между установками приложения | ## Анонимные пользователи \{#anonymous-users\} Если у вас нет серверной аутентификации, **вам не нужно обрабатывать аутентификацию в коде приложения**: 1. Когда SDK активируется при первом запуске приложения, Adapty **создаёт новый профиль для пользователя**. 2. Когда пользователь совершает покупку в приложении, она **привязывается к его профилю в Adapty и к его аккаунту в сторе**. 3. Когда пользователь **переустанавливает** приложение или устанавливает его на **новом устройстве**, Adapty **создаёт новый анонимный профиль при активации**. 4. Если пользователь ранее совершал покупки в вашем приложении, по умолчанию они автоматически синхронизируются из App Store при активации SDK. :::note Восстановление из резервной копии работает иначе, чем переустановка. По умолчанию при восстановлении из бэкапа SDK сохраняет кэшированные данные и не создаёт новый профиль. Это поведение можно настроить с помощью параметра `clearDataOnBackup`. [Подробнее](sdk-installation-ios#clear-data-on-backup-restore). ::: Таким образом, для анонимных пользователей при каждой установке будет создаваться новый профиль — но это не проблема, поскольку в аналитике Adapty можно [настроить, что считать новой установкой](general#4-installs-definition-for-analytics). Для анонимных пользователей установки считаются по **идентификаторам устройств**. При этом каждая установка приложения на устройство считается отдельной установкой, включая повторные. ## Идентифицированные пользователи \{#identified-users\} У вас есть два способа идентифицировать пользователей в приложении: - [**При входе/регистрации:**](#during-loginsignup) Если пользователи входят в систему после запуска приложения, вызовите `identify()` с идентификатором пользователя в момент аутентификации. - [**При активации SDK:**](#during-the-sdk-activation) Если идентификатор пользователя уже сохранён на момент запуска приложения, передайте его при вызове `activate()`. :::important По умолчанию, когда Adapty получает покупку от Customer User ID, который в данный момент привязан к другому Customer User ID, уровень доступа становится общим — оба профиля получают платный доступ. Вы можете настроить это поведение так, чтобы платный доступ передавался от одного профиля к другому, или полностью отключить совместное использование. Подробнее см. в [статье](general#6-sharing-paid-access-between-user-accounts). ::: <img src="/assets/shared/img/identify-diagram.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### При входе в систему или регистрации \{#during-loginsignup\} Если вы идентифицируете пользователей уже после запуска приложения (например, после входа в аккаунт или регистрации), используйте метод `identify`, чтобы задать их customer user ID. - Если вы **раньше не использовали этот customer user ID**, Adapty автоматически привяжет его к текущему профилю. - Если вы **уже использовали этот customer user ID для идентификации пользователя**, Adapty переключится на работу с профилем, связанным с этим customer user ID. :::important Идентификаторы пользователей должны быть уникальными для каждого пользователя. Если задать значение параметра жёстко в коде, все пользователи будут считаться одним. ::: Всегда используйте `await` для `identify` перед вызовом других методов SDK. Параллельные вызовы приводят к ошибке `#3006 profileWasChanged` или попаданию в анонимный профиль. Подробнее: [Порядок вызовов в iOS SDK](ios-sdk-call-order). <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { try await Adapty.identify("YOUR_USER_ID") // Уникальный для каждого пользователя } catch { // обработайте ошибку } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers // Идентификаторы пользователей должны быть уникальными для каждого пользователя Adapty.identify("YOUR_USER_ID") { error in if let error { // обработайте ошибку } } ``` </TabItem> </Tabs> ### При активации SDK \{#during-the-sdk-activation\} Если вы знаете customer user ID в момент активации SDK, его можно передать прямо в методе `activate`, не вызывая `identify` отдельно. Если вы знаете customer user ID, но задаёте его только после активации, то при активации Adapty создаст новый анонимный профиль и переключится на существующий лишь после того, как вы вызовете `identify`. Вы можете передать как существующий customer user ID (тот, что использовался ранее), так и новый. Если передать новый, профиль, созданный при активации, автоматически привяжется к этому customer user ID. :::note По умолчанию создание анонимных профилей не влияет на аналитические дашборды, поскольку установки считаются на основе идентификаторов устройств. Идентификатор устройства соответствует одной установке приложения из стора на конкретном устройстве и обновляется только при переустановке приложения. Он не зависит от того, первая это установка или повторная, и используется ли существующий пользовательский ID. Создание профиля (при активации SDK или выходе из аккаунта), вход в систему или обновление приложения без переустановки не генерируют дополнительных событий установки. Если вы хотите считать установки по уникальным пользователям, а не устройствам, перейдите в **App settings** и настройте [**Installs definition for analytics**](general#4-installs-definition-for-analytics). ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers // Place in the app main struct for SwiftUI or in AppDelegate for UIKit let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. do { try await Adapty.activate(with: configurationBuilder.build()) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers // Place in the app main struct for SwiftUI or in AppDelegate for UIKit let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. Adapty.activate(with: configurationBuilder.build()) { error in // handle the error } ``` </TabItem> </Tabs> ### Выход пользователей из системы \{#log-users-out\} Если в вашем приложении есть кнопка выхода, используйте метод `logout`. :::important При выходе из системы для пользователя создаётся новый анонимный профиль. ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { try await Adapty.logout() } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.logout { error in if error == nil { // successful logout } } ``` </TabItem> </Tabs> :::info Чтобы повторно авторизовать пользователей в приложении, используйте метод `identify`. ::: ### Разрешение покупок без авторизации \{#allow-purchases-without-login\} Если пользователи могут совершать покупки как до, так и после входа в аккаунт, необходимо убедиться, что после авторизации они сохранят доступ к приобретённому контенту: 1. Когда пользователь без аккаунта совершает покупку, Adapty привязывает её к анонимному ID профиля. 2. Когда пользователь входит в аккаунт, Adapty переключается на работу с его идентифицированным профилем. - Если customer user ID новый (например, покупка была совершена до регистрации), Adapty присваивает этот customer user ID текущему профилю, сохраняя всю историю покупок. - Если customer user ID уже существует (он уже привязан к другому профилю), нужно получить актуальный уровень доступа после переключения профиля. Можно вызвать [`getProfile`](ios-check-subscription-status) сразу после идентификации или [подписаться на обновления профиля](ios-check-subscription-status), чтобы данные синхронизировались автоматически. ## Следующие шаги \{#next-steps\} Поздравляем! Вы реализовали логику встроенных покупок в своём приложении! Желаем вам успехов в монетизации! Чтобы получить от Adapty ещё больше, изучите эти темы: - [**Тестирование**](test-purchases-in-sandbox): убедитесь, что всё работает как ожидается - [**Онбординги**](ios-onboardings): вовлекайте пользователей с помощью онбордингов и повышайте удержание - [**Интеграции**](configuration): интегрируйтесь с сервисами маркетинговой атрибуции и аналитики буквально в одну строку кода - [**Установка пользовательских атрибутов профиля**](setting-user-attributes): добавляйте пользовательские атрибуты к профилям, создавайте сегменты и запускайте A/B-тесты или показывайте разные пейволы разным пользователям --- # File: adapty-sdk-integration-skill --- --- title: "Интеграция Adapty в iOS-приложение с помощью навыка SDK Integration" description: "Используйте навык adapty-sdk-integration для полной интеграции Adapty SDK в ваше iOS-приложение с помощью AI-инструмента для разработки." --- <AdaptySdkIntegrationSkill platform="iOS" /> :::important Навык находится в бета-версии. Если он зависает или ведёт себя непредсказуемо, воспользуйтесь [пошаговым руководством по интеграции](adapty-cursor) — оно проведёт ваш AI-инструмент через каждый этап с нужной документацией. ::: [Скилл adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) автоматизирует интеграцию Adapty от начала до конца: настройку дашборда, установку SDK, пейвол и проверку на каждом этапе. Он автоматически определяет вашу платформу и подгружает нужную документацию Adapty на каждом шаге. **Поддерживаемые инструменты**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Для установки выберите команду для своего инструмента. Полный список — в [README скилла](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex или любой другой инструмент** — используйте [skills CLI](https://skills.sh) (обратите внимание, что скиллы, установленные таким способом, не обновляются автоматически): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` Либо клонируйте репозиторий и скопируйте `skills/adapty-sdk-integration/` в директорию скиллов вашего инструмента. После установки запустите скилл в вашем проекте: ``` /adapty-sdk-integration ``` Скилл задаст несколько вопросов по настройке, а затем проведёт через настройку дашборда, установку SDK, пейвол и проверку. --- # File: adapty-cursor --- --- title: "Интеграция Adapty в iOS-приложение с помощью ИИ" description: "Пошаговое руководство по интеграции Adapty в ваше iOS-приложение с использованием Cursor, Context7, ChatGPT, Claude или других ИИ-инструментов." --- Этот гайд проведёт вас через интеграцию Adapty в iOS-приложение шаг за шагом с помощью AI-инструмента для написания кода — вы подаёте ему нужную документацию Adapty в правильном порядке. For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## Перед началом: настройка дашборда \{#before-you-start-dashboard-setup\} Прежде чем писать код SDK, нужно настроить дашборд Adapty. Это можно сделать с помощью интерактивного LLM-навыка или вручную через дашборд. ### Подход через skill (рекомендуется) \{#skill-approach-recommended\} Skill Adapty CLI позволяет вашему LLM настроить приложение, продукты, уровни доступа, пейволы и плейсменты напрямую — без необходимости открывать дашборд на каждом шаге. Нужно лишь [подключить стор](integrate-payments) в дашборде. ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` После добавления skill запустите `/adapty-cli` в вашем агенте. Он проведёт вас через каждый шаг — включая момент, когда нужно открыть дашборд для подключения стора. ### Подход через дашборд Если вы предпочитаете настраивать всё вручную, вот что нужно подготовить перед написанием кода. LLM не может самостоятельно получить значения из дашборда — их придётся указать самостоятельно. 1. **Подключите стор**: В дашборде Adapty перейдите в **App settings → General**. Это обязательный шаг для работы покупок. [Подключить App Store](integrate-payments) 2. **Скопируйте публичный SDK-ключ**: В дашборде Adapty перейдите в **App settings → General** и найдите раздел **API keys**. В коде это строка, которую вы передаёте в `Adapty.activate("YOUR_PUBLIC_SDK_KEY")`. 3. **Создайте хотя бы один продукт**: В дашборде Adapty перейдите на страницу **Products**. Ссылаться на продукты напрямую в коде не нужно — Adapty передаёт их через флоу или пейволы. [Добавить продукты](quickstart-products) 4. **Создайте флоу или пейвол и плейсмент**: В дашборде Adapty создайте флоу (или пейвол, если UI будете строить самостоятельно), а затем привяжите его к плейсменту на странице **Placements**. В коде идентификатор плейсмента — это строка, которую вы передаёте в `Adapty.getFlow("YOUR_PLACEMENT_ID")`. [Создать флоу](quickstart-paywalls) 5. **Настройте уровни доступа**: в дашборде Adapty настройте каждый продукт на странице **Products**. В коде проверяйте строку `profile.accessLevels["premium"]`. Уровень доступа `premium` по умолчанию подходит большинству приложений. Если платные пользователи получают доступ к разным функциям в зависимости от продукта (например, план `basic` и план `pro`), [создайте дополнительные уровни доступа](assigning-access-level-to-a-product) до начала разработки. :::tip Как только у вас есть все пять, можно писать код. Скажите LLM: «Мой Public SDK key — X, мой placement ID — Y», и он сгенерирует правильный код инициализации и получения пейвола. ::: ### Настройте, когда будете готовы \{#set-up-when-ready\} Это не обязательно для начала разработки, но понадобится по мере развития интеграции: - **A/B-тесты**: Настройте на странице **Placements**. Изменения кода не требуются. [A/B-тесты](ab-tests) - **Дополнительные флоу и плейсменты**: Добавьте больше вызовов `getFlow` с разными идентификаторами плейсментов. - **Аналитические интеграции**: Настройте на странице **Integrations**. Настройка зависит от интеграции. См. [аналитические интеграции](analytics-integration) и [интеграции атрибуции](attribution-integration). ## Передайте документацию Adapty вашему LLM \{#feed-adapty-docs-to-your-llm\} ### Используйте Context7 (рекомендуется) \{#use-context7-recommended\} [Context7](https://context7.com) — MCP-сервер, который даёт вашей языковой модели прямой доступ к актуальной документации Adapty. Модель сама подтягивает нужные документы на основе вашего запроса — никаких ссылок вставлять вручную не нужно. Context7 работает с **Cursor**, **Claude Code**, **Windsurf** и другими MCP-совместимыми инструментами. Чтобы настроить его, выполните: ``` npx ctx7 setup ``` Команда определит ваш редактор и настроит сервер Context7. Для ручной настройки см. [репозиторий Context7 на GitHub](https://github.com/upstash/context7). После настройки используйте библиотеку Adapty в своих запросах: ``` Use the adaptyteam/adapty-docs library to look up how to install the iOS SDK ``` :::warning Несмотря на то что Context7 избавляет от необходимости вручную вставлять ссылки на документацию, порядок реализации важен. Следуйте [пошаговому руководству](#implementation-walkthrough) ниже, чтобы всё заработало корректно. ::: ### Используйте документацию в текстовом формате Вы можете получить любую статью Adapty в формате Markdown. Добавьте `.md` к концу URL или нажмите **Copy for LLM** под заголовком статьи. Например: [adapty-cursor.md](https://adapty.io/docs/ru/adapty-cursor.md). Каждый шаг [пошагового руководства по интеграции](#implementation-walkthrough) ниже содержит блок «Отправьте это в свой LLM» со ссылками `.md` для вставки. Чтобы получить несколько статей сразу, смотрите [индексные файлы и платформенные подборки](#plain-text-doc-index-files) ниже. ## Пошаговая реализация \{#implementation-walkthrough\} В этом гайде мы разберём интеграцию Adapty в порядке реализации. Для каждого этапа указаны документы, которые нужно передать LLM, ожидаемый результат и типичные проблемы. ### Планирование интеграции \{#plan-your-integration\} Прежде чем переходить к коду, попросите LLM проанализировать ваш проект и составить план реализации. Если ваш AI-инструмент поддерживает режим планирования (например, plan mode в Cursor или Claude Code), используйте его — так LLM сможет изучить структуру вашего проекта и документацию Adapty перед тем, как начать писать код. Укажите LLM, какой подход к покупкам вы используете — от этого зависит, какими гайдами он должен руководствоваться: - [**Adapty Flow Builder**](adapty-flow-builder): Вы создаёте флоу в no-code конструкторе Adapty, а SDK отображает их автоматически. - [**Пейволы, созданные вручную**](ios-quickstart-manual): Вы сами строите UI пейвола в коде, но используете Adapty для получения продуктов и обработки покупок. - [**Режим наблюдателя**](observer-vs-full-mode): Вы сохраняете существующую инфраструктуру покупок и используете Adapty только для аналитики и интеграций. Не знаете, что выбрать? Прочитайте [сравнительную таблицу в quickstart](ios-quickstart-paywalls). ### Установите и настройте SDK \{#install-and-configure-the-sdk\} Установите пакет Adapty SDK через Swift Package Manager в Xcode и активируйте его с помощью вашего публичного ключа SDK. Это основа — без неё всё остальное не работает. **Гайд:** [Установка и настройка Adapty SDK](sdk-installation-ios) Отправьте это в ваш LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/sdk-installation-ios.md ``` :::tip[Контрольная точка] - **Ожидаемый результат:** приложение собирается и запускается. В консоли Xcode отображается лог активации Adapty. - **Частая ошибка:** «Public API key is missing» → убедитесь, что вы заменили плейсхолдер на реальный ключ из **App settings**. ::: ### Показ флоу или пейволов и обработка покупок \{#show-flows-or-paywalls-and-handle-purchases\} Получите флоу или пейвол по идентификатору плейсмента, отобразите его и обрабатывайте события покупок. Нужные вам гайды зависят от того, как вы обрабатываете покупки. Тестируйте каждую покупку в песочнице по мере разработки — не откладывайте на конец. Инструкции по настройке см. в [Тестирование покупок в песочнице](test-purchases-in-sandbox). <Tabs groupId="paywall-approach"> <TabItem value="builder" label="Flow Builder" default> **Гайды:** - [Включить покупки с помощью Flow Builder (быстрый старт)](ios-quickstart-paywalls) - [Получить флоу и их конфигурацию](get-pb-paywalls) - [Отобразить флоу](ios-present-paywalls) - [Обработать события флоу](ios-handling-events) - [Реагировать на действия кнопок](handle-paywall-actions) Отправьте это своей LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/ios-quickstart-paywalls.md - https://adapty.io/docs/ru/get-pb-paywalls.md - https://adapty.io/docs/ru/ios-present-paywalls.md - https://adapty.io/docs/ru/ios-handling-events.md - https://adapty.io/docs/ru/handle-paywall-actions.md ``` :::tip[Контрольная точка] - **Ожидаемый результат:** Флоу отображается с настроенными продуктами. Нажатие на продукт вызывает диалог покупки в песочнице. - **Частая ошибка:** Пустой флоу или ошибка `getFlow` → убедитесь, что ID плейсмента точно совпадает с дашбордом и к плейсменту привязана аудитория. ::: </TabItem> <TabItem value="manual" label="Пейволы вручную"> **Гайды:** - [Включить покупки в пользовательском пейволе (быстрый старт)](ios-quickstart-manual) - [Получить пейволы и продукты](fetch-paywalls-and-products) - [Отобразить пейвол, созданный с помощью Remote Config](present-remote-config-paywalls) - [Совершить покупки](making-purchases) - [Восстановить покупки](restore-purchase) Это сообщение предназначено для передачи вашей языковой модели, а не для перевода. Пожалуйста, предоставьте MDX-документацию на английском языке, которую нужно перевести на русский. :::tip[Checkpoint] - **Ожидаемый результат:** Ваш пользовательский пейвол отображает продукты, полученные из Adapty. При нажатии на продукт открывается диалог покупки в песочнице. - **Частая ошибка:** Пустой массив продуктов → убедитесь, что в дашборде пейволу назначены продукты и у плейсмента задана аудитория. ::: </TabItem> <TabItem value="observer" label="Observer mode"> **Гайды:** - [Обзор Observer mode](observer-vs-full-mode) - [Реализация Observer mode](implement-observer-mode) - [Регистрация транзакций в Observer mode](report-transactions-observer-mode) Отправьте это своему LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/observer-vs-full-mode.md - https://adapty.io/docs/ru/implement-observer-mode.md - https://adapty.io/docs/ru/report-transactions-observer-mode.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После тестовой покупки в песочнице через ваш существующий флоу покупки транзакция появляется в **Event Feed** дашборда Adapty. - **Частая ошибка:** Нет событий → убедитесь, что вы передаёте транзакции в Adapty и настроены уведомления App Store Server Notifications. ::: </TabItem> </Tabs> ### Проверка статуса подписки \{#check-subscription-status\} После покупки проверьте профиль пользователя на наличие активного уровня доступа, чтобы открыть доступ к премиум-контенту. **Гайд:** [Проверка статуса подписки](ios-check-subscription-status) Отправьте это в ваш LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/ios-check-subscription-status.md ``` :::tip[Контрольная точка] - **Ожидаемый результат:** После покупки в песочнице `profile.accessLevels["premium"]?.isActive` возвращает `true`. - **Частая ошибка:** Пустой `accessLevels` после покупки → проверьте, что продукту назначен уровень доступа на дашборде. ::: ### Идентифицируйте пользователей \{#identify-users\} Свяжите аккаунты пользователей вашего приложения с профилями Adapty, чтобы покупки сохранялись на всех устройствах. :::important Пропустите этот шаг, если в вашем приложении нет аутентификации. ::: **Гайд:** [Идентифицируйте пользователей](ios-quickstart-identify) Отправьте это в ваш LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/ios-quickstart-identify.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После вызова `Adapty.identify("your-user-id")` в разделе **Profiles** дашборда отображается ваш пользовательский ID. - **Важно:** Вызывайте `identify` после активации, но до загрузки пейволов, чтобы избежать атрибуции к анонимному профилю. ::: ### Подготовка к релизу \{#prepare-for-release\} Когда интеграция заработает в песочнице, пройдитесь по чеклисту релиза и убедитесь, что всё готово к продакшену. **Гайд:** [Чеклист релиза](release-checklist) Отправьте это в свой LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/ru/release-checklist.md ``` :::tip[Checkpoint] - **Ожидается:** Все пункты чеклиста подтверждены: подключение стора, серверные уведомления, процесс покупки, проверки уровня доступа и требования к конфиденциальности. - **Частая ошибка:** Не настроены App Store Server Notifications → настройте их в **App settings → iOS SDK**, иначе события не будут появляться в дашборде. ::: ## Текстовые индексные файлы документации \{#plain-text-doc-index-files\} Если вам нужно дать LLM более широкий контекст помимо отдельных страниц, мы размещаем индексные файлы, которые перечисляют или объединяют всю документацию Adapty: - [`llms.txt`](https://adapty.io/docs/ru/llms.txt): Список всех страниц со ссылками `.md`. [Формирующийся стандарт](https://llmstxt.org/) для обеспечения доступности сайтов для LLM. Обратите внимание, что для некоторых ИИ-агентов (например, ChatGPT) потребуется скачать `llms.txt` и загрузить его в чат в виде файла. - [`llms-full.txt`](https://adapty.io/docs/ru/llms-full.txt): Вся документация Adapty, объединённая в один файл. Очень большой — используйте только тогда, когда нужна полная картина. - iOS-специфичные [`ios-llms.txt`](https://adapty.io/docs/ru/ios-llms.txt) и [`ios-llms-full.txt`](https://adapty.io/docs/ru/ios-llms-full.txt): Подмножества, специфичные для платформы, которые экономят токены по сравнению с полным сайтом. --- # File: get-pb-paywalls --- --- title: "Получение флоу и пейволов — iOS" description: "Получайте флоу и пейволы из Adapty в вашем iOS-приложении." --- <SDKv4> <MethodPromo method="getFlow" /> После того как вы [разработали флоу или пейвол в Paywall Builder](adapty-paywall-builder), можно показывать его в мобильном приложении. Первый шаг — получить флоу или пейвол, связанный с плейсментом, и его конфигурацию отображения, как описано ниже. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Ознакомьтесь с нашими [примерами приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Перед началом работы</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте флоу/пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них флоу/пейвол](create-placement) в дашборде Adapty. 4. Установите [SDK Adapty](sdk-installation-ios) в своё мобильное приложение. </details> ## Получение флоу/пейвола \{#fetch-flowpaywall\} Если вы создали флоу или пейвол с помощью Flow Builder или Paywall Builder, вам не нужно беспокоиться о том, как его отрендерить в коде мобильного приложения. Такой флоу или пейвол уже содержит всё необходимое: что показывать и как показывать. Тем не менее вам нужно получить его ID через плейсмент, загрузить конфигурацию отображения и затем показать его в мобильном приложении. Получайте флоу или пейвол и его [конфигурацию отображения](get-pb-paywalls#fetch-the-view-configuration) как можно раньше — в идеале задолго до показа. Как только конфигурация получена, SDK начинает загружать и кэшировать изображения в фоне. Чем раньше вы её запросите, тем больше времени будет для завершения загрузок. К моменту показа флоу или пейвола его конфигурация и изображения могут уже быть закэшированы и готовы к отображению. Чтобы получить флоу или пейвол, используйте метод `getFlow`: <Tabs> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") // запрошенный флоу/пейвол } catch { // обработка ошибки } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(flow): // запрошенный флоу/пейвол case let .failure(error): // обработка ошибки } } ``` </TabItem> </Tabs> Параметры: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают самые актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильный интернет, рассмотрите использование `.returnCacheDataElseLoad` — оно возвращает кешированные данные, если они есть. В этом случае пользователи могут не получить самые последние данные, но загрузка будет быстрее независимо от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при переустановке или вручную.</p><p></p><p>Adapty SDK хранит пейволы локально на двух уровнях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](fallback-paywalls). Мы также используем CDN для более быстрой загрузки пейволов и отдельный резервный сервер на случай недоступности CDN. Система разработана так, чтобы вы всегда получали актуальную версию пейволов, даже при нестабильном интернете.</p> | | **loadTimeout** | по умолчанию: 5 сек | <p>Это значение ограничивает таймаут метода. Если таймаут истёк, будут возвращены кешированные данные или локальный резервный пейвол.</p><p>Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения, указанного в `loadTimeout`, поскольку операция может состоять из нескольких внутренних запросов.</p> | Параметры ответа: | Параметр | Описание | | :-------- | :---------- | | Flow | Объект `AdaptyFlow`, содержащий плейсмент, идентификаторы (`id`, `variationId`), имя, Remote Config и флаг `hasViewConfiguration`, указывающий, включает ли флоу конфигурацию представления. Чтобы получить продукты для предзагрузки, кастомного UI или программных проверок, вызовите `getPaywallProducts(flow:)`. | ## Получение конфигурации представления \{#fetch-the-view-configuration\} После получения флоу или пейвола проверьте, включает ли он конфигурацию представления, с помощью `flow.hasViewConfiguration`. Этот флаг показывает, как был создан плейсмент в дашборде Adapty: - **`true`** — плейсмент создан во **Flow Builder** (флоу) или **Paywall Builder** (пейвол). Adapty отрисовывает интерфейс за вас. Продолжайте выполнять шаги ниже, чтобы получить конфигурацию представления и [показать флоу или пейвол](ios-present-paywalls). - **`false`** — плейсмент является кастомным пейволом без интерфейса Builder. Используйте метод `getFlowConfiguration`, чтобы загрузить конфигурацию представления. ```swift showLineNumbers guard flow.hasViewConfiguration else { // handle as remote config paywall return } let flowConfiguration = try await AdaptyUI.getFlowConfiguration(forFlow: flow) ``` Параметры: | Параметр | Наличие | Описание | | :----------------------- | :------------- | :---------- | | **forFlow** | обязательный | Объект `AdaptyFlow`, полученный через `Adapty.getFlow`. | | **locale** | <p>необязательный</p><p>по умолчанию: `nil`</p> | Идентификатор [локализации пейвола](add-paywall-locale-in-adapty-paywall-builder). Ожидается в виде языкового кода с одним или двумя подтегами, разделёнными `-` (например, `en`, `pt-br`). См. [Локализации и коды локалей](localizations-and-locale-codes). | | **loadTimeout** | по умолчанию: 5 сек | Ограничивает тайм-аут для этого метода. Если тайм-аут истекает, возвращаются кэшированные данные или локальный резервный пейвол. Обратите внимание, что в редких случаях метод может завершиться чуть позже указанного в `loadTimeout` значения, поскольку операция может включать несколько запросов. | | **products** | необязательный | Передайте массив объектов `AdaptyPaywallProduct`, чтобы оптимизировать время отображения продуктов на экране. Если передать `nil`, AdaptyUI автоматически загрузит необходимые продукты. | | **systemRequestsHandler** | необязательный | Объект, реализующий `AdaptySystemRequestsHandler`, который обрабатывает запросы системных разрешений и запросы на отзыв, инициированные действиями флоу. Требуется только если ваш флоу включает такие действия. | | **assetsResolver** | необязательный | Словарь `[String: AdaptyCustomAsset]`, переопределяющий изображения и видео во флоу/пейволе. См. [Настройка ресурсов](#customize-assets). | | **timerResolver** | необязательный | Объект, реализующий `AdaptyTimerResolver`, который предоставляет даты окончания для таймеров, определённых разработчиком. См. [Настройка таймеров разработчика](#set-up-developer-defined-timers). | После загрузки [отобразите флоу/пейвол](ios-present-paywalls). ## Получите флоу или пейвол для аудитории по умолчанию, чтобы ускорить загрузку \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} Как правило, флоу и пейволы загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и плейсментов, а пользователи работают с медленным интернетом, загрузка флоу или пейвола может занять больше времени, чем хотелось бы. В таких ситуациях имеет смысл показывать флоу или пейвол по умолчанию — это обеспечит плавный пользовательский опыт вместо пустого экрана. Чтобы решить эту проблему, используйте метод `getFlowForDefaultAudience`, который получает флоу или пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать флоу или пейвол через метод `getFlow`, как описано в разделе [Получение информации о пейволе](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder) выше. :::warning Почему мы рекомендуем использовать `getFlow` Метод `getFlowForDefaultAudience` имеет ряд существенных недостатков: - **Потенциальные проблемы обратной совместимости**: если нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать пейволы с учётом текущей (устаревшей) версии, либо смириться с тем, что пользователи на ней могут столкнуться с нерендерящимися пейволами. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, настроенный для аудитории **All Users**, — а значит, вы теряете персонализированный таргетинг (в том числе по странам, маркетинговой атрибуции или собственным кастомным атрибутам). Если вы готовы принять эти ограничения ради более быстрой загрузки флоу или пейвола, используйте метод `getFlowForDefaultAudience` следующим образом. В противном случае используйте `getFlow`, описанный [выше](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder). ::: ```swift showLineNumbers Adapty.getFlowForDefaultAudience(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(flow): // the requested flow case let .failure(error): // handle the error } } ``` | Параметр | Обязательность | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера, а в случае ошибки возвращает кешированные данные. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите вариант `.returnCacheDataElseLoad`: он возвращает кешированные данные, если они есть. В этом случае пользователи могут не получить самые свежие данные, зато загрузка будет быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому использовать его в течение сессии для снижения количества сетевых запросов безопасно.</p><p></p><p>Обратите внимание: кеш сохраняется после перезапуска приложения и очищается только при его переустановке или принудительной очистке вручную.</p> | ## Настройка ресурсов \{#customize-assets\} Чтобы кастомизировать изображения и видео в пейволе или флоу, используйте пользовательские ресурсы. Для hero-изображений и видео есть предопределённые идентификаторы: `hero_image` и `hero_video`. В пакете пользовательских ресурсов вы обращаетесь к этим элементам по их идентификаторам и задаёте нужное поведение. Для остальных изображений и видео необходимо [задать пользовательский идентификатор](custom-media) в дашборде Adapty. Например, вы можете: - Показывать разные изображения или видео разным пользователям. - Показывать локальное превью, пока загружается основное изображение с сервера. - Показывать превью перед воспроизведением видео. - Указать разрешение видео в пикселях, чтобы плеер заранее зарезервировал место в макете (соотношение сторон = `width / height`) до загрузки видео. Передайте `nil`, чтобы пропустить этот параметр. Вот пример того, как передавать пользовательские ресурсы через простой словарь: ```swift showLineNumbers let customAssets: [String: AdaptyCustomAsset] = [ // Show a local image using a custom ID "custom_image": .image( .uiImage(value: UIImage(named: "image_name")!) ), // Show a local preview image while a remote main image is loading "hero_image": .image( .remote( url: URL(string: "https://example.com/image.jpg")!, preview: UIImage(named: "preview_image") ) ), // Show a local video with a preview image and a known resolution "hero_video": .video( .file( url: Bundle.main.url(forResource: "custom_video", withExtension: "mp4")!, preview: .uiImage(value: UIImage(named: "video_preview")!), resolution: CGSize(width: 1080, height: 1920) ) ), ] let flowConfig = try await AdaptyUI.getFlowConfiguration( forFlow: flow, assetsResolver: customAssets ) ``` :::note Если ресурс не найден, пейвол/флоу отобразится с внешним видом по умолчанию. ::: ## Настройка таймеров, заданных разработчиком \{#set-up-developer-defined-timers\} Чтобы использовать пользовательские таймеры в мобильном приложении, создайте объект, реализующий протокол `AdaptyTimerResolver`. Этот объект определяет, как должен отображаться каждый таймер. Если хотите, можно напрямую использовать словарь `[String: Date]` — он уже соответствует этому протоколу. Пример: ```swift showLineNumbers @MainActor struct AdaptyTimerResolverImpl: AdaptyTimerResolver { func timerEndAtDate(for timerId: String) -> Date { switch timerId { case "CUSTOM_TIMER_6H": Date(timeIntervalSinceNow: 3600.0 * 6.0) // 6 hours case "CUSTOM_TIMER_NY": Calendar.current.date(from: DateComponents(year: 2025, month: 1, day: 1)) ?? Date(timeIntervalSinceNow: 3600.0) default: Date(timeIntervalSinceNow: 3600.0) // 1 hour } } } ``` В этом примере `CUSTOM_TIMER_NY` и `CUSTOM_TIMER_6H` — это **Timer ID** пользовательских таймеров, заданных вами в дашборде Adapty. `timerResolver` гарантирует, что приложение динамически обновляет каждый таймер правильным значением. Например: - `CUSTOM_TIMER_NY`: время, оставшееся до окончания таймера, например до Нового года. - `CUSTOM_TIMER_6H`: время, оставшееся в 6-часовом периоде, который начался, когда пользователь открыл пейвол. </SDKv4> <SDKv3> После того как вы [создали визуальную часть пейвола](adapty-paywall-builder) с помощью Paywall Builder в дашборде Adapty, вы можете отобразить его в своём мобильном приложении. Первый шаг — получить пейвол, связанный с плейсментом, и его конфигурацию отображения, как описано ниже. Обратите внимание, что эта тема относится к пейволам, настроенным с помощью Paywall Builder. Если вы реализуете пейволы вручную, обратитесь к разделу [Получение пейволов и продуктов для пейволов с Remote Config](fetch-paywalls-and-products). :::tip Прежде чем начать показывать пейволы в своём мобильном приложении, ознакомьтесь с реальным примером интеграции Adapty SDK — загляните в наши [примеры приложений](sample-apps), где показана полная настройка: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Прежде чем начать показывать пейволы в мобильном приложении</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них пейвол](create-placement) в дашборде Adapty. 4. Установите [SDK Adapty](sdk-installation-ios) в своём мобильном приложении. </details> ## Получение пейвола, созданного в Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\} Если вы [создали пейвол с помощью Paywall Builder](adapty-paywall-builder), вам не нужно беспокоиться о его отрисовке в коде мобильного приложения для показа пользователю. Такой пейвол содержит как то, что должно отображаться, так и то, как именно это должно выглядеть. Тем не менее вам нужно получить его ID через плейсмент, конфигурацию отображения, а затем показать пейвол в приложении. Чтобы обеспечить оптимальную производительность, важно получать пейвол и его [конфигурацию отображения](get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) как можно раньше — это даёт достаточно времени для загрузки изображений до того, как пользователь увидит экран. Чтобы получить пейвол, используйте метод `getPaywall`: <Tabs> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let paywall = try await Adapty.getPaywall("YOUR_PLACEMENT_ID") // the requested paywall } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en") { result in switch result { case let .success(paywall): // the requested paywall case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> Параметры: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>необязательный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-paywall-locale-in-adapty-paywall-builder). Ожидается в виде языкового кода из одного или двух подтегов, разделённых символом минус (**-**). Первый подтег — язык, второй — регион.</p><p></p><p>Например: `en` означает английский, `pt-br` — бразильский португальский.</p><p>Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают самые актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `.returnCacheDataElseLoad` — оно возвращает кешированные данные при их наличии. В этом случае пользователи могут получить не самые последние данные, зато время загрузки будет минимальным вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.</p><p></p><p>Adapty SDK хранит пейволы локально на двух уровнях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](fallback-paywalls). Для ускорения загрузки пейволов мы также используем CDN и отдельный резервный сервер на случай недоступности CDN. Эта система обеспечивает получение актуальной версии пейволов и надёжную работу даже при слабом интернет-соединении.</p> | | **loadTimeout** | по умолчанию: 5 сек | <p>Ограничивает тайм-аут этого метода. По истечении тайм-аута возвращаются кешированные данные или локальный резервный пейвол.</p><p>Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения `loadTimeout`, поскольку операция может включать несколько запросов внутри.</p> | | Параметр | Описание | | :-------- | :---------- | | Paywall | Объект [`AdaptyPaywall`](https://swift.adapty.io/documentation/adapty/adaptypaywall) со списком идентификаторов продуктов, идентификатором пейвола, Remote Config и другими свойствами. | ## Получение конфигурации представления для пейвола, созданного в Paywall Builder \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important Убедитесь, что в Paywall Builder включён переключатель **Show on device**. Если он выключен, конфигурация представления не будет доступна для получения. ::: После получения пейвола проверьте, содержит ли он конфигурацию представления — это означает, что пейвол был создан с помощью Paywall Builder. Наличие или отсутствие конфигурации определяет способ отображения пейвола. Если конфигурация представления есть — работайте с ним как с пейволом Paywall Builder; если нет — [обработайте его как пейвол с Remote Config](present-remote-config-paywalls). Используйте метод `getPaywallConfiguration` для загрузки конфигурации представления. ```swift showLineNumbers guard paywall.hasViewConfiguration else { // use your custom logic return } do { let paywallConfiguration = try await AdaptyUI.getPaywallConfiguration( forPaywall: paywall, products: products ) // use loaded configuration } catch { // handle the error } ``` Параметры: | Параметр | Наличие | Описание | | :----------------------- | :------------- | :------- | | **paywall** | обязательный | Объект `AdaptyPaywall` для получения контроллера нужного пейвола. | | **loadTimeout** | по умолчанию: 5 сек | Ограничивает таймаут для этого метода. Если таймаут истекает, возвращаются кэшированные данные или локальный резервный вариант. В редких случаях метод может завершиться чуть позже указанного значения `loadTimeout`, поскольку операция может включать несколько запросов под капотом. | | **products** | необязательный | Передайте массив объектов `AdaptyPaywallProduct`, чтобы оптимизировать время отображения продуктов на экране. Если передано `nil`, AdaptyUI автоматически получит необходимые продукты. | :::note Если вы используете несколько языков, узнайте, как добавить [локализацию Paywall Builder](add-paywall-locale-in-adapty-paywall-builder) и как правильно использовать коды локалей [здесь](localizations-and-locale-codes). ::: После загрузки [отобразите пейвол](ios-present-paywalls). ## Получение пейвола для аудитории по умолчанию ради более быстрой загрузки \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} Как правило, пейволы загружаются практически мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и пейволов, а пользователи работают с медленным интернетом, загрузка пейвола может занять больше времени, чем хотелось бы. В таких ситуациях имеет смысл показывать пейвол по умолчанию — это обеспечит плавный пользовательский опыт вместо того, чтобы не показывать пейвол вовсе. Чтобы решить эту задачу, используйте метод `getPaywallForDefaultAudience`, который получает пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать пейвол методом `getPaywall`, как описано в разделе [Получение информации о пейволе](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder) выше. :::warning Почему мы рекомендуем использовать `getPaywall` Метод `getPaywallForDefaultAudience` имеет ряд существенных недостатков: - **Возможные проблемы с обратной совместимостью**: если нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать пейволы с поддержкой текущей (устаревшей) версии, либо смириться с тем, что у пользователей на старой версии пейволы могут не отображаться корректно. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, настроенный для аудитории **All Users**, — это означает отказ от персонализированного таргетинга (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вы готовы принять эти компромиссы ради более быстрой загрузки пейвола, используйте метод `getPaywallForDefaultAudience` следующим образом. В противном случае используйте `getPaywall`, описанный [выше](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder). ::: ```swift showLineNumbers Adapty.getPaywallForDefaultAudience(placementId: "YOUR_PLACEMENT_ID", locale: "en") { result in switch result { case let .success(paywall): // the requested paywall case let .failure(error): // handle the error } } ``` :::note Метод `getPaywallForDefaultAudience` доступен начиная с версии iOS SDK 2.11.2. ::: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение, которое вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>необязательный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается, что этот параметр будет языковым кодом, состоящим из одного или нескольких подтегов, разделённых символом минус (**-**). Первый подтег обозначает язык, второй — регион.</p><p></p><p>Например: `en` означает английский, `pt-br` — бразильский португальский.</p><p></p><p>Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кэшированные данные в случае неудачи. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `.returnCacheDataElseLoad` для возврата кэшированных данных при их наличии. В этом случае пользователи могут получать не самые свежие данные, зато время загрузки будет меньше вне зависимости от качества соединения. Кэш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание, что кэш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.</p> | ## Настройка ресурсов \{#customize-assets\} Чтобы кастомизировать изображения и видео в пейволе, используйте кастомные ресурсы. Для hero-изображений и видео есть предустановленные ID: `hero_image` и `hero_video`. В бандле кастомных ресурсов вы обращаетесь к этим элементам по их ID и меняете их поведение. Для остальных изображений и видео нужно [задать кастомный ID](custom-media) в дашборде Adapty. Например, можно: - Показывать одним пользователям одно изображение или видео, другим — другое. - Показывать локальное превью, пока загружается основное изображение с сервера. - Показывать превью перед воспроизведением видео. :::important Чтобы использовать эту функцию, обновите Adapty iOS SDK до версии 3.7.0 или выше. ::: Вот пример того, как можно передать пользовательские ресурсы через простой словарь: ```swift showLineNumbers let customAssets: [String: AdaptyCustomAsset] = [ // Показать локальное изображение с пользовательским ID "custom_image": .image( .uiImage(value: UIImage(named: "image_name")!) ), // Показать локальное превью, пока загружается основное изображение "hero_image": .image( .remote( url: URL(string: "https://example.com/image.jpg")!, preview: UIImage(named: "preview_image") ) ), // Показать локальное видео с превью-изображением "hero_video": .video( .file( url: Bundle.main.url(forResource: "custom_video", withExtension: "mp4")!, preview: .uiImage(value: UIImage(named: "video_preview")!) ) ), ] let paywallConfig = try await AdaptyUI.getPaywallConfiguration( forPaywall: paywall, assetsResolver: customAssets ) ``` :::note Если ресурс не найден, пейвол вернётся к своему виду по умолчанию. ::: ## Настройка таймеров, определённых разработчиком \{#set-up-developer-defined-timers\} Чтобы использовать пользовательские таймеры в мобильном приложении, создайте объект, реализующий протокол `AdaptyTimerResolver`. Этот объект определяет, как должен отображаться каждый таймер. Если хотите, можно напрямую использовать словарь `[String: Date]` — он уже соответствует этому протоколу. Пример: ```swift showLineNumbers @MainActor struct AdaptyTimerResolverImpl: AdaptyTimerResolver { func timerEndAtDate(for timerId: String) -> Date { switch timerId { case "CUSTOM_TIMER_6H": Date(timeIntervalSinceNow: 3600.0 * 6.0) // 6 часов case "CUSTOM_TIMER_NY": Calendar.current.date(from: DateComponents(year: 2025, month: 1, day: 1)) ?? Date(timeIntervalSinceNow: 3600.0) default: Date(timeIntervalSinceNow: 3600.0) // 1 час } } } ``` В этом примере `CUSTOM_TIMER_NY` и `CUSTOM_TIMER_6H` — это **Timer ID** таймеров, заданных разработчиком в дашборде Adapty. `timerResolver` гарантирует, что приложение динамически обновляет каждый таймер нужным значением. Например: - `CUSTOM_TIMER_NY`: время, оставшееся до окончания таймера, например до Нового года. - `CUSTOM_TIMER_6H`: время, оставшееся в 6-часовом периоде, который начался, когда пользователь открыл пейвол. </SDKv3> --- # File: ios-present-paywalls --- --- title: "Отображение флоу и пейволов — iOS" description: "Отображение флоу и пейволов для пользователей в iOS-приложении." --- <SDKv4> <MethodPromo method="getFlow" label="Отображение флоу и пейволов" /> Если вы создали флоу или пейвол, вам не нужно беспокоиться об их отрисовке в коде мобильного приложения — всё, что должно быть показано и как именно, уже содержится внутри самого флоу или пейвола. Чтобы получить объект `AdaptyUI.FlowConfiguration`, используемый ниже, см. [Получение флоу и пейволов](get-pb-paywalls). ## Отображение флоу и пейволов в SwiftUI \{#present-flows-and-paywalls-in-swiftui\} ### Отображение в виде модального окна \{#present-as-a-modal-view\} Чтобы отобразить флоу или пейвол на экране устройства в виде модального окна, используйте модификатор `.flow` в SwiftUI. Минимальный вызов требует `isPresented`, `flowConfiguration` и пяти обязательных колбэков: ```swift showLineNumbers title="SwiftUI" .flow( isPresented: $flowPresented, flowConfiguration: <AdaptyUI.FlowConfiguration>, didFinishPurchase: { _, _ in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { _, _ in /* handle the error */ }, didFinishRestore: { _ in /* check access level and dismiss */ }, didFailRestore: { _ in /* handle the error */ }, didReceiveError: { _ in flowPresented = false } ) ``` Для более тонкого управления добавьте необязательные колбэки, например `didPerformAction`, для обработки нажатий на кнопки: ```swift showLineNumbers title="SwiftUI" @State var flowPresented = false // ensure that you manage this variable state and set it to `true` at the moment you want to show the flow or paywall var body: some View { Text("Hello, AdaptyUI!") .flow( isPresented: $flowPresented, flowConfiguration: <AdaptyUI.FlowConfiguration>, didPerformAction: { action in switch action { case .close: flowPresented = false default: // Handle other actions break } }, didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false } ) } ``` Parameters: | Параметр | Обязательный | Описание | |:-----------------------|:-------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **isPresented** | обязательный | Привязка, управляющая отображением флоу или пейвола. | | **flowConfiguration** | обязательный | Объект `AdaptyUI.FlowConfiguration`, содержащий визуальные данные флоу или пейвола. Используйте метод `AdaptyUI.getFlowConfiguration(forFlow:)`. Подробнее см. в разделе [Получение флоу и пейволов](get-pb-paywalls). | | **didFinishPurchase** | обязательный | Вызывается при успешном завершении `Adapty.makePurchase()`. Флоу не закрывается автоматически — здесь нужно установить привязку представления в `false` или оставить всё как есть, чтобы флоу продолжил работу после покупки. | | **didFailPurchase** | обязательный | Вызывается при ошибке `Adapty.makePurchase()`. | | **didFinishRestore** | обязательный | Вызывается при успешном завершении `Adapty.restorePurchases()`. | | **didFailRestore** | обязательный | Вызывается при ошибке `Adapty.restorePurchases()`. | | **didReceiveError** | обязательный | Вызывается при ошибке рендеринга или ошибке выполнения в скрипте флоу (например, исключение JavaScript, код `AdaptyUIError` `4105`). При ошибках рендеринга [обратитесь в поддержку Adapty](mailto:support@adapty.io). | | **fullScreen** | необязательный | Определяет, отображается ли флоу или пейвол в полноэкранном режиме или как sheet. По умолчанию `true`. | | **didAppear** | необязательный | Вызывается, когда флоу или пейвол был показан. | | **didDisappear** | необязательный | Вызывается, когда флоу или пейвол был закрыт. | | **didPerformAction** | необязательный | Вызывается при нажатии пользователем на кнопку. Два идентификатора действий предопределены: `close` и `openURL`; остальные задаются в билдере. | | **didSelectProduct** | необязательный | Вызывается, когда пользователь или система выбирает продукт для покупки. | | **didStartPurchase** | необязательный | Вызывается, когда пользователь начинает процесс покупки. | | **didFinishWebPaymentNavigation** | необязательный | Вызывается по завершении навигации при веб-оплате. | | **didStartRestore** | необязательный | Вызывается, когда пользователь начинает процесс восстановления покупок. | | **didFailLoadingProducts** | необязательный | Вызывается при ошибках загрузки продуктов. Верните `true`, чтобы повторить загрузку. | | **didPartiallyLoadProducts** | необязательный | Вызывается при частичной загрузке продуктов. | | **showAlertItem** | необязательный | Привязка, управляющая отображением элементов предупреждения поверх флоу или пейвола. | | **showAlertBuilder** | необязательный | Функция для рендеринга представления предупреждения. | | **placeholderBuilder** | необязательный | Функция для рендеринга placeholder-представления во время загрузки флоу или пейвола. По умолчанию используется `ProgressView`. | Подробнее о параметрах см. в разделе [iOS — Обработка событий](ios-handling-events). ### Представление в виде немодального окна \{#present-as-a-non-modal-view\} Вы также можете представлять флоу и пейволы как пункты навигации или встроенные представления в навигационном флоу вашего приложения. Используйте `AdaptyFlowView` непосредственно в SwiftUI-представлениях: ```swift showLineNumbers title="SwiftUI" AdaptyFlowView( flowConfiguration: <AdaptyUI.FlowConfiguration>, didFinishPurchase: { product, purchaseResult in // Dismiss the view, or do nothing to let the flow continue }, didFailPurchase: { product, error in // Handle purchase failure }, didFinishRestore: { profile in // Handle successful restore }, didFailRestore: { error in // Handle restore failure }, didReceiveError: { error in // Handle the error (rendering or JS exception from the flow script). } ) ``` ## Отображение флоу и пейволов в UIKit \{#present-flows-and-paywalls-in-uikit\} Чтобы отобразить флоу или пейвол на экране устройства, выполните следующие шаги: 1. Инициализируйте визуальный флоу, который хотите отобразить, с помощью метода `AdaptyUI.flowController(with:delegate:)`: ```swift showLineNumbers title="Swift" import AdaptyUI let visualFlow = try AdaptyUI.flowController( with: <AdaptyUI.FlowConfiguration>, delegate: <AdaptyFlowControllerDelegate> ) ``` Параметры запроса: | Параметр | Обязательный | Описание | | :----------------------- | :------- | :---------- | | **flowConfiguration** | required | Объект `AdaptyUI.FlowConfiguration`, содержащий визуальные детали флоу или пейвола. Используйте метод `AdaptyUI.getFlowConfiguration(forFlow:)`. Подробнее — в разделе [Получение флоу и пейволов](get-pb-paywalls). | | **delegate** | required | `AdaptyFlowControllerDelegate` для прослушивания событий флоу и пейвола. Подробнее — в разделе [Обработка событий флоу и пейвола](ios-handling-events). | Возвращает: | Объект | Описание | | :---------------------- | :------------------------------------------------------- | | **AdaptyFlowController** | Объект, представляющий запрошенный флоу или экран пейвола. | 2. После того как объект успешно создан, его можно отобразить на экране устройства: ```swift showLineNumbers title="Swift" present(visualFlow, animated: true) ``` :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Ознакомьтесь с нашими [примерами приложений](sample-apps), которые демонстрируют полную настройку: отображение пейволов, совершение покупок и другой базовый функционал. ::: </SDKv4> <SDKv3> Если вы настроили пейвол с помощью Paywall Builder, вам не нужно беспокоиться о его рендеринге в коде мобильного приложения для отображения пользователю. Такой пейвол содержит как то, что должно быть показано, так и то, как именно это должно отображаться. Чтобы получить объект `AdaptyUI.PaywallConfiguration`, используемый ниже, см. раздел [Получение пейволов Paywall Builder и их конфигурации](get-pb-paywalls). ## Отображение пейволов в SwiftUI \{#present-paywalls-in-swiftui\} ### Отображение в виде модального окна \{#present-as-a-modal-view\} Чтобы отобразить визуальный пейвол на экране устройства в виде модального окна, используйте модификатор `.paywall` в SwiftUI: ```swift showLineNumbers title="SwiftUI" @State var paywallPresented = false // ensure that you manage this variable state and set it to `true` at the moment you want to show the paywall var body: some View { Text("Hello, AdaptyUI!") .paywall( isPresented: $paywallPresented, paywallConfiguration: <AdaptyUI.PaywallConfiguration>, didPerformAction: { action in switch action { case .close: paywallPresented = false default: // Handle other actions break } }, didFinishPurchase: { product, profile in paywallPresented = false }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didFailRendering: { error in paywallPresented = false } ) } ``` Parameters: | Параметр | Обязателен | Описание | |:----------------------------------|:-----------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **isPresented** | обязателен | Биндинг, управляющий отображением экрана пейвола. | | **paywallConfiguration** | обязателен | Объект `AdaptyUI.PaywallConfiguration`, содержащий визуальные параметры пейвола. Используйте метод `AdaptyUI.paywallConfiguration(for:products:viewConfiguration:observerModeResolver:tagResolver:timerResolver:)`. Подробнее см. в разделе [Получение пейволов Paywall Builder и их конфигурации](get-pb-paywalls). | | **didFailPurchase** | обязателен | Вызывается при ошибке `Adapty.makePurchase()`. | | **didFinishRestore** | обязателен | Вызывается при успешном завершении `Adapty.restorePurchases()`. | | **didFailRestore** | обязателен | Вызывается при ошибке `Adapty.restorePurchases()`. | | **didFailRendering** | обязателен | Вызывается при ошибке рендеринга интерфейса. В этом случае [обратитесь в поддержку Adapty](mailto:support@adapty.io). | | **fullScreen** | опционален | Определяет, отображается ли пейвол в полноэкранном режиме или как модальное окно. По умолчанию `true`. | | **didAppear** | опционален | Вызывается, когда представление пейвола было показано. | | **didDisappear** | опционален | Вызывается, когда представление пейвола было скрыто. | | **didPerformAction** | опционален | Вызывается при нажатии пользователем кнопки. У разных кнопок разные идентификаторы действий. Два идентификатора заданы заранее: `close` и `openURL`; остальные — пользовательские и задаются в билдере. | | **didSelectProduct** | опционален | Вызывается, если продукт был выбран для покупки (пользователем или системой). | | **didStartPurchase** | опционален | Вызывается, когда пользователь начинает процесс покупки. | | **didFinishPurchase** | опционален | Вызывается при успешном завершении `Adapty.makePurchase()`. | | **didFinishWebPaymentNavigation** | опционален | Вызывается при завершении навигации веб-платежа. | | **didStartRestore** | опционален | Вызывается, когда пользователь начинает процесс восстановления покупок. | | **didFailLoadingProducts** | опционален | Вызывается при ошибках загрузки продуктов. Верните `true`, чтобы повторить загрузку. | | **didPartiallyLoadProducts** | опционален | Вызывается, когда продукты загружены частично. | | **showAlertItem** | опционален | Биндинг, управляющий отображением элементов алерта поверх пейвола. | | **showAlertBuilder** | опционален | Функция для рендеринга представления алерта. | | **placeholderBuilder** | опционален | Функция для рендеринга представления-заглушки, пока пейвол загружается. | Подробнее о параметрах см. в разделе [iOS — Обработка событий](ios-handling-events). ### Отображение в виде немодального представления \{#present-as-a-non-modal-view\} Пейволы также можно встраивать как навигационные экраны или встроенные представления в навигационный флоу вашего приложения. Используйте `AdaptyPaywallView` напрямую в SwiftUI-представлениях: ```swift showLineNumbers title="SwiftUI" AdaptyPaywallView( paywallConfiguration: <AdaptyUI.PaywallConfiguration>, didFailPurchase: { product, error in // Handle purchase failure }, didFinishRestore: { profile in // Handle successful restore }, didFailRestore: { error in // Handle restore failure }, didFailRendering: { error in // Handle rendering error } ) ``` ## Отображение пейволов в UIKit \{#present-paywalls-in-uikit\} Чтобы отобразить визуальный пейвол на экране устройства, выполните следующие шаги: 1. Инициализируйте визуальный пейвол, который хотите отобразить, с помощью метода `.paywallController(for:products:viewConfiguration:delegate:)`: ```swift showLineNumbers title="Swift" import AdaptyUI let visualPaywall = AdaptyUI.paywallController( with: <paywall configuration object>, delegate: <AdaptyPaywallControllerDelegate> ) ``` Параметры запроса: | Параметр | Наличие | Описание | | :----------------------- | :------- | :---------- | | **paywall configuration** | required | Объект `AdaptyUI.PaywallConfiguration`, содержащий визуальные детали пейвола. Используйте метод `AdaptyUI.getPaywallConfiguration(forPaywall:locale:)`. Подробнее см. в разделе [Получение пейволов Paywall Builder и их конфигурации](get-pb-paywalls). | | **delegate** | required | `AdaptyPaywallControllerDelegate` для обработки событий пейвола. Подробнее см. в разделе [Обработка событий пейвола](ios-handling-events). | Возвращает: | Объект | Описание | | :---------------------- | :--------------------------------------------------- | | **AdaptyPaywallController** | Объект, представляющий запрошенный экран пейвола | 2. После успешного создания объекта его можно отобразить на экране устройства: ```swift showLineNumbers title="Swift" present(visualPaywall, animated: true) ``` :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку, включая отображение пейволов, совершение покупок и другие базовые функции. ::: </SDKv3> --- # File: handle-paywall-actions --- --- title: "Реагирование на действия флоу — iOS" description: "Обрабатывайте нажатия кнопок и пользовательский ввод из флоу пейволов и онбордингов в своём iOS-приложении." --- <SDKv4> Если вы создаёте флоу или пейволы с помощью Adapty Flow Builder или Paywall Builder, важно правильно настроить кнопки: 1. Добавьте [кнопку в Paywall Builder](paywall-buttons) и назначьте ей готовое действие или создайте произвольный ID действия. 2. Напишите код в приложении для обработки каждого назначенного действия. В этом гайде показано, как обрабатывать пользовательские и готовые действия в коде. :::warning **Только закрытие флоу/пейвола и открытие URL обрабатываются автоматически.** Все остальные действия кнопок требуют явной реализации в коде приложения. ::: :::note iOS SDK может реагировать на системные запросы разрешений, такие как push-уведомления или доступ к камере, через `AdaptySystemRequestsHandler`. Флоу пока не инициируют такие запросы, поэтому сейчас это обрабатывать не нужно. ::: ## Закрытие флоу и пейволов \{#close-flows-and-paywalls\} Чтобы добавить кнопку для закрытия флоу или пейвола: 1. В билдере добавьте кнопку и назначьте ей действие **Close**. 2. В коде приложения реализуйте обработчик действия `close`. :::info В iOS SDK действие `close` по умолчанию закрывает флоу или пейвол. При необходимости это поведение можно переопределить в коде. Например, закрытие одного флоу может вызывать открытие другого. ::: ```swift .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case .close: flowPresented = false // dismiss the flow or paywall default: break } }, didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false } ) ``` ## Открытие URL из флоу и пейволов \{#open-urls-from-flows-and-paywalls\} :::tip Если нужно добавить группу ссылок (например, условия использования и восстановление покупок), добавьте элемент **Link** в билдере и обработайте его так же, как кнопки с действием **Open URL**. ::: Чтобы добавить кнопку, открывающую ссылку из флоу или пейвола (например, **Terms of use** или **Privacy policy**): 1. В билдере добавьте кнопку, назначьте ей действие **Open URL** и введите нужный URL. 2. В коде приложения реализуйте обработчик действия `openURL`, который открывает полученный URL в браузере. :::info В iOS SDK действие `openURL` по умолчанию открывает URL. При необходимости это поведение можно переопределить в коде. ::: ```swift .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case let .openURL(url): UIApplication.shared.open(url, options: [:]) // default behavior default: break } }, didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false } ) ``` ## Обработка пользовательских действий \{#handle-custom-actions\} Чтобы добавить кнопку для обработки произвольных действий: 1. В билдере добавьте кнопку, назначьте ей действие **Custom** и задайте идентификатор. 2. В коде приложения реализуйте обработчик для созданного идентификатора действия. Например, если у вас есть другой набор предложений по подписке или разовых покупок, вы можете добавить кнопку, которая откроет другой флоу или пейвол: ```swift .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case let .custom(id): if id == "openNewPaywall" { // Display another flow or paywall } default: break } }, didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false } ) ``` </SDKv4> <SDKv3> Если вы создаёте пейволы с помощью Adapty Paywall Builder, важно правильно настроить кнопки: 1. Добавьте [кнопку в Paywall Builder](paywall-buttons) и назначьте ей существующее действие или создайте собственный ID действия. 2. Напишите в приложении код для обработки каждого назначенного действия. В этом гайде показано, как обрабатывать пользовательские и встроенные действия в коде. :::warning **Покупки, восстановление, закрытие пейвола и переходы по ссылкам обрабатываются автоматически.** Все остальные действия кнопок требуют явной реализации в коде приложения. ::: ## Закрытие пейволов \{#close-paywalls\} Чтобы добавить кнопку закрытия пейвола: 1. В Paywall Builder добавьте кнопку и назначьте ей действие **Close**. 2. В коде приложения реализуйте обработчик действия `close`, который закрывает пейвол. :::info В iOS SDK действие `close` по умолчанию закрывает пейвол. Однако при необходимости это поведение можно переопределить в коде. Например, закрытие одного пейвола может инициировать открытие другого. ::: ```swift func paywallController(_ controller: AdaptyPaywallController, didPerform action: AdaptyUI.Action) { switch action { case .close: controller.dismiss(animated: true) // поведение по умолчанию break } } ``` ## Открытие URL с пейволов \{#open-urls-from-paywalls\} :::tip Если вы хотите добавить группу ссылок (например, пользовательское соглашение и восстановление покупок), добавьте элемент **Link** в Paywall Builder и обработайте его так же, как кнопки с действием **Open URL**. ::: Чтобы добавить кнопку, открывающую ссылку с вашего пейвола (например, **Terms of use** или **Privacy policy**): 1. В Paywall Builder добавьте кнопку, назначьте ей действие **Open URL** и введите URL, который нужно открыть. 2. В коде вашего приложения реализуйте обработчик действия `openUrl`, который открывает полученный URL в браузере. :::info В iOS SDK действие `openUrl` по умолчанию открывает URL. При необходимости это поведение можно переопределить в коде. ::: ```swift func paywallController(_ controller: AdaptyPaywallController, didPerform action: AdaptyUI.Action) { switch action { case let .openURL(url): UIApplication.shared.open(url, options: [:]) // default behavior break } } ``` ## Войти в приложение \{#log-into-the-app\} Чтобы добавить кнопку входа в приложение: 1. В Paywall Builder добавьте кнопку и назначьте ей действие **Login**. 2. В коде приложения реализуйте обработчик действия `login`, который идентифицирует пользователя. ```swift func paywallController(_ controller: AdaptyPaywallController, didPerform action: AdaptyUI.Action) { switch action { case .login: // Show a login screen let loginVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "LoginViewController") controller.present(loginVC, animated: true) } } ``` ## Обработка пользовательских действий \{#handle-custom-actions\} Чтобы добавить кнопку с произвольным действием: 1. В Paywall Builder добавьте кнопку, назначьте ей действие **Custom** и задайте ID. 2. В коде приложения реализуйте обработчик для созданного ID действия. Например, если у вас есть другой набор предложений подписки или разовых покупок, можно добавить кнопку, которая откроет другой пейвол: ```swift func paywallController(_ controller: AdaptyPaywallController, didPerform action: AdaptyUI.Action) { switch action { case let .custom(id): if id == "openNewPaywall" { // Display another paywall } } break } } ``` </SDKv3> --- # File: ios-handling-events --- --- title: "Обработка событий флоу и пейвола - iOS" description: "Обрабатывайте события флоу и пейвола в вашем iOS-приложении." --- <SDKv4> :::important Этот гайд охватывает обработку событий для покупок, восстановлений, выбора продукта и рендеринга пейвола. Вам также необходимо реализовать обработку кнопок (закрытие пейвола, открытие ссылок и т. д.). Подробнее — в нашем [гайде по обработке действий кнопок](handle-paywall-actions). ::: Флоу и пейволы не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют события, на которые ваше приложение может реагировать. К таким событиям относятся нажатия кнопок (кнопки закрытия, URL, выбор продуктов и т. д.), а также уведомления о действиях, связанных с покупками. Узнайте ниже, как реагировать на эти события. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Ознакомьтесь с нашими [примерами приложений](sample-apps), которые демонстрируют полную настройку, включая отображение пейволов, совершение покупок и другие базовые функции. ::: ## Обработка событий в SwiftUI \{#handling-events-in-swiftui\} Для управления и отслеживания процессов на экране флоу или пейвола в вашем мобильном приложении используйте модификатор `.flow` в SwiftUI: ```swift showLineNumbers title="Swift" @State var flowPresented = false var body: some View { Text("Hello, AdaptyUI!") .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case .close: flowPresented = false case let .openURL(url): // handle opening the URL (incl. for terms and privacy) default: // handle other actions } }, didSelectProduct: { product in /* Handle the event */ }, didStartPurchase: { product in /* Handle the event */ }, didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didStartRestore: { /* Handle the event */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false }, didFailLoadingProducts: { error in // Return `true` to retry loading return false } ) } ``` Вы можете регистрировать только те параметры замыкания, которые вам нужны, и опускать те, которые не нужны. | Параметр | Обязательный | Описание | |:-----------------------|:-------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **isPresented** | обязательный | Привязка, управляющая отображением экрана флоу или пейвола. | | **flowConfiguration** | обязательный | Объект `AdaptyUI.FlowConfiguration`, содержащий визуальные данные флоу или пейвола. Подробнее см. в разделе [Получение флоу и пейволов](get-pb-paywalls). | | **didFinishPurchase** | обязательный | Вызывается при успешном завершении `Adapty.makePurchase()`. Флоу не закрывается автоматически — здесь можно установить привязку презентации в `false` или ничего не делать, позволив флоу продолжить работу после покупки. | | **didFailPurchase** | обязательный | Вызывается при ошибке `Adapty.makePurchase()`. | | **didFinishRestore** | обязательный | Вызывается при успешном завершении `Adapty.restorePurchases()`. | | **didFailRestore** | обязательный | Вызывается при ошибке `Adapty.restorePurchases()`. | | **didReceiveError** | обязательный | Вызывается при возникновении ошибки рендеринга или ошибки времени выполнения в скрипте флоу (например, исключение JavaScript, код `AdaptyUIError` `4105`). В случае ошибки рендеринга [обратитесь в поддержку Adapty](mailto:support@adapty.io). | | **placeholderBuilder** | необязательный | Функция для отображения плейсхолдера во время загрузки флоу или пейвола. По умолчанию используется `ProgressView`. | | **fullScreen** | необязательный | Определяет, отображается ли флоу или пейвол в полноэкранном режиме или в виде шторки. По умолчанию `true`. | | **didAppear** | необязательный | Вызывается, когда экран флоу или пейвола появляется на экране. | | **didDisappear** | необязательный | Вызывается, когда экран флоу или пейвола был закрыт. | | **didPerformAction** | необязательный | Вызывается при нажатии пользователем на кнопку. Предопределены два идентификатора действий: `close` и `openURL`; остальные — пользовательские и задаются в билдере. | | **didSelectProduct** | необязательный | Вызывается, когда пользователь или система выбирает продукт для покупки. | | **didStartPurchase** | необязательный | Вызывается, когда пользователь начинает процесс покупки. | | **didFinishWebPaymentNavigation** | необязательный | Вызывается при завершении навигации в рамках веб-оплаты. | | **didStartRestore** | необязательный | Вызывается, когда пользователь начинает процесс восстановления покупок. | | **didFailLoadingProducts** | необязательный | Вызывается при ошибках во время загрузки продуктов. Верните `true`, чтобы повторить загрузку. | | **didPartiallyLoadProducts** | необязательный | Вызывается, когда продукты загружены частично. | | **showAlertItem** | необязательный | Привязка, управляющая отображением алертов поверх флоу или пейвола. | | **showAlertBuilder** | необязательный | Функция для отображения алерта. | ## Обработка событий в UIKit \{#handling-events-in-uikit\} Для приложений на UIKit события обрабатываются через протокол `AdaptyFlowControllerDelegate`. Подробнее о настройке `AdaptyFlowController` с `AdaptyFlowControllerDelegate` смотрите в разделе [Отображение флоу и пейволов — iOS](ios-present-paywalls). Протокол объявляет 13 методов. Четыре из них не имеют реализации по умолчанию и должны быть реализованы при подписке на протокол: `didFinishPurchase`, `didFailPurchase`, `didFinishRestoreWith` и `didFailRestoreWith`. Остальные имеют реализацию по умолчанию (no-op) и могут быть переопределены при необходимости. Методы сгруппированы ниже по назначению. ### Жизненный цикл \{#lifecycle\} ```swift showLineNumbers title="Swift" func flowControllerDidAppear(_ controller: AdaptyFlowController) { } func flowControllerDidDisappear(_ controller: AdaptyFlowController) { } ``` Эти методы срабатывают при появлении и закрытии флоу или пейвола. ### Действия пользователя ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didPerform action: AdaptyUI.Action ) { } ``` Варианты `AdaptyUI.Action`: - `.close` — по умолчанию закрывает контроллер. Переопределите, если нужно оставить контроллер на экране или выполнить дополнительную очистку. - `.openURL(url:)` — по умолчанию открывает URL через `UIApplication.shared.open(...)`. - `.custom(id:)` — срабатывает для кнопок, которым в билдере назначен пользовательский идентификатор действия. ### Выбор продукта \{#product-selection\} ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didSelectProduct product: AdaptyPaywallProduct ) { } ``` Вызывается, когда пользователь или система выбирают продукт для покупки. Продукт содержит полную информацию об офере (в v4 совместимость определяется автоматически — отдельного типа `AdaptyPaywallProductWithoutDeterminingOffer` больше нет). ### События покупки \{#purchase-events\} ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didStartPurchase product: AdaptyPaywallProduct ) { } func flowController( _ controller: AdaptyFlowController, didFinishPurchase product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { if !purchaseResult.isPurchaseCancelled { controller.dismiss(animated: true) // or do nothing to let the flow continue } } func flowController( _ controller: AdaptyFlowController, didFailPurchase product: AdaptyPaywallProduct, error: AdaptyError ) { } ``` `didFinishPurchase` и `didFailPurchase` не имеют реализаций по умолчанию и должны быть реализованы. Контроллер не закрывается автоматически после успешной покупки — вызовите `controller.dismiss(animated:)` в нужный момент или оставьте всё как есть, чтобы многоэкранный флоу продолжился после покупки. ### События восстановления покупок \{#restore-events\} ```swift showLineNumbers title="Swift" func flowControllerDidStartRestore(_ controller: AdaptyFlowController) { } func flowController( _ controller: AdaptyFlowController, didFinishRestoreWith profile: AdaptyProfile ) { } func flowController( _ controller: AdaptyFlowController, didFailRestoreWith error: AdaptyError ) { } ``` `didFinishRestoreWith` и `didFailRestoreWith` не имеют реализаций по умолчанию. Перед закрытием контроллера убедитесь, что возвращённый `AdaptyProfile` содержит нужный уровень доступа. ### Ошибки флоу и ошибки загрузки продуктов \{#flow-errors-and-product-loading-errors\} ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didReceiveError error: AdaptyUIError ) { } func flowController( _ controller: AdaptyFlowController, didFailLoadingProductsWith error: AdaptyError ) -> Bool { // Return `true` to retry product loading; default returns `false`. return false } func flowController( _ controller: AdaptyFlowController, didPartiallyLoadProducts failedIds: [String] ) { } ``` `didReceiveError` срабатывает при ошибках рендеринга и при ошибках выполнения из скрипта флоу (исключения JavaScript, `AdaptyUIError` код `4105`). При ошибках рендеринга [обратитесь в поддержку Adapty](mailto:support@adapty.io). При ошибках загрузки верните `true` из `didFailLoadingProductsWith`, чтобы повторить попытку — это удобно при временных сбоях сети. ### Навигация при веб-оплате \{#web-payment-navigation\} ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didFinishWebPaymentNavigation product: AdaptyPaywallProduct?, error: AdaptyError? ) { } ``` Вызывается после завершения навигации при веб-оплате — как успешной, так и неуспешной. </SDKv4> <SDKv3> :::important Этот гайд охватывает обработку событий для покупок, восстановлений, выбора продуктов и отображения пейвола. Вам также необходимо реализовать обработку кнопок (закрытие пейвола, открытие ссылок и т. д.). Подробнее — в [гайде по обработке действий кнопок](handle-paywall-actions). ::: Пейволы, настроенные с помощью [Paywall Builder](adapty-paywall-builder), не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют события, на которые ваше приложение может реагировать. Среди этих событий — нажатия на кнопки (кнопки закрытия, URL-адреса, выбор продуктов и т. д.), а также уведомления о действиях, связанных с покупками, выполненных на пейволе. Ниже описано, как обрабатывать эти события. Этот гайд предназначен **только для пейволов нового Paywall Builder**, которые требуют Adapty SDK версии 3.0 или выше. :::tip Хотите посмотреть, как SDK Adapty интегрируется в реальное мобильное приложение? Изучите наши [примеры приложений](sample-apps) — в них показана полная настройка: отображение пейволов, совершение покупок и другие базовые функции. ::: ## Обработка событий в SwiftUI \{#handling-events-in-swiftui\} Для управления и отслеживания процессов на экране пейвола в вашем мобильном приложении используйте модификатор `.paywall` в SwiftUI: ```swift showLineNumbers title="Swift" @State var paywallPresented = false var body: some View { Text("Hello, AdaptyUI!") .paywall( isPresented: $paywallPresented, paywall: paywall, viewConfiguration: viewConfig, didPerformAction: { action in switch action { case .close: paywallPresented = false case let .openURL(url): // handle opening the URL (incl. for terms and privacy) default: // handle other actions } }, didSelectProduct: { /* Handle the event */ }, didStartPurchase: { /* Handle the event */ }, didFinishPurchase: { product, info in /* Handle the event */ }, didFailPurchase: { product, error in /* Handle the event */ }, didStartRestore: { /* Handle the event */ }, didFinishRestore: { /* Handle the event */ }, didFailRestore: { /* Handle the event */ }, didFailRendering: { error in paywallPresented = false }, didFailLoadingProducts: { error in return false } ) } ``` Вы можете регистрировать только те параметры замыкания, которые вам нужны, и опускать те, которые не нужны. В этом случае неиспользуемые параметры замыкания не будут созданы. | Параметр | Обязательный | Описание | |:----------------------------------|:-------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **isPresented** | обязательный | Биндинг, управляющий отображением экрана пейвола. | | **paywallConfiguration** | обязательный | Объект `AdaptyUI.PaywallConfiguration`, содержащий визуальные настройки пейвола. Используйте метод `AdaptyUI.paywallConfiguration(for:products:viewConfiguration:observerModeResolver:tagResolver:timerResolver:)`. Подробнее см. в разделе [Получение пейволов Paywall Builder и их конфигурации](get-pb-paywalls). | | **didFailPurchase** | обязательный | Вызывается при ошибке покупки (например, платёж не разрешён, проблемы с сетью, неверный продукт). Не вызывается при отмене пользователем или ожидающих платежах. | | **didFinishRestore** | обязательный | Вызывается при успешном завершении покупки. | | **didFailRestore** | обязательный | Вызывается при ошибке восстановления покупки. | | **didFailRendering** | обязательный | Вызывается при ошибке рендеринга интерфейса. В этом случае [обратитесь в поддержку Adapty](mailto:support@adapty.io). | | **fullScreen** | необязательный | Определяет, отображается ли пейвол в полноэкранном режиме или как модальное окно. По умолчанию `true`. | | **didAppear** | необязательный | Вызывается, когда экран пейвола появляется на экране. Также вызывается, когда пользователь нажимает [кнопку веб-пейвола](web-paywall#step-2a-add-a-web-purchase-button) внутри пейвола и веб-пейвол открывается во встроенном браузере. | | **didDisappear** | необязательный | Вызывается, когда экран пейвола закрывается. Также вызывается, когда [веб-пейвол](web-paywall#step-2a-add-a-web-purchase-button), открытый из пейвола во встроенном браузере, исчезает с экрана. | | **didPerformAction** | необязательный | Вызывается при нажатии пользователем кнопки. У разных кнопок разные идентификаторы действий. Два идентификатора заданы заранее: `close` и `openURL`, остальные — пользовательские и задаются в билдере. | | **didSelectProduct** | необязательный | Вызывается, если продукт выбран для покупки — пользователем или системой. | | **didStartPurchase** | необязательный | Вызывается, когда пользователь начинает процесс покупки. | | **didFinishPurchase** | необязательный | Вызывается при успешном завершении покупки. | | **didFinishWebPaymentNavigation** | необязательный | Вызывается после попытки открыть [веб-пейвол](web-paywall) для покупки — успешной или нет. | | **didStartRestore** | необязательный | Вызывается, когда пользователь начинает процесс восстановления покупок. | | **didFailLoadingProducts** | необязательный | Вызывается при ошибках загрузки продуктов. Верните `true`, чтобы повторить загрузку. | | **didPartiallyLoadProducts** | необязательный | Вызывается, когда продукты загружены частично. | | **showAlertItem** | необязательный | Биндинг, управляющий отображением элементов алерта поверх пейвола. | | **showAlertBuilder** | необязательный | Функция для рендеринга представления алерта. | | **placeholderBuilder** | необязательный | Функция для рендеринга заглушки во время загрузки пейвола. | ## Обработка событий в UIKit \{#handling-events-in-uikit\} Для управления процессами, происходящими на экране пейвола в вашем мобильном приложении, или их отслеживания реализуйте методы `AdaptyPaywallControllerDelegate`. ### События, генерируемые пользователем \{#user-generated-events\} #### Выбор продукта \{#product-selection\} Если пользователь выбирает продукт для покупки, будет вызван следующий метод: ```swift showLineNumbers title="Swift" func paywallController( _ controller: AdaptyPaywallController, didSelectProduct product: AdaptyPaywallProductWithoutDeterminingOffer ) { } ``` <Details> <summary>Пример события (нажмите, чтобы раскрыть)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ``` </Details> #### Покупка начата \{#started-purchase\} Если пользователь инициирует процесс покупки, будет вызван этот метод: ```swift showLineNumbers title="Swift" func paywallController(_ controller: AdaptyPaywallController, didStartPurchase product: AdaptyPaywallProduct) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ``` </Details> Не вызывается в режиме Observer. Подробнее см. в разделе [iOS — отображение пейволов Paywall Builder в режиме Observer](ios-present-paywall-builder-paywalls-in-observer-mode). #### Покупка через веб-пейвол \{#started-purchase-using-a-web-paywall\} Если пользователь инициирует процесс покупки через [веб-пейвол](web-paywall), будет вызван этот метод: ```swift showLineNumbers title="Swift" func paywallController( _ controller: AdaptyPaywallController, shouldContinueWebPaymentNavigation product: AdaptyPaywallProduct ) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ``` </Details> #### Успешная или отменённая покупка \{#successful-or-canceled-purchase\} Если покупка прошла успешно, будет вызван следующий метод: ```swift showLineNumbers title="Swift" func paywallController( _ controller: AdaptyPaywallController, didFinishPurchase product: AdaptyPaywallProductWithoutDeterminingOffer, purchaseResult: AdaptyPurchaseResult ) { } } ``` <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```javascript // Successful purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } } } // Cancelled purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "cancelled" } } ``` </Details> Мы рекомендуем в этом случае закрывать экран пейвола. В режиме Observer mode это не вызывается. Подробнее см. в разделе [iOS — отображение пейволов Paywall Builder в режиме Observer mode](ios-present-paywall-builder-paywalls-in-observer-mode). #### Неудачная покупка \{#failed-purchase\} Если покупка завершается с ошибкой, вызывается этот метод. Сюда входят ошибки StoreKit (ограничения платежей, неверные продукты, сетевые сбои), сбои верификации транзакций и системные ошибки. Обратите внимание: отмена пользователем вызывает `didFinishPurchase` с результатом «отменено», а ожидающие платежи этот метод не вызывают. ```swift showLineNumbers title="Swift" func paywallController( _ controller: AdaptyPaywallController, didFailPurchase product: AdaptyPaywallProduct, error: AdaptyError ) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } } } ``` </Details> Не вызывается в режиме Observer mode. Подробнее см. в разделе [iOS — Отображение пейволов Paywall Builder в режиме Observer mode](ios-present-paywall-builder-paywalls-in-observer-mode). #### Ошибка покупки через веб-пейвол \{#failed-purchase-using-a-web-paywall\} Если `Adapty.openWebPaywall()` завершается с ошибкой, будет вызван этот метод: ```swift showLineNumbers title="Swift" func paywallController( _ controller: AdaptyPaywallController, didFailWebPaymentNavigation product: AdaptyPaywallProduct, error: AdaptyError ) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "web_payment_failed", "message": "Web payment navigation failed", "details": { "underlyingError": "Network connection error" } } } ``` </Details> #### Успешное восстановление покупки \{#successful-restore\} Если восстановление покупки прошло успешно, будет вызван этот метод: ```swift showLineNumbers title="Swift" func paywallController( _ controller: AdaptyPaywallController, didFinishRestoreWith profile: AdaptyProfile ) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } ``` </Details> Мы рекомендуем закрывать экран, если у пользователя есть нужный `accessLevel`. Обратитесь к разделу [Статус подписки](subscription-status), чтобы узнать, как его проверить. #### Ошибка восстановления покупок \{#failed-restore\} Если восстановление покупки завершится ошибкой, будет вызван этот метод: ```swift showLineNumbers title="Swift" public func paywallController( _ controller: AdaptyPaywallController, didFailRestoreWith error: AdaptyError ) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ``` </Details> ### Загрузка данных и рендеринг \{#data-fetching-and-rendering\} #### Ошибки загрузки продуктов \{#product-loading-errors\} Если вы не передаёте массив продуктов при инициализации, AdaptyUI самостоятельно получит нужные объекты с сервера. Если эта операция завершится ошибкой, AdaptyUI сообщит об этом через следующий метод: ```swift showLineNumbers title="Swift" public func paywallController( _ controller: AdaptyPaywallController, didFailLoadingProductsWith error: AdaptyError ) -> Bool { return true } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ``` </Details> Если вы вернёте `true`, AdaptyUI повторит запрос через 2 секунды. #### Ошибки рендеринга \{#rendering-errors\} Если во время рендеринга интерфейса возникает ошибка, она будет передана через этот метод: ```swift showLineNumbers title="Swift" public func paywallController( _ controller: AdaptyPaywallController, didFailRenderingWith error: AdaptyError ) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } ``` </Details> В обычной ситуации такие ошибки не должны возникать, поэтому если вы с ними столкнулись — сообщите нам. </SDKv3> --- # File: ios-use-fallback-paywalls --- --- title: "iOS - Использование резервных пейволов" description: "Обработка случаев, когда пользователи офлайн или серверы Adapty недоступны" --- :::warning Резервные пейволы поддерживаются iOS SDK версии 2.11 и выше. ::: Чтобы поддерживать бесперебойный пользовательский опыт, важно настроить [резервные пейволы](/fallback-paywalls) для флоу, [пейволов](paywalls) и [онбордингов](onboardings). Это позволит приложению продолжить работу при частичной или полной потере интернет-соединения. * **Если приложение не может обратиться к серверам Adapty:** Оно сможет отобразить резервный флоу или пейвол, а также использовать локальную конфигурацию онбординга. * **Если приложение не может подключиться к интернету:** Оно сможет отобразить резервный флоу или пейвол. Онбординги содержат удалённый контент и требуют интернет-соединения для работы. :::important Прежде чем следовать шагам этого гайда, [скачайте](/local-fallback-paywalls) файлы резервной конфигурации из Adapty. ::: ## Конфигурация \{#configuration\} 1. Добавьте резервный JSON-файл в бандл проекта: откройте меню **File** в XCode и выберите **Add Files to "YourProjectName"**. 2. Вызовите метод `.setFallback` **до** того, как загружаете целевой флоу, пейвол или онбординг. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { if let urlPath = Bundle.main.url(forResource: fileName, withExtension: "json") { try await Adapty.setFallback(fileURL: urlPath) } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers if let url = Bundle.main.url(forResource: "ios_fallback", withExtension: "json") { Adapty.setFallback(fileURL: url) } ``` </TabItem> </Tabs> Параметры: | Параметр | Описание | | :---------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **fileURL** | Путь к файлу резервной конфигурации. | --- # File: localizations-and-locale-codes --- --- title: "Использование локализаций и кодов языков в iOS SDK" description: "Управляйте локализациями приложения и кодами языков для работы с глобальной аудиторией в вашем iOS-приложении." --- ## Почему это важно \{#why-this-is-important\} Коды языков используются в нескольких сценариях — например, когда нужно получить правильный пейвол для текущей локализации вашего приложения. Поскольку коды языков довольно сложны и могут различаться в зависимости от платформы, мы придерживаемся внутреннего стандарта для всех поддерживаемых платформ. Но именно из-за этой сложности важно понимать, что именно вы отправляете на наш сервер для получения нужной локализации и что происходит дальше — чтобы всегда получать ожидаемый результат. ## Стандарт кодов языков в Adapty \{#locale-code-standard-at-adapty\} Для кодов языков Adapty использует слегка модифицированный стандарт [BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): каждый код состоит из подтегов в нижнем регистре, разделённых дефисами. Примеры: `en` (английский), `pt-br` (португальский (Бразилия)), `zh` (упрощённый китайский), `zh-hant` (традиционный китайский). ## Сопоставление кодов языков \{#locale-code-matching\} Когда Adapty получает запрос от клиентского SDK с кодом языка и начинает поиск соответствующей локализации пейвола, происходит следующее: 1. Входящая строка с кодом языка приводится к нижнему регистру, а все подчёркивания (`_`) заменяются дефисами (`-`) 2. Выполняется поиск локализации с полностью совпадающим кодом языка 3. Если совпадение не найдено, берётся подстрока до первого дефиса (`pt` для `pt-br`) и выполняется поиск по ней 4. Если совпадение снова не найдено, возвращается локализация по умолчанию — `en` Таким образом устройство iOS, отправившее `'pt_BR'`, устройство Android, отправившее `pt-BR`, и другое устройство, отправившее `pt-br`, получат одинаковый результат. ## Реализация локализаций: рекомендуемый способ \{#implementing-localizations-recommended-way\} Если вас интересуют локализации, скорее всего, вы уже работаете с файлами локализованных строк в своём проекте. В таком случае мы рекомендуем добавить пару ключ-значение с нужным кодом языка Adapty в каждый из ваших файлов для соответствующих локализаций, а затем извлекать значение по этому ключу при вызове нашего SDK: ```swift showLineNumbers // 1. Modify your Localizable.strings files /* Localizable.strings - Spanish */ adapty_paywalls_locale = "es"; /* Localizable.strings - Portuguese (Brazil) */ adapty_paywalls_locale = "pt-br"; // 2. Extract and use the locale code let locale = NSLocalizedString("adapty_paywalls_locale", comment: "") // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` Так вы полностью контролируете, какая локализация будет загружена для каждого пользователя вашего приложения. ## Реализация локализаций: альтернативный способ \{#implementing-localizations-the-other-way\} Похожего (но не идентичного) результата можно добиться, не задавая явно коды языков для каждой локализации. Для этого нужно извлекать код языка из других объектов, предоставляемых платформой: ```swift showLineNumbers let locale = Locale.current.identifier // pass locale code to AdaptyUI.getViewConfiguration or Adapty.getPaywall method ``` Мы не рекомендуем этот подход по ряду причин: 1. На iOS предпочитаемые языки и текущая локаль — не одно и то же. Чтобы локализация выбиралась корректно, придётся либо полагаться на логику Apple (которая работает автоматически при использовании рекомендованного подхода с файлами локализованных строк), либо воссоздавать её самостоятельно. 2. Сложно предсказать, что именно получит сервер Adapty. Например, на iOS устройство может вернуть локаль вида `ar_OM@numbers='latn'`, и в ответ вы получите не локализацию `ar-om`, которую ожидали, а `ar` — что, скорее всего, окажется неожиданным. Если вы всё же решите использовать этот подход — убедитесь, что предусмотрели все актуальные сценарии. --- # File: ios-troubleshoot-paywall-builder --- --- title: "Устранение неполадок Paywall Builder в iOS SDK" description: "Устранение неполадок Paywall Builder в iOS SDK" --- Этот гайд поможет вам устранить распространённые проблемы при использовании пейволов, созданных в Adapty Paywall Builder, в iOS SDK. ## Ошибка при получении конфигурации пейвола \{#getting-a-paywall-configuration-fails\} **Проблема**: Метод `getPaywallConfiguration` не может получить конфигурацию пейвола. **Причина**: Пейвол не включён для отображения на устройстве в Paywall Builder. **Решение**: Включите переключатель **Show on device** в Paywall Builder. <img src="/assets/shared/img/show-on-device.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Число просмотров пейвола слишком велико \{#the-paywall-view-number-is-too-big\} **Проблема**: Счётчик просмотров пейвола показывает вдвое больше ожидаемого числа. **Причина**: Возможно, в вашем коде вызывается `logShowFlow` (iOS SDK v4+) / `logShowPaywall`, что дублирует счётчик просмотров при использовании Paywall Builder или Flow Builder. Для флоу и пейволов, созданных с помощью этих инструментов, аналитика отслеживается автоматически, поэтому использовать этот метод не нужно. **Решение**: Убедитесь, что вы не вызываете `logShowFlow` (iOS SDK v4+) / `logShowPaywall` в своём коде, если используете Paywall Builder или Flow Builder. ## Другие проблемы \{#other-issues\} **Проблема**: Вы столкнулись с другими проблемами, связанными с Paywall Builder, которые не описаны выше. **Решение**: При необходимости обновите SDK до последней версии, воспользовавшись [гайдами по миграции](ios-sdk-migration-guides). Многие проблемы уже исправлены в новых версиях SDK. --- # File: ios-present-paywall-builder-paywalls-in-observer-mode --- --- title: "Отображение пейволов Paywall Builder в режиме Observer в iOS SDK" description: "Узнайте, как отображать пейволы PB в режиме Observer для более глубокого анализа." --- Если вы создали пейвол с помощью Paywall Builder, вам не нужно беспокоиться о его отображении в коде мобильного приложения — всё уже настроено. Такой пейвол содержит и то, что должно быть показано, и то, как именно это должно быть показано. :::warning Этот раздел относится только к [режиму Observer](observer-vs-full-mode). Если вы не работаете в режиме Observer, обратитесь к разделу [iOS — отображение пейволов, созданных в Paywall Builder](ios-present-paywalls). ::: <SDKv4> <details> <summary>Перед тем как начать отображать флоу (нажмите, чтобы развернуть)</summary> 1. Настройте первоначальную интеграцию Adapty [с App Store](initial_ios). 2. Установите и настройте SDK. Убедитесь, что параметр `observerMode` установлен в `true`. Обратитесь к [руководству по установке iOS SDK](sdk-installation-ios#activate-adapty-module-of-adapty-sdk). 3. [Создайте продукты](create-product) в дашборде Adapty. 4. [Настройте флоу или пейволы в билдерах](create-paywall) и привяжите к ним продукты. 5. [Создайте плейсменты и назначьте им флоу или пейволы](create-placement). 6. [Получите флоу и их конфигурацию](get-pb-paywalls) в коде мобильного приложения. </details> <p> </p> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> 1. Реализуйте объект `AdaptyObserverModeResolver`. Протокол не изменился по сравнению с SDK v3 — режим наблюдателя работает одинаково как для флоу, так и для пейвола: ```swift showLineNumbers title="Swift" func observerMode(didInitiatePurchase product: AdaptyPaywallProduct, onStartPurchase: @escaping () -> Void, onFinishPurchase: @escaping () -> Void) { // use the product object to handle the purchase // call onStartPurchase / onFinishPurchase to notify AdaptyUI about the purchase progress } func observerModeDidInitiateRestorePurchases(onStartRestore: @escaping () -> Void, onFinishRestore: @escaping () -> Void) { // call onStartRestore / onFinishRestore to notify AdaptyUI about the restore progress } ``` 2. Создайте объект конфигурации флоу, передав ваш resolver в качестве параметра `observerModeResolver:`: ```swift showLineNumbers title="Swift" do { let flowConfiguration = try await AdaptyUI.getFlowConfiguration( forFlow: flow, observerModeResolver: <AdaptyObserverModeResolver> ) } catch { // handle the error } ``` Параметры запроса: | Параметр | Наличие | Описание | | :----------------------- | :------- | :----------------------------------------------------------------------------------------------------------------------- | | **forFlow** | обязательный | Объект `AdaptyFlow`, полученный через `Adapty.getFlow(placementId:)`. См. [Получение флоу и пейволов](get-pb-paywalls). | | **observerModeResolver** | обязательный | Реализованный вами `AdaptyObserverModeResolver`. | 3. Инициализируйте контроллер флоу с помощью `AdaptyUI.flowController(with:delegate:)`: ```swift showLineNumbers title="Swift" import AdaptyUI let visualFlow = try AdaptyUI.flowController( with: flowConfiguration, delegate: <AdaptyFlowControllerDelegate> ) ``` Параметры запроса: | Параметр | Наличие | Описание | | :------------------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------- | | **flowConfiguration** | required | Объект `AdaptyUI.FlowConfiguration`, содержащий визуальные детали флоу. См. [Получение флоу и пейволов](get-pb-paywalls). | | **delegate** | required | `AdaptyFlowControllerDelegate` для прослушивания событий флоу. См. [Обработка событий флоу и пейвола](ios-handling-events). | Возвращаемые значения: | Объект | Описание | | :------------------- | :----------------------------------------------------- | | AdaptyFlowController | Объект, представляющий запрошенный экран флоу. | 4. Отобразите контроллер: ```swift showLineNumbers title="Swift" present(visualFlow, animated: true) ``` :::warning Не забудьте [привязать пейволы к транзакциям покупок](report-transactions-observer-mode). В противном случае Adapty не сможет определить, из какого пейвола была совершена покупка. ::: </TabItem> <TabItem value="swiftui" label="SwiftUI" default> В SwiftUI получите конфигурацию флоу с помощью резолвера и передайте её в модификатор `.flow`: ```swift showLineNumbers title="SwiftUI" @State var flowPresented = false @State var flowConfiguration: AdaptyUI.FlowConfiguration? var body: some View { Text("Hello, AdaptyUI!") .flow( isPresented: $flowPresented, flowConfiguration: flowConfiguration, didPerformAction: { action in switch action { case .close: flowPresented = false default: break } }, didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didReceiveError: { error in flowPresented = false } ) .task { flowConfiguration = try? await AdaptyUI.getFlowConfiguration( forFlow: flow, observerModeResolver: <AdaptyObserverModeResolver> ) } } ``` Параметр `observerModeResolver:` в `getFlowConfiguration` обеспечивает соблюдение вашей кастомной логики покупок в отображаемом флоу — сам модификатор использует те же коллбэки, что и полный режим. :::warning Не забудьте [связать пейволы с транзакциями покупок](report-transactions-observer-mode). Иначе Adapty не сможет определить, из какого пейвола была совершена покупка. ::: </TabItem> </Tabs> </SDKv4> <SDKv3> <Tabs groupId="current-os" queryString> <TabItem value="sdk3" label="Paywall Builder (SDK 3.x)" default> <details> <summary>Прежде чем показывать пейволы (нажмите, чтобы развернуть)</summary> 1. Настройте начальную интеграцию Adapty [с Google Play](initial-android) и [с App Store](initial_ios). 2. Установите и настройте Adapty SDK. Убедитесь, что параметр `observerMode` установлен в значение `true`. Обратитесь к нашим инструкциям для конкретных фреймворков: [iOS](sdk-installation-ios#activate-adapty-module-of-adapty-sdk). 3. [Создайте продукты](create-product) в дашборде Adapty. 4. [Настройте пейволы, добавьте к ним продукты](create-paywall) и кастомизируйте их с помощью Paywall Builder в дашборде Adapty. 5. [Создайте плейсменты и назначьте им пейволы](create-placement) в дашборде Adapty. 6. [Получите пейволы Paywall Builder и их конфигурацию](get-pb-paywalls) в коде вашего мобильного приложения. </details> <p> </p> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> 1. Реализуйте объект `AdaptyObserverModeResolver`: ```swift showLineNumbers title="Swift" func observerMode(didInitiatePurchase product: AdaptyPaywallProduct, onStartPurchase: @escaping () -> Void, onFinishPurchase: @escaping () -> Void) { // use the product object to handle the purchase // use the onStartPurchase and onFinishPurchase callbacks to notify AdaptyUI about the process of the purchase } func observerModeDidInitiateRestorePurchases(onStartRestore: @escaping () -> Void, onFinishRestore: @escaping () -> Void) { // use the onStartRestore and onFinishRestore callbacks to notify AdaptyUI about the process of the restore } ``` Событие `observerMode(didInitiatePurchase:onStartPurchase:onFinishPurchase:)` уведомит вас о том, что пользователь инициировал покупку. В ответ на этот колбэк вы можете запустить собственный флоу покупки. Событие `observerModeDidInitiateRestorePurchases(onStartRestore:onFinishRestore:)` уведомит вас о том, что пользователь инициировал восстановление покупок. В ответ на этот колбэк вы можете запустить собственный флоу восстановления. Также не забудьте вызывать следующие колбэки, чтобы уведомить AdaptyUI о процессе покупки или восстановления. Это необходимо для корректной работы пейвола — например, для отображения загрузчика: | Callback | Описание | | :----------------- | :---------------------------------------------------------------------------------------------- | | onStartPurchase() | Колбэк необходимо вызвать, чтобы уведомить AdaptyUI о начале покупки. | | onFinishPurchase() | Колбэк необходимо вызвать, чтобы уведомить AdaptyUI о завершении покупки. | | onStartRestore() | Колбэк необходимо вызвать, чтобы уведомить AdaptyUI о начале восстановления покупок. | | onFinishRestore() | Колбэк необходимо вызвать, чтобы уведомить AdaptyUI о завершении восстановления покупок. | 2. Создайте объект конфигурации пейвола: ```swift showLineNumbers title="Swift" do { let paywallConfiguration = try AdaptyUI.getPaywallConfiguration( forPaywall: <paywall object>, observerModeResolver: <AdaptyObserverModeResolver> ) } catch { // handle the error } ``` Параметры запроса: | Параметр | Наличие | Описание | | :----------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Paywall** | обязательный | Объект `AdaptyPaywall` для получения контроллера нужного пейвола. | | **ObserverModeResolver** | обязательный | Объект `AdaptyObserverModeResolver`, реализованный на предыдущем шаге. | 3. Инициализируйте визуальный пейвол, который хотите отобразить, с помощью метода `.paywallController(for:products:viewConfiguration:delegate:)`: ```swift showLineNumbers title="Swift" import AdaptyUI let visualPaywall = AdaptyUI.paywallController( with: <paywall configuration object>, delegate: <AdaptyPaywallControllerDelegate> ) ``` Параметры запроса: | Параметр | Наличие | Описание | | :----------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Paywall Configuration** | обязательный | Объект `AdaptyUI.PaywallConfiguration`, содержащий визуальные данные пейвола. Используйте метод `AdaptyUI.getPaywallConfiguration(forPaywall:locale:)`. Подробнее см. в разделе [Получение пейволов Paywall Builder и их конфигурации](get-pb-paywalls). | | **Delegate** | обязательный | Объект `AdaptyPaywallControllerDelegate` для прослушивания событий пейвола. Подробнее см. в разделе [Обработка событий пейвола](ios-handling-events). | Возвращает: | Объект | Описание | | :---------------------- | :--------------------------------------------------- | | AdaptyPaywallController | Объект, представляющий запрошенный экран пейвола | После успешного создания объекта его можно отобразить следующим образом: ```swift showLineNumbers title="Swift" present(visualPaywall, animated: true) ``` :::warning Не забудьте [связать пейволы с транзакциями покупок](report-transactions-observer-mode). Иначе Adapty не сможет определить, с какого пейвола была совершена покупка. ::: </TabItem> <TabItem value="swiftui" label="SwiftUI" default> Чтобы отобразить визуальный пейвол на экране устройства, используйте модификатор `.paywall` в SwiftUI: ```swift showLineNumbers title="SwiftUI" @State var paywallPresented = false var body: some View { Text("Hello, AdaptyUI!") .paywall( isPresented: $paywallPresented, paywallConfiguration: <paywall configuration object>, didPerformAction: { action in switch action { case .close: paywallPresented = false default: // Handle other actions break } }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didFailRendering: { error in paywallPresented = false } ) } ``` Параметры запроса: | Параметр | Наличие | Описание | | :----------------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Paywall Configuration** | обязательный | Объект `AdaptyUI.PaywallConfiguration`, содержащий визуальные параметры пейвола. Используйте метод `AdaptyUI.getPaywallConfiguration(forPaywall:locale:)`. Подробнее см. в разделе [Получение пейволов Paywall Builder и их конфигурации](get-pb-paywalls). | | **Products** | необязательный | Передайте массив объектов `AdaptyPaywallProduct`, чтобы оптимизировать время отображения продуктов на экране. Если передать `nil`, AdaptyUI автоматически загрузит необходимые продукты. | | **TagResolver** | необязательный | Задайте словарь пользовательских тегов и их значений. Пользовательские теги служат плейсхолдерами в контенте пейвола и динамически заменяются конкретными строками для персонализации содержимого. Подробнее см. в разделе «Пользовательские теги в Paywall Builder». | | **ObserverModeResolver** | необязательный | Объект `AdaptyObserverModeResolver`, реализованный на предыдущем шаге. | Параметры замыкания: | Параметр замыкания | Описание | | :------------------- | :------------------------------------------------------------------------------------------------ | | **didFinishRestore** | Вызывается, если Adapty.restorePurchases() выполняется успешно. | | **didFailRestore** | Вызывается, если Adapty.restorePurchases() завершается с ошибкой. | | **didFailRendering** | Вызывается, если в процессе отрисовки интерфейса возникает ошибка. | Обратитесь к разделу [iOS - Обработка событий](ios-handling-events) за описанием других параметров замыкания. :::warning Не забудьте [связать пейволы с транзакциями покупок](report-transactions-observer-mode). Иначе Adapty не сможет определить, из какого пейвола была совершена покупка. ::: </TabItem> </Tabs> </TabItem> <TabItem value="sdk2" label="Legacy Paywall Builder (SDK up to 2.x)" default> <details> <summary>Before you start presenting paywalls (Click to Expand)</summary> 1. Настройте базовую интеграцию Adapty [с Google Play](initial-android) и [с App Store](initial_ios). 1. Установите и настройте Adapty SDK. Обязательно задайте параметру `observerMode` значение `true`. Смотрите инструкции для конкретных фреймворков: [iOS](sdk-installation-ios#activate-adapty-module-of-adapty-sdk), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk) и [Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk). 2. [Создайте продукты](create-product) в дашборде Adapty. 3. [Настройте пейволы, добавьте к ним продукты](create-paywall) и кастомизируйте их с помощью Paywall Builder в дашборде Adapty. 4. [Создайте плейсменты и привяжите к ним пейволы](create-placement) в дашборде Adapty. 5. [Загрузите пейволы Paywall Builder и их конфигурацию](get-pb-paywalls) в коде вашего мобильного приложения. </details> <p> </p> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> 1. Реализуйте объект `AdaptyObserverModeDelegate`: ```swift showLineNumbers title="Swift" func paywallController(_ controller: AdaptyPaywallController, didInitiatePurchase product: AdaptyPaywallProduct, onStartPurchase: @escaping () -> Void, onFinishPurchase: @escaping () -> Void) { // use the product object to handle the purchase // use the onStartPurchase and onFinishPurchase callbacks to notify AdaptyUI about the process of the purchase } ``` `paywallController(_:didInitiatePurchase:onStartPurchase:onFinishPurchase:)` уведомляет о том, что пользователь инициировал покупку. В ответ на это событие вы можете запустить собственный флоу покупки. Также не забудьте вызвать следующие колбэки, чтобы уведомить AdaptyUI о ходе покупки. Это необходимо для корректного поведения пейвола, например для отображения лоадера: | Callback | Description | | :--------------- | :------------------------------------------------------------------------------- | | onStartPurchase | Этот коллбэк нужно вызвать, чтобы уведомить AdaptyUI о начале покупки. | | onFinishPurchase | Этот коллбэк нужно вызвать, чтобы уведомить AdaptyUI о завершении покупки. | 2. Инициализируйте визуальный пейвол, который нужно отобразить, с помощью метода `.paywallController(for:products:viewConfiguration:delegate:observerModeDelegate:)`: ```swift showLineNumbers title="Swift" import AdaptyUI let visualPaywall = AdaptyUI.paywallController( for: <paywall object>, products: <paywall products array>, viewConfiguration: <LocalizedViewConfiguration>, delegate: <AdaptyPaywallControllerDelegate> observerModeDelegate: <AdaptyObserverModeDelegate> ) ``` Параметры запроса: | Параметр | Обязательность | Описание | | :----------------------- | :------------- | :----------------------------------------------------------- | | **Paywall** | обязательный | Объект `AdaptyPaywall` для получения контроллера нужного пейвола. | | **Products** | необязательный | Передайте массив объектов `AdaptyPaywallProduct`, чтобы оптимизировать время отображения продуктов на экране. Если передать `nil`, AdaptyUI автоматически загрузит необходимые продукты. | | **ViewConfiguration** | обязательный | Объект `AdaptyUI.LocalizedViewConfiguration`, содержащий визуальные настройки пейвола. Используйте метод `AdaptyUI.getViewConfiguration(paywall:locale:)`. Подробнее см. в разделе [Получение пейволов Paywall Builder и их конфигурации](get-pb-paywalls). | | **Delegate** | обязательный | Объект `AdaptyPaywallControllerDelegate` для обработки событий пейвола. Подробнее см. в разделе [Обработка событий пейвола](ios-handling-events). | | **ObserverModeDelegate** | обязательный | Объект `AdaptyObserverModeDelegate`, реализованный на предыдущем шаге. | | **TagResolver** | необязательный | Определите словарь пользовательских тегов и их значений. Пользовательские теги служат плейсхолдерами в контенте пейвола и динамически заменяются конкретными строками для персонализации содержимого. Подробнее см. в разделе «Пользовательские теги в Paywall Builder». | Возвращает: | Объект | Описание | | :---------------------- | :--------------------------------------------------- | | AdaptyPaywallController | Объект, представляющий запрошенный экран пейвола | После успешного создания объекта его можно отобразить следующим образом: ```swift showLineNumbers title="Swift" present(visualPaywall, animated: true) ``` :::warning Не забудьте [связать пейволы с транзакциями покупок](report-transactions-observer-mode). Иначе Adapty не сможет определить, из какого пейвола была совершена покупка. ::: </TabItem> <TabItem value="swiftui" label="SwiftUI" default> Чтобы отобразить визуальный пейвол на экране устройства, используйте модификатор `.paywall` в SwiftUI: ```swift showLineNumbers title="SwiftUI" @State var paywallPresented = false var body: some View { Text("Hello, AdaptyUI!") .paywall( isPresented: $paywallPresented, paywall: <paywall object>, configuration: <LocalizedViewConfiguration>, didPerformAction: { action in switch action { case .close: paywallPresented = false default: // Handle other actions break } }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didFailRendering: { error in paywallPresented = false }, observerModeDidInitiatePurchase: { product, onStartPurchase, onFinishPurchase in // use the product object to handle the purchase // use the onStartPurchase and onFinishPurchase callbacks to notify AdaptyUI about the process of the purchase }, ) } ``` Параметры запроса: | Параметр | Обязательность | Описание | | :---------------- | :------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Paywall** | обязательный | Объект `AdaptyPaywall` для получения контроллера нужного пейвола. | | **Product** | необязательный | Передайте массив объектов `AdaptyPaywallProduct`, чтобы оптимизировать время отображения продуктов на экране. Если передать `nil`, AdaptyUI автоматически загрузит необходимые продукты. | | **Configuration** | обязательный | Объект `AdaptyUI.LocalizedViewConfiguration`, содержащий визуальные параметры пейвола. Используйте метод `AdaptyUI.getViewConfiguration(paywall:locale:)`. Подробнее см. в разделе [Получение пейволов Paywall Builder и их конфигурации](get-pb-paywalls). | | **TagResolver** | необязательный | Задайте словарь пользовательских тегов и их значений. Пользовательские теги служат плейсхолдерами в содержимом пейвола и динамически заменяются конкретными строками для персонализации контента. Подробнее см. в разделе о пользовательских тегах в Paywall Builder. | Параметры замыкания: | Параметр closure | Описание | | :---------------------------------- | :---------------------------------------------------------------------------------------------- | | **didFinishRestore** | Вызывается, если Adapty.restorePurchases() выполняется успешно. | | **didFailRestore** | Вызывается, если Adapty.restorePurchases() завершается с ошибкой. | | **didFailRendering** | Вызывается, если во время отрисовки интерфейса возникает ошибка. | | **observerModeDidInitiatePurchase** | Вызывается, когда пользователь инициирует покупку. | Дополнительные параметры замыкания описаны в разделе [iOS - Обработка событий](ios-handling-events). :::warning Не забудьте [привязать пейволы к транзакциям покупок](report-transactions-observer-mode). Иначе Adapty не сможет определить, с какого пейвола была совершена покупка. ::: </TabItem> </Tabs> </TabItem> </Tabs> </SDKv3> --- # File: ios-quickstart-manual --- --- title: "Включение покупок в кастомном пейволе в iOS SDK" description: "Интегрируйте Adapty SDK в свои кастомные iOS пейволы для поддержки встроенных покупок." --- В этом гайде описана интеграция Adapty в кастомные пейволы. Вы сохраняете полный контроль над реализацией пейвола, а SDK Adapty берёт на себя загрузку продуктов, обработку новых покупок и восстановление предыдущих. :::important **Этот гайд предназначен для разработчиков, которые реализуют пользовательские пейволы.** Если вы хотите самый простой способ подключить покупки, воспользуйтесь [Adapty Flow Builder](ios-quickstart-paywalls). С Flow Builder вы создаёте флоу в визуальном редакторе без кода, Adapty автоматически берёт на себя всю логику покупок, а тестировать разные дизайны можно без повторной публикации приложения. ::: ## Перед началом работы \{#before-you-start\} ### Настройка продуктов \{#set-up-products\} Чтобы подключить встроенные покупки, нужно разобраться в трёх ключевых понятиях: - [**Продукты**](product) – всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ) - [**Пейволы**](paywalls) – конфигурации, определяющие, какие продукты предлагать. В Adapty пейволы — единственный способ получить продукты, но такой подход позволяет менять продукты, цены и офферы без изменений в коде приложения. - [**Плейсменты**](placements) – где и когда показывать пейволы в приложении (например, `main`, `onboarding`, `settings`). Вы настраиваете пейволы для плейсментов в дашборде, а затем запрашиваете их по ID плейсмента в коде. Это упрощает проведение A/B-тестов и показ разных пейволов разным пользователям. Убедитесь, что вы разобрались с этими концепциями, даже если используете собственный пейвол. По сути, это просто способ управлять продуктами, которые вы продаёте в приложении. Чтобы реализовать собственный пейвол, нужно создать **пейвол** и добавить его в **плейсмент**. Это позволит вам получать свои продукты. Чтобы понять, что нужно сделать в дашборде, следуйте quickstart-гайду [здесь](quickstart). ### Управление пользователями \{#manage-users\} Вы можете работать как с backend-аутентификацией на вашей стороне, так и без неё. Однако Adapty SDK по-разному обрабатывает анонимных и идентифицированных пользователей. Прочитайте [гайд по идентификации](ios-quickstart-identify), чтобы разобраться в особенностях и правильно работать с пользователями. ## Шаг 1. Получите продукты \{#step-1-get-products\} Чтобы получить продукты для вашего кастомного пейвола, нужно: 1. Получить объект `flow`, передав ID [плейсмента](placements) в метод `getFlow`. 2. Получить массив продуктов для этого флоу с помощью метода `getPaywallProducts`. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift func loadPaywall() async { do { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") let products = try await Adapty.getPaywallProducts(flow: flow) // Use products to build your custom paywall UI } catch { // Handle the error } } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift func loadPaywall() { Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(flow): Adapty.getPaywallProducts(flow: flow) { result in switch result { case let .success(products): // Use products to build your custom paywall UI case let .failure(error): // Handle the error } } case let .failure(error): // Handle the error } } } ``` </TabItem> </Tabs> ## Шаг 2. Обработка покупок \{#step-2-accept-purchases\} Когда пользователь нажимает на продукт в вашем кастомном пейволе, вызовите метод `makePurchase` с выбранным продуктом. Это запустит процесс покупки и вернёт обновлённый профиль. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift func purchaseProduct(_ product: AdaptyPaywallProduct) async { do { let purchaseResult = try await Adapty.makePurchase(product: product) switch purchaseResult { case .userCancelled: // Пользователь отменил покупку break case .pending: // Покупка ожидает подтверждения (например, требуется разрешение родителей) break case let .success(profile, transaction): // Покупка успешна, профиль обновлён break } } catch { // Обработка ошибки } } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift func purchaseProduct(_ product: AdaptyPaywallProduct) { Adapty.makePurchase(product: product) { result in switch result { case let .success(purchaseResult): switch purchaseResult { case .userCancelled: // Пользователь отменил покупку break case .pending: // Покупка ожидает подтверждения (например, ожидает родительского одобрения) break case let .success(profile, transaction): // Покупка успешна, профиль обновлён break } case let .failure(error): // Обработка ошибки } } } ``` </TabItem> </Tabs> ## Шаг 3. Восстановление покупок \{#step-3-restore-purchases\} Apple требует, чтобы все приложения с подписками предоставляли пользователям возможность восстановить свои покупки. Хотя покупки восстанавливаются автоматически при входе в систему через Apple ID, вы всё равно обязаны добавить кнопку восстановления в своё приложение. Вызывайте метод `restorePurchases`, когда пользователь нажимает на кнопку восстановления. Это синхронизирует историю покупок с Adapty и вернёт обновлённый профиль. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift func restorePurchases() async { do { let profile = try await Adapty.restorePurchases() // Restore successful, profile updated } catch { // Handle the error } } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift func restorePurchases() { Adapty.restorePurchases { result in switch result { case let .success(profile): // Восстановление прошло успешно, профиль обновлён case let .failure(error): // Обработка ошибки } } } ``` </TabItem> </Tabs> ## Следующие шаги \{#next-steps\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Ваш пейвол готов к отображению в приложении. [Протестируйте покупки в режиме песочницы](test-purchases-in-sandbox), чтобы убедиться, что вы можете выполнить тестовую покупку через пейвол. Затем [проверьте, совершили ли пользователи покупку](ios-check-subscription-status), чтобы решить, показывать пейвол или предоставить доступ к платным функциям. --- # File: fetch-paywalls-and-products --- --- title: "Получение пейволов и продуктов для пейволов с Remote Config в iOS SDK" description: "Получайте пейволы и продукты в Adapty iOS SDK для улучшения монетизации пользователей." --- <SDKv4> Прежде чем отображать Remote Config и кастомные пейволы, нужно получить информацию о них. Обратите внимание: эта тема посвящена Remote Config и кастомным пейволам. Для получения флоу или пейволов, настроенных в **Flow Builder** или **Paywall Builder**, обратитесь к <InlineTooltip tooltip="гайдам по получению флоу и пейволов в приложении">[iOS](get-pb-paywalls), [Android](android-get-pb-paywalls), [React Native](react-native-get-pb-paywalls), [Flutter](flutter-get-pb-paywalls), и [Unity](unity-get-pb-paywalls)</InlineTooltip>. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Прежде чем начать получать флоу и продукты в мобильном приложении (нажмите, чтобы развернуть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте флоу или пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них флоу или пейвол](create-placement) в дашборде Adapty. 4. [Установите Adapty SDK](sdk-installation-ios) в своём мобильном приложении. </details> ## Получение информации о флоу \{#fetch-flow-information\} В Adapty [продукт](product) объединяет в себе продукты из App Store и Google Play. Эти кросс-платформенные продукты интегрируются во флоу и пейволы, позволяя демонстрировать их в конкретных плейсментах мобильного приложения. Чтобы отобразить продукты, необходимо получить `AdaptyFlow` из одного из ваших [плейсментов](placements) с помощью метода `getFlow`. :::important **Не хардкодьте идентификаторы продуктов.** Единственный ID, который нужно хардкодить, — это ID плейсмента. Флоу настраиваются удалённо, поэтому количество продуктов и доступных офферов может меняться в любой момент. Ваше приложение должно обрабатывать эти изменения динамически — если сегодня флоу возвращает два продукта, а завтра три, отображайте все из них без изменений в коде. ::: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") // the requested flow } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(flow): // the requested flow case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают самые актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите вариант `.returnCacheDataElseLoad`: он возвращает кешированные данные при их наличии. В этом случае пользователи могут не получить самые свежие данные, зато время загрузки будет быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы сократить количество сетевых запросов.</p><p></p><p>Кеш сохраняется после перезапуска приложения и очищается только при его переустановке или вручную.</p><p></p><p>Adapty SDK хранит флоу и пейволы в двух слоях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](fallback-paywalls). Для ускоренной загрузки флоу и пейволов также используется CDN, а на случай его недоступности — отдельный резервный сервер.</p> | | **loadTimeout** | по умолчанию: 5 сек | <p>Это значение ограничивает таймаут метода. При его истечении возвращаются кешированные данные или локальный резерв.</p><p></p><p>Обратите внимание, что в редких случаях метод может завершиться с небольшим превышением таймаута, заданного в `loadTimeout`, поскольку операция может состоять из нескольких запросов под капотом.</p> | :::note В v4 параметр `locale` перенесён из `getFlow` в `getFlowConfiguration` (используется только при рендеринге через AdaptyUI). Для кастомных пейволов все доступные локали возвращаются вместе в `flow.remoteConfigs` — выберите ту, которая соответствует языку устройства пользователя или настройкам вашего приложения. ::: Не прописывайте ID продуктов в коде! Поскольку флоу настраиваются удалённо, доступные продукты, их количество и специальные предложения (например, бесплатные пробные периоды) могут меняться со временем. Убедитесь, что ваш код корректно обрабатывает подобные сценарии. Например, если изначально вы получаете 2 продукта, приложение должно отображать именно 2 продукта. Но если впоследствии вы получите 3 продукта, приложение должно показать все 3 без каких-либо изменений в коде. Единственное, что нужно прописать в коде — это ID плейсмента. Параметры ответа: | Параметр | Описание | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | Объект `AdaptyFlow`, содержащий плейсмент, идентификаторы (`id`, `variationId`), название, массив `remoteConfigs` (по одной записи на каждую настроенную локаль) и флаг `hasViewConfiguration`. Чтобы получить продукты для флоу, вызовите `getPaywallProducts(flow:)`. | ## Получение продуктов \{#fetch-products\} После получения флоу вы можете запросить массив продуктов, соответствующий ему: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let products = try await Adapty.getPaywallProducts(flow: flow) // the requested products array } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getPaywallProducts(flow: flow) { result in switch result { case let .success(products): // запрошенный массив продуктов case let .failure(error): // обработка ошибки } } ``` </TabItem> </Tabs> Параметры ответа: | Параметр | Описание | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Список объектов [`AdaptyPaywallProduct`](https://swift.adapty.io/documentation/adapty/adaptypaywallproduct) с идентификатором продукта, его названием, ценой, валютой, длительностью подписки и рядом других свойств. | При реализации собственного дизайна пейвола вам, скорее всего, понадобятся свойства объекта [`AdaptyPaywallProduct`](https://swift.adapty.io/documentation/adapty/adaptypaywallproduct). Ниже показаны наиболее часто используемые свойства, однако полный список доступных свойств приведён в документации по ссылке. | Свойство | Описание | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Чтобы отобразить название продукта, используйте `product.localizedTitle`. Локализация основана на стране стора, выбранной пользователем, а не на локали устройства. | | **Price** | Чтобы отобразить локализованную цену, используйте `product.localizedPrice`. Локализация основана на локали устройства. Цену в числовом виде можно получить через `product.price` — значение будет в местной валюте. Для получения символа валюты используйте `product.currencySymbol`. | | **Subscription Period** | Чтобы отобразить период (например, неделя, месяц, год и т. д.), используйте `product.localizedSubscriptionPeriod`. Локализация основана на локали устройства. Для программного получения периода подписки используйте `product.subscriptionPeriod`. Из этого объекта доступен enum `unit` с единицей периода (day, week, month, year или unknown). Значение `numberOfUnits` возвращает количество единиц периода. Например, для квартальной подписки в свойстве unit будет `.month`, а в numberOfUnits — `3`. | | **Introductory Offer** | Чтобы показать бейдж или другой индикатор наличия introductory offer, обратитесь к свойству `product.subscriptionOffer`. Этот объект содержит следующие полезные свойства:<br/>• `offerType`: enum со значениями `introductory`, `promotional` и `winBack`. Бесплатные пробные периоды и начальные скидочные подписки имеют тип `introductory`.<br/>• `price`: скидочная цена в числовом виде. Для бесплатных пробных периодов здесь будет `0`.<br/>• `localizedPrice`: отформатированная цена скидки для локали пользователя.<br/>• `localizedNumberOfPeriods`: строка, локализованная по локали устройства, описывающая длительность предложения. Например, для трёхдневного пробного периода в этом поле будет `3 days`.<br/>• `subscriptionPeriod`: для получения отдельных деталей периода предложения можно использовать это свойство — оно работает так же, как описано в предыдущем разделе.<br/>• `localizedSubscriptionPeriod`: отформатированный период подписки скидочного предложения для локали пользователя. | :::note В v4 все продукты, возвращаемые методом `getPaywallProducts(flow:)`, уже содержат информацию о подходящих офферах. Отдельный вызов `getPaywallProductsWithoutDeterminingOffer` из v3 был удалён. ::: ## Ускорьте загрузку флоу с помощью флоу для аудитории по умолчанию \{#speed-up-flow-fetching-with-default-audience-flow\} Как правило, флоу загружаются практически мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и плейсментов, а у ваших пользователей слабое интернет-соединение, загрузка флоу может занять больше времени, чем хотелось бы. В таких ситуациях имеет смысл показывать флоу по умолчанию — это обеспечит плавный пользовательский опыт вместо пустого экрана. Чтобы решить эту задачу, можно использовать метод `getFlowForDefaultAudience`, который получает флоу указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать флоу через метод `getFlow`, как описано в разделе [Получение информации о флоу](fetch-paywalls-and-products#fetch-flow-information) выше. :::warning Почему мы рекомендуем использовать `getFlow` Метод `getFlowForDefaultAudience` имеет ряд существенных недостатков: - **Возможные проблемы с обратной совместимостью**: если вам нужно показывать разные флоу для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать флоу с поддержкой текущей (устаревшей) версии, либо мириться с тем, что пользователи на этой версии могут столкнуться с нерендерящимися флоу. - **Потеря таргетинга**: все пользователи будут видеть один и тот же флоу, настроенный для аудитории **All Users**, — а значит, вы теряете персонализированный таргетинг (в том числе по странам, маркетинговой атрибуции или собственным кастомным атрибутам). Если вас устраивают эти недостатки ради более быстрой загрузки флоу, используйте метод `getFlowForDefaultAudience` следующим образом. В противном случае придерживайтесь метода `getFlow`, описанного [выше](fetch-paywalls-and-products#fetch-flow-information). ::: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let flow = try await Adapty.getFlowForDefaultAudience(placementId: "YOUR_PLACEMENT_ID") // the requested flow } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getFlowForDefaultAudience(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(flow): // запрошенный флоу case let .failure(error): // обработка ошибки } } ``` </TabItem> </Tabs> | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указываете при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `.returnCacheDataElseLoad` — этот режим возвращает кешированные данные, если они есть. В таком сценарии пользователи могут получить не самые свежие данные, зато загрузка будет быстрее независимо от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии во избежание лишних сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или ручной очистке.</p> | </SDKv4> <SDKv3> Прежде чем отображать Remote Config и кастомные пейволы, нужно получить информацию о них. Обратите внимание, что этот раздел посвящён Remote Config и кастомным пейволам. Чтобы узнать, как получать пейволы, оформленные с помощью Paywall Builder, обратитесь к <InlineTooltip tooltip="гайды по получению пейволов Paywall Builder в вашем приложении">[iOS](get-pb-paywalls), [Android](android-get-pb-paywalls), [React Native](react-native-get-pb-paywalls), [Flutter](flutter-get-pb-paywalls) и [Unity](unity-get-pb-paywalls)</InlineTooltip>. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Прежде чем начать получать пейволы и продукты в мобильном приложении (нажмите, чтобы развернуть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте пейвол в плейсмент](create-placement) в дашборде Adapty. 4. [Установите Adapty SDK](sdk-installation-ios) в мобильном приложении. </details> ## Получение информации о пейволе \{#fetch-paywall-information\} В Adapty [продукт](product) объединяет продукты из App Store и Google Play. Эти кросс-платформенные продукты добавляются в пейволы, что позволяет показывать их в нужных плейсментах мобильного приложения. Чтобы отобразить продукты, нужно получить [пейвол](paywalls) из одного из ваших [плейсментов](placements) с помощью метода `getPaywall`. :::important **Не хардкодьте ID продуктов.** Единственный ID, который можно захардкодить — это ID плейсмента. Пейволы настраиваются удалённо, поэтому количество продуктов и доступных офферов может меняться в любой момент. Приложение должно обрабатывать эти изменения динамически — если сегодня пейвол возвращает два продукта, а завтра три, отображайте все без изменений в коде. ::: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let paywall = try await Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID") // the requested paywall } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en") { result in switch result { case let .success(paywall): // the requested paywall case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>необязательный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается языковой код, состоящий из одного или нескольких подтегов, разделённых символом минус (**-**). Первый подтег — язык, второй — регион.</p><p></p><p>Пример: `en` означает английский, `pt-br` — бразильский португальский.</p><p></p><p>Подробнее о кодах локализаций и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `.returnCacheDataElseLoad` — оно возвращает кешированные данные, если они есть. В этом случае пользователи могут не получить самые свежие данные, зато загрузка будет быстрее независимо от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при переустановке или ручной очистке.</p><p></p><p>Adapty SDK хранит пейволы в двух слоях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](fallback-paywalls). Также используется CDN для ускорения загрузки пейволов и отдельный резервный сервер на случай недоступности CDN. Эта система гарантирует, что вы всегда получаете актуальную версию пейволов, обеспечивая надёжность даже при слабом интернете.</p> | | **loadTimeout** | по умолчанию: 5 сек | <p>Это значение ограничивает таймаут метода. Если таймаут истекает, возвращаются кешированные данные или локальный резервный вариант.</p><p></p><p>Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения, указанного в `loadTimeout`, поскольку операция может включать несколько запросов под капотом.</p> | Не задавайте product ID в коде! Поскольку пейволы настраиваются удалённо, доступные продукты, их количество и специальные предложения (например, бесплатные пробные периоды) могут меняться со временем. Убедитесь, что ваш код учитывает эти сценарии. Например, если изначально вы получаете 2 продукта, приложение должно отображать именно 2 продукта. Но если позже вы получите 3 продукта, приложение должно показать все 3 без каких-либо изменений в коде. Единственное, что нужно задать в коде, — это ID плейсмента. Параметры ответа: | Параметр | Описание | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Объект [`AdaptyPaywall`](https://swift.adapty.io/documentation/adapty/adaptypaywall) содержит: список ID продуктов, идентификатор пейвола, Remote Config и ряд других свойств. | ## Получить продукты \{#fetch-products\} Когда у вас есть пейвол, вы можете запросить массив продуктов, соответствующих ему: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let products = try await Adapty.getPaywallProducts(paywall: paywall) // the requested products array } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getPaywallProducts(paywall: paywall) { result in switch result { case let .success(products): // запрошенный массив продуктов case let .failure(error): // обработка ошибки } } ``` </TabItem> </Tabs> Параметры ответа: | Параметр | Описание | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Список объектов [`AdaptyPaywallProduct`](https://swift.adapty.io/documentation/adapty/adaptypaywallproduct) с: идентификатором продукта, названием продукта, ценой, валютой, длительностью подписки и рядом других свойств. | Реализуя собственный дизайн пейвола, вам, скорее всего, понадобятся свойства объекта [`AdaptyPaywallProduct`](https://swift.adapty.io/documentation/adapty/adaptypaywallproduct). Ниже приведены наиболее часто используемые из них — полный список доступных свойств смотрите в связанной документации. | Свойство | Описание | |-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Чтобы отобразить название продукта, используйте `product.localizedTitle`. Локализация основана на стране стора, выбранной пользователем, а не на языке устройства. | | **Price** | Чтобы отобразить локализованную цену, используйте `product.localizedPrice`. Локализация основана на настройках локали устройства. Цену в виде числа можно получить через `product.price` — значение будет в местной валюте. Для получения символа валюты используйте `product.currencySymbol`. | | **Subscription Period** | Чтобы отобразить период (например, неделя, месяц, год и т. д.), используйте `product.localizedSubscriptionPeriod`. Локализация основана на локали устройства. Для программного получения периода подписки используйте `product.subscriptionPeriod`. Из этого объекта можно получить enum `unit`, который возвращает единицу измерения (день, неделя, месяц, год или unknown). Значение `numberOfUnits` содержит количество периодических единиц. Например, для квартальной подписки в свойстве `unit` будет `.month`, а в `numberOfUnits` — `3`. | | **Introductory Offer** | Чтобы отобразить бейдж или другой индикатор наличия introductory offer у подписки, используйте свойство `product.subscriptionOffer`. Этот объект содержит следующие полезные свойства:<br/>• `offerType`: enum со значениями `introductory`, `promotional` и `winBack`. Бесплатные пробные периоды и первоначальные скидки относятся к типу `introductory`.<br/>• `price`: скидочная цена в виде числа. Для бесплатных пробных периодов здесь будет `0`.<br/>• `localizedPrice`: отформатированная цена скидки для локали пользователя.<br/>• `localizedNumberOfPeriods`: строка, локализованная с учётом локали устройства, описывающая длительность предложения. Например, для трёхдневного пробного периода в этом поле будет `3 days`.<br/>• `subscriptionPeriod`: альтернативный способ получить отдельные параметры периода предложения. Работает так же, как описано в предыдущем разделе.<br/>• `localizedSubscriptionPeriod`: отформатированный период подписки скидки для локали пользователя. | ## Проверка доступности introductory offer на iOS \{#check-intro-offer-eligibility-on-ios\} По умолчанию метод `getPaywallProducts` проверяет доступность introductory offer, promotional offer и win-back offer. Если нужно показать продукты до того, как SDK определит доступность офферов, используйте вместо него метод `getPaywallProductsWithoutDeterminingOffer`. :::note После показа первоначальных продуктов обязательно вызовите стандартный метод `getPaywallProducts`, чтобы обновить продукты с актуальной информацией о доступности офферов. ::: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let products = try await Adapty.getPaywallProductsWithoutDeterminingOffer(paywall: paywall) // the requested products array without subscriptionOffer } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getPaywallProductsWithoutDeterminingOffer(paywall: paywall) { result in switch result { case let .success(products): // запрошенный массив продуктов без subscriptionOffer case let .failure(error): // обработка ошибки } } ``` </TabItem> </Tabs> ## Ускорьте загрузку пейвола с помощью пейвола для аудитории по умолчанию \{#speed-up-paywall-fetching-with-default-audience-paywall\} Как правило, пейволы загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и пейволов, а пользователь работает с нестабильным интернетом, загрузка пейвола может занять дольше, чем хотелось бы. В таких случаях имеет смысл показывать пейвол по умолчанию — это обеспечит плавный пользовательский опыт вместо ситуации, когда пейвол вообще не отображается. Чтобы решить эту задачу, можно использовать метод `getPaywallForDefaultAudience`, который загружает пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — загружать пейвол с помощью метода `getPaywall`, как описано в разделе [Получение информации о пейволе](fetch-paywalls-and-products#fetch-paywall-information) выше. :::warning Почему мы рекомендуем использовать `getPaywall` Метод `getPaywallForDefaultAudience` имеет ряд существенных недостатков: - **Потенциальные проблемы с обратной совместимостью**: если вам нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать пейволы с поддержкой текущей (устаревшей) версии, либо смириться с тем, что пользователи этой версии могут столкнуться с проблемами при отображении пейволов. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, настроенный для аудитории **All Users**, — это означает отказ от персонализированного таргетинга (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вы готовы принять эти ограничения ради более быстрой загрузки пейвола, используйте метод `getPaywallForDefaultAudience` следующим образом. В противном случае придерживайтесь метода `getPaywall`, описанного [выше](fetch-paywalls-and-products#fetch-paywall-information). ::: <Tabs group="current-os"> <TabItem value="swift" label="Swift"> ```swift showLineNumbers do { let paywall = try await Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID") // the requested paywall } catch { // handle the error } ``` </TabItem> <TabItem value="callback" label="Swift-Callback"> ```swift showLineNumbers Adapty.getPaywallForDefaultAudience(placementId: "YOUR_PLACEMENT_ID", locale: "en") { result in switch result { case let .success(paywall): // запрошенный пейвол case let .failure(error): // обработка ошибки } } ``` </TabItem> </Tabs> :::note Метод `getPaywallForDefaultAudience` доступен начиная с iOS SDK версии 2.11.2. ::: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>необязательный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается языковой код, состоящий из одного или нескольких подтегов, разделённых символом минус (**-**). Первый подтег — язык, второй — регион.</p><p></p><p>Пример: `en` — английский, `pt-br` — бразильский португальский.</p><p></p><p>Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите вариант `.returnCacheDataElseLoad` — он возвращает кешированные данные, если они есть. В этом случае пользователи могут получать не самые свежие данные, зато загрузка будет быстрее независимо от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы сократить количество сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или принудительной очистке вручную.</p> | </SDKv3> --- # File: present-remote-config-paywalls --- --- title: "Отображение пейвола на основе Remote Config в iOS SDK" description: "Узнайте, как показывать пейволы на основе Remote Config в Adapty для персонализации пользовательского опыта." --- <SDKv4> Если вы настроили пейвол с помощью Remote Config, вам нужно реализовать его отображение в коде мобильного приложения. Поскольку Remote Config гибко адаптируется под ваши задачи, вы сами решаете, что включить и как будет выглядеть пейвол. Adapty предоставляет метод для получения Remote Config, оставляя за вами полный контроль над отображением кастомного пейвола. Не забудьте [проверить, имеет ли пользователь право на introductory offer в iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios) и настроить отображение пейвола с учётом этого случая. ## Получение Remote Config флоу и его отображение \{#get-flow-remote-config-and-present-it\} В v4 флоу содержит по одной записи `AdaptyRemoteConfig` на каждый настроенный язык в массиве `remoteConfigs`. Выберите локаль, соответствующую предпочтениям пользователя, затем считайте нужные значения. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") let config = flow.remoteConfigs.first(where: { $0.locale == "en" }) ?? flow.remoteConfigs.first let headerText = config?.dictionary?["header_text"] as? String } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") { result in let flow = try? result.get() let config = flow?.remoteConfigs.first(where: { $0.locale == "en" }) ?? flow?.remoteConfigs.first let headerText = config?.dictionary?["header_text"] as? String } ``` </TabItem> </Tabs> На этом этапе, получив все необходимые значения, можно приступить к отрисовке и сборке визуально привлекательного экрана. Убедитесь, что дизайн адаптирован под различные размеры экранов и ориентации мобильных устройств — это обеспечит удобный и понятный интерфейс на любых девайсах. :::warning Обязательно [зафиксируйте событие просмотра пейвола](present-remote-config-paywalls#track-paywall-view-events), как описано ниже, чтобы аналитика Adapty собирала данные для воронок и A/B-тестов. ::: После того как вы отобразили пейвол, настройте флоу покупки. Когда пользователь совершает покупку, просто вызовите `.makePurchase()` с продуктом из вашего флоу. Подробнее о методе `.makePurchase()` читайте в разделе [Совершение покупок](making-purchases). Рекомендуем [создать резервный пейвол](fallback-paywalls). Он будет отображаться пользователю при отсутствии интернет-соединения или кэша, обеспечивая бесперебойную работу даже в таких ситуациях. ## Отслеживание событий просмотра пейвола \{#track-paywall-view-events\} Adapty помогает измерять эффективность ваших пейволов. Данные о покупках мы собираем автоматически, однако логирование просмотров пейвола требует вашего участия — только вы знаете, когда пользователь видит пейвол. Чтобы зафиксировать событие просмотра пейвола, вызовите `.logShowFlow(flow)` — это отразится в метриках вашего пейвола в воронках и A/B-тестах. :::important Вызывать `.logShowFlow(flow)` не нужно, если вы отображаете флоу или пейволы, отрисованные с помощью [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder). В этих случаях Adapty отслеживает просмотры автоматически. ::: ```swift showLineNumbers try await Adapty.logShowFlow(flow) ``` Параметры запроса: | Параметр | Наличие | Описание | | :-------- | :------- |:-----------------------------------------------------------------------------------------| | **flow** | обязательный | Объект `AdaptyFlow`, полученный через `Adapty.getFlow(placementId:)`. | </SDKv4> <SDKv3> Если вы настроили пейвол с помощью Remote Config, вам нужно реализовать его отображение в коде мобильного приложения. Поскольку Remote Config даёт полную гибкость под ваши нужды, вы сами определяете, что включить и как будет выглядеть пейвол. Мы предоставляем метод для получения Remote Config, а дальше вы управляете отображением своего пейвола самостоятельно. Не забудьте [проверить право пользователя на introductory offer в iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios) и настроить отображение пейвола с учётом этого случая. ## Получите Remote Config пейвола и отобразите его \{#get-paywall-remote-config-and-present-it\} Чтобы получить Remote Config пейвола, обратитесь к свойству `remoteConfig` и извлеките нужные значения. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let paywall = try await Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID") let headerText = paywall.remoteConfig?.dictionary?["header_text"] as? String } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID") { result in let paywall = try? result.get() let headerText = paywall?.remoteConfig?.dictionary?["header_text"] as? String } ``` </TabItem> </Tabs> На этом этапе, получив все необходимые значения, можно приступать к рендерингу и сборке визуально привлекательной страницы. Убедитесь, что дизайн адаптирован под разные размеры экранов и ориентации мобильных устройств — это обеспечит удобный пользовательский опыт на любых девайсах. :::warning Обязательно [записывайте событие просмотра пейвола](present-remote-config-paywalls#track-paywall-view-events), как описано ниже — это позволяет аналитике Adapty собирать данные для воронок и A/B-тестов. ::: После того как пейвол отображён, настройте флоу покупки. Когда пользователь совершает покупку, просто вызовите `.makePurchase()` с продуктом из вашего пейвола. Подробнее о методе `.makePurchase()` читайте в разделе [Совершение покупок](making-purchases). Мы рекомендуем [создать резервный пейвол](fallback-paywalls). Он будет отображаться пользователю при отсутствии интернета или кеша, обеспечивая бесперебойную работу в таких ситуациях. ## Отслеживание событий просмотра пейвола \{#track-paywall-view-events\} Adapty помогает измерять эффективность ваших пейволов. Данные о покупках собираются автоматически, но события просмотра пейвола нужно логировать вручную — только вы знаете, когда пользователь видит пейвол. Чтобы залогировать событие просмотра, вызовите `.logShowPaywall(paywall)` — это отразится в метриках пейвола в воронках и A/B-тестах. :::important Вызывать `.logShowPaywall(paywall)` не нужно, если вы отображаете пейволы, созданные в [Paywall Builder](adapty-paywall-builder). ::: ```swift showLineNumbers Adapty.logShowPaywall(paywall) ``` | Параметр | Наличие | Описание | | :---------- | :------- |:------------------------------------------------------------------------------------------------------| | **paywall** | обязательный | Объект [`AdaptyPaywall`](https://swift.adapty.io/documentation/adapty/adaptypaywall). | </SDKv3> --- # File: making-purchases --- --- title: "Совершение покупок в мобильном приложении с iOS SDK" description: "Руководство по обработке встроенных покупок и подписок с помощью Adapty." --- Отображение пейволов в мобильном приложении — обязательный шаг для предоставления пользователям доступа к премиум-контенту или сервисам. Однако простого показа пейволов достаточно для поддержки покупок только в том случае, если вы используете [Paywall Builder](adapty-paywall-builder) для их настройки. Если вы не используете Paywall Builder, для совершения покупки и открытия нужного контента необходимо использовать отдельный метод `.makePurchase()`. Он служит точкой входа для пользователей: через него они взаимодействуют с пейволами и совершают нужные транзакции. Если для продукта, который пользователь хочет купить, на пейволе активен promotional offer, Adapty автоматически применит его в момент покупки. :::warning Обратите внимание: introductory offer применяется автоматически только при использовании пейволов, настроенных в Paywall Builder. В остальных случаях нужно [проверить право пользователя на introductory offer на iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios). Пропуск этого шага может привести к отклонению приложения при релизе, а также к списанию полной цены у пользователей, которым доступен introductory offer. ::: Убедитесь, что вы выполнили [начальную настройку](quickstart), не пропустив ни одного шага. Без неё мы не сможем валидировать покупки. ## Совершить покупку \{#make-purchase\} :::note **Используете [Paywall Builder](adapty-paywall-builder)?** Покупки обрабатываются автоматически — этот шаг можно пропустить. **Нужна пошаговая инструкция?** Смотрите [quickstart guide](ios-implement-paywalls-manually) — там полный цикл реализации с контекстом. ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let purchaseResult = try await Adapty.makePurchase(product: product) switch purchaseResult { case .userCancelled: // Handle the case where the user canceled the purchase case .pending: // Handle deferred purchases (e.g., the user will pay offline with cash) case let .success(profile, transaction): if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // Grant access to the paid features } } } catch { // Handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.makePurchase(product: product) { result in switch result { case let .success(purchaseResult): switch purchaseResult { case .userCancelled: // Handle the case where the user canceled the purchase case .pending: // Handle deferred purchases (e.g., the user will pay offline with cash) case let .success(profile, transaction): if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // Grant access to the paid features } } case let .failure(error): // Handle the error } } ``` </TabItem> </Tabs> Параметры запроса: | Параметр | Наличие | Описание | | :---------- | :------- | :-------------------------------------------------------------------------------------------------- | | **Product** | required | Объект [`AdaptyPaywallProduct`](https://swift.adapty.io/documentation/adapty/adaptypaywallproduct), полученный из пейвола. | Параметры ответа: | Параметр | Описание | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>Если запрос выполнен успешно, ответ содержит этот объект. Объект [AdaptyProfile](https://swift.adapty.io/documentation/adapty/adaptyprofile) предоставляет исчерпывающую информацию об уровнях доступа пользователя, подписках и разовых покупках внутри приложения.</p><p>Проверьте статус уровня доступа, чтобы убедиться, что пользователь имеет необходимый доступ к приложению.</p> | :::warning **Примечание:** если вы используете Apple StoreKit версии ниже v2.0 и Adapty SDK версии ниже v2.9.0, вам нужно указать [Apple App Store shared secret](app-store-connection-configuration#step-5-enter-app-store-shared-secret). Этот метод устарел и больше не рекомендуется Apple. ::: ## Встроенные покупки из App Store \{#in-app-purchases-from-the-app-store\} Когда пользователь инициирует покупку в App Store и транзакция передаётся в ваше приложение, у вас есть два варианта: - **Обработать транзакцию немедленно:** Верните `true` в `shouldAddStorePayment`. Это сразу откроет экран покупки Apple. - **Сохранить объект продукта для последующей обработки:** Верните `false` в `shouldAddStorePayment`, а затем вызовите `makePurchase` с сохранённым продуктом позже. Это может быть полезно, если перед запуском покупки нужно показать пользователю что-то своё. Вот полный фрагмент кода: ```swift showLineNumbers title="Swift" final class YourAdaptyDelegateImplementation: AdaptyDelegate { nonisolated func shouldAddStorePayment(for product: AdaptyDeferredProduct) -> Bool { // 1a. // Return `true` to continue the transaction in your app. The Apple purchase system screen will show automatically. // 1b. // Store the product object and return `false` to defer or cancel the transaction. false } // 2. Continue the deferred purchase later on by passing the product to `makePurchase` when the timing is appropriate func continueDeferredPurchase() async { let storedProduct: AdaptyDeferredProduct = // get the product object from 1b. do { try await Adapty.makePurchase(product: storedProduct) } catch { // handle the error } } } ``` ## Активация промокодов в iOS \{#redeem-offer-codes-in-ios\} <Details> <summary>Об офферных кодах</summary> Офферные коды позволяют предоставлять скидки или бесплатные пробные периоды конкретным пользователям. В отличие от обычных офферов, которые применяются автоматически, офферные коды распространяются за пределами приложения — через email-рассылки, социальные сети или печатные материалы. Пользователи активируют их, вводя код в App Store, переходя по ссылке для активации или через диалог внутри приложения. Чтобы настроить офферные коды, откройте подписку в App Store Connect и перейдите в раздел **Offer Codes**. Вы можете создать [три вида](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes) офферных кодов: - **Free** — подписка бесплатна на заданный период, следующее продление — по полной цене. - **Pay as you go** — пользователь платит сниженную цену в каждом расчётном периоде на протяжении заданного срока, после чего подписка продлевается по полной цене. - **Pay up front** — пользователь единовременно платит сниженную цену за весь срок оффера, после чего подписка продлевается по полной цене. Добавлять офферные коды в Adapty не нужно. Apple помечает каждую транзакцию в период действия оффера категорией офферного кода. Это касается как первоначальной активации, так и всех последующих продлений со скидкой. Adapty обнаруживает метку и записывает каждую транзакцию с категорией оффера `offer_code`. Как только период оффера заканчивается и подписка продлевается по полной цене, метка исчезает. Вы можете фильтровать аналитику по типу оффера **Offer Code** в [дашборде Adapty](controls-filters-grouping-compare-proceeds). #### Устранение расхождений в выручке \{#revenue-discrepancy-troubleshooting\} Если транзакция по офферному коду отображается в Adapty по полной цене продукта вместо сниженной цены оффера, проверьте следующее в App Store Connect: - Для офферного кода настроены корректные цены для всех регионов, где пользователи могут его активировать. - Цена оффера задана для конкретной страны или региона пользователя. Apple передаёт региональную цену в транзакции. Если для оффера не настроена региональная цена, Apple может передать полную цену продукта. Вы можете фильтровать и проверять транзакции по офферным кодам в [дашборде Adapty](controls-filters-grouping-compare-proceeds) по фильтрам типа оффера **Offer Code** и **Offer Discount Type**. #### Устаревшие промокоды (deprecated) \{#legacy-promo-codes-deprecated\} :::warning Apple прекратила поддержку промокодов для встроенных покупок в марте 2026 года. Офферные коды заменяют их с расширенными возможностями: настраиваемые условия применения, сроки действия и до 1 миллиона кодов в квартал. Если вы ранее использовали промокоды для встроенных покупок, перейдите на офферные коды в App Store Connect. ::: Устаревшие промокоды (не более 100 на приложение на версию) предоставляли бесплатный доступ к подписке. В отличие от офферных кодов, Apple не включала информацию о скидке в транзакции по промокодам — в чеке указывалась полная цена продукта. В результате Adapty записывал эти транзакции по полной цене, что приводило к расхождениям в выручке между аналитикой Adapty и App Store Connect. Если вы видите исторические транзакции по полной цене, которые должны были быть бесплатными, скорее всего, они связаны с устаревшими промокодами. Поскольку эти коды больше не поддерживаются, перейдите на офферные коды для точного учёта выручки. </Details> Чтобы отобразить экран активации промокода в вашем приложении: ```swift showLineNumbers Adapty.presentCodeRedemptionSheet() ``` :::danger По нашим наблюдениям, экран активации промокода (Offer Code Redemption) в некоторых приложениях работает ненадёжно. Мы рекомендуем перенаправлять пользователя напрямую в App Store. Для этого откройте URL в следующем формате: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: --- # File: restore-purchase --- --- title: "Восстановление покупок в мобильном приложении с iOS SDK" description: "Узнайте, как восстанавливать покупки в Adapty для обеспечения бесперебойного пользовательского опыта." --- Восстановление покупок — это функция, которая позволяет пользователям снова получить доступ к ранее приобретённому контенту (подпискам или встроенным покупкам) без повторной оплаты. Она особенно полезна, когда пользователь удалил и переустановил приложение или перешёл на новое устройство и хочет получить доступ к уже купленному контенту. :::note В пейволах, созданных с помощью [Paywall Builder](adapty-paywall-builder), покупки восстанавливаются автоматически без дополнительного кода с вашей стороны. Если это ваш случай — можете пропустить этот шаг. ::: Чтобы восстановить покупку, если вы не используете [Paywall Builder](adapty-paywall-builder) для настройки пейвола, вызовите метод `.restorePurchases()`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let profile = try await Adapty.restorePurchases() if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // successful access restore } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.restorePurchases { [weak self] result in switch result { case let .success(profile): if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // successful access restore } case let .failure(error): // handle the error } } ``` </TabItem> </Tabs> Параметры ответа: | Параметр | Описание | |---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>Объект [`AdaptyProfile`](https://swift.adapty.io/documentation/adapty/adaptyprofile). Эта модель содержит информацию об уровнях доступа, подписках и разовых покупках.</p><p>Проверьте **статус уровня доступа**, чтобы определить, есть ли у пользователя доступ к приложению.</p> | :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: --- # File: ios-transaction-management --- --- title: "Расширенное управление транзакциями в iOS SDK" description: "Завершайте транзакции вручную в iOS-приложении с помощью Adapty SDK." --- :::note Расширенное управление транзакциями поддерживается в Adapty iOS SDK начиная с версии 3.12. ::: Расширенное управление транзакциями в Adapty даёт вам больше контроля над тем, как транзакции обрабатываются, верифицируются и завершаются. Эта функциональность включает три опциональных возможности, которые работают вместе: | Возможность | Назначение | |-------------------------------------------------------------|----------| | [`appAccountToken`](#assign-appaccounttoken) | Связывает транзакции Apple с вашим внутренним идентификатором пользователя | | [`jwsTransaction`](#access-the-jws-representation) | Предоставляет подписанный Apple пейлоад транзакции для валидации | | [Ручное завершение](#control-transaction-finishing-behavior) | Позволяет завершать транзакции только после подтверждения успеха от вашего бэкенда | В совокупности эти инструменты позволяют выстроить надёжные кастомные процессы валидации, пока Adapty продолжает синхронизировать транзакции со своим бэкендом. :::important Большинству приложений это не нужно. По умолчанию Adapty автоматически валидирует и завершает транзакции StoreKit. Используйте этот гайд только если вы проводите собственную валидацию на бэкенде или хотите полностью управлять жизненным циклом покупок. ::: ## Назначение `appAccountToken` \{#assign-appaccounttoken\} [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) — это **UUID**, который позволяет связать транзакции App Store с внутренней идентичностью пользователя в вашей системе. StoreKit привязывает этот токен к каждой транзакции, чтобы ваш бэкенд мог сопоставить данные App Store с вашими пользователями. Используйте стабильный UUID, генерируемый для каждого пользователя, и повторно применяйте его для одного аккаунта на разных устройствах. Это гарантирует корректную привязку покупок и уведомлений App Store. Токен можно задать двумя способами — при активации SDK или при идентификации пользователя. :::important Вы всегда должны передавать `appAccountToken` вместе с `customerUserId`. Если передать только токен, он не будет включён в транзакцию. ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers // During configuration: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID", withAppAccountToken: UUID()) do { try await Adapty.activate(with: configurationBuilder.build()) } catch { // handle the error } // Or when identifying a user: do { try await Adapty.identify("YOUR_USER_ID", withAppAccountToken: UUID()) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers // During configuration: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID", withAppAccountToken: <APP_ACCOUNT_TOKEN>) Adapty.activate(with: configurationBuilder.build()) { error in // handle the error } // Or when identifying a user: Adapty.identify("YOUR_USER_ID", withAppAccountToken: <APP_ACCOUNT_TOKEN>) { error in if let error { // handle the error } } ``` </TabItem> </Tabs> ## Доступ к JWS-представлению \{#access-the-jws-representation\} При совершении покупки результат содержит транзакцию Apple в [формате JWS Compact Serialization](https://developer.apple.com/documentation/storekit/verificationresult/jwsrepresentation-21vgo). Это значение можно передать на ваш бэкенд для независимой валидации или логирования. ```swift let result = try await Adapty.makePurchase(product: paywallProduct) let jwsRepresentation = result.jwsTransaction ``` ## Управление завершением транзакций \{#control-transaction-finishing-behavior\} По умолчанию Adapty автоматически завершает транзакции StoreKit после валидации. Если нужно отложить завершение до получения подтверждения от вашего бэкенда, установите режим ручного завершения транзакций. В этом режиме: - Adapty по-прежнему валидирует покупки и синхронизирует их со своим бэкендом. - Транзакции остаются незавершёнными, пока вы явно не вызовете `finish()`. ```swift var configBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_API_KEY") .with(transactionFinishBehavior: .manual) try await Adapty.activate(with: configBuilder.build()) ``` При использовании ручного завершения транзакций необходимо реализовать метод делегата `onUnfinishedTransaction` для обработки незавершённых транзакций: ```swift showLineNumbers title="Swift" extension YourApp: AdaptyDelegate { func onUnfinishedTransaction(_ transaction: AdaptyUnfinishedTransaction) async { // Perform your custom validation logic here // When ready, finish the transaction await transaction.finish() } } ``` Чтобы получить все текущие незавершённые транзакции, используйте метод `getUnfinishedTransactions()`: ```swift let unfinishedTransactions = try await Adapty.getUnfinishedTransactions() ``` --- # File: implement-observer-mode --- --- title: "Реализация Observer mode в iOS SDK" description: "Реализуйте Observer mode в Adapty для отслеживания событий подписки пользователей в iOS SDK." --- Если у вас уже есть собственная инфраструктура покупок и вы не готовы полностью переходить на Adapty, вы можете воспользоваться [Observer mode](observer-vs-full-mode). В базовом варианте Observer Mode предоставляет расширенную аналитику и бесшовную интеграцию с системами атрибуции и аналитики. Если этого достаточно для ваших задач, вам нужно только: 1. Включить его при настройке Adapty SDK, установив параметр `observerMode` в `true`. 2. [Передавать транзакции](report-transactions-observer-mode) из вашей существующей инфраструктуры покупок в Adapty. Если вам также нужны пейволы и A/B-тесты, потребуется дополнительная настройка, описанная ниже. ## Настройка Observer mode \{#observer-mode-setup\} Включите Observer mode, если вы самостоятельно обрабатываете покупки и статус подписки и используете Adapty для отправки событий подписки и аналитики. :::important При работе в Observer mode Adapty SDK не закрывает транзакции, поэтому убедитесь, что вы обрабатываете их самостоятельно. ::: <Tabs groupId="current-os" queryString> <TabItem value="swiftui" label="SwiftUI"> ```swift showLineNumbers @main struct YourApp: App { init() { // Configure Adapty SDK let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") // Get from Adapty dashboard .with(observerMode: true) let config = configurationBuilder.build() // Activate Adapty SDK asynchronously Task { do { try await Adapty.activate(with: configurationBuilder) } catch { // Handle error appropriately for your app print("Adapty activation failed: ", error) } } var body: some Scene { WindowGroup { // Your content view } } } } ``` </TabItem> <TabItem value="swift" label="UIKit" default> ```swift showLineNumbers Task { do { let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "YOUR_PUBLIC_SDK_KEY") // Get from Adapty dashboard .with(observerMode: true) let config = configurationBuilder.build() try await Adapty.activate(with: config) } catch { // Handle error appropriately for your app print("Adapty activation failed: ", error) } } ``` </TabItem> </Tabs> Параметры: | Параметр | Описание | | --------------------------- | ------------------------------------------------------------ | | observerMode | Булево значение, управляющее [Observer mode](observer-vs-full-mode). Значение по умолчанию — `false`. | ## Использование пейволов Adapty в Observer Mode \{#using-adapty-paywalls-in-observer-mode\} Если вы хотите также использовать пейволы и A/B-тесты Adapty, это возможно — но в Observer mode потребуется дополнительная настройка. Вот что нужно сделать в дополнение к шагам выше: 1. Отображайте пейволы как обычно для [пейволов на основе Remote Config](present-remote-config-paywalls). Для пейволов на основе Paywall Builder следуйте специальным гайдам для [iOS](ios-present-paywall-builder-paywalls-in-observer-mode). 3. [Свяжите пейволы](report-transactions-observer-mode) с транзакциями покупок. --- # File: report-transactions-observer-mode --- --- title: "Отчёт о транзакциях в Observer Mode в iOS SDK" description: "Отправляйте транзакции покупок в Adapty Observer Mode для получения аналитики пользователей и отслеживания выручки в iOS SDK." --- <Tabs groupId="sdk-version" queryString> <TabItem value="current" label="Adapty SDK v3.4+ (current)" default> В Observer Mode Adapty SDK не может самостоятельно отслеживать покупки, сделанные через вашу существующую систему. Вам нужно самостоятельно передавать транзакции из стора. Это важно настроить **до** выпуска приложения, чтобы избежать ошибок в аналитике. Используйте `reportTransaction`, чтобы явно сообщить Adapty о каждой транзакции. :::warning **Не пропускайте отчёт о транзакциях!** Если вы не вызовете `reportTransaction`, Adapty не распознает транзакцию: она не появится в аналитике и не будет отправлена в интеграции. ::: Если вы используете пейволы Adapty, передавайте `variationId` при отчёте о транзакции. Это свяжет покупку с пейволом, который её инициировал, и обеспечит точную аналитику пейволов. ```swift showLineNumbers do { // every time when calling transasction.finish() try await Adapty.reportTransaction(transaction, withVariationId: <YOUR_PAYWALL_VARIATION_ID>) } catch { // handle the error } ``` Параметры: | Параметр | Обязательность | Описание | | --------------- | -------------- | ------------------------------------------------------------ | | **transaction** | обязательный | <ul><li> Для StoreKit 1: SKPaymentTransaction.</li><li> Для StoreKit 2: Transaction.</li></ul> | | **variationId** | опциональный | Уникальный ID варианта пейвола. Получите его из свойства `variationId` объекта [AdaptyPaywall](https://swift.adapty.io/documentation/adapty/adaptypaywall). | </TabItem> <TabItem value="old" label="Adapty SDK 3.3.x (legacy)" default> В Observer Mode Adapty SDK не может самостоятельно отслеживать покупки, сделанные через вашу существующую систему. Вам нужно самостоятельно передавать транзакции из стора или восстанавливать их. Это важно настроить **до** выпуска приложения, чтобы избежать ошибок в аналитике. Используйте `reportTransaction`, чтобы отправить данные о транзакции в Adapty. :::warning **Не пропускайте отчёт о транзакциях!** Если вы не вызовете `reportTransaction`, Adapty не распознает транзакцию: она не появится в аналитике и не будет отправлена в интеграции. ::: Если вы используете пейволы Adapty, передавайте `withVariationId` при отчёте о транзакции. Это свяжет покупку с пейволом, который её инициировал, и обеспечит точную аналитику пейволов. ```swift showLineNumbers do { // every time when calling transasction.finish() try await Adapty.reportTransaction(transaction, withVariationId: <YOUR_PAYWALL_VARIATION_ID>) } catch { // handle the error } ``` Параметры: | Параметр | Обязательность | Описание | | --------------- | -------------- | ------------------------------------------------------------ | | **transaction** | обязательный | <ul><li> Для StoreKit 1: SKPaymentTransaction.</li><li> Для StoreKit 2: Transaction.</li></ul> | | **variationId** | опциональный | Уникальный ID варианта пейвола. Получите его из свойства `variationId` объекта [AdaptyPaywall](https://swift.adapty.io/documentation/adapty/adaptypaywall). | </TabItem> <TabItem value="old2" label="Adapty SDK up to 3.2.x (legacy)" default> **Отчёт о транзакциях** - Версии до 3.1.x автоматически отслеживают транзакции в App Store, поэтому ручной отчёт не требуется. - Версия 3.2 не поддерживает Observer Mode. **Привязка пейволов к транзакциям** Adapty SDK не может определить источник покупок, поскольку их обрабатываете вы сами. Поэтому, если вы планируете использовать пейволы и/или A/B-тесты в Observer Mode, вам нужно связать транзакцию из вашего стора с соответствующим пейволом в коде мобильного приложения. Это важно настроить до выпуска приложения — иначе в аналитике появятся ошибки. ```swift let variationId = paywall.variationId // There are two overloads: for StoreKit 1 and StoreKit 2 Adapty.setVariationId(variationId, forPurchasedTransaction: transactionId) { error in if error == nil { // successful binding } } ``` Параметры запроса: | Параметр | Обязательность | Описание | | ------------- | -------------- | ------------------------------------------------------------ | | variationId | обязательный | Строковый идентификатор варианта. Получите его из свойства `variationId` объекта [AdaptyPaywall](https://swift.adapty.io/documentation/adapty/adaptypaywall). | | transactionId | обязательный | <p>Для StoreKit 1: объект [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction).</p><p>Для StoreKit 2: объект [Transaction](https://developer.apple.com/documentation/storekit/transaction).</p> | </TabItem> </Tabs> --- # File: ios-troubleshoot-purchases --- --- title: "Устранение проблем с покупками в iOS SDK" description: "Устранение проблем с покупками в iOS SDK" --- Этот гайд поможет решить распространённые проблемы при ручной реализации покупок в iOS SDK. ## AdaptyError.cantMakePayments в режиме наблюдателя \{#adaptyerrorcantmakepayments-in-observer-mode\} **Проблема**: Вы получаете `AdaptyError.cantMakePayments` при использовании `makePurchase` в режиме наблюдателя. **Причина**: В режиме наблюдателя покупки нужно обрабатывать на вашей стороне, а не использовать метод `makePurchase` из Adapty. **Решение**: Если вы используете `makePurchase` для покупок, отключите режим наблюдателя. Нужно либо использовать `makePurchase`, либо обрабатывать покупки самостоятельно в режиме наблюдателя. Подробнее см. в разделе [Реализация режима наблюдателя](implement-observer-mode). ## Not found makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\} **Проблема**: Возникают ошибки, связанные с тем, что `makePurchasesCompletionHandlers` не найден. **Причина**: Как правило, это связано с проблемами тестирования в песочнице. **Решение**: Создайте нового пользователя в песочнице и попробуйте снова. Это обычно решает проблемы с обработчиком завершения покупки в песочнице. ## Другие проблемы \{#other-issues\} **Проблема**: Вы столкнулись с другими проблемами, связанными с покупками, которые не описаны выше. **Решение**: При необходимости обновите SDK до последней версии с помощью [гайдов по миграции](ios-sdk-migration-guides). Многие проблемы уже исправлены в новых версиях SDK. --- # File: ios-web-paywall --- --- title: "Реализация веб-пейволов в iOS SDK" description: "Настройте веб-пейвол для приёма платежей без комиссий и проверок App Store." --- :::important Перед началом работы убедитесь, что вы [настроили веб-пейвол в дашборде](web-paywall) и установили Adapty SDK версии 3.6.1 или выше. ::: ## Веб-пейволы \{#open-web-paywalls\} Если вы работаете с пейволом, разработанным самостоятельно, для обработки веб-пейволов нужно использовать метод SDK. Метод `.openWebPaywall`: 1. Генерирует уникальный URL, позволяющий Adapty связать конкретный показанный пользователю пейвол с веб-страницей, на которую он перенаправляется. 2. Отслеживает момент возврата пользователя в приложение и затем с короткими интервалами запрашивает `.getProfile`, чтобы определить, обновились ли права доступа профиля. Таким образом, если платёж прошёл успешно и права доступа обновлены, подписка активируется в приложении практически мгновенно. ```swift showLineNumbers title="Swift" do { try await Adapty.openWebPaywall(for: product) } catch { print("Failed to open web paywall: \(error)") } ``` :::note Существуют две версии метода `openWebPaywall`: 1. `openWebPaywall(product)` — генерирует URL по пейволу и добавляет в URL данные о продукте. 2. `openWebPaywall(paywall)` — генерирует URL по пейволу без добавления данных о продукте. Используйте этот вариант, если продукты в пейволе Adapty отличаются от продуктов в веб-пейволе. ::: ## Обработка ошибок \{#handle-errors\} | Ошибка | Описание | Рекомендуемое действие | |-----------------------------------------|-----------------------------------------------------------------|--------------------------------------------------------------------------------------| | AdaptyError.paywallWithoutPurchaseUrl | У пейвола не настроен URL для веб-покупки | Проверьте, правильно ли настроен пейвол в дашборде Adapty | | AdaptyError.productWithoutPurchaseUrl | У продукта отсутствует URL для веб-покупки | Проверьте настройки продукта в дашборде Adapty | | AdaptyError.failedOpeningWebPaywallUrl | Не удалось открыть URL в браузере | Проверьте настройки устройства или предложите альтернативный способ покупки | | AdaptyError.failedDecodingWebPaywallUrl | Не удалось корректно закодировать параметры в URL | Убедитесь, что параметры URL корректны и правильно отформатированы | ## Пример реализации \{#implementation-example\} ```swift showLineNumbers title="Swift" class SubscriptionViewController: UIViewController { var paywall: AdaptyPaywall? @IBAction func purchaseButtonTapped(_ sender: UIButton) { guard let paywall = paywall, let product = paywall.products.first else { return } Task { await offerWebPurchase(for: product) } } func offerWebPurchase(for paywallProduct: AdaptyPaywallProduct) async { do { // Attempt to open web paywall try await Adapty.openWebPaywall(for: paywallProduct) } catch let error as AdaptyError { switch error { case .paywallWithoutPurchaseUrl, .productWithoutPurchaseUrl: showAlert(message: "Web purchase is not available for this product.") case .failedOpeningWebPaywallUrl: showAlert(message: "Could not open web browser. Please try again.") default: showAlert(message: "An error occurred: \(error.localizedDescription)") } } catch { showAlert(message: "An unexpected error occurred.") } } // Helper methods private func showAlert(message: String) { /* ... */ } } ``` :::note После того как пользователи возвращаются в приложение, обновите UI, чтобы отразить изменения профиля. `AdaptyDelegate` будет получать и обрабатывать события обновления профиля. ::: ## Открытие веб-пейволов во встроенном браузере \{#open-web-paywalls-in-an-in-app-browser\} :::important Открытие веб-пейволов во встроенном браузере поддерживается начиная с Adapty SDK v3.15. ::: По умолчанию веб-пейволы открываются во внешнем браузере. Чтобы обеспечить бесшовный пользовательский опыт, вы можете открывать веб-пейволы во встроенном браузере. Это позволяет отображать страницу веб-покупки прямо внутри приложения, и пользователям не нужно переключаться между приложениями для завершения транзакции. Чтобы включить эту возможность, передайте параметру `in` значение `.inAppBrowser`: ```swift showLineNumbers title="Swift" do { try await Adapty.openWebPaywall(for: product, in: .inAppBrowser) // default – .externalBrowser } catch { print("Failed to open web paywall: \(error)") } ``` --- # File: identifying-users --- --- title: "Идентификация пользователей в iOS SDK" description: "Идентифицируйте пользователей в Adapty для улучшения персонализированного опыта подписки." --- Adapty создаёт внутренний ID профиля для каждого пользователя. Однако если у вас есть собственная система аутентификации, вам следует задать свой Customer User ID. Вы можете находить пользователей по их Customer User ID в разделе [Профили](profiles-crm) и использовать его в [серверном API](getting-started-with-server-side-api) — он будет передаваться во все интеграции. ## Установите customer user ID при настройке \{#set-customer-user-id-on-configuration\} Если у вас есть ID пользователя на момент настройки, просто передайте его как параметр `customerUserId` в метод `.activate()`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers // In your AppDelegate class: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID") do { try await Adapty.activate(with: configurationBuilder.build()) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers // In your AppDelegate class: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID") Adapty.activate(with: configurationBuilder.build()) { error in // handle the error } ``` </TabItem> </Tabs> :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ## Задайте customer user ID после конфигурации \{#set-customer-user-id-after-configuration\} Если у вас нет user ID при конфигурации SDK, вы можете задать его позже в любой момент с помощью метода `.identify()`. Чаще всего этот метод используется после регистрации или авторизации — когда пользователь переходит из анонимного состояния в аутентифицированное. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { try await Adapty.identify("YOUR_USER_ID") } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.identify("YOUR_USER_ID") { error in if let error { // handle the error } } ``` </TabItem> </Tabs> Параметры запроса: - **Customer User ID** (обязательный): строковый идентификатор пользователя. :::warning Повторная отправка важных данных пользователя В некоторых случаях, например когда пользователь повторно входит в аккаунт, серверы Adapty уже располагают информацией об этом пользователе. В таких сценариях SDK автоматически переключается на работу с новым пользователем. Если вы передавали данные анонимному пользователю — например, пользовательские атрибуты или атрибуцию из сторонних сетей — необходимо повторно отправить эти данные для идентифицированного пользователя. Также важно помнить, что после идентификации пользователя следует заново запросить все пейволы и продукты, поскольку данные нового пользователя могут отличаться. ::: ## Выход из системы и вход \{#logging-out-and-logging-in\} Вы можете выйти из аккаунта пользователя в любой момент, вызвав метод `.logout()`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { try await Adapty.logout() } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.logout { error in if error == nil { // successful logout } } ``` </TabItem> </Tabs> После этого вы можете войти под другим пользователем с помощью метода `.identify()`. ## Установка appAccountToken \{#set-appaccounttoken\} [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) — это UUID, который помогает StoreKit 2 от Apple идентифицировать пользователей при переустановке приложения и смене устройств. Начиная с Adapty iOS SDK 3.10.2, вы можете передавать `appAccountToken` при настройке SDK или при идентификации пользователя: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers // During configuration: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID", withAppAccountToken: UUID()) do { try await Adapty.activate(with: configurationBuilder.build()) } catch { // handle the error } // Or when identifying a user: do { try await Adapty.identify("YOUR_USER_ID", withAppAccountToken: UUID()) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers // During configuration: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(customerUserId: "YOUR_USER_ID", withAppAccountToken: UUID()) Adapty.activate(with: configurationBuilder.build()) { error in // handle the error } // Or when identifying a user: Adapty.identify("YOUR_USER_ID", withAppAccountToken: UUID()) { error in if let error { // handle the error } } ``` </TabItem> </Tabs> Затем вы можете авторизовать пользователя с помощью метода `.identify()`. ## Определение пользователей на разных устройствах \{#detect-users-across-devices\} При активации SDK он автоматически считывает существующие права пользователя из StoreKit (iOS) или Google Play Billing (Android) и синхронизирует их с бэкендом Adapty. Активная подписка появляется в профиле Adapty без вызова `restorePurchases` со стороны приложения. Что **не** происходит автоматически — так это распознавание того, что профиль на новом устройстве принадлежит тому же пользователю, что и профиль на исходном устройстве. Adapty сопоставляет профили по Customer User ID, поэтому непрерывность идентификации зависит от того, что вы используете в качестве CUID. **Что Adapty может определить между устройствами** | Ваша настройка | Что Adapty определяет | Что нужно сделать | | --- | --- | --- | | Customer User ID = `device_id` (без входа в аккаунт) | Новое устройство получает другой CUID и, следовательно, другой профиль. Подписка синхронизируется с новым профилем через событие **Access level updated**, но `subscription_started` не срабатывает — новый профиль считается наследником исходной покупки. Аналитика, основанная на `subscription_started`, будет занижать количество возвращающихся пользователей. | Используйте стабильный идентификатор аккаунта в качестве Customer User ID, чтобы вернувшийся пользователь соответствовал существующему профилю на разных устройствах. | | Customer User ID = стабильный идентификатор аккаунта (вход на каждом устройстве) | SDK автоматически синхронизирует подписку при `activate()`, а `identify()` сопоставляет существующий профиль по CUID. | Никаких дополнительных действий не требуется — и идентификация, и подписка разрешаются автоматически. | | Наследник Apple Family Sharing | Член семьи получает подписку только через событие **Access level updated** — `subscription_started` не срабатывает. | Отслеживайте событие **Access level updated**. Полную матрицу событий см. в разделе [Apple Family Sharing](apple-family-sharing). | | Один аккаунт Apple/Google, разные пользователи внутри приложения | Первый профиль, зафиксировавший покупку, становится родительским. Последующие профили видят подписку через цепочку наследования с одним событием **Access level updated**. | Требуйте входа в аккаунт, затем выберите [режим совместного использования](sharing-paid-access-between-user-accounts), подходящий для вашей модели. | **Восстановление покупок на новом устройстве** Добавьте кнопку «Восстановить покупки», инициируемую пользователем, на свой пейвол. Apple App Review (руководство 3.1.1) её требует, и она служит запасным вариантом на случай, если автоматическая синхронизация пропустит граничный случай. Кнопка должна вызывать `restorePurchases` в вашем SDK. Программный вызов `restorePurchases` при первом запуске не нужен для обычного использования — SDK уже выполняет аналогичную операцию при `activate()`. Программные вызовы стоит использовать только для принудительной проверки чека, например при отладке отсутствующего доступа после завершения `activate()`. --- # File: setting-user-attributes --- --- title: "Установка атрибутов пользователя в iOS SDK" description: "Узнайте, как устанавливать атрибуты пользователей в Adapty для более точной сегментации аудитории." --- Вы можете задавать дополнительные атрибуты пользователей вашего приложения: email, номер телефона и т. д. Эти атрибуты можно использовать для создания [сегментов](segments) пользователей или просматривать их в CRM. ### Установка атрибутов пользователя \{#setting-user-attributes\} Чтобы установить атрибуты пользователя, вызовите метод `.updateProfile()`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers let builder = AdaptyProfileParameters.Builder() .with(email: "email@email.com") .with(phoneNumber: "+18888888888") .with(firstName: "John") .with(lastName: "Appleseed") .with(gender: .other) .with(birthday: Date()) do { try await Adapty.updateProfile(params: builder.build()) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers let builder = AdaptyProfileParameters.Builder() .with(email: "email@email.com") .with(phoneNumber: "+18888888888") .with(firstName: "John") .with(lastName: "Appleseed") .with(gender: .other) .with(birthday: Date()) Adapty.updateProfile(params: builder.build()) { error in if error != nil { // handle the error } } ``` </TabItem> </Tabs> Обратите внимание: атрибуты, ранее установленные с помощью метода `updateProfile`, сбрасываться не будут. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Список допустимых ключей \{#the-allowed-keys-list\} Допустимые ключи `<Key>` объекта `AdaptyProfileParameters.Builder` и соответствующие значения `<Value>` приведены ниже: | Ключ | Значение | |---|-----| | <p>email</p><p>phoneNumber</p><p>firstName</p><p>lastName</p> | String | | gender | Enum, допустимые значения: `female`, `male`, `other` | | birthday | Date | ### Пользовательские атрибуты \{#custom-user-attributes\} Вы можете задавать собственные атрибуты — как правило, они связаны с тем, как пользователь взаимодействует с вашим приложением. Например, для фитнес-приложений это может быть количество тренировок в неделю, а для приложений по изучению языков — уровень знаний пользователя. Такие атрибуты можно использовать в сегментах для создания таргетированных пейволов и предложений, а также в аналитике, чтобы понять, какие продуктовые метрики сильнее всего влияют на выручку. ```swift showLineNumbers do { builder = try builder.with(customAttribute: "value1", forKey: "key1") } catch { // handle key/value validation error } ``` Чтобы удалить существующий ключ, используйте метод `.withRemoved(customAttributeForKey:)`: ```swift showLineNumbers do { builder = try builder.withRemoved(customAttributeForKey: "key2") } catch { // handle error } ``` Иногда нужно узнать, какие пользовательские атрибуты уже установлены. Для этого используйте поле `customAttributes` объекта `AdaptyProfile`. :::warning Имейте в виду, что значение `customAttributes` может быть устаревшим: атрибуты пользователя могут отправляться с разных устройств в любое время, поэтому данные на сервере могут измениться после последней синхронизации. ::: ### Ограничения \{#limits\} - Не более 30 пользовательских атрибутов на одного пользователя - Длина имени ключа — до 30 символов. Имя ключа может содержать буквенно-цифровые символы и любые из следующих: `_` `-` `.` - Значение может быть строкой или числом с плавающей точкой длиной не более 50 символов. --- # File: subscription-status --- --- title: "Проверка статуса подписки в iOS SDK" description: "Отслеживайте и управляйте статусом подписки пользователя в Adapty для улучшения удержания клиентов." --- С Adapty отслеживать статус подписки очень просто. Вам не нужно вручную прописывать идентификаторы продуктов в коде — достаточно проверить наличие активного [уровня доступа](access-level), чтобы убедиться, что у пользователя есть подписка. Перед тем как приступить к проверке статуса подписки, настройте [App Store Server Notifications](enable-app-store-server-notifications). ## Уровень доступа и объект AdaptyProfile \{#access-level-and-the-adaptyprofile-object\} Уровни доступа — это свойства объекта [AdaptyProfile](https://swift.adapty.io/documentation/adapty/adaptyprofile). Рекомендуем получать профиль при запуске приложения, например, когда вы [идентифицируете пользователя](identifying-users#set-customer-user-id-on-configuration), и обновлять его при каждом изменении. Так вы сможете использовать объект профиля, не запрашивая его повторно. Чтобы получать уведомления об обновлениях профиля, подпишитесь на изменения профиля, как описано в разделе [Подписка на обновления статуса подписки](subscription-status#listening-for-subscription-status-updates) ниже. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ## Получение уровня доступа с сервера \{#retrieving-the-access-level-from-the-server\} Чтобы получить уровень доступа с сервера, используйте метод `.getProfile()`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let profile = try await Adapty.getProfile() if profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.getProfile { result in if let profile = try? result.get() { // check the access profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false { // grant access to premium features } } } ``` </TabItem> </Tabs> Параметры ответа: | Параметр | Описание | | --------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Profile | <p>Объект [AdaptyProfile](https://swift.adapty.io/documentation/adapty/adaptyprofile). Как правило, для определения наличия у пользователя премиум-доступа достаточно проверить статус уровня доступа профиля.</p><p></p><p>Метод `.getProfile` возвращает максимально актуальные данные, поскольку всегда пытается обратиться к API. Если по какой-либо причине (например, из-за отсутствия подключения к интернету) Adapty SDK не может получить данные с сервера, возвращаются данные из кэша. Важно отметить, что Adapty SDK регулярно обновляет кэш `AdaptyProfile`, чтобы данные оставались как можно более актуальными.</p> | Метод `.getProfile()` возвращает профиль пользователя, из которого можно получить статус уровня доступа. В одном приложении может быть несколько уровней доступа. Например, если у вас новостное приложение с независимыми подписками на разные темы, вы можете создать уровни доступа «sports» и «science». Однако в большинстве случаев достаточно одного уровня доступа — тогда можно просто использовать уровень «premium» по умолчанию. Пример проверки уровня доступа «premium» по умолчанию: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let profile = try await Adapty.getProfile() let isPremium = profile.accessLevels["premium"]?.isActive ?? false // grant access to premium features } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers Adapty.getProfile { result in if let profile = try? result.get(), profile.accessLevels["premium"]?.isActive ?? false { // grant access to premium features } } ``` </TabItem> </Tabs> ### Подписка на обновления статуса подписки \{#listening-for-subscription-status-updates\} Adapty генерирует событие каждый раз, когда подписка пользователя изменяется. Чтобы получать сообщения от Adapty, необходимо выполнить дополнительную настройку: ```swift showLineNumbers Adapty.delegate = self // To receive subscription updates, extend `AdaptyDelegate` with this method: nonisolated func didLoadLatestProfile(_ profile: AdaptyProfile) { // handle any changes to subscription state } ``` Adapty также генерирует событие при запуске приложения. В этом случае передаётся кэшированный статус подписки. ### Кэш статуса подписки \{#subscription-status-cache\} Кэш, реализованный в Adapty SDK, хранит статус подписки профиля. Это означает, что даже при недоступности сервера можно обратиться к кэшированным данным для получения информации о статусе подписки профиля. При этом важно учитывать, что напрямую запрашивать данные из кэша невозможно. SDK периодически обращается к серверу каждую минуту, чтобы проверить наличие обновлений, связанных с профилем. При наличии изменений — например, новых транзакций или других обновлений — они передаются в кэш, чтобы синхронизировать его с сервером. --- # File: ios-deal-with-att --- --- title: "Работа с ATT в iOS SDK" description: "Начните работу с Adapty на iOS для упрощения настройки подписок и управления ими." --- Если ваше приложение использует фреймворк AppTrackingTransparency и показывает пользователю запрос на авторизацию отслеживания, необходимо передать [статус авторизации](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/) в Adapty. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers let builder = AdaptyProfileParameters.Builder() .with(appTrackingTransparencyStatus: .authorized) do { try await Adapty.updateProfile(params: builder.build()) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="Swift-Callback" default> ```swift showLineNumbers if #available(iOS 14, macOS 11.0, *) { let builder = AdaptyProfileParameters.Builder() .with(appTrackingTransparencyStatus: .authorized) Adapty.updateProfile(params: builder.build()) { [weak self] error in if error != nil { // handle the error } } } ``` </TabItem> </Tabs> :::warning Настоятельно рекомендуем передавать это значение как можно раньше при каждом его изменении — только в этом случае данные будут своевременно отправлены в настроенные вами интеграции. ::: --- # File: kids-mode --- --- title: "Режим Kids Mode в iOS SDK" description: "Легко включите Kids Mode для соответствия политикам Apple. В iOS SDK не собираются IDFA и рекламные данные." --- <SDKv4> Если ваше iOS-приложение предназначено для детей, необходимо соблюдать политики [Apple](https://developer.apple.com/kids/). Если вы используете Adapty SDK, несколько простых шагов помогут настроить его в соответствии с этими требованиями и пройти проверку в App Store. ## Что нужно настроить? \{#whats-required\} Вам нужно настроить SDK, чтобы отключить сбор следующих данных: - [IDFA (идентификатор рекламодателя)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) - [IP-адрес](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Кроме того, рекомендуем осторожно обращаться с customer user ID. ID в формате `<FirstName.LastName>` однозначно расценивается как сбор персональных данных — как и использование email. Для Kids Mode лучше всего использовать рандомизированные или анонимизированные идентификаторы (например, хешированные ID или UUID, сгенерированные устройством) — это обеспечит соответствие требованиям. ## Включение детского режима \{#enabling-kids-mode\} ### Обновления в дашборде Adapty В дашборде Adapty нужно отключить сбор IP-адресов. Перейдите в [App settings](https://app.adapty.io/settings/general) и нажмите **Disable IP address collection** в разделе **Collect users' IP address**. ### Обновления в коде вашего мобильного приложения Начиная с SDK 4.0, Kids Mode — это трейт Swift-пакета под названием `KidsMode`. При включении трейта IDFA и AdSupport полностью исключаются из компиляции по всему SDK — вы по-прежнему используете стандартные модули **Adapty** и **AdaptyUI** и стандартные операторы `import Adapty` / `import AdaptyUI`. :::note Трейт `KidsMode` доступен начиная с SDK версии 4.0. Начиная с SDK 4.0, SDK устанавливается только через Swift Package Manager — CocoaPods больше не поддерживается. ::: <Tabs> <TabItem value="xcode" label="Xcode" default> 1. [Установите Adapty SDK](sdk-installation-ios) как обычно, выбрав стандартные модули **Adapty** и **AdaptyUI**. 2. В Xcode 26.4 или новее откройте настройки проекта, перейдите в раздел **Package Dependencies** и включите трейт **KidsMode** для зависимости AdaptySDK-iOS. :::note В версиях Xcode ниже 26.4 нельзя включить трейты для проекта Xcode через интерфейс. В этом случае создайте небольшой локальный Swift-пакет, который зависит от Adapty с включённым трейтом `KidsMode` (см. вкладку **Package.swift**), и добавьте зависимость от этого пакета в целевой объект приложения. ::: </TabItem> <TabItem value="spm" label="Package.swift"> Если вы добавляете Adapty как зависимость в `Package.swift`, включите трейт в объявлении пакета. Трейты требуют `swift-tools-version` 6.1 или выше. ```swift showLineNumbers title="Package.swift" .package( url: "https://github.com/adaptyteam/AdaptySDK-iOS.git", from: "4.0.0", traits: ["KidsMode"] ) ``` </TabItem> </Tabs> </SDKv4> <SDKv3> Если ваше iOS-приложение предназначено для детей, вы обязаны соблюдать правила [Apple](https://developer.apple.com/kids/). При использовании Adapty SDK несколько простых шагов помогут настроить его в соответствии с этими правилами и успешно пройти проверку в сторе. ## Что необходимо настроить? \{#whats-required\} Вам нужно настроить SDK, чтобы отключить сбор: - [IDFA (Identifier for Advertisers)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) - [IP-адреса](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Кроме того, рекомендуем внимательно подходить к выбору customer user ID. Идентификатор в формате `<FirstName.LastName>` однозначно расценивается как сбор персональных данных — так же, как и использование email. Для режима «Дети» рекомендуется использовать случайные или анонимизированные идентификаторы (например, хешированные ID или UUID, сгенерированные на устройстве), чтобы обеспечить соответствие требованиям. ## Включение режима Kids Mode \{#enabling-kids-mode\} ### Изменения в дашборде Adapty В дашборде Adapty нужно отключить сбор IP-адресов. Перейдите в [App settings](https://app.adapty.io/settings/general) и нажмите **Disable IP address collection** в разделе **Collect users' IP address**. ### Обновления в коде вашего мобильного приложения \{#updates-in-your-mobile-app-code\} Чтобы соблюдать требования политик, отключите сбор IDFA и IP-адреса пользователя. <Tabs> <TabItem value="spm" label="Swift Package Manager" default> Если вы используете Swift Package Manager, можно включить Kids Mode, выбрав модуль **Adapty_KidsMode** в Xcode при установке SDK. В Xcode перейдите в **File** -> **Add Package Dependency...**. Обратите внимание, что шаги добавления зависимостей могут отличаться в разных версиях Xcode — при необходимости обратитесь к документации Xcode. 1. Введите URL репозитория: ``` https://github.com/adaptyteam/AdaptySDK-iOS.git ``` 2. Выберите версию (рекомендуется последняя стабильная) и нажмите **Add Package**. 3. В окне **Choose Package Products** выберите нужные модули: - **Adapty_KidsMode** (основной модуль) - **AdaptyUI_KidsMode** (опционально — только если планируете использовать Paywall Builder) Остальные пакеты не нужны. 4. Нажмите **Add Package**, чтобы завершить установку. 5. В коде используйте `import Adapty_KidsMode` вместо `import Adapty`, и `import AdaptyUI_KidsMode` вместо `import AdaptyUI`: ```swift ``` </TabItem> <TabItem value="cocoapods" label="CocoaPods"> 1. Обновите Podfile: - Если у вас **нет** секции `post_install`, добавьте весь блок кода ниже целиком. - Если секция `post_install` **уже есть**, добавьте в неё выделенные строки. ```ruby showLineNumbers title="Podfile" def adapty_enable_kids_mode(installer) installer.pods_project.targets.each do |target| next unless target.name == 'Adapty' target.build_configurations.each do |config| flags = config.build_settings['OTHER_SWIFT_FLAGS'] || '$(inherited)' flags = flags.join(' ') if flags.is_a?(Array) config.build_settings['OTHER_SWIFT_FLAGS'] = "#{flags} -DADAPTY_KIDS_MODE" end target.frameworks_build_phase.files.dup.each do |bf| target.frameworks_build_phase.remove_build_file(bf) if bf.display_name.to_s.include?('AdSupport') end end installer.pods_project.save Dir.glob(File.join(installer.sandbox.root, 'Target Support Files', '**', '*.xcconfig')).each do |xc| File.write(xc, File.read(xc).gsub(/\s*-framework\s+"?AdSupport"?/, '')) end end post_install do |installer| # ... keep your existing post_install body (Flutter adds one automatically) ... adapty_enable_kids_mode(installer) # <-- enable Adapty Kids Mode end ``` 2. Выполните следующую команду, чтобы применить изменения: ```sh showLineNumbers title="Shell" pod install ``` </TabItem> </Tabs> </SDKv3> --- # File: get-onboardings --- --- title: "Получение онбордингов и их конфигурации" description: "Узнайте, как получать онбординги в Adapty." --- :::tip **Начиная с SDK v4**, вы можете создавать [флоу](get-pb-paywalls) как более мощную альтернативу онбордингам. В отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это даёт более плавные анимации, нативный внешний вид iOS, быструю загрузку и отсутствие зависимости от WebView. Подробнее: [Получение флоу и пейволов](get-pb-paywalls) и [Отображение флоу и пейволов](ios-present-paywalls). ::: После того как вы [разработали визуальную часть онбординга](design-onboarding) с помощью билдера в дашборде Adapty, его можно показать в мобильном приложении. Первый шаг — получить онбординг, связанный с плейсментом, и конфигурацию его отображения, как описано ниже. Прежде чем начать, убедитесь, что: 1. У вас установлен [Adapty iOS, Android, React Native или Flutter SDK](installation-of-adapty-sdks) версии 3.8.0 или выше. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). ## Получение онбординга \{#fetch-onboarding\} Когда вы создаёте [онбординг](onboardings) в нашем no-code редакторе, он сохраняется как контейнер с конфигурацией, которую приложение должно загрузить и отобразить. Этот контейнер управляет всем процессом: какой контент показывать, как его представлять и как обрабатывать действия пользователя (например, ответы на вопросы или заполнение форм). Контейнер также автоматически отслеживает аналитические события, поэтому отдельно реализовывать отслеживание просмотров не нужно. Для лучшей производительности загружайте конфигурацию онбординга заранее — чтобы изображения успели загрузиться до того, как пользователь увидит экран. Чтобы получить онбординг, используйте метод `getOnboarding`: ```swift showLineNumbers do { let onboarding = try await Adapty.getOnboarding(placementId: "YOUR_PLACEMENT_ID") // the requested onboarding } catch { // handle the error } ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указывали при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | <p>Идентификатор локализации онбординга. Ожидается языковой код из одного или двух подтегов, разделённых символом минус (**-**). Первый подтег — язык, второй — регион.</p><p></p><p>Например: `en` — английский, `pt-br` — бразильский португальский.</p><p>Подробнее о кодах локализации и рекомендациях по их использованию — в разделе [Локализации и коды локалей](localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите вариант `.returnCacheDataElseLoad` — он возвращает кешированные данные, если они есть. В этом случае пользователи могут не получить самые свежие данные, зато загрузка будет быстрее вне зависимости от качества связи. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.</p><p></p><p>Adapty SDK хранит онбординги локально в двух слоях: регулярно обновляемый кеш, описанный выше, и резервные онбординги. Мы также используем CDN для более быстрой загрузки онбордингов и отдельный резервный сервер на случай недоступности CDN. Система спроектирована так, чтобы вы всегда получали актуальную версию онбордингов даже при нестабильном интернете.</p> | | **loadTimeout** | по умолчанию: 5 сек | <p>Это значение ограничивает таймаут метода. Если таймаут истекает, возвращаются кешированные данные или локальный резервный вариант.</p><p>Обратите внимание: в редких случаях метод может завершиться с небольшим превышением значения, указанного в `loadTimeout`, поскольку операция может включать несколько запросов под капотом.</p> | | Параметр | Описание | |:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Объект [`AdaptyOnboarding`](https://swift.adapty.io/documentation/adapty/adaptyonboarding) с идентификатором и конфигурацией онбординга, Remote Config и рядом других свойств. | ## Ускорьте загрузку онбординга с помощью онбординга для аудитории по умолчанию \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} Как правило, онбординги загружаются почти мгновенно, и беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и онбордингов, а у пользователей слабое интернет-соединение, загрузка онбординга может занять больше времени, чем хотелось бы. В таких случаях лучше показать онбординг по умолчанию, чтобы не оставлять пользователя ни с чем. Чтобы решить эту задачу, можно использовать метод `getOnboardingForDefaultAudience`, который получает онбординг указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать онбординг через метод `getOnboarding`, как описано в разделе [Получение онбординга](#fetch-onboarding) выше. :::warning Рассмотрите возможность использования `getOnboarding` вместо `getOnboardingForDefaultAudience`, поскольку последний имеет важные ограничения: - **Проблемы совместимости**: может вызвать трудности при поддержке нескольких версий приложения — придётся либо делать обратно совместимые дизайны, либо мириться с тем, что старые версии будут отображать контент некорректно. - **Без персонализации**: показывает контент только для аудитории «Все пользователи», без учёта страны, атрибуции или пользовательских атрибутов. Если более быстрая загрузка важнее этих недостатков в вашем случае, используйте `getOnboardingForDefaultAudience`, как показано ниже. В противном случае используйте `getOnboarding`, как описано [выше](#fetch-onboarding). ::: ```swift showLineNumbers Adapty.getOnboardingForDefaultAudience(placementId: "YOUR_PLACEMENT_ID") { result in switch result { case let .success(onboarding): // запрошенный онбординг case let .failure(error): // обработка ошибки } } ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | <p>Идентификатор локализации онбординга. Ожидается код языка, состоящий из одного или двух подтегов, разделённых символом минуса (**-**). Первый подтег обозначает язык, второй — регион.</p><p></p><p>Например: `en` — английский, `pt-br` — бразильский португальский.</p><p>Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите `.returnCacheDataElseLoad` — он возвращает кешированные данные, если они есть. В этом случае данные могут быть не самыми свежими, зато загрузка будет быстрее независимо от качества связи. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы сократить количество сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.</p><p></p><p>Adapty SDK хранит онбординги локально в двух слоях: регулярно обновляемый кеш, описанный выше, и резервные онбординги. Для более быстрой загрузки онбордингов используется CDN, а в случае его недоступности — отдельный резервный сервер. Эта система обеспечивает получение актуальных версий онбордингов при надёжной работе даже при слабом интернет-соединении.</p> | --- # File: ios-present-onboardings --- --- title: "Present onboardings in iOS SDK" description: "Discover how to present onboardings on iOS to boost conversions and revenue." --- :::tip **Начиная с SDK v4**, вы можете создавать [флоу](get-pb-paywalls) как более мощную альтернативу онбордингам. В отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает более плавные анимации, нативный вид iOS, быструю загрузку и отсутствие зависимости от WebView. Подробнее в разделах [Получение флоу и пейволов](get-pb-paywalls) и [Отображение флоу и пейволов](ios-present-paywalls). ::: Если вы создали онбординг с помощью конструктора, вам не нужно беспокоиться о его рендеринге в коде мобильного приложения для отображения пользователю. Такой онбординг содержит и то, что должно показываться, и то, как это должно показываться. Перед началом убедитесь, что: 1. Вы установили [Adapty iOS SDK](sdk-installation-ios) версии 3.8.0 или выше. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). ## Показ онбордингов в Swift \{#present-onboardings-in-swift\} Чтобы отобразить визуальный онбординг на экране устройства, выполните следующие шаги: 1. Получите конфигурацию онбординга с помощью метода `.getOnboardingConfiguration`. 2. Инициализируйте визуальный онбординг с помощью метода `.onboardingController`: Параметры запроса: | Параметр | Наличие | Описание | |:-----------------------------|:---------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onboarding configuration** | required | Объект `AdaptyUI.OnboardingConfiguration`, содержащий все свойства онбординга. Используйте метод `AdaptyUI.getOnboardingConfiguration` для его получения. | | **delegate** | required | Объект `AdaptyOnboardingControllerDelegate` для прослушивания событий онбординга. | Возвращает: | Объект | Описание | |:-------------------------------|:--------------------------------------------------------| | **AdaptyOnboardingController** | Объект, представляющий запрошенный экран онбординга | 3. После успешного создания объекта его можно отобразить на экране устройства: ```swift showLineNumbers title="Swift" import Adapty import AdaptyUI // 0. Get an onboarding if you haven't done it yet let onboarding = try await Adapty.getOnboarding(placementId: "YOUR_PLACEMENT_ID") // 1. Obtain the onboarding view configuration: let configuration = try AdaptyUI.getOnboardingConfiguration(forOnboarding: onboarding) // 2. Create Onboarding View Controller let onboardingController = try AdaptyUI.onboardingController( with: configuration, delegate: <AdaptyOnboardingControllerDelegate> ) // 3. Present it to the user present(onboardingController, animated: true) ``` ## Показ онбординга в SwiftUI \{#present-onboardings-in-swiftui\} Чтобы отобразить визуальный онбординг на экране устройства в SwiftUI: ```swift showLineNumbers title="SwiftUI" // 1. Obtain the onboarding view configuration: let configuration = try AdaptyUI.getOnboardingConfiguration(forOnboarding: onboarding) // 2. Display the Onboarding View within your view hierarchy AdaptyOnboardingView( configuration: configuration, placeholder: { Text("Your Placeholder View") }, onCloseAction: { action in // hide the onboarding view }, onError: { error in // handle the error } ) ``` ## Добавление плавных переходов между сплэш-экраном и онбордингом \{#add-smooth-transitions-between-the-splash-screen-and-onboarding\} По умолчанию между сплэш-экраном и онбордингом отображается экран загрузки, пока онбординг полностью не загрузится. Если вы хотите сделать переход более плавным, можно его настроить: либо продлить отображение сплэш-экрана, либо показать что-то другое. Для этого задайте placeholder (то, что будет отображаться во время загрузки онбординга). Если placeholder задан, онбординг будет загружаться в фоне и автоматически отобразится, как только будет готов. <Tabs> <TabItem value="swift" label="UIKit"> ```swift showLineNumbers extension YourOnboardingManagerClass: AdaptyOnboardingControllerDelegate { func onboardingsControllerLoadingPlaceholder( _ controller: AdaptyOnboardingController ) -> UIView? { // instantiate and return the UIView which will be presented while onboarding is being loaded } } ``` </TabItem> <TabItem value="swiftui" label="SwiftUI"> ```swift showLineNumbers AdaptyOnboardingView( configuration: configuration, placeholder: { // define your placeholder view, which will be presented while onboarding is being loaded }, // the rest of the implementation ) ``` </TabItem> </Tabs> ## Настройка открытия ссылок в онбордингах \{#customize-how-links-open-in-onboardings\} :::important Настройка открытия ссылок в онбордингах поддерживается начиная с Adapty SDK v3.15.1. ::: По умолчанию ссылки в онбордингах открываются во встроенном браузере. Это обеспечивает бесшовный пользовательский опыт: веб-страницы отображаются прямо внутри приложения, не вынуждая пользователя переключаться между приложениями. Если вы хотите открывать ссылки во внешнем браузере, измените это поведение, установив параметр `externalUrlsPresentation` в значение `.externalBrowser`: ```swift showLineNumbers let configuration = try AdaptyUI.getOnboardingConfiguration( forOnboarding: onboarding, externalUrlsPresentation: .externalBrowser // default – .inAppBrowser ) ``` --- # File: ios-handling-onboarding-events --- --- title: "Обработка событий онбординга в iOS SDK" description: "Обработка событий онбординга в iOS с помощью Adapty." --- :::tip **Начиная с SDK v4**, вы можете создавать [флоу](get-pb-paywalls) как более мощную альтернативу онбордингам. В отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает более плавные анимации, нативный внешний вид iOS, быструю загрузку и отсутствие зависимости от WebView. Подробнее: [Получение флоу и пейволов](get-pb-paywalls) и [Отображение флоу и пейволов](ios-present-paywalls). ::: Прежде чем начать, убедитесь, что: 1. Вы установили [Adapty iOS SDK](sdk-installation-ios) версии 3.8.0 или новее. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). Онбординги, настроенные с помощью конструктора, генерируют события, на которые ваше приложение может реагировать. Ниже описано, как это делать. Чтобы управлять процессами на экране онбординга в вашем мобильном приложении или отслеживать их, реализуйте методы `AdaptyOnboardingControllerDelegate`. ## Пользовательские действия \{#custom-actions\} В конструкторе можно добавить **пользовательское** действие к кнопке и назначить ему идентификатор. <img src="/assets/shared/img/ios-events-1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Затем вы можете использовать этот ID в своём коде и обрабатывать его как пользовательское действие. Например, если пользователь нажимает на кастомную кнопку — скажем, **Login** или **Allow notifications** — метод делегата `onboardingController` будет вызван с кейсом `.custom(id:)`, где параметр `actionId` соответствует **Action ID** из билдера. Вы можете задавать собственные ID, например "allowNotifications". ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, onCustomAction action: AdaptyOnboardingsCustomAction) { if action.actionId == "allowNotifications" { // Request notification permissions } } func onboardingController(_ controller: AdaptyOnboardingController, didFailWithError error: AdaptyUIError) { // Handle errors } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ``` </Details> ## Закрытие онбординга \{#closing-onboarding\} Онбординг считается закрытым, когда пользователь нажимает кнопку с назначенным действием **Close**. <img src="/assets/shared/img/ios-events-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important Обратите внимание: вам нужно самостоятельно обработать закрытие онбординга пользователем — например, скрыть экран онбординга. ::: Например: ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, onCloseAction action: AdaptyOnboardingsCloseAction) { controller.dismiss(animated: true) } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> ## Открытие пейвола \{#opening-a-paywall\} :::tip Обрабатывайте это событие, если хотите открыть пейвол внутри онбординга. Если же вы хотите открыть пейвол после его закрытия, есть более простой способ — обработайте [`AdaptyOnboardingsCloseAction`](#closing-onboarding) и откройте пейвол без привязки к данным события. ::: Самый удобный подход при работе с пейволами в онбордингах — сделать action ID равным placement ID пейвола. Тогда после получения `AdaptyOnboardingsOpenPaywallAction` можно сразу использовать placement ID, чтобы получить и открыть нужный пейвол. Обратите внимание, что одновременно на экране может отображаться только одно представление (пейвол или онбординг). Если вы показываете пейвол поверх онбординга, программно управлять онбордингом в фоне не получится. Попытка закрыть онбординг закроет пейвол, оставив онбординг видимым. Чтобы избежать этого, всегда закрывайте представление онбординга перед показом пейвола. ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, onPaywallAction action: AdaptyOnboardingsOpenPaywallAction) { // Закрываем онбординг перед показом флоу controller.dismiss(animated: true) { Task { do { // Получаем флоу по идентификатору плейсмента из action let flow = try await Adapty.getFlow(placementId: action.actionId) // Получаем конфигурацию флоу let flowConfiguration = try await AdaptyUI.getFlowConfiguration( forFlow: flow ) // Создаём и показываем контроллер флоу let flowController = try AdaptyUI.flowController( with: flowConfiguration, delegate: self ) // Показываем флоу из корневого view controller if let rootVC = UIApplication.shared.windows.first?.rootViewController { rootVC.present(flowController, animated: true) } } catch { // Обрабатываем ошибки, возникшие при загрузке флоу print("Failed to present flow: \(error)") } } } } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ``` </Details> ## Завершение загрузки онбординга \{#finishing-loading-onboarding\} Когда онбординг завершает загрузку, вызывается следующий метод: ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, didFinishLoading action: OnboardingsDidFinishLoadingAction) { // Handle loading completion } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ``` </Details> ## Отслеживание навигации \{#tracking-navigation\} Метод `onAnalyticsEvent` вызывается при возникновении различных аналитических событий в ходе онбординга. Объект `event` может быть одного из следующих типов: |Тип | Описание | |------------|-------------| | `onboardingStarted` | Когда онбординг загружен | | `screenPresented` | Когда отображается любой экран | | `screenCompleted` | Когда экран завершён. Включает необязательный `elementId` (идентификатор завершённого элемента) и необязательный `reply` (ответ пользователя). Срабатывает, когда пользователь выполняет любое действие для выхода с экрана. | | `secondScreenPresented` | Когда отображается второй экран | | `userEmailCollected` | Срабатывает, когда email пользователя собирается через поле ввода | | `onboardingCompleted` | Срабатывает, когда пользователь достигает экрана с ID `final`. Если вам нужно это событие, [назначьте ID `final` последнему экрану](design-onboarding). | | `unknown` | Для любого нераспознанного типа события. Включает `name` (название неизвестного события) и `meta` (дополнительные метаданные) | Каждое событие содержит мета-информацию в поле `meta`: | Поле | Описание | |------------|-------------| | `onboardingId` | Уникальный идентификатор флоу онбординга | | `screenClientId` | Идентификатор текущего экрана | | `screenIndex` | Позиция текущего экрана во флоу | | `screensTotal` | Общее количество экранов во флоу | Вот пример того, как можно использовать события аналитики для отслеживания: ```swift func onboardingController(_ controller: AdaptyOnboardingController, onAnalyticsEvent event: AdaptyOnboardingsAnalyticsEvent) { switch event { case .onboardingStarted(let meta): // Отслеживаем начало онбординга trackEvent("onboarding_started", meta: meta) case .screenPresented(let meta): // Отслеживаем отображение экрана trackEvent("screen_presented", meta: meta) case .screenCompleted(let meta, let elementId, let reply): // Отслеживаем завершение экрана с ответом пользователя trackEvent("screen_completed", meta: meta, elementId: elementId, reply: reply) case .onboardingCompleted(let meta): // Отслеживаем успешное завершение онбординга trackEvent("onboarding_completed", meta: meta) case .unknown(let meta, let name): // Обрабатываем неизвестные события trackEvent(name, meta: meta) // При необходимости обработайте остальные случаи } } ``` <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```javascript // onboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // screenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // screenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // secondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // userEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // onboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> --- # File: ios-onboarding-input --- --- title: "Обработка данных из онбордингов в iOS SDK" description: "Сохраняйте и используйте данные из онбордингов в вашем iOS-приложении с помощью Adapty SDK." --- :::tip **Начиная с SDK v4**, вы можете создавать [флоу](get-pb-paywalls) как более мощную альтернативу онбордингам. В отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает более плавные анимации, единый стиль iOS, быструю загрузку и отсутствие зависимости от WebView. Смотрите [Получение флоу и пейволов](get-pb-paywalls) и [Отображение флоу и пейволов](ios-present-paywalls), чтобы начать. ::: Когда пользователи отвечают на вопрос викторины или вводят данные в поле ввода, вызывается метод `onStateUpdatedAction`. Вы можете сохранять или обрабатывать тип поля в своём коде. Например: ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, onStateUpdatedAction action: AdaptyOnboardingsStateUpdatedAction) { // Store user preferences or responses switch action.params { case .select(let params): // Handle single selection case .multiSelect(let params): // Handle multiple selections case .input(let params): // Handle text input case .datePicker(let params): // Handle date selection } } ``` Объект `action` содержит: | Параметр | Описание | |----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `elementId` | Уникальный идентификатор элемента ввода. Можно использовать для сопоставления вопросов с ответами при их сохранении. | | `params` | Объект с данными, введёнными пользователем, содержащий свойства type и value. | | `params.type` | Тип элемента ввода. Возможные значения:<br/>• `"select"` — выбор одного варианта из списка<br/>• `"multiSelect"` — выбор нескольких вариантов из списка<br/>• `"input"` — текстовое поле ввода<br/>• `"datePicker"` — выбор даты | | `params.value` | Значение или значения, выбранные либо введённые пользователем. Структура зависит от типа:<br/>• `select`: объект с полями `id`, `value`, `label`<br/>• `multiSelect`: массив объектов с полями `id`, `value`, `label`<br/>• `input`: объект с полями `type`, `value`<br/>• `datePicker`: объект с полями `day`, `month`, `year` | <Details> <summary>Примеры сохранённых данных (в вашей реализации могут отличаться)</summary> ```javascript // Example of a saved select action { "elementId": "preference_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "preferences_screen", "screenIndex": 1, "screensTotal": 3 }, "params": { "type": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" } } } // Example of a saved multi-select action { "elementId": "interests_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "screensTotal": 3 }, "params": { "type": "multiSelect", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ] } } // Example of a saved input action { "elementId": "name_input", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "input", "value": { "type": "text", "value": "John Doe" } } } // Example of a saved date picker action { "elementId": "birthday_picker", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "datePicker", "value": { "day": 15, "month": 6, "year": 1990 } } } ``` </Details> ## Варианты использования \{#use-cases\} ### Обогащение профилей пользователей данными \{#enrich-user-profiles-with-data\} Если вы хотите сразу связать введённые данные с профилем пользователя и не спрашивать его дважды об одном и том же, нужно [обновить профиль пользователя](setting-user-attributes) с этими данными при обработке действия. Например, вы просите пользователей ввести имя в текстовое поле с ID `name` и хотите установить значение этого поля как имя пользователя. Также вы просите их ввести email в поле `email`. В коде приложения это может выглядеть так: ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, onStateUpdatedAction action: AdaptyOnboardingsStateUpdatedAction) { // Сохраняем пользовательские настройки или ответы switch action.params { case .input(let params): // Обрабатываем текстовый ввод let builder = AdaptyProfileParameters.Builder() // Сопоставляем elementId с соответствующим полем профиля switch action.elementId { case "name": builder.with(firstName: params.value.value) case "email": builder.with(email: params.value.value) default: break } // Методы делегата синхронные; запускаем асинхронное обновление в Task. Task { do { try await Adapty.updateProfile(params: builder.build()) } catch { // обрабатываем ошибку } } default: break } } ``` ### Настройка пейволов на основе ответов \{#customize-paywalls-based-on-answers\} С помощью квизов в онбординге можно настраивать пейволы, которые пользователи видят после его прохождения. Например, можно спросить пользователей об их опыте в спорте и показывать разным группам разные CTA и продукты. 1. [Добавьте квиз](onboarding-quizzes) в конструкторе онбординга и задайте значимые идентификаторы его вариантам ответов. 2. Обработайте ответы квиза по их идентификаторам и [задайте пользовательские атрибуты](setting-user-attributes) для пользователей. ```swift showLineNumbers func onboardingController(_ controller: AdaptyOnboardingController, onStateUpdatedAction action: AdaptyOnboardingsStateUpdatedAction) { // Handle quiz responses and set custom attributes switch action.params { case .select(let params): // Handle quiz selection let builder = AdaptyProfileParameters.Builder() // Map quiz responses to custom attributes switch action.elementId { case "experience": // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) try? builder.with(customAttribute: params.value.value, forKey: "experience") default: break } // Delegate methods are synchronous; kick off the async update in a Task. Task { do { try await Adapty.updateProfile(params: builder.build()) } catch { // handle the error } } default: break } } ``` 3. [Создайте сегменты](segments) для каждого значения пользовательского атрибута. 4. Создайте [плейсмент](placements) и добавьте [аудитории](audience) для каждого созданного сегмента. 5. [Отобразите пейвол](ios-paywalls) для плейсмента в коде вашего приложения. Если в онбординге есть кнопка, открывающая пейвол, реализуйте код пейвола как [реакцию на действие этой кнопки](ios-handling-onboarding-events#opening-a-paywall). --- # File: ios-sdk-call-order --- --- title: "Порядок вызовов в iOS SDK" description: "Избегайте потери доступа к премиум-функциям, пропущенной атрибуции и периодических ошибок #2002, вызывая методы Adapty SDK в правильном порядке." --- `Adapty.activate()` должен завершиться раньше, чем вы вызовете любой другой метод Adapty SDK. До его завершения у SDK нет состояния. Любой вызов, сделанный до или параллельно с `activate()`, завершится ошибкой [`#2002 notActivated`](ios-sdk-error-handling#network-errors). Если приложение аутентифицирует пользователей и вы получаете customer user ID после запуска, вызовите `Adapty.identify()` в этот момент. Не вызывайте методы, связанные с действиями пользователя, пока `identify` не завершится. Вызовы, которые выполняются параллельно с ним, либо завершаются ошибкой [`#3006 profileWasChanged`](ios-sdk-error-handling#general-errors), либо применяются к анонимному профилю, созданному при активации. В таком случае атрибуция, MMP ID вроде `appsflyer_id` и принадлежность установки не всегда переносятся на идентифицированный профиль. Если приложение не аутентифицирует пользователей, пропустите `identify` и продолжайте работу с анонимным профилем. MMP и аналитические SDK (AppsFlyer, Adjust, Branch, PostHog) подчиняются тому же правилу. Инициализируйте их первыми и дождитесь коллбэков с UID, прежде чем вызывать `Adapty.activate`. Иначе MMP ID попадёт в краткий анонимный профиль и не всегда переносится в идентифицированный. Подробнее об AppsFlyer — в разделе [AppsFlyer](appsflyer). ## Правильный порядок \{#the-correct-order\} Ваш путь зависит от двух вещей: когда вы узнаёте идентификатор пользователя (customer user ID) и используете ли вы MMP или аналитический SDK. - **Шаги 2 и 5**: обязательны для каждого приложения. Активируйте SDK, затем вызывайте его методы. - **Шаги 1 и 3**: нужны только если вы интегрируете MMP или аналитический SDK (AppsFlyer, Adjust, Branch, PostHog). - **Шаг 4**: нужен только если ваше приложение аутентифицирует пользователей и получает customer user ID после запуска. Если ID пользователя известен при запуске приложения, передайте его напрямую в `activate()` (шаг 2a). В этом случае анонимный профиль не создаётся, поэтому шаг 4 не нужен. | Шаг | Вызов | Когда | Примечания | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | Инициализация MMP или аналитического SDK (AppsFlyer, Adjust, PostHog, Branch) | При запуске приложения, первым делом | Дождитесь колбэка с UID от MMP, например `getAppsFlyerUID`. | | 2a | `Adapty.activate(with: config)` с указанным `customerUserId` в конфиге | При запуске приложения, после шага 1, если у вас есть customer user ID | Рекомендуется. Анонимный профиль не создаётся. | | 2b | `Adapty.activate(with: config)` без `customerUserId` | При запуске приложения, после шага 1, если у вас нет customer user ID (или он не нужен) | Adapty создаёт анонимный профиль. | | 3 | `Adapty.setIntegrationIdentifier(...)` для каждого MMP | После шага 2, до любых вызовов, связанных с действиями пользователя | Обязательно, чтобы идентификаторы MMP попали в нужный профиль. | | 4 | `try await Adapty.identify("YOUR_USER_ID")` | После шага 3 (или шага 2, если MMP нет), до шага 5 — только на пути 2b с аутентификацией | Всегда используйте `await`. Параллельные вызовы во время `identify` приводят к ошибке `#3006 profileWasChanged`. | | 5 | `getPaywall`, `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution`, `updateProfile` | После шага 4, если вы вызываете `identify`; иначе после шага 3 (или шага 2, если MMP нет) | Эти вызовы требуют стабильного профиля. | :::important Пропуск этих шагов приводит к потере уровня доступа у вернувшихся пользователей, отсутствию `appsflyer_id` в профилях и показу пейволов для неправильной аудитории. ::: ## Установки через web2app и веб-воронки \{#web2app-and-web-funnel-installs\} Если пользователь совершает покупку через веб-чекаут (Stripe, Paddle, FunnelFox), а затем устанавливает нативное приложение, первый вызов `activate()` на устройстве создаёт новый анонимный профиль. Этот профиль не связан с веб-профилем. Если вы можете определить пользовательский ID до запуска приложения (из вашего auth-флоу или install referrer) — передайте его напрямую в `activate()`. В противном случае веб-покупка останется невидимой на устройстве, пока вы не вызовете `identify("YOUR_USER_ID")`, а затем `restorePurchases`. Какие метаданные передавать при каждом веб-чекауте, смотрите здесь: - [Stripe](stripe) - [Paddle](paddle) --- # File: ios-optimize-paywall-fetching --- --- title: "Оптимизация загрузки пейвола в iOS SDK" description: "Надёжная загрузка пейволов Adapty: тайминг, кэширование и резервные сценарии для iOS." --- Надёжная загрузка пейвола на iOS решает три задачи: быстрый рендеринг, возврат пейвола с нужной аудиторией и корректный фолбэк при медленной сети. Правила ниже охватывают тайминг, кэширование и резервные сценарии. :::tip Правила предполагают, что `Adapty.activate()` и `Adapty.identify()` уже завершились. См. [Порядок вызовов в iOS SDK](ios-sdk-call-order). ::: ## Правила и подводные камни \{#rules-and-pitfalls\} | Делайте так | Не делайте так | Почему | |---|---|---| | Загружайте только тот плейсмент, который собираетесь показать. | Не загружайте все плейсменты одновременно при запуске. | Массовая предзагрузка блокирует главный поток и вызывает чёрный экран во время всплеска нагрузки. | | Вызывайте `getPaywall` после того, как атрибуция успела разрешиться — например, через 1–2 секунды после `activate` или после срабатывания `onProfileUpdate`. | Не вызывайте `getPaywall` в `App.init()`. | Атрибуция ещё не пришла. Пейвол разрешается по аудитории по умолчанию и молча игнорирует сегменты и персонализацию ASA. | | Задавайте `loadTimeout` и настраивайте [резервный пейвол](fallback-paywalls) для каждого плейсмента. | Не ждите ответа `getPaywall` бесконечно. | Без таймаута пользователи с плохим соединением видят пустой экран до тех пор, пока сеть не ответит — или просто закрывают приложение. | Подробнее о параметрах `fetchPolicy` и `loadTimeout` — в разделе [Получение пейволов и продуктов](fetch-paywalls-and-products), о выборе подходящего плейсмента — в разделе [Плейсменты](placements). ## Настройка для слабого соединения \{#tune-for-poor-connectivity\} Для рынков с постоянно плохим интернетом (сельская местность, транспорт, регионы с проблемами маршрутизации): - Используйте `fetchPolicy: .returnCacheDataElseLoad` для всех запросов, кроме самого первого. - Настройте [резервный пейвол](fallback-paywalls) для каждого плейсмента в дашборде Adapty. - Установите `loadTimeout` в диапазоне 3–5 секунд и принимайте резервный пейвол при срабатывании таймаута. - Не блокируйте отображение пейвола вызовом `getProfile()`. Вызывайте `getPaywall` независимо, чтобы медленный профиль не задерживал интерфейс. --- # File: ios-show-aa-targeted-paywall --- --- title: "Показать пейвол с таргетингом Apple Ads при первом запуске в iOS SDK" description: "Дождитесь атрибуции Apple Ads перед запросом пейвола на iOS, используя AdaptyProfile.appliedAttributionSources." --- Apple Ads (AA) атрибуция поступает асинхронно после вызова `Adapty.activate()`. Если вы вызываете `getPaywall` слишком рано, атрибуция зачастую ещё не применена, и Adapty определяет плейсмент по аудитории по умолчанию — минуя ваши пейволы, сегментированные по AA. `AdaptyProfile.appliedAttributionSources` позволяет приложению определить момент, когда AA-атрибуция уже применена к профилю, чтобы запрос пейвола дождался корректного разрешения AA-сегментации. ## Прежде чем начать \{#before-you-start\} Вам потребуется: - Adapty iOS SDK версии **3.17.1** или выше. - Apple Ads, настроенный для приложения в Adapty. См. [Apple Ads](apple-search-ads). ## Как это работает \{#how-it-works\} После вызова `Adapty.activate()` SDK в фоновом режиме запрашивает у Apple данные атрибуции Apple Ads и передаёт результат в бэкенд Adapty. Когда AA становится активным источником атрибуции для профиля, SDK возвращает обновлённый `AdaptyProfile`, в массиве `appliedAttributionSources` которого содержится `.appleAds`. Пустой массив может означать следующее: - Атрибуция Apple Ads для этого профиля ещё не обработана. - Данные атрибуции не поступали вовсе. Даже с пустым массивом вызов `getPaywall` безопасен — Adapty разрешает запрос относительно аудитории, соответствующей текущему состоянию профиля (как правило, это аудитория по умолчанию). :::important Ожидание применяется только при **первом запуске**. Как только атрибуция Apple Ads зафиксирована, она сохраняется в профиле навсегда. При каждом последующем запуске кэшированный профиль уже содержит `.appleAds` в `appliedAttributionSources`, `didLoadLatestProfile` срабатывает с этим значением немедленно, а `getPaywall` возвращает пейвол с сегментацией по Apple Ads без каких-либо задержек. ::: ## Реализация \{#implementation\} При первом запуске следите за `.appleAds` в профиле и используйте жёсткий таймаут — если атрибуция Apple Ads так и не придёт, эти пользователи всё равно должны увидеть пейвол. 1. **Активируйте SDK.** См. [Установка и настройка iOS SDK](sdk-installation-ios). 2. **Подпишитесь на обновления профиля**, реализовав протокол `AdaptyDelegate` и метод `didLoadLatestProfile`. Если делегат ещё не настроен, см. [Отслеживание обновлений подписки](ios-check-subscription-status#listen-to-subscription-updates). 3. **Следите за `.appleAds` в `appliedAttributionSources`.** Когда оно появится, запросите пейвол — Adapty вернёт вариант, сегментированный по Apple Ads: ```swift extension <YourAdaptyDelegateImpl>: AdaptyDelegate { nonisolated func didLoadLatestProfile(_ profile: AdaptyProfile) { if profile.appliedAttributionSources.contains(where: { $0 == .appleAds }) { // load paywall via Adapty.getPaywall(placementId:) } } } ``` 4. **Запустите таймер на 3–5 секунд параллельно с подпиской.** Если таймер сработает раньше, чем появится `.appleAds`, всё равно запросите пейвол: Whichever path fires first should load the paywall; the other path should be skipped. Use a single state flag (for example, `hasLoadedPaywall`) to deduplicate so the paywall isn't fetched twice. Configure a [резервный пейвол](fallback-paywalls) for the placement so the user is never stuck if the network request fails. ## Полный пример \{#complete-example\} Реализация ниже запускает гонку между атрибуцией и таймаутом, параллельно предзагружает пейвол для аудитории по умолчанию и возвращает подходящий пейвол. Вызывающий код ожидает единственную асинхронную функцию — никаких делегатов и флагов состояния на стороне вызова. `ProfileObserver` — переиспользуемый синглтон, публикующий обновления профиля из `AdaptyDelegate`. `PaywallLoader.getPaywallOrDefault` запускает гонку с помощью `TaskGroup` в рамках структурированного параллелизма: - Если атрибуция поступает в пределах `timeout`, возвращается пейвол для сегментированной аудитории через `getPaywall(placementId:)`. - Если `timeout` истекает первым, возвращается заранее загруженный пейвол для аудитории по умолчанию через `getPaywallForDefaultAudience(placementId:)`. ```swift title="PaywallLoader.swift" /// Демонстрирует, как получить пейвол, зависящий от применения атрибуции, /// с резервным переходом к пейволу аудитории по умолчанию, если атрибуция /// не успевает прийти вовремя. /// /// Не имеет состояния и самодостаточен: каждый вызов запускает собственный /// предварительный запрос к аудитории по умолчанию и соревнует его /// с получением атрибуции + сегментированного пейвола. enum PaywallLoader { static func getPaywallOrDefault( placementId: String, timeout: TimeInterval ) async throws -> AdaptyPaywall { struct TimedOut: Error {} // Сразу запускаем запрос к аудитории по умолчанию, чтобы у него было // всё окно `timeout` для загрузки. Либо отменим его при успехе, // либо дождёмся результата при таймауте — без дублирования сетевых вызовов. let defaultPaywallTask = Task { try await Adapty.getPaywallForDefaultAudience(placementId: placementId) } do { // Соревнуем две дочерние задачи: первая завершившаяся побеждает. let result = try await withThrowingTaskGroup(of: AdaptyPaywall.self) { group in // 1. Ждём атрибуцию, затем запрашиваем у Adapty сегментированный пейвол. group.addTask { await waitForAttribution() return try await Adapty.getPaywall(placementId: placementId) } // 2. Таймер: выбрасывает `TimedOut` через `timeout` секунд. group.addTask { try await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000)) throw TimedOut() } guard let value = try await group.next() else { throw CancellationError() } group.cancelAll() // останавливаем проигравшего (таймер или ожидание атрибуции). return value } // Сегментированный пейвол победил — предварительный запрос к аудитории по умолчанию больше не нужен. defaultPaywallTask.cancel() return result } catch is TimedOut { // Атрибуция не успела примениться — возвращаем предзагруженный пейвол по умолчанию // (мгновенно, если уже готов, иначе ждём текущий запрос). return try await defaultPaywallTask.value } } /// Приостанавливается до появления профиля с нужным источником атрибуции. /// `@Published.values` сразу отдаёт текущий профиль при подписке, /// поэтому возвращает результат на первой итерации, если атрибуция уже применена. @MainActor private static func waitForAttribution() async { for await profile in ProfileObserver.shared.$profile.values { if profile?.appliedAttributionSources.contains(.appleAds) == true { return } } } } @MainActor final class ProfileObserver: AdaptyDelegate { static let shared = ProfileObserver() @Published private(set) var profile: AdaptyProfile? nonisolated func didLoadLatestProfile(_ profile: AdaptyProfile) { Task { @MainActor [weak self] in self?.profile = profile } } } ``` Подключите `ProfileObserver` к `AdaptyDelegate` один раз, после того как `Adapty.activate()` завершится: ```swift Adapty.delegate = ProfileObserver.shared ``` Вызовите с экрана-заставки: ```swift do { let paywall = try await PaywallLoader.getPaywallOrDefault( placementId: "YOUR_PLACEMENT_ID", timeout: 5 ) // показать пейвол } catch { // обработать ошибку или показать резервный пейвол } ``` Если в вашем приложении уже используется `AdaptyDelegate` для других целей (например, для [отслеживания обновлений подписки](ios-check-subscription-status#listen-to-subscription-updates)), передавайте `didLoadLatestProfile` в `ProfileObserver.shared` из существующего делегата вместо того, чтобы устанавливать `Adapty.delegate = ProfileObserver.shared`. --- # File: ios-test --- --- title: "Тестирование и релиз в iOS SDK" description: "Узнайте, как проверить статус подписки в вашем iOS-приложении с помощью Adapty." --- Если вы уже интегрировали Adapty SDK в своё iOS-приложение, стоит убедиться, что всё настроено правильно и покупки работают как ожидается. Для этого нужно протестировать как интеграцию SDK, так и сами покупки. ## Тестирование приложения \{#test-your-app\} Подробное руководство по тестированию встроенных покупок, включая тестирование в песочнице и через TestFlight, см. в нашем [гайде по тестированию](test-purchases-in-sandbox). ## Подготовка к релизу \{#prepare-for-release\} Перед отправкой приложения в стор выполните [чек-лист перед релизом](release-checklist) и убедитесь, что: - Подключение к стору и серверные уведомления настроены - Покупки выполняются и передаются в Adapty - Уровни доступа корректно открываются и восстанавливаются - Выполнены требования по конфиденциальности и модерации --- # File: InvalidProductIdentifiers --- --- title: "Исправление ошибки Code-1000 noProductIDsFound" description: "Устраните ошибки с недействительными идентификаторами продуктов при управлении подписками в Adapty." --- Ошибка с кодом 1000, `noProductIDsFound`, означает, что ни один из продуктов, запрошенных на пейволе, недоступен для покупки в App Store, хотя они там и указаны. Иногда вместе с этой ошибкой может появляться предупреждение `InvalidProductIdentifiers`. Если предупреждение появляется без ошибки — просто игнорируйте его. Если вы столкнулись с ошибкой `noProductIDsFound`, выполните следующие шаги для её устранения: ## Шаг 1. Проверьте Bundle ID \{#step-2-check-bundle-id\} 1. Откройте [App Store Connect](https://appstoreconnect.apple.com/apps). Выберите своё приложение и перейдите в раздел **General** → **App Information**. 2. Скопируйте **Bundle ID** в подразделе **General Information**. <Zoom> <img src="/docs/img/afd5012-bundle_id_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. Откройте вкладку [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) в верхнем меню Adapty и вставьте скопированное значение в поле **Bundle ID**. <Zoom> <img src="/docs/img/2d64163-bundle_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 4. Вернитесь на страницу **App information** в App Store Connect и скопируйте **Apple ID**. 5. На странице [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) в дашборде Adapty вставьте этот ID в поле **Apple app ID**. ## Шаг 2. Проверка продуктов \{#step-2-check-products\} 1. Откройте **App Store Connect** и перейдите в [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions) в меню слева. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите на название группы подписок. В разделе **Subscriptions** появится список ваших продуктов. 3. Убедитесь, что тестируемый продукт имеет статус **Ready to Submit**. Если нет, следуйте инструкциям на странице [Продукт в App Store](app-store-products). <img src="/assets/shared/img/ready-to-submit.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Сравните ID продукта из таблицы с тем, что указан во вкладке [**Products**](https://app.adapty.io/products) дашборда Adapty. Если ID не совпадают, скопируйте ID продукта из таблицы и [создайте продукт](create-product) с этим ID в дашборде Adapty. <img src="/assets/shared/img/product-id-copy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 3. Проверьте доступность продукта \{#step-4-check-product-availability\} 1. Вернитесь в **App Store Connect** и откройте раздел **Subscriptions**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите на название группы подписок, чтобы просмотреть продукты. 3. Выберите продукт, который хотите протестировать. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Прокрутите до раздела **Availability** и убедитесь, что в нём указаны все необходимые страны и регионы. <img src="/assets/shared/img/product-availability.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 4. Проверьте цены продуктов \{#step-5-check-product-prices\} 1. Снова перейдите в раздел **Monetization** → **Subscriptions** в **App Store Connect**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите на название группы подписок. 3. Выберите продукт, который хотите протестировать. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Прокрутите вниз до раздела **Subscription Pricing** и разверните раздел **Current Pricing for New Subscribers**. <img src="/assets/shared/img/check-prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Убедитесь, что все необходимые цены указаны. <img src="/assets/shared/img/product-pricing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 5. Убедитесь, что платный статус приложения, банковский счёт и налоговые формы активны \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\} 1. На главной странице [**App Store Connect**](https://appstoreconnect.apple.com/) нажмите **Business**. <img src="/assets/shared/img/business.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Выберите название вашей компании. 3. Прокрутите вниз и убедитесь, что ваши **Paid Apps Agreement**, **Bank Account** и **Tax forms** отображаются со статусом **Active**. Следуя этим шагам, вы сможете устранить предупреждение `InvalidProductIdentifiers` и запустить свои продукты в сторе. ## Шаг 6. Пересоздайте продукт, если он завис \{#step-6-recreate-the-product-if-its-stuck\} Шаги 1–5 могут пройти успешно — статус `Approved`, совпадающий Bundle ID, корректный API-ключ — а SDK всё равно возвращает `1000 noProductIDsFound`. В таком случае продукт может быть завис в реестре Apple. Реестр продуктов Apple иногда переходит в состояние, при котором продукт существует в интерфейсе App Store Connect, но недоступен для поиска через StoreKit. Удалите продукт в App Store Connect и пересоздайте его с тем же идентификатором. После пересоздания подождите до 24 часов — столько может занять распространение изменений. --- # File: cantMakePayments --- --- title: "Исправление ошибки Code-1003 cantMakePayment" description: "Устраните ошибку при совершении платежей при управлении подписками в Adapty." --- Ошибка 1003, `cantMakePayments`, означает, что на этом устройстве нельзя совершать встроенные покупки. Если вы столкнулись с ошибкой `cantMakePayments`, обычно это происходит по одной из следующих причин: - Ограничения устройства: ошибка не связана с Adapty. Способы решения описаны ниже. - Настройка Observer mode: метод `makePurchase` и Observer mode нельзя использовать одновременно. Подробнее — в соответствующем разделе ниже. ## Проблема: ограничения устройства \{#issue-device-restrictions\} | Проблема | Решение | |---------------------------------|-------------------------------------------------------------------------------------------------------------------| | Ограничения Screen Time | Отключите ограничения встроенных покупок в [Screen Time](https://support.apple.com/en-us/102470) | | Аккаунт заблокирован | Обратитесь в службу поддержки Apple для решения проблем с аккаунтом | | Региональные ограничения | Используйте аккаунт App Store из поддерживаемого региона | ## Проблема: одновременное использование Observer mode и makePurchase \{#issue-using-both-observer-mode-and-makepurchase\} Если вы используете `makePurchase` для обработки покупок, Observer mode не нужен. [Observer mode](observer-vs-full-mode) требуется только в том случае, если логику покупок вы реализуете самостоятельно. Таким образом, если вы используете `makePurchase`, можно смело убрать активацию Observer mode из кода инициализации SDK. --- # File: migration-to-ios-sdk-v4 --- --- title: "Миграция Adapty iOS SDK на v4.0" description: "Миграция на Adapty iOS SDK v4.0: замена paywall API на flow API, совместимые как с Flow Builder, так и с Paywall Builder." --- Adapty iOS SDK 4.0 вводит флоу и переименовывает API пейволов. Новые API работают как с новым Flow Builder, так и с существующим Paywall Builder — никаких изменений в настройках дашборда Adapty не требуется. ## Краткий справочник \{#quick-reference\} | v3 | v4 | |---|---| | `Adapty.getPaywall(placementId:locale:)` | `Adapty.getFlow(placementId:)` | | `AdaptyUI.getPaywallConfiguration(forPaywall:)` | `AdaptyUI.getFlowConfiguration(forFlow:locale:)` | | `Adapty.getPaywallProducts(paywall:)` | `Adapty.getPaywallProducts(flow:)` | | `Adapty.logShowPaywall(_:)` | `Adapty.logShowFlow(_:)` | | `AdaptyPaywallController` | `AdaptyFlowController` | | `AdaptyPaywallControllerDelegate` | `AdaptyFlowControllerDelegate` | | `AdaptyUI.paywallController(with:delegate:)` | `AdaptyUI.flowController(with:delegate:)` | | `.paywall()` (SwiftUI modifier) | `.flow()` | | `AdaptyPaywallView` | `AdaptyFlowView` | | `didFailRenderingWith:` / `didFailRendering:` | `didReceiveError:` | | `didFinishPurchase` (опциональный, автоматически закрывается при успехе) | `didFinishPurchase` (обязательный, без автоматического закрытия) | | Продукты пакета `Adapty_KidsMode` / `AdaptyUI_KidsMode` | Трейт пакета `KidsMode` | | `Adapty.updateAttribution(_:source:)` (`source: String`) | `Adapty.updateAttribution(_:source:)` (`source: AdaptyAttributionSource`) | | `Adapty.setIntegrationIdentifier(key:value:)` | `Adapty.setIntegrationIdentifier(_:)` (`AdaptyIntegrationIdentifier`) | ## Минимальная версия iOS \{#minimum-ios-version\} Adapty iOS SDK 4.0 повышает минимальный deployment target с iOS 13.0 до **iOS 15.0**. Перед обновлением установите iOS Deployment Target вашего проекта на версию 15.0 или выше. ## Установка: CocoaPods больше не поддерживается \{#installation-cocoapods-no-longer-supported\} Adapty iOS SDK 4.0 отказывается от поддержки CocoaPods. Устанавливайте SDK через [Swift Package Manager](sdk-installation-ios#install-adapty-sdk). Если ваш проект всё ещё использует CocoaPods, удалите поды `Adapty` и `AdaptyUI` из `Podfile`, выполните `pod install`, чтобы убрать их, а затем добавьте пакет в Xcode через **File → Add Package Dependency**, указав `https://github.com/adaptyteam/AdaptySDK-iOS.git`. ## Kids Mode: отдельные продукты заменены на трейт пакета \{#kids-mode-separate-products-replaced-by-a-package-trait\} В v3 [Kids Mode](kids-mode) включался выбором отдельных продуктов **Adapty_KidsMode** и **AdaptyUI_KidsMode** с переименованием импортов. В v4.0 эти продукты удалены. Теперь Kids Mode — это трейт Swift-пакета с именем `KidsMode` для обычного пакета Adapty: при его включении IDFA и AdSupport исключаются из компиляции во всём SDK. Чтобы перейти на новую версию: 1. В окне **Choose Package Products** выберите обычные продукты **Adapty** и **AdaptyUI** вместо **Adapty_KidsMode** и **AdaptyUI_KidsMode**. 2. Включите трейт `KidsMode`. В Xcode 26.4 или новее включите его для зависимости AdaptySDK-iOS в разделе **Package Dependencies** вашего проекта. Если вы добавляете Adapty как зависимость в `Package.swift` (требуется `swift-tools-version` 6.1 или новее), включите его там: ```swift showLineNumbers title="Package.swift" .package( url: "https://github.com/adaptyteam/AdaptySDK-iOS.git", from: "4.0.0", traits: ["KidsMode"] ) ``` 3. Верните импорты обратно на обычные модули: ```diff showLineNumbers - import Adapty_KidsMode - import AdaptyUI_KidsMode + import Adapty + import AdaptyUI ``` :::note Версии Xcode ниже 26.4 не позволяют включать трейты для проекта Xcode через интерфейс. В таком случае создайте небольшой локальный Swift-пакет, который зависит от Adapty с включённым трейтом `KidsMode`, и добавьте зависимость от этого пакета в таргет приложения. ::: ## Удалённые API \{#removed-apis\} - **`Adapty.getPaywallProductsWithoutDeterminingOffer(paywall:)`** — удалён. Все продукты теперь включают информацию об офере, поэтому отдельный проход для определения доступности больше не нужен. - **`AdaptyPaywallProductWithoutDeterminingOffer`** — удалён. Коллбэки, которые раньше передавали этот тип (например, `didSelectProduct`), теперь передают `AdaptyPaywallProduct`. ## Временное удаление поддержки promoted in-app purchases из App Store \{#app-store-promoted-in-app-purchases-temporarily-removed\} В рамках миграции на StoreKit 2 Adapty iOS SDK 4.0 убирает поддержку promoted in-app purchases из App Store. Метод делегата `shouldAddStorePayment(for:)` и тип `AdaptyDeferredProduct`, который он принимает, недоступны в версии 4.0. :::warning Это временное ограничение — поддержка promoted in-app purchases вернётся в одном из следующих релизов 4.x. Если ваше приложение использует promoted in-app purchases, оставайтесь на iOS SDK 3.x до её возвращения. ::: ## Получение пейволов \{#fetching-paywalls\} ### getPaywall + getPaywallConfiguration → getFlow + getFlowConfiguration Возвращаемые типы меняются с `AdaptyPaywall` / `AdaptyUI.PaywallConfiguration` на `AdaptyFlow` / `AdaptyUI.FlowConfiguration`. Параметр `locale` переносится из вызова получения данных в `getFlowConfiguration`: ```diff showLineNumbers - let paywall = try await Adapty.getPaywall(placementId: "YOUR_PLACEMENT_ID", locale: "en") - let paywallConfiguration = try await AdaptyUI.getPaywallConfiguration(forPaywall: paywall) + let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") + let flowConfiguration = try await AdaptyUI.getFlowConfiguration(forFlow: flow, locale: "en") ``` ### getPaywallProducts(paywall:) → getPaywallProducts(flow:) `getPaywallProducts` теперь принимает `AdaptyFlow`, возвращаемый `Adapty.getFlow`: ```diff showLineNumbers - let products = try await Adapty.getPaywallProducts(paywall: paywall) + let products = try await Adapty.getPaywallProducts(flow: flow) ``` ## Отслеживание просмотров пейвола \{#tracking-paywall-views\} ### logShowPaywall(_:) → logShowFlow(_:) `logShowPaywall` переименован в `logShowFlow` и теперь принимает `AdaptyFlow` вместо `AdaptyPaywall`. Событие по-прежнему записывается к тому же варианту, поэтому существующие метрики воронки и A/B-тестов продолжают работать без изменений в дашборде. ```diff showLineNumbers - try await Adapty.logShowPaywall(paywall) + try await Adapty.logShowFlow(flow) ``` Как и в v3, вам не нужно вызывать этот метод при отображении флоу или пейволов, отрисованных с помощью [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder) — Adapty отслеживает эти просмотры автоматически. ## didFinishPurchase теперь обязателен \{#didfinishpurchase-is-now-required\} В v3 `didFinishPurchase` был необязательным: если вы его не реализовывали, пейвол закрывался автоматически после успешной покупки. В v4.0 эта автоматическая логика закрытия убрана, чтобы флоу мог продолжаться после успешной покупки — например, чтобы показать оставшиеся экраны флоу. Теперь вы сами решаете, что происходит после покупки: закрыть экран или ничего не делать и дать флоу продолжить выполнение. - **UIKit**: те, кто реализует `AdaptyFlowControllerDelegate`, должны реализовать `didFinishPurchase` — у него больше нет реализации по умолчанию. - **SwiftUI**: замыкание `didFinishPurchase` в `.flow(...)` и `AdaptyFlowView(...)` теперь обязательно, как и `didFailPurchase` и `didFinishRestore`. Чтобы сохранить поведение v3, закрывайте экран самостоятельно: ```swift showLineNumbers title="Swift" func flowController( _ controller: AdaptyFlowController, didFinishPurchase product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { if !purchaseResult.isPurchaseCancelled { controller.dismiss(animated: true) } } ``` ## UIKit \{#uikit\} ### AdaptyPaywallController → AdaptyFlowController Переименуйте тип контроллера и фабричный метод: ```diff showLineNumbers - let controller = try AdaptyUI.paywallController( - with: paywallConfiguration, - delegate: self - ) + let controller = try AdaptyUI.flowController( + with: flowConfiguration, + delegate: self + ) ``` ### AdaptyPaywallControllerDelegate → AdaptyFlowControllerDelegate \{#adaptypaywall-controller-delegate--adaptyflo-controller-delegate\} Переименуйте протокол и обновите каждую сигнатуру метода. Обратите внимание, что `didSelectProduct` теперь принимает `AdaptyPaywallProduct` вместо удалённого `AdaptyPaywallProductWithoutDeterminingOffer`, а `didFinishPurchase` [теперь обязателен к реализации](#didfinishpurchase-is-now-required) — у него больше нет реализации по умолчанию. ```diff showLineNumbers - class YourClass: AdaptyPaywallControllerDelegate { + class YourClass: AdaptyFlowControllerDelegate { - func paywallControllerDidAppear(_ controller: AdaptyPaywallController) { } + func flowControllerDidAppear(_ controller: AdaptyFlowController) { } - func paywallControllerDidDisappear(_ controller: AdaptyPaywallController) { } + func flowControllerDidDisappear(_ controller: AdaptyFlowController) { } - func paywallController(_ controller: AdaptyPaywallController, - didPerform action: AdaptyUI.Action) { } + func flowController(_ controller: AdaptyFlowController, + didPerform action: AdaptyUI.Action) { } - func paywallController(_ controller: AdaptyPaywallController, - didSelectProduct product: AdaptyPaywallProductWithoutDeterminingOffer) { } + func flowController(_ controller: AdaptyFlowController, + didSelectProduct product: AdaptyPaywallProduct) { } - func paywallController(_ controller: AdaptyPaywallController, - didStartPurchase product: AdaptyPaywallProduct) { } + func flowController(_ controller: AdaptyFlowController, + didStartPurchase product: AdaptyPaywallProduct) { } - func paywallController(_ controller: AdaptyPaywallController, - didFinishPurchase product: AdaptyPaywallProduct, - purchaseResult: AdaptyPurchaseResult) { } + func flowController(_ controller: AdaptyFlowController, + didFinishPurchase product: AdaptyPaywallProduct, + purchaseResult: AdaptyPurchaseResult) { } - func paywallController(_ controller: AdaptyPaywallController, - didFailPurchase product: AdaptyPaywallProduct, - error: AdaptyError) { } + func flowController(_ controller: AdaptyFlowController, + didFailPurchase product: AdaptyPaywallProduct, + error: AdaptyError) { } - func paywallControllerDidStartRestore(_ controller: AdaptyPaywallController) { } + func flowControllerDidStartRestore(_ controller: AdaptyFlowController) { } - func paywallController(_ controller: AdaptyPaywallController, - didFinishRestoreWith profile: AdaptyProfile) { } + func flowController(_ controller: AdaptyFlowController, + didFinishRestoreWith profile: AdaptyProfile) { } - func paywallController(_ controller: AdaptyPaywallController, - didFailRestoreWith error: AdaptyError) { } + func flowController(_ controller: AdaptyFlowController, + didFailRestoreWith error: AdaptyError) { } - func paywallController(_ controller: AdaptyPaywallController, - didFailRenderingWith error: AdaptyUIError) { } + func flowController(_ controller: AdaptyFlowController, + didReceiveError error: AdaptyUIError) { } - func paywallController(_ controller: AdaptyPaywallController, - didFailLoadingProductsWith error: AdaptyError) -> Bool { } + func flowController(_ controller: AdaptyFlowController, + didFailLoadingProductsWith error: AdaptyError) -> Bool { } - func paywallController(_ controller: AdaptyPaywallController, - didPartiallyLoadProducts failedIds: [String]) { } + func flowController(_ controller: AdaptyFlowController, + didPartiallyLoadProducts failedIds: [String]) { } - func paywallController(_ controller: AdaptyPaywallController, - didFinishWebPaymentNavigation product: AdaptyPaywallProduct?, - error: AdaptyError?) { } + func flowController(_ controller: AdaptyFlowController, + didFinishWebPaymentNavigation product: AdaptyPaywallProduct?, + error: AdaptyError?) { } } ``` ## SwiftUI \{#swiftui\} ### Модификатор .paywall() → .flow() \{#paywall-modifier--flow\} Переименуйте модификатор, обновите имя параметра конфигурации и добавьте [обязательное теперь](#didfinishpurchase-is-now-required) замыкание `didFinishPurchase`: ```diff showLineNumbers @State var flowPresented = false // rename freely — the variable name is your choice var body: some View { Text("Hello, AdaptyUI!") - .paywall( + .flow( isPresented: $flowPresented, - paywallConfiguration: paywallConfiguration, + flowConfiguration: flowConfiguration, + didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, - didFailRendering: { error in flowPresented = false } + didReceiveError: { error in flowPresented = false } ) } ``` Переименованный колбэк срабатывает при тех же ошибках рендеринга, что и `didFailRendering`, плюс при новых ошибках выполнения из скрипта флоу (JavaScript-исключения с кодом `AdaptyUIError` `4105` — `.jsException`). Менять код в теле обработчика не нужно — достаточно переименовать параметр. ### AdaptyPaywallView → AdaptyFlowView Переименуйте вью, обновите параметр конфигурации, добавьте [обязательное теперь](#didfinishpurchase-is-now-required) замыкание `didFinishPurchase` и обновите замыкание `didSelectProduct` — теперь оно принимает `AdaptyPaywallProduct` вместо удалённого `AdaptyPaywallProductWithoutDeterminingOffer`: ```diff showLineNumbers - AdaptyPaywallView( - paywallConfiguration: paywallConfiguration, - didSelectProduct: { product: AdaptyPaywallProductWithoutDeterminingOffer in /* handle */ }, + AdaptyFlowView( + flowConfiguration: flowConfiguration, + didSelectProduct: { product: AdaptyPaywallProduct in /* handle */ }, + didFinishPurchase: { product, purchaseResult in /* dismiss, or do nothing to let the flow continue */ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, - didFailRendering: { error in /* handle the error */ } + didReceiveError: { error in /* handle the error */ } ) ``` ## Пользовательские ресурсы AdaptyUI \{#adaptyui-custom-assets\} ### AdaptyUICustomVideoAsset Два изменения затрагивают все существующие места вызова: - `.player` теперь принимает `AVPlayer` вместо `AVQueuePlayer`. - В каждый case добавлен завершающий параметр `resolution: CGSize?`. Передайте `nil`, чтобы сохранить текущее поведение, или передайте фактический размер в пикселях, чтобы плеер мог зарезервировать место в лэйауте (соотношение сторон = `width / height`) до загрузки видео. ```diff showLineNumbers - case file(url: URL, preview: AdaptyUICustomImageAsset?) - case remote(url: URL, preview: AdaptyUICustomImageAsset?) - case player(item: AVPlayerItem, player: AVQueuePlayer, preview: AdaptyUICustomImageAsset?) + case file(url: URL, preview: AdaptyUICustomImageAsset?, resolution: CGSize?) + case remote(url: URL, preview: AdaptyUICustomImageAsset?, resolution: CGSize?) + case player(item: AVPlayerItem, player: AVPlayer, preview: AdaptyUICustomImageAsset?, resolution: CGSize?) ``` ## Идентификаторы атрибуции и интеграций \{#attribution-and-integration-identifiers\} ### updateAttribution(_:source:) Параметр `source` изменился с типа `String` на новый тип `AdaptyAttributionSource`, а вложенный ранее `AdaptyProfile.AttributionSource` переименован в `AdaptyAttributionSource` на верхнем уровне. Используйте один из предопределённых источников или передайте строковый литерал для любого другого источника — `AdaptyAttributionSource` поддерживает `ExpressibleByStringLiteral`, поэтому существующие вызовы со строковыми литералами продолжат компилироваться. ```diff showLineNumbers - try await Adapty.updateAttribution(attribution, source: "adjust") + try await Adapty.updateAttribution(attribution, source: .adjust) ``` Предопределённые источники: `.appleAds`, `.adjust`, `.appsflyer`, `.branch`, `.tenjin`. Если источник хранится в переменной типа `String`, оберните его: `AdaptyAttributionSource(rawValue: yourSource)`. ### setIntegrationIdentifier(_:) `setIntegrationIdentifier(key:value:)` заменён вариативным методом, который принимает одно или несколько значений `AdaptyIntegrationIdentifier`. Используйте предопределённые фабричные методы вместо строковых ключей: ```diff showLineNumbers - try await Adapty.setIntegrationIdentifier(key: "appsflyer_id", value: uid) + try await Adapty.setIntegrationIdentifier(.appsflyerId(uid)) ``` Можно задать сразу несколько идентификаторов в одном вызове: ```swift showLineNumbers try await Adapty.setIntegrationIdentifier( .appsflyerId(uid), .adjustDeviceId(adid) ) ``` Замените каждую старую строку ключа соответствующим фабричным методом: | ключ v3 | фабрика v4 | |---|---| | `"adjust_device_id"` | `.adjustDeviceId(_:)` | | `"airbridge_device_id"` | `.airbridgeDeviceId(_:)` | | `"amplitude_user_id"` | `.amplitudeUserId(_:)` | | `"amplitude_device_id"` | `.amplitudeDeviceId(_:)` | | `"appmetrica_device_id"` | `.appmetricaDeviceId(_:)` | | `"appmetrica_profile_id"` | `.appmetricaProfileId(_:)` | | `"appsflyer_id"` | `.appsflyerId(_:)` | | `"branch_id"` | `.branchId(_:)` | | `"facebook_anonymous_id"` | `.facebookAnonymousId(_:)` | | `"firebase_app_instance_id"` | `.firebaseAppInstanceId(_:)` | | `"mixpanel_user_id"` | `.mixpanelUserId(_:)` | | `"one_signal_subscription_id"` | `.oneSignalSubscriptionId(_:)` | | `"one_signal_player_id"` | `.oneSignalPlayerId(_:)` | | `"posthog_distinct_user_id"` | `.posthogDistinctUserId(_:)` | | `"pushwoosh_hwid"` | `.pushwooshHWID(_:)` | | `"tenjin_analytics_installation_id"` | `.tenjinAnalyticsInstallationId(_:)` | --- # File: migration-to-ios-315 --- --- title: "Миграция Adapty iOS SDK на v3.15" description: "Перейдите на Adapty iOS SDK v3.15 для повышения производительности и доступа к новым функциям монетизации." --- Если вы используете [Paywall Builder](adapty-paywall-builder) в [режиме Observer](observer-vs-full-mode), начиная с iOS SDK 3.15, необходимо реализовать новый метод `observerModeDidInitiateRestorePurchases(onStartRestore:onFinishRestore:)`. Этот метод обеспечивает более гибкое управление логикой восстановления покупок и позволяет обрабатывать их в рамках собственного флоу. Подробности реализации см. в разделе [Отображение пейволов Paywall Builder в режиме Observer](ios-present-paywall-builder-paywalls-in-observer-mode). ```diff showLineNumbers func observerMode(didInitiatePurchase product: AdaptyPaywallProduct, onStartPurchase: @escaping () -> Void, onFinishPurchase: @escaping () -> Void) { // use the product object to handle the purchase // use the onStartPurchase and onFinishPurchase callbacks to notify AdaptyUI about the process of the purchase } + func observerModeDidInitiateRestorePurchases(onStartRestore: @escaping () -> Void, + onFinishRestore: @escaping () -> Void) { + // use the onStartRestore and onFinishRestore callbacks to notify AdaptyUI about the process of the restore + } ``` --- # File: migration-to-ios-sdk-34 --- --- title: "Миграция Adapty iOS SDK на версию 3.4" description: "Мигрируйте на Adapty iOS SDK v3.4 для повышения производительности и новых функций монетизации." --- Adapty SDK 3.4.0 — это мажорный релиз, который включает улучшения, требующие шагов по миграции с вашей стороны. ## Обновление активации Adapty SDK \{#update-adapty-sdk-activation\} <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```diff showLineNumbers // In your AppDelegate class: let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") - Adapty.activate(with: configurationBuilder) { error in + Adapty.activate(with: configurationBuilder.build()) { error in // handle the error } ``` **Обновите файлы резервных пейволов** Обновите файлы резервных пейволов, чтобы обеспечить совместимость с новой версией SDK: 1. [Скачайте обновлённые файлы резервных пейволов](fallback-paywalls) из дашборда Adapty. 2. [Замените существующие резервные пейволы в вашем мобильном приложении](ios-use-fallback-paywalls) на новые файлы. </TabItem> <TabItem value="swiftui" label="SwiftUI" default> ```diff showLineNumbers @main struct SampleApp: App { init() { let configurationBuilder = AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") Task { - try await Adapty.activate(with: configurationBuilder) + try await Adapty.activate(with: configurationBuilder.build()) } } var body: some Scene { WindowGroup { ContentView() } } } ``` **Обновите файлы резервных пейволов** Обновите файлы резервных пейволов, чтобы обеспечить совместимость с новой версией SDK: 1. [Скачайте обновлённые файлы резервного пейвола](fallback-paywalls) из дашборда Adapty. 2. [Замените существующие резервные пейволы в своём мобильном приложении](ios-use-fallback-paywalls) новыми файлами. </TabItem> </Tabs> --- # File: migration-to-ios330 --- --- title: "Миграция Adapty iOS SDK на v3.3" description: "Перейдите на Adapty iOS SDK v3.3 для повышения производительности и новых возможностей монетизации." --- Adapty SDK 3.3.0 — это мажорный релиз, который принёс ряд улучшений, однако для перехода на него могут потребоваться некоторые шаги по миграции. 1. Переименуйте `Adapty.Configuration` в `AdaptyConfiguration`. 2. Переименуйте метод `getViewConfiguration` в `getPaywallConfiguration`. 3. Удалите параметры `didCancelPurchase` и `paywall` из SwiftUI, а параметр `viewConfiguration` переименуйте в `paywallConfiguration`. 4. Обновите обработку promotional встроенных покупок из App Store, удалив параметр `defermentCompletion` из метода `AdaptyDelegate`. 5. Удалите метод `getProductsIntroductoryOfferEligibility`. 6. Обновите конфигурации интеграций для Adjust, AirBridge, Amplitude, AppMetrica, Appsflyer, Branch, Facebook Ads, Firebase and Google Analytics, Mixpanel, OneSignal, Pushwoosh. 7. Обновите реализацию режима Observer. <div style={{ textAlign: 'center' }}> <iframe width="560" height="315" src="https://www.youtube.com/embed/9Xs8d0lt_RY?si=xvWhUO2tlG1tKP5f" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen> </iframe> </div> ## Переименование Adapty.Configuration в AdaptyConfiguration \{#rename-adaptyconfiguration-to-adaptyconfiguration\} Обновите код активации Adapty iOS SDK следующим образом: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```diff showLineNumbers // In your AppDelegate class: let configurationBuilder = - Adapty.Configuration + AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(observerMode: false) .with(customerUserId: "YOUR_USER_ID") .with(idfaCollectionDisabled: false) .with(ipAddressCollectionDisabled: false) Adapty.activate(with: configurationBuilder) { error in // handle the error } ``` </TabItem> <TabItem value="swiftui" label="SwiftUI" default> ```diff showLineNumbers @main struct SampleApp: App { init() let configurationBuilder = - Adapty.Configuration + AdaptyConfiguration .builder(withAPIKey: "PUBLIC_SDK_KEY") .with(observerMode: false) // optional .with(customerUserId: "YOUR_USER_ID") // optional .with(idfaCollectionDisabled: false) // optional .with(ipAddressCollectionDisabled: false) // optional Task { try await Adapty.activate(with: configurationBuilder) } } var body: some Scene { WindowGroup { ContentView() } } } ``` </TabItem> </Tabs> ## Переименование метода getViewConfiguration в getPaywallConfiguration \{#rename-getviewconfiguration-method-to-getpaywallconfiguration\} Обновите название метода для получения `viewConfiguration` пейвола: ```diff showLineNumbers guard paywall.hasViewConfiguration else { // use your custom logic return } do { - let paywallConfiguration = try await AdaptyUI.getViewConfiguration( + let paywallConfiguration = try await AdaptyUI.getPaywallConfiguration( forPaywall: paywall ) // use loaded configuration } catch { // handle the error } ``` Подробнее о методе читайте в разделе [Получение конфигурации представления пейвола, созданного в Paywall Builder](get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). ## Изменения параметров в SwiftUI \{#change-parameters-in-swiftui\} В SwiftUI внесены следующие изменения: 1. Параметр `didCancelPurchase` удалён. Используйте вместо него `didFinishPurchase`. 2. Метод `.paywall()` больше не принимает объект пейвола. 3. Параметр `paywallConfiguration` заменил параметр `viewConfiguration`. Обновите свой код следующим образом: ```diff showLineNumbers @State var paywallPresented = false var body: some View { Text("Hello, AdaptyUI!") .paywall( isPresented: $paywallPresented, - paywall: <paywall object>, - viewConfiguration: <LocalizedViewConfiguration>, + paywallConfiguration: <AdaptyUI.PaywallConfiguration>, didPerformAction: { action in switch action { case .close: paywallPresented = false default: // Handle other actions break } }, - didFinishPurchase: { product, profile in paywallPresented = false }, + didFinishPurchase: { product, purchaseResult in /* handle the result*/ }, didFailPurchase: { product, error in /* handle the error */ }, didFinishRestore: { profile in /* check access level and dismiss */ }, didFailRestore: { error in /* handle the error */ }, didFailRendering: { error in paywallPresented = false } - didCancelPurchase: { product in /* handle the result*/} ) } ``` ## Обновление обработки promotional in-app purchases из App Store \{#update-handling-of-promotional-in-app-purchases-from-app-store\} Обновите обработку promotional in-app purchases из App Store, удалив параметр `defermentCompletion` из метода `AdaptyDelegate`, как показано в примере ниже: ```swift showLineNumbers title="Swift" final class YourAdaptyDelegateImplementation: AdaptyDelegate { nonisolated func shouldAddStorePayment(for product: AdaptyDeferredProduct) -> Bool { // 1a. // Return `true` to continue the transaction in your app. // 1b. // Store the product object and return `false` to defer or cancel the transaction. false } // 2. Continue the deferred purchase later on by passing the product to `makePurchase` func continueDeferredPurchase() async { let storedProduct: AdaptyDeferredProduct = // get the product object from the 1b. do { try await Adapty.makePurchase(product: storedProduct) } catch { // handle the error } } } ``` ## Удаление метода getProductsIntroductoryOfferEligibility \{#remove-getproductsintroductoryoffereligibility-method\} До версии Adapty iOS SDK 3.3.0 объект продукта всегда включал офферы вне зависимости от того, имел ли пользователь право на их использование. Вам приходилось вручную проверять право доступа перед использованием оффера. Теперь объект продукта включает оффер только в том случае, если пользователь имеет на него право. Это означает, что проверка права доступа больше не нужна — если оффер присутствует, пользователь имеет на него право. Если вы всё же хотите просматривать офферы для пользователей, не имеющих права доступа, обращайтесь к `sk1Product` и `sk2Product`. ## Обновление конфигурации SDK для сторонних интеграций \{#update-third-party-integration-sdk-configuration\} Начиная с Adapty iOS SDK 3.3.0, мы обновили публичный API метода `updateAttribution`. Ранее он принимал словарь `[AnyHashable: Any]`, позволяя передавать объекты атрибуции напрямую из различных сервисов. Теперь требуется `[String: any Sendable]`, поэтому перед передачей необходимо конвертировать объекты атрибуции. Чтобы интеграции корректно работали с Adapty iOS SDK 3.3.0 и выше, обновите конфигурации SDK для перечисленных ниже интеграций согласно соответствующим разделам. ### Adjust Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [настройка SDK для интеграции с Adjust](adjust#connect-your-app-to-adjust). <Tabs groupId="current-os" queryString> <TabItem value="v5" label="Adjust 5.x+" default> ```diff showLineNumbers class AdjustModuleImplementation { - func updateAdjustAttribution() { - Adjust.attribution { attribution in - guard let attributionDictionary = attribution?.dictionary()?.toSendableDict() else { return } - - Adjust.adid { adid in - guard let adid else { return } - - Adapty.updateAttribution(attributionDictionary, source: .adjust, networkUserId: adid) { error in - // handle the error - } - } - } - } + func updateAdjustAdid() { + Adjust.adid { adid in + guard let adid else { return } + + Adapty.setIntegrationIdentifier(key: "adjust_device_id", value: adid) + } + } + + func updateAdjustAttribution() { + Adjust.attribution { attribution in + guard let attribution = attribution?.dictionary() else { + return + } + + Adapty.updateAttribution(attribution, source: "adjust") + } + } } ``` </TabItem> <TabItem value="v4" label="Adjust 4.x" default> ```diff showLineNumbers class YourAdjustDelegateImplementation { // Find your implementation of AdjustDelegate // and update adjustAttributionChanged method: func adjustAttributionChanged(_ attribution: ADJAttribution?) { - if let attribution = attribution?.dictionary()?.toSendableDict() { - Adapty.updateAttribution(attribution, source: .adjust) + if let attribution = attribution?.dictionary() { + Adapty.updateAttribution(attribution, source: "adjust") } } } ``` </TabItem> </Tabs> ### AirBridge \{#airbridge\} Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции AirBridge](airbridge#connect-your-app-to-airbridge). ```diff showLineNumbers import AirBridge - let builder = AdaptyProfileParameters.Builder() - .with(airbridgeDeviceId: AirBridge.deviceUUID()) - - Adapty.updateProfile(params: builder.build()) + do { + try await Adapty.setIntegrationIdentifier( + key: "airbridge_device_id", + value: AirBridge.deviceUUID() + ) + } catch { + // handle the error + } ``` ### Amplitude Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [настройка SDK для интеграции с Amplitude](amplitude#sdk-configuration). ```diff showLineNumbers import Amplitude - let builder = AdaptyProfileParameters.Builder() - .with(amplitudeUserId: Amplitude.instance().userId) - .with(amplitudeDeviceId: Amplitude.instance().deviceId) - - Adapty.updateProfile(params: builder.build()) + do { + try await Adapty.setIntegrationIdentifier( + key: "amplitude_user_id", + value: Amplitude.instance().userId + ) + try await Adapty.setIntegrationIdentifier( + key: "amplitude_device_id", + value: Amplitude.instance().deviceId + ) + } catch { + // handle the error + } ``` ### AppMetrica Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с AppMetrica](appmetrica#sdk-configuration). ```diff showLineNumbers import AppMetricaCore - if let deviceID = AppMetrica.deviceID { - let builder = AdaptyProfileParameters.Builder() - .with(appmetricaDeviceId: deviceID) - .with(appmetricaProfileId: "YOUR_ADAPTY_CUSTOMER_USER_ID") - - Adapty.updateProfile(params: builder.build()) - } + if let deviceID = AppMetrica.deviceID { + do { + try await Adapty.setIntegrationIdentifier( + key: "appmetrica_device_id", + value: deviceID + ) + try await Adapty.setIntegrationIdentifier( + key: "appmetrica_profile_id", + value: "YOUR_ADAPTY_CUSTOMER_USER_ID" + ) + } catch { + // handle the error + } + } ``` ### AppsFlyer Обновите код мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [Настройка SDK для интеграции с AppsFlyer](appsflyer#connect-your-app-to-appsflyer). ```diff showLineNumbers class YourAppsFlyerLibDelegateImplementation { // Find your implementation of AppsFlyerLibDelegate // and update onConversionDataSuccess method: func onConversionDataSuccess(_ conversionInfo: [AnyHashable : Any]) { let uid = AppsFlyerLib.shared().getAppsFlyerUID() - Adapty.updateAttribution( - conversionInfo.toSendableDict(), - source: .appsflyer, - networkUserId: uid - ) + Adapty.setIntegrationIdentifier(key: "appsflyer_id", value: uid) + Adapty.updateAttribution(conversionInfo, source: "appsflyer") } } ``` ### Branch Обновите код вашего мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с Branch](branch#connect-your-app-to-branch). ```diff showLineNumbers class YourBranchImplementation { func initializeBranch() { // Pass the attribution you receive from the initializing method of Branch iOS SDK to Adapty. Branch.getInstance().initSession(launchOptions: launchOptions) { (data, error) in - if let data = data?.toSendableDict() { - Adapty.updateAttribution(data, source: .branch) - } + if let data { + Adapty.updateAttribution(data, source: "branch") + } } } } ``` ### Facebook Ads Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [настройка SDK для интеграции с Facebook Ads](facebook-ads#connect-your-app-to-facebook-ads). ```diff showLineNumbers import FacebookCore - let builder = AdaptyProfileParameters.Builder() - .with(facebookAnonymousId: AppEvents.shared.anonymousID) - - do { - try Adapty.updateProfile(params: builder.build()) - } catch { - // handle the error - } + do { + try await Adapty.setIntegrationIdentifier( + key: "facebook_anonymous_id", + value: AppEvents.shared.anonymousID + ) + } catch { + // handle the error + } ``` ### Firebase и Google Analytics \{#firebase-and-google-analytics\} Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с Firebase и Google Analytics](firebase-and-google-analytics). ```diff showLineNumbers import FirebaseCore import FirebaseAnalytics FirebaseApp.configure() - if let appInstanceId = Analytics.appInstanceID() { - let builder = AdaptyProfileParameters.Builder() - .with(firebaseAppInstanceId: appInstanceId) - Adapty.updateProfile(params: builder.build()) { error in - // handle error - } - } + if let appInstanceId = Analytics.appInstanceID() { + do { + try await Adapty.setIntegrationIdentifier( + key: "firebase_app_instance_id", + value: appInstanceId + ) + } catch { + // handle the error + } + } ``` ### Mixpanel Обновите код мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [Настройка SDK для интеграции с Mixpanel](mixpanel#sdk-configuration). ```diff showLineNumbers import Mixpanel - let builder = AdaptyProfileParameters.Builder() - .with(mixpanelUserId: Mixpanel.mainInstance().distinctId) - - do { - try await Adapty.updateProfile(params: builder.build()) - } catch { - // handle the error - } + do { + try await Adapty.setIntegrationIdentifier( + key: "mixpanel_user_id", + value: Mixpanel.mainInstance().distinctId + ) + } catch { + // handle the error + } ``` ### OneSignal Обновите код мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [Настройка SDK для интеграции с OneSignal](onesignal#sdk-configuration). ```diff showLineNumbers // PlayerID (pre-v5 OneSignal SDK) // in your OSSubscriptionObserver implementation func onOSSubscriptionChanged(_ stateChanges: OSSubscriptionStateChanges) { if let playerId = stateChanges.to.userId { - let params = AdaptyProfileParameters.Builder() - .with(oneSignalPlayerId: playerId) - .build() - - Adapty.updateProfile(params:params) { error in - // check error - } + Task { + try await Adapty.setIntegrationIdentifier( + key: "one_signal_player_id", + value: playerId + ) + } } } // SubscriptionID (v5+ OneSignal SDK) OneSignal.Notifications.requestPermission({ accepted in - let id = OneSignal.User.pushSubscription.id - - let builder = AdaptyProfileParameters.Builder() - .with(oneSignalSubscriptionId: id) - - Adapty.updateProfile(params: builder.build()) + Task { + try await Adapty.setIntegrationIdentifier( + key: "one_signal_subscription_id", + value: OneSignal.User.pushSubscription.id + ) + } }, fallbackToSettings: true) ``` ### Pushwoosh Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с Pushwoosh](pushwoosh#sdk-configuration). ```diff showLineNumbers - let params = AdaptyProfileParameters.Builder() - .with(pushwooshHWID: Pushwoosh.sharedInstance().getHWID()) - .build() - - Adapty.updateProfile(params: params) { error in - // handle the error - } + do { + try await Adapty.setIntegrationIdentifier( + key: "pushwoosh_hwid", + value: Pushwoosh.sharedInstance().getHWID() + ) + } catch { + // handle the error + } ``` ## Обновление реализации режима Observer \{#update-observer-mode-implementation\} Обновите способ привязки пейволов к транзакциям. Раньше для назначения `variationId` использовался метод `setVariationId`. Теперь можно передавать `variationId` напрямую при записи транзакции с помощью нового метода `reportTransaction`. Ознакомьтесь с итоговым примером кода в разделе [Привязка пейволов к транзакциям покупок в режиме Observer](report-transactions-observer-mode). :::warning Не забудьте зафиксировать транзакцию с помощью метода `reportTransaction`. Если пропустить этот шаг, Adapty не распознает транзакцию, не предоставит уровни доступа, не включит её в аналитику и не передаст в интеграции. Этот шаг обязателен! ::: ```diff showLineNumbers - let variationId = paywall.variationId - - // There are two overloads: for StoreKit 1 and StoreKit 2 - Adapty.setVariationId(variationId, forPurchasedTransaction: transaction) { error in - if error == nil { - // successful binding - } - } + do { + // every time when calling transaction.finish() + try await Adapty.reportTransaction(transaction, withVariationId: <YOUR_PAYWALL_VARIATION_ID>) + } catch { + // handle the error + } ``` --- # File: migration-to-ios-sdk-v3 --- --- title: "Миграция iOS SDK Adapty на версию 3.0" description: "Перейдите на iOS SDK Adapty версии 3.0 для повышения производительности и новых возможностей монетизации." --- SDK Adapty версии 3.0 добавляет поддержку нового [Paywall Builder](adapty-paywall-builder) — обновлённого no-code инструмента для создания пейволов с богатыми возможностями дизайна. Максимальная гибкость настройки сделает ваши пейволы ещё эффективнее. :::info Обратите внимание: библиотека AdaptyUI устарела и теперь входит в состав AdaptySDK. ::: ## Переустановка Adapty SDK v3.x через Swift Package Manager \{#reinstall-adapty-sdk-v3x-via-swift-package-manager\} 1. Удалите зависимость пакета AdaptyUI SDK из вашего проекта — она больше не нужна. 2. Несмотря на то что она уже была добавлена, вам потребуется заново добавить зависимость Adapty SDK. Для этого в Xcode откройте **File** -> **Add Package Dependency...**. Обратите внимание, что способ добавления пакетов может отличаться в разных версиях Xcode. При необходимости обратитесь к документации Xcode. 3. Введите URL репозитория `https://github.com/adaptyteam/AdaptySDK-iOS.git` 4. Выберите версию и нажмите кнопку **Add package**. 5. Выберите нужные модули: 1. **Adapty** — обязательный модуль 2. **AdaptyUI** — опциональный модуль, необходимый, если вы планируете использовать [Adapty Paywall Builder](adapty-paywall-builder). 6. Xcode добавит зависимость пакета в ваш проект, после чего вы сможете её импортировать. В окне **Choose Package Products** нажмите кнопку **Add package** ещё раз. Пакет появится в списке **Packages**. ## Переустановка Adapty SDK v3.x через CocoaPods \{#reinstall-adapty-sdk-v3x-via-cocoapods\} 1. Добавьте Adapty в ваш `Podfile`. Выберите нужные модули: 1. **Adapty** — обязательный модуль. 2. **AdaptyUI** — опциональный модуль, необходимый, если вы планируете использовать [Adapty Paywall Builder](adapty-paywall-builder). ```shell showLineNumbers title="Podfile" pod 'Adapty', '~> 3.2.0' pod 'AdaptyUI', '~> 3.2.0' # optional module needed only for Paywall Builder ``` 3. Выполните: ```sh showLineNumbers title="Shell" pod install ``` Это создаст файл `.xcworkspace` для вашего приложения. Используйте этот файл для всей дальнейшей разработки. Активируйте модули SDK Adapty и AdaptyUI. До версии v3.0 AdaptyUI не активировался — не забудьте **добавить активацию AdaptyUI**. Параметры не изменились, оставьте их как есть. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers // In your AppDelegate class: let configurationBuilder = AdaptyConfiguration .Builder(withAPIKey: "PUBLIC_SDK_KEY") .with(observerMode: false) .with(customerUserId: "YOUR_USER_ID") .with(idfaCollectionDisabled: false) .with(ipAddressCollectionDisabled: false) Adapty.activate(with: configurationBuilder) { error in // handle the error } // Only if you are going to use AdaptyUI AdaptyUI.activate() ``` </TabItem> <TabItem value="swiftui" label="SwiftUI" default> ```swift title="" showLineNumbers @main struct SampleApp: App { init() let configurationBuilder = AdaptyConfiguration .Builder(withAPIKey: "PUBLIC_SDK_KEY") .with(observerMode: false) // optional .with(customerUserId: "YOUR_USER_ID") // optional .with(idfaCollectionDisabled: false) // optional .with(ipAddressCollectionDisabled: false) // optional Adapty.activate(with: configurationBuilder) { error in // handle the error } // Only if you are going to use AdaptyUI AdaptyUI.activate() } var body: some Scene { WindowGroup { ContentView() } } } ``` </TabItem> </Tabs> --- # End of Documentation _Generated on: 2026-07-24T13:01:12.782Z_ _Successfully processed: 44/44 files_ # KMP - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: ru Generated on: 2026-07-24T13:01:12.785Z Total files: 48 --- # File: kmp-sdk-overview --- --- title: "Обзор Kotlin Multiplatform SDK" description: "Узнайте об Adapty Kotlin Multiplatform SDK и его ключевых возможностях." --- [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-KMP.svg?style=flat&logo=kotlin)](https://github.com/adaptyteam/AdaptySDK-KMP/releases) Добро пожаловать! Мы здесь, чтобы сделать встроенные покупки простыми и удобными 🚀 Мы создали Adapty SDK для Kotlin Multiplatform, чтобы избавить вас от головной боли с встроенными покупками — и вы могли сосредоточиться на главном: создании крутых приложений. Вот что мы берём на себя: - Обработка покупок, валидация чеков и управление подписками «из коробки» - Создание и тестирование пейволов без обновления приложения - Подробная аналитика покупок без настройки — когорты, LTV, отток и воронки включены - Актуальный статус подписки пользователя во всех сессиях и на всех устройствах - Интеграция с сервисами маркетинговой атрибуции и аналитики в одну строку кода :::note Прежде чем переходить к коду, вам нужно интегрировать Adapty с Google Play Console и настроить продукты в дашборде. Ознакомьтесь с нашим [гайдом по быстрому старту](quickstart), чтобы всё настроить. ::: ## Начало работы \{#get-started\} For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. Вот что мы разберём в этом руководстве по интеграции: 1. [Установите и настройте SDK](sdk-installation-kotlin-multiplatform): Добавьте SDK как зависимость в проект и активируйте его в коде. 2. [Включите покупки через флоу](kmp-quickstart-paywalls): Настройте флоу покупок, чтобы пользователи могли приобретать продукты. Если хотите создать собственный UI, см. [Реализация пейволов вручную](kmp-quickstart-manual). 3. [Проверьте статус подписки](kmp-check-subscription-status): Автоматически отслеживайте статус подписки пользователя и управляйте его доступом к платному контенту. 4. [Идентифицируйте пользователей (необязательно)](kmp-quickstart-identify): Привяжите пользователей к их профилям Adapty, чтобы данные корректно сохранялись на всех устройствах. ### Смотрите в действии \{#see-it-in-action\} Хотите увидеть, как это всё работает вместе? Мы подготовили для вас: - **Пример приложения**: Посмотрите наш [полный пример](https://github.com/adaptyteam/AdaptySDK-KMP/tree/main/example) с готовой настройкой - **Видеоурок**: Следите за пошаговым руководством по реализации ниже <iframe width="560" height="315" src="https://www.youtube.com/embed/JfwJvwnloNw?si=HskPxRk4WGkF_u9s" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> ## Основные концепции \{#main-concepts\} Прежде чем переходить к коду, познакомимся с ключевыми понятиями, которые лежат в основе Adapty. Главное преимущество подхода Adapty в том, что в коде приложения жёстко зашиты только плейсменты. Всё остальное — продукты, дизайн пейволов, цены и предложения — можно гибко настраивать в дашборде Adapty без обновления приложения: 1. [**Продукт**](product) — всё, что можно купить в вашем приложении: подписка, расходуемая покупка или пожизненный доступ. 2. **Флоу или пейвол** — продукты в связке с конфигурацией, привязанные к плейсменту. Два варианта: - **[Флоу](adapty-flow-builder)** — визуальный no-code интерфейс, созданный в Flow Builder. Adapty сам отрисовывает UI и обрабатывает покупку. - **[Пейвол](paywalls)** — без визуальной конфигурации; UI строите сами в коде и самостоятельно вызываете `makePurchase`. См. [Реализация пейволов вручную](kmp-quickstart-manual). В коде SDK оба варианта извлекаются через один и тот же метод `getFlow`. 3. [**Плейсмент**](placements) - Стратегическая точка в пользовательском пути, где вы хотите показать флоу или пейвол. Плейсменты отвечают на вопросы «где» и «когда» в вашей стратегии монетизации. Распространённые плейсменты: - `main` - Основное место показа пейвола - `onboarding` - Показывается во время онбординга пользователя - `settings` - Доступен из настроек приложения Начните с базовых плейсментов — `main` или `onboarding` — при первой интеграции, а затем [подумайте, в каких ещё местах приложения пользователи могут быть готовы к покупке](choose-meaningful-placements). 4. [**Профиль**](profiles-crm) - Когда пользователи покупают продукт, их профилю присваивается **уровень доступа**, который вы используете для определения доступа к платным функциям. --- # File: sdk-installation-kotlin-multiplatform --- --- title: "Установка и настройка Adapty Kotlin Multiplatform SDK" description: "Установка и настройка Adapty SDK для приложений на Kotlin Multiplatform." --- SDK Adapty включает два ключевых модуля для бесшовной интеграции в ваше мобильное приложение: - **Core Adapty**: Этот основной SDK необходим для корректной работы Adapty в вашем приложении. - **AdaptyUI** (`io.adapty:adapty-kmp-ui`): Этот модуль нужен, если вы используете [Adapty Paywall Builder](adapty-paywall-builder) со слоем рендеринга Compose Multiplatform (`view.present()`). Если ваш проект не использует Compose Multiplatform, вместо него можно использовать [`createNativePaywallView`](kmp-present-paywalls#without-compose-multiplatform) и [`createNativeOnboardingView`](kmp-present-onboardings#without-compose-multiplatform) из основного модуля. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наше [демо-приложение](https://github.com/adaptyteam/AdaptySDK-KMP/tree/main/example), которое демонстрирует полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: Для полного разбора реализации можно также посмотреть видео: <iframe width="560" height="315" src="https://www.youtube.com/embed/JfwJvwnloNw?si=HskPxRk4WGkF_u9s" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> ## Требования \{#requirements\} Adapty Kotlin Multiplatform SDK совместим с Xcode 16.2 и более поздними версиями. :::info Adapty совместим с Google Play Billing Library до версии 8.x включительно. По умолчанию Adapty работает с Google Play Billing Library v.7.0.0, но если вы хотите принудительно использовать более позднюю версию, можно вручную [добавить зависимость](https://developer.android.com/google/play/billing/integrate#dependency). ::: :::info Установка SDK — это шаг 5 настройки Adapty. Прежде чем покупки заработают в вашем приложении, вам также нужно подключить приложение к сторам, а затем создать продукты, пейвол и плейсмент в дашборде Adapty. [Гайд по быстрому старту](quickstart) описывает все необходимые шаги. ::: ## Установка Adapty SDK через Gradle \{#install-adapty-sdk-via-gradle\} Установка Adapty SDK через Gradle необходима как для Android, так и для iOS приложений. Выберите способ подключения зависимостей: - Стандартный Gradle: добавьте зависимости в `build.gradle` **на уровне модуля** - Если в вашем проекте используются файлы `.gradle.kts`, добавьте зависимости в `build.gradle.kts` **на уровне модуля** - Если вы используете каталоги версий, добавьте зависимости в файл `libs.versions.toml`, а затем сошлитесь на него в `build.gradle.kts` :::important Adapty Kotlin Multiplatform SDK 4.0 находится в стадии предрелиза. Gradle не выбирает предрелизные версии через динамические диапазоны версий (например, `+` или `latest.release`), поэтому необходимо указать точную версию — например, `io.adapty:adapty-kmp:4.0.0-beta.1` или `adapty-kmp = "4.0.0-beta.1"` в `libs.versions.toml`. См. [Миграция Adapty Kotlin Multiplatform SDK на v4](migration-to-kmp-sdk-v4). ::: <Tabs> <TabItem value="module-level build.gradle" label="module-level build.gradle" default> ```kotlin showLineNumbers kotlin { sourceSets { commonMain { dependencies { implementation libs.adapty.kmp } } } } ``` </TabItem> <TabItem value="module-level build.gradle.kts" label="module-level build.gradle.kts" default> ```kotlin showLineNumbers kotlin { sourceSets { val commonMain by getting { dependencies { implementation(libs.adapty.kmp) } } } } ``` </TabItem> <TabItem value="version-catalog" label="Библиотека версий" default> ```toml showLineNumbers // libs.versions.toml [versions] .. adapty-kmp = "<the latest SDK version>" [libraries] .. adapty-kmp = { module = "io.adapty:adapty-kmp", version.ref = "adapty-kmp" } // build.gradle.kts kotlin { sourceSets { val commonMain by getting { dependencies { implementation(libs.adapty.kmp) } } } } ``` </TabItem> </Tabs> :::note Если вы получаете ошибку, связанную с Maven, убедитесь, что в ваших Gradle-скриптах есть `mavenCentral()`. <details> <summary>Инструкция по его добавлению</summary> Если в вашем `settings.gradle` нет `dependencyResolutionManagement`, добавьте следующее в корневой `build.gradle` в конце блока repositories: ```groovy showLineNumbers title="top-level build.gradle" allprojects { repositories { ... mavenCentral() } } ``` В противном случае добавьте следующее в `settings.gradle` в блок `repositories` секции `dependencyResolutionManagement`: ```groovy showLineNumbers title="settings.gradle" dependencyResolutionManagement { ... repositories { ... google() mavenCentral() } } ``` </details> ::: ## Активация Adapty SDK \{#activate-adapty-sdk\} ### Базовая настройка \{#basic-setup\} Добавьте инициализацию как можно раньше — обычно в общем Kotlin-коде для обеих платформ. :::note SDK нужно активировать в приложении только один раз. ::: ```kotlin title="Kotlin" showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .build() Adapty.activate(configuration = config) .onSuccess { Log.d("Adapty", "SDK initialised") } .onError { error -> Log.e("Adapty", "Adapty init error: ${error.message}") } ``` :::important Дождитесь завершения `activate` перед вызовом любых других методов Adapty SDK. Полная последовательность описана в разделе [Порядок вызовов в Kotlin Multiplatform SDK](kmp-sdk-call-order). ::: Чтобы получить **Public SDK Key**: 1. Откройте дашборд Adapty и перейдите в [App settings → General](https://app.adapty.io/settings/general). 2. В разделе **Api keys** скопируйте **Public SDK Key** (НЕ Secret Key). 3. Замените `"YOUR_PUBLIC_SDK_KEY"` в коде на скопированный ключ. :::info - Убедитесь, что для инициализации Adapty используется публичный SDK-ключ. Секретный ключ предназначен только для [серверного API](getting-started-with-server-side-api). - SDK-ключи уникальны для каждого приложения, поэтому если у вас несколько приложений, убедитесь, что выбран правильный ключ. ::: Теперь настройте пейволы в вашем приложении: - Если вы используете [Adapty Paywall Builder](adapty-paywall-builder), сначала [активируйте модуль AdaptyUI](#activate-adaptyui-module-of-adapty-sdk) ниже, затем следуйте [быстрому старту Paywall Builder](kmp-quickstart-paywalls). - Если вы создаёте собственный интерфейс пейвола, см. [быстрый старт для кастомных пейволов](kmp-quickstart-manual). ## Активация модуля AdaptyUI в SDK Adapty \{#activate-adaptyui-module-of-adapty-sdk\} Если вы планируете подключить модуль **AdaptyUI** для использования [Adapty Paywall Builder](kmp-present-paywalls), добавьте `.withActivateUI(true)` в настройки конфигурации. :::info important В вашем коде необходимо активировать основной модуль Adapty перед активацией AdaptyUI. ::: ```kotlin title="Kotlin" showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withActivateUI(true) // true for activating the AdaptyUI module .build() Adapty.activate(configuration = config) .onSuccess { Log.d("Adapty", "SDK initialised") } .onError { error -> Log.e("Adapty", "Adapty init error: ${error.message}") } ``` ## Настройка Proguard (Android) \{#configure-proguard-android\} Перед выпуском приложения в продакшн вам может потребоваться добавить `-keep class com.adapty.** { *; }` в конфигурацию Proguard. ## Дополнительная настройка \{#optional-setup\} ### Логирование \{#logging\} #### Настройка системы логирования \{#set-up-the-logging-system\} Adapty записывает ошибки и другую важную информацию, чтобы вы могли понять, что происходит. Доступны следующие уровни логирования: | Level | Description | | :----------------------- | :------------------------------------------------------------------------------------------------------------------------ | | `AdaptyLogLevel.ERROR` | Будут записываться только ошибки. | | `AdaptyLogLevel.WARN` | Будут записываться ошибки и сообщения от SDK, которые не вызывают критических сбоев, но заслуживают внимания. | | `AdaptyLogLevel.INFO` | Будут записываться ошибки, предупреждения и различные информационные сообщения. Значение по умолчанию. | | `AdaptyLogLevel.VERBOSE` | Будет записываться любая дополнительная информация, которая может быть полезна при отладке: вызовы функций, API-запросы и т. д. | | `AdaptyLogLevel.DEBUG` | Будет записываться наиболее подробная информация, включая внутренние отладочные данные. | Вы можете задать уровень логирования в вашем приложении до настройки Adapty: ```kotlin title="Kotlin" showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withLogLevel(AdaptyLogLevel.VERBOSE) // recommended for development .build() ``` ### Политики работы с данными \{#data-policies\} #### Отключение сбора и передачи IP-адресов \{#disable-ip-address-collection-and-sharing\} При активации модуля Adapty установите `ipAddressCollectionDisabled` в значение `true`, чтобы отключить сбор и передачу IP-адресов пользователей. По умолчанию используется значение `false`. Используйте этот параметр для усиления конфиденциальности пользователей, соблюдения региональных требований по защите данных (например, GDPR или CCPA), а также для сокращения избыточного сбора данных в случаях, когда функции на основе IP-адреса вашему приложению не нужны. ```kotlin title="Kotlin" showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withIpAddressCollectionDisabled(true) .build() ``` #### Отключение сбора и передачи рекламных идентификаторов \{#disable-advertising-id-collection-and-sharing\} При активации модуля Adapty установите `appleIdfaCollectionDisabled` (iOS) или `googleAdvertisingIdCollectionDisabled` (Android) в значение true, чтобы отключить сбор рекламных идентификаторов. Значение по умолчанию — false. Используйте этот параметр для соблюдения требований App Store/Play Store, чтобы не вызывать запрос App Tracking Transparency, или если ваше приложение не использует рекламную атрибуцию или аналитику на основе рекламных идентификаторов. ```kotlin title="Kotlin" showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withGoogleAdvertisingIdCollectionDisabled(true) // Android only .withAppleIdfaCollectionDisabled(true) // iOS only .build() ``` #### Настройка конфигурации кэша медиа для AdaptyUI \{#set-up-media-cache-configuration-for-adaptyui\} По умолчанию AdaptyUI кэширует медиафайлы (изображения и видео) для повышения производительности и снижения сетевого трафика. Вы можете настроить параметры кэша, передав собственную конфигурацию. Используйте `mediaCache`, чтобы переопределить настройки кэша по умолчанию: ```kotlin val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withMediaCacheConfiguration( AdaptyConfig.MediaCacheConfiguration( memoryStorageTotalCostLimit = 200 * 1024 * 1024, // 200 MB memoryStorageCountLimit = Int.MAX_VALUE, diskStorageSizeLimit = 200 * 1024 * 1024 // 200 MB ) ) .build() ``` ### Включение локальных уровней доступа (Android) \{#enable-local-access-levels-android\} По умолчанию [локальные уровни доступа](local-access-levels) на Android отключены. Чтобы включить их, установите `withLocalAccessLevelAllowed` в `true`: ```kotlin title="Kotlin" showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withGoogleLocalAccessLevelAllowed(true) .build() ``` ### Очистка данных при восстановлении из резервной копии \{#clear-data-on-backup-restore\} Когда `withAppleClearDataOnBackup` установлено в `true`, SDK определяет, что приложение было восстановлено из резервной копии iCloud, и удаляет все локально сохранённые данные SDK, включая кэшированную информацию профиля, сведения о продуктах и пейволы. После этого SDK инициализируется с чистого состояния. Значение по умолчанию — `false`. :::note Удаляется только локальный кэш SDK. История транзакций с Apple и данные пользователя на серверах Adapty остаются без изменений. ::: ```swift showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withAppleClearDataOnBackup(true) .build() ``` ## Устранение неполадок \{#troubleshooting\} #### Правила резервного копирования Android (настройка Auto Backup) \{#android-backup-rules-auto-backup-configuration\} Некоторые SDK (включая Adapty) поставляются с собственной конфигурацией Android Auto Backup. Если вы используете несколько SDK, каждый из которых определяет правила резервного копирования, слияние манифестов Android может завершиться ошибкой, связанной с `android:fullBackupContent`, `android:dataExtractionRules` или `android:allowBackup`. Типичные симптомы ошибки: `Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/your_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` :::note Эти изменения нужно вносить в директорию Android-платформы (обычно находится в папке `android/` вашего проекта). ::: Чтобы решить проблему, необходимо: - Указать механизму слияния манифестов использовать значения вашего приложения для атрибутов, связанных с резервным копированием. - Создать файлы правил резервного копирования, объединяющие правила Adapty с правилами других SDK. #### 1. Добавьте пространство имён `tools` в манифест В файле `AndroidManifest.xml` убедитесь, что корневой тег `<manifest>` включает tools: ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.app"> ... </manifest> ``` #### 2. Переопределите атрибуты резервного копирования в `<application>` В том же файле `AndroidManifest.xml` обновите тег `<application>`, чтобы ваше приложение предоставляло итоговые значения и указывало механизму слияния манифестов заменять значения библиотек: ```xml <application android:name=".App" android:allowBackup="true" android:fullBackupContent="@xml/sample_backup_rules" android:dataExtractionRules="@xml/sample_data_extraction_rules" tools:replace="android:fullBackupContent,android:dataExtractionRules"> ... </application> ``` Если какой-либо SDK также задаёт `android:allowBackup`, включите его в `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. Создайте объединённые файлы правил резервного копирования Создайте XML-файлы в директории `res/xml/` вашего Android-проекта, объединяющие правила Adapty с правилами других SDK. Android использует разные форматы правил резервного копирования в зависимости от версии ОС, поэтому создание обоих файлов обеспечивает совместимость со всеми версиями Android, которые поддерживает ваше приложение. :::note В примерах ниже в качестве стороннего SDK используется AppsFlyer. Замените или добавьте правила для других SDK, которые используются в вашем приложении. ::: **Для Android 12 и выше** (используется новый формат правил извлечения данных): ```xml title="sample_data_extraction_rules.xml" <?xml version="1.0" encoding="utf-8"?> <data-extraction-rules> <cloud-backup> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </cloud-backup> <device-transfer> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </device-transfer> </data-extraction-rules> ``` **Для Android 11 и ниже** (используется устаревший формат полного резервного копирования): ```xml title="sample_backup_rules.xml" <?xml version="1.0" encoding="utf-8"?> <full-backup-content> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> :::important В проекте Kotlin Multiplatform применяйте эти изменения в модуле Android-приложения (том, который собирает APK/AAB), например `androidApp` или `app`: - Манифест: `androidApp/src/main/AndroidManifest.xml` - XML с правилами резервного копирования: `androidApp/src/main/res/xml/` ::: #### Покупки не проходят после возврата из другого приложения на Android \{#purchases-fail-after-returning-from-another-app-in-android\} Если Activity, запускающая флоу покупки, использует нестандартный `launchMode`, Android может некорректно пересоздать или переиспользовать её, когда пользователь возвращается из Google Play, банковского приложения или браузера. Это может привести к потере результата покупки или к тому, что она будет считаться отменённой. Чтобы покупки работали корректно, используйте только режимы `standard` или `singleTop` для Activity, запускающей флоу покупки, и избегайте любых других режимов. В файле `AndroidManifest.xml` убедитесь, что Activity, запускающая флоу покупки, имеет значение `standard` или `singleTop`: ```xml <activity android:name=".MainActivity" android:launchMode="standard" /> ``` --- # File: kmp-quickstart-paywalls --- --- title: "Включение покупок через Flow Builder в Kotlin Multiplatform SDK" description: "Быстрый старт по настройке встроенных покупок с помощью Adapty Flow Builder." --- Это руководство использует API Adapty Kotlin Multiplatform SDK v4 (beta). Если вы используете v3, ознакомьтесь с [руководством по миграции](migration-to-kmp-sdk-v4) для получения соответствующих имён методов. Чтобы подключить встроенные покупки, вам нужно разобраться с тремя ключевыми концепциями: - [**Products**](product) – всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ) - [**Flows**](adapty-flow-builder) – последовательности экранов, которые показывают продукты пользователям и создаются в конструкторе флоу без кода. SDK получает их с помощью `getFlow`. Если вы предпочитаете строить интерфейс в собственном коде, используйте пейвол — см. [Реализация пейволов вручную](kmp-quickstart-manual). - [**Placements**](placements) – где и когда в приложении показываются флоу (например, `main`, `onboarding`, `settings`). Вы прикрепляете флоу к плейсментам в дашборде, а затем запрашиваете их по ID плейсмента в коде. Это упрощает проведение A/B-тестов и показ разных флоу разным пользователям. Adapty предлагает три способа подключить покупки в приложении. Выберите один из них в зависимости от требований вашего приложения: | Реализация | Сложность | Когда использовать | |---|---|---| | Adapty Flow Builder | ✅ Просто | Вы [создаёте готовый к покупке флоу в no-code конструкторе](quickstart-paywalls). Adapty автоматически отображает его и берёт на себя весь сложный процесс покупки, валидацию чеков и управление подписками. | | Пейволы, созданные вручную | 🟡 Средне | Вы реализуете интерфейс пейвола в коде приложения, но всё равно получаете объект флоу от Adapty, сохраняя гибкость в управлении продуктами. См. [гайд](kmp-quickstart-manual). | | Observer mode | 🔴 Сложно | У вас уже есть собственная инфраструктура обработки покупок, и вы хотите продолжать её использовать. Обратите внимание, что observer mode имеет ограничения в Adapty. См. [статью](observer-vs-full-mode). | :::important **Шаги ниже показывают, как реализовать флоу, созданный в Adapty Flow Builder.** Если вы предпочитаете создавать UI пейвола самостоятельно, смотрите [Реализация пейволов вручную](kmp-quickstart-manual). ::: Чтобы отобразить флоу, созданный в Adapty Flow Builder, в коде приложения нужно сделать следующее: 1. **Получить флоу**: Получите его из Adapty. 2. **Отобразить его — Adapty сам обработает покупки**: Покажите view в вашем приложении. 3. **Обработать действия кнопок**: Свяжите действия пользователя с реакцией приложения на них. Например, открывайте ссылки или закрывайте флоу при нажатии кнопок. ## Прежде чем начать \{#before-you-start\} Прежде чем начать, выполните следующие шаги: 1. Подключите ваше приложение к [App Store](initial_ios) и/или [Google Play](initial-android) в дашборде Adapty. 2. [Создайте продукты](create-product) в Adapty. 3. [Создайте флоу и добавьте в него продукты](create-paywall). 4. [Создайте плейсмент и добавьте в него флоу](create-placement). 5. [Установите и активируйте SDK](sdk-installation-kotlin-multiplatform) в коде вашего приложения. :::tip Самый быстрый способ выполнить эти шаги — следовать [руководству по быстрому старту](quickstart) или создавать флоу и плейсменты с помощью [Developer CLI](developer-cli-quickstart). ::: ## 1. Получение флоу \{#1-get-the-flow\} Флоу привязаны к плейсментам, настроенным в дашборде. Плейсменты позволяют показывать разные флоу разным аудиториям или запускать [A/B-тесты](ab-tests). Чтобы получить флоу, созданный в Adapty Flow Builder, нужно: 1. Получить объект `flow` по ID [плейсмента](placements) с помощью метода `getFlow`. 2. Создать представление флоу с помощью метода `createFlowView`. Представление содержит UI-элементы и стили, необходимые для отображения флоу. Если у флоу нет настроенного представления, `createFlowView` возвращает ошибку — обработайте её в `onError`. :::important Чтобы получить отображение, необходимо включить переключатель **Show on device** во Flow Builder. В противном случае `createFlowView` вернёт ошибку, и флоу не отобразится. ::: ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") .onSuccess { flow -> AdaptyUI.createFlowView(flow) .onSuccess { view -> view.present() } .onError { error -> // the flow has no view configured, or view creation failed } } .onError { error -> // handle the error } ``` ## 2. Отобразите флоу \{#2-display-the-flow\} Теперь, когда у вас есть флоу, достаточно добавить несколько строк, чтобы отобразить его. Чтобы показать визуальный флоу на экране устройства, нужно сначала создать представление. Для этого вызовите метод `AdaptyUI.createFlowView()`: ```kotlin showLineNumbers AdaptyUI.createFlowView(flow) .onSuccess { view -> view.present() } .onError { error -> // handle the error } ``` После успешного создания представления его можно отобразить на экране устройства. Каждое представление можно использовать только один раз: после вызова `dismiss()` вызовите `createFlowView` ещё раз, чтобы снова показать флоу. :::tip Подробнее о том, как отображать флоу, читайте в нашем [гайде](kmp-present-paywalls). ::: ## 3. Обработка нажатий на кнопки \{#3-handle-button-actions\} Когда пользователи нажимают кнопки во флоу, Kotlin Multiplatform SDK автоматически обрабатывает покупки, восстановление, закрытие флоу и открытие ссылок. Однако у других кнопок есть пользовательские или предопределённые идентификаторы, и их действия нужно обрабатывать в вашем коде. Также вы можете переопределить их поведение по умолчанию. Например, ниже показано поведение кнопки закрытия по умолчанию. Добавлять его в код не нужно, но здесь вы можете увидеть, как это делается при необходимости. Обратите внимание, что по умолчанию флоу остаётся открытым после успешной покупки. Если вы хотите закрыть его после завершения покупки, скройте вью в колбэке `flowViewDidFinishPurchase`. :::tip Читайте наши гайды о том, как обрабатывать [действия](kmp-handle-paywall-actions) и [события](kmp-handling-events) кнопок. ::: ```kotlin showLineNumbers AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CloseAction -> mainUiScope.launch { view.dismiss() } else -> Unit } } override fun flowViewDidFinishPurchase( view: AdaptyUIFlowView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { if (purchaseResult !is AdaptyPurchaseResult.UserCanceled) { mainUiScope.launch { view.dismiss() } } } }) ``` ## Следующие шаги \{#next-steps\} Ваш пейвол готов к показу в приложении. Протестируйте покупки в [песочнице App Store](test-purchases-in-sandbox) или в [Google Play Store](testing-on-android), чтобы убедиться, что тестовая покупка через пейвол проходит успешно. Теперь вам нужно [проверить уровень доступа пользователей](kmp-check-subscription-status), чтобы показывать пейвол или открывать платные функции нужным пользователям. ## Полный пример \{#full-example\} Вот как все эти шаги можно объединить в вашем приложении. ```kotlin showLineNumbers // Set up the observer for handling flow events AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CloseAction -> mainUiScope.launch { view.dismiss() } else -> Unit } } override fun flowViewDidFinishPurchase( view: AdaptyUIFlowView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { if (purchaseResult !is AdaptyPurchaseResult.UserCanceled) { mainUiScope.launch { view.dismiss() } } } }) // Get and display the flow Adapty.getFlow("YOUR_PLACEMENT_ID") .onSuccess { flow -> AdaptyUI.createFlowView(flow) .onSuccess { view -> view.present() } .onError { error -> // the flow has no view configured — use custom logic } } .onError { error -> // handle the error } ``` --- # File: kmp-check-subscription-status --- --- title: "Проверка статуса подписки в Kotlin Multiplatform SDK" description: "Узнайте, как проверить статус подписки в приложении на Kotlin Multiplatform с помощью Adapty." --- Чтобы решить, может ли пользователь получить доступ к платному контенту или должен ли он увидеть пейвол, нужно проверить его [уровень доступа](access-level) в профиле. В этой статье показано, как обращаться к состоянию профиля для принятия решений: показывать пейвол или открывать платные возможности. ## Получение статуса подписки \{#get-subscription-status\} Когда вы решаете, показывать ли пользователю пейвол или платный контент, вы проверяете его [уровень доступа](access-level) в профиле. Есть два варианта: - Вызовите `getProfile`, если нужны актуальные данные прямо сейчас (например, при запуске приложения) или хотите принудительно обновить профиль. - Настройте **автоматические обновления профиля**, чтобы хранить локальную копию, которая обновляется автоматически при изменении статуса подписки. ### Получить профиль \{#get-profile\} Самый простой способ получить статус подписки — использовать метод `getProfile`: ```kotlin showLineNumbers Adapty.getProfile() .onSuccess { profile -> // check the access } .onError { error -> // handle the error } ``` ### Подписаться на обновления подписки \{#listen-to-subscription-updates\} Чтобы автоматически получать обновления профиля в приложении: 1. Используйте `Adapty.setOnProfileUpdatedListener()` для отслеживания изменений профиля — Adapty будет вызывать этот метод автоматически при каждом изменении статуса подписки пользователя. 2. Сохраняйте обновлённые данные профиля при каждом вызове метода, чтобы использовать их в приложении без лишних сетевых запросов. ```kotlin showLineNumbers class SubscriptionManager { private var currentProfile: AdaptyProfile? = null init { // Listen for profile updates Adapty.setOnProfileUpdatedListener { profile -> currentProfile = profile // Update UI, unlock content, etc. } } // Use stored profile instead of calling getProfile() fun hasAccess(): Boolean { return currentProfile?.accessLevels?.get("YOUR_ACCESS_LEVEL")?.isActive == true } } ``` :::note Adapty автоматически вызывает слушатель обновлений профиля при запуске приложения, предоставляя кэшированные данные о подписке даже при отсутствии интернета. ::: ## Связь профиля с логикой пейвола \{#connect-profile-with-paywall-logic\} Когда нужно оперативно решить, показывать ли пейвол или открывать доступ к платным функциям, можно проверить профиль пользователя напрямую. Это удобно при запуске приложения, входе в разделы с премиум-контентом или перед отображением определённого контента. ```kotlin showLineNumbers private fun checkAccessAndShowPaywall() { // First, check if user has access Adapty.getProfile() .onSuccess { profile -> val hasAccess = profile.accessLevels?.get("YOUR_ACCESS_LEVEL")?.isActive == true if (!hasAccess) { // User doesn't have access, show paywall showPaywall() } else { // User has access, show premium content showPremiumContent() } } .onError { error -> // If we can't check access, show paywall as fallback showPaywall() } } private fun showPaywall() { // Get and display paywall using the KMP SDK Adapty.getPaywall("YOUR_PLACEMENT_ID") .onSuccess { paywall -> if (paywall.hasViewConfiguration) { val paywallView = AdaptyUI.createPaywallView(paywall = paywall) paywallView?.present() } else { // Handle remote config paywall or show custom UI handleRemoteConfigPaywall(paywall) } } .onError { error -> // Handle paywall loading error showError("Unable to load paywall") } } private fun showPremiumContent() { // Show your premium content here // This is where you unlock paid features } ``` ## Дальнейшие шаги \{#next-steps\} Теперь, когда вы знаете, как отслеживать статус подписки, узнайте, как [работать с профилями пользователей](kmp-quickstart-identify), чтобы они могли получить доступ к тому, за что заплатили. --- # File: kmp-quickstart-identify --- --- title: "Идентификация пользователей в Kotlin Multiplatform SDK" description: "Быстрый старт по настройке Adapty для управления встроенными подписками в KMP." --- :::important Этот гайд для тех, у кого есть собственная система аутентификации. Здесь вы узнаете, как работать с профилями пользователей в Adapty, чтобы всё корректно взаимодействовало с вашей системой аутентификации. ::: То, как вы управляете покупками пользователей, зависит от модели аутентификации в вашем приложении: - Если приложение не использует бэкенд-аутентификацию и не хранит данные пользователей, см. [раздел об анонимных пользователях](#anonymous-users). - Если в приложении есть (или будет) бэкенд-аутентификация, см. [раздел об идентифицированных пользователях](#identified-users). **Ключевые понятия**: - **Профили** — сущности, необходимые для работы SDK. Adapty создаёт их автоматически. - Они могут быть анонимными **(без customer user ID)** или идентифицированными **(с customer user ID)**. - Вы передаёте **customer user ID**, чтобы связать профили в Adapty с вашей внутренней системой авторизации. Вот в чём разница между анонимными и идентифицированными пользователями: | | Анонимные пользователи | Идентифицированные пользователи | |-------------------------|----------------------------------------------------------------|----------------------------------------------------------------------------------------------| | **Управление покупками** | Восстановление покупок на уровне стора | История покупок сохраняется на всех устройствах через customer user ID | | **Управление профилем** | Новый профиль при каждой переустановке | Один и тот же профиль во всех сессиях и на всех устройствах | | **Сохранность данных** | Данные анонимных пользователей привязаны к установке приложения | Данные идентифицированных пользователей сохраняются между установками приложения | ## Анонимные пользователи \{#anonymous-users\} Если у вас нет серверной аутентификации, **вам не нужно реализовывать аутентификацию в коде приложения**: 1. При первом запуске приложения и активации SDK Adapty **создаёт новый профиль для пользователя**. 2. Когда пользователь совершает покупку в приложении, она **привязывается к его профилю в Adapty и аккаунту в сторе**. 3. Если пользователь **переустанавливает** приложение или устанавливает его на **новом устройстве**, Adapty **создаёт новый анонимный профиль при активации**. 4. Если пользователь ранее совершал покупки в вашем приложении, по умолчанию они автоматически синхронизируются из App Store при активации SDK. Итак, при анонимных пользователях новые профили создаются при каждой установке, но это не проблема: в аналитике Adapty можно [настроить, что считать новой установкой](general#4-installs-definition-for-analytics). Для анонимных пользователей установки нужно считать по **идентификаторам устройств**. В этом случае каждая установка приложения на устройство считается отдельной установкой, включая переустановки. :::note Резервное восстановление работает иначе, чем переустановка. По умолчанию при восстановлении из резервной копии SDK сохраняет кешированные данные и не создаёт новый профиль. Это поведение можно настроить с помощью параметра `withAppleClearDataOnBackup`. [Подробнее](sdk-installation-kotlin-multiplatform#clear-data-on-backup-restore). ::: ## Идентификация пользователей \{#identified-users\} У вас есть два способа идентифицировать пользователей в приложении: - [**При входе/регистрации:**](#during-loginsignup) Если пользователи входят в систему после запуска приложения, вызовите `identify()` с customer user ID в момент аутентификации. - [**При активации SDK:**](#during-the-sdk-activation) Если у вас уже есть сохранённый customer user ID на момент запуска приложения, передайте его при вызове `activate()`. :::important По умолчанию, когда Adapty получает покупку от Customer User ID, который в данный момент связан с другим Customer User ID, уровень доступа становится общим — оба профиля получают платный доступ. Вы можете настроить это поведение так, чтобы платный доступ переносился с одного профиля на другой, или полностью отключить его совместное использование. Подробнее см. в [статье](general#6-sharing-paid-access-between-user-accounts). ::: <img src="/assets/shared/img/identify-diagram.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### В момент входа или регистрации \{#during-loginsignup\} Если вы идентифицируете пользователей после запуска приложения (например, после входа в аккаунт или регистрации), используйте метод `identify`, чтобы задать их customer user ID. - Если вы **ещё не использовали этот customer user ID**, Adapty автоматически привяжет его к текущему профилю. - Если вы **уже использовали этот customer user ID для идентификации пользователя раньше**, Adapty переключится на работу с профилем, связанным с этим customer user ID. :::important Идентификаторы пользователей (Customer User ID) должны быть уникальными для каждого пользователя. Если вы укажете фиксированное значение, все пользователи будут считаться одним. ::: Дождитесь завершения `identify` (в колбэке `onSuccess`), прежде чем вызывать другие методы SDK. Конкурентные вызовы могут попасть на анонимный профиль. См. [Порядок вызовов в Kotlin Multiplatform SDK](kmp-sdk-call-order). ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID") // Уникален для каждого пользователя .onSuccess { // successful identify } .onError { error -> // handle the error } ``` ### При активации SDK \{#during-the-sdk-activation\} Если вы уже знаете идентификатор пользователя на момент активации SDK, передайте его прямо в методе `activate` — вызывать `identify` отдельно не нужно. Если идентификатор пользователя известен, но вы задаёте его только после активации, то при старте SDK Adapty создаст новый анонимный профиль и переключится на существующий лишь после вызова `identify`. Вы можете передать как существующий customer user ID (тот, который вы уже использовали ранее), так и новый. Если передать новый, профиль, созданный при активации, будет автоматически привязан к этому customer user ID. :::note По умолчанию создание анонимных профилей не влияет на аналитику дашборда, поскольку установки считаются на основе идентификаторов устройств. A device ID представляет собой одну установку приложения из стора на устройстве и regenerated only after the app is reinstalled. Он не зависит от того, является ли это первой или повторной установкой, и от того, используется ли существующий customer user ID. Создание профиля (при активации SDK или выходе из системы), вход в систему или обновление приложения без его переустановки не генерирует дополнительных событий установки. Если вы хотите считать установки по уникальным пользователям, а не устройствам, перейдите в **App settings** и настройте [**Installs definition for analytics**](general#4-installs-definition-for-analytics). ::: ```kotlin showLineNumbers AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId("user123") // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. .build() ``` ### Выход пользователей из системы \{#log-users-out\} Если в вашем приложении есть кнопка выхода, используйте метод `logout`. :::important Выход из аккаунта создаёт новый анонимный профиль для пользователя. ::: ```kotlin showLineNumbers Adapty.logout() .onSuccess { // successful logout } .onError { error -> // handle the error } ``` :::info Чтобы снова войти в приложение, используйте метод `identify`. ::: ### Разрешите покупки без входа \{#allow-purchases-without-login\} Если пользователи могут совершать покупки как до, так и после входа в приложение, необходимо убедиться, что после авторизации они сохранят доступ к своим покупкам: 1. Когда пользователь, не вошедший в систему, совершает покупку, Adapty привязывает её к анонимному ID профиля. 2. Когда пользователь входит в свой аккаунт, Adapty переключается на работу с идентифицированным профилем. - Если это новый customer user ID (например, покупка была совершена до регистрации), Adapty присваивает customer user ID текущему профилю, и вся история покупок сохраняется. - Если это существующий customer user ID (customer user ID уже привязан к профилю), после переключения профиля нужно получить актуальный уровень доступа. Можно либо вызвать [`getProfile`](kmp-check-subscription-status) сразу после идентификации, либо [подписаться на обновления профиля](kmp-check-subscription-status) — тогда данные будут синхронизироваться автоматически. ## Дальнейшие шаги \{#next-steps\} Поздравляем! Вы реализовали логику встроенных покупок в своём приложении! Желаем вам успехов в монетизации! Чтобы получить от Adapty ещё больше, изучите следующие темы: - [**Тестирование**](troubleshooting-test-purchases): убедитесь, что всё работает как ожидается - [**Интеграции**](configuration): подключите сервисы маркетинговой атрибуции и аналитики буквально в одну строку кода - [**Настройка атрибутов профиля**](kmp-setting-user-attributes): добавляйте пользовательские атрибуты к профилям, создавайте сегменты и запускайте A/B-тесты или показывайте разные пейволы разным пользователям --- # File: adapty-sdk-integration-skill-kmp --- --- title: "Интеграция Adapty в приложение Kotlin Multiplatform с помощью навыка интеграции SDK" description: "Используйте навык adapty-sdk-integration для полной интеграции SDK Adapty в ваше приложение Kotlin Multiplatform с вашим AI-инструментом для написания кода." --- [Скилл adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) автоматизирует интеграцию Adapty от начала до конца: настройку дашборда, установку SDK, пейвол и проверку на каждом этапе. Он автоматически определяет вашу платформу и подгружает нужную документацию Adapty на каждом шаге. **Поддерживаемые инструменты**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Для установки выберите команду для своего инструмента. Полный список — в [README скилла](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex или любой другой инструмент** — используйте [skills CLI](https://skills.sh) (обратите внимание, что скиллы, установленные таким способом, не обновляются автоматически): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` Либо клонируйте репозиторий и скопируйте `skills/adapty-sdk-integration/` в директорию скиллов вашего инструмента. После установки запустите скилл в вашем проекте: ``` /adapty-sdk-integration ``` Скилл задаст несколько вопросов по настройке, а затем проведёт через настройку дашборда, установку SDK, пейвол и проверку. :::important Навык находится в бета-версии. Если он зависает или ведёт себя неожиданно, используйте [пошаговый гайд по интеграции](adapty-cursor-kmp) — он проведёт ваш AI-инструмент через каждый этап с нужной документацией. ::: [Скилл adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) автоматизирует интеграцию Adapty от начала до конца: настройку дашборда, установку SDK, пейвол и проверку на каждом этапе. Он автоматически определяет вашу платформу и подгружает нужную документацию Adapty на каждом шаге. **Поддерживаемые инструменты**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Для установки выберите команду для своего инструмента. Полный список — в [README скилла](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex или любой другой инструмент** — используйте [skills CLI](https://skills.sh) (обратите внимание, что скиллы, установленные таким способом, не обновляются автоматически): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` Либо клонируйте репозиторий и скопируйте `skills/adapty-sdk-integration/` в директорию скиллов вашего инструмента. После установки запустите скилл в вашем проекте: ``` /adapty-sdk-integration ``` Скилл задаст несколько вопросов по настройке, а затем проведёт через настройку дашборда, установку SDK, пейвол и проверку. --- # File: adapty-cursor-kmp --- --- title: "Интеграция Adapty в ваше приложение Kotlin Multiplatform с помощью ИИ" description: "Пошаговый гайд по интеграции Adapty в ваше приложение Kotlin Multiplatform с использованием Cursor, Context7, ChatGPT, Claude или других ИИ-инструментов." --- Этот гайд проведёт вас через интеграцию Adapty в ваше Kotlin Multiplatform-приложение шаг за шагом с помощью AI-инструмента — вам нужно передавать ему нужную документацию Adapty в правильном порядке. For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## Прежде чем начать: настройка дашборда \{#before-you-start-dashboard-setup\} Adapty требует предварительной настройки в дашборде, прежде чем вы напишете какой-либо код SDK. Это можно сделать с помощью интерактивного LLM-инструмента или вручную через дашборд. ### Подход через skill (рекомендуется) \{#skill-approach-recommended\} Skill Adapty CLI позволяет вашему LLM настраивать приложение, продукты, уровни доступа, пейволы и плейсменты напрямую — без необходимости заходить в дашборд на каждом шаге. Вам нужно только [подключить сторы](integrate-payments) в дашборде. ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` После добавления skill запустите `/adapty-cli` в своём агенте. Он проведёт вас через каждый шаг — включая моменты, когда нужно открыть дашборд для подключения сторов. ### Подход через дашборд Если вы предпочитаете настраивать всё вручную, вот что нужно сделать до написания кода. LLM не сможет получить значения из дашборда за вас — их придётся указать самостоятельно. 1. **Подключите сторы**: В дашборде Adapty перейдите в **App settings → General**. Подключите App Store и Google Play, если ваше KMP-приложение поддерживает обе платформы. Это необходимо для работы покупок. [Подключить сторы](integrate-payments) 2. **Скопируйте публичный SDK-ключ**: В дашборде Adapty перейдите в **App settings → General** и найдите раздел **API keys**. В коде это строка, которую вы передаёте в конфигуратор Adapty. 3. **Создайте хотя бы один продукт**: В дашборде Adapty перейдите на страницу **Products**. В коде вы не обращаетесь к продуктам напрямую — Adapty передаёт их через пейволы. [Добавьте продукты](quickstart-products) 4. **Создайте пейвол и плейсмент**: в дашборде Adapty создайте пейвол на странице **Paywalls**, затем привяжите его к плейсменту на странице **Placements**. В коде идентификатор плейсмента — это строка, которую вы передаёте в `Adapty.getPaywall("YOUR_PLACEMENT_ID")`. [Создать пейвол](quickstart-paywalls) 5. **Настройте уровни доступа**: В дашборде Adapty настройте каждый продукт на странице **Products**. В коде проверяется строка `profile.accessLevels["premium"]?.isActive`. Уровень доступа `premium` по умолчанию подходит для большинства приложений. Если платящие пользователи получают доступ к разным функциям в зависимости от продукта (например, план `basic` и план `pro`), [создайте дополнительные уровни доступа](assigning-access-level-to-a-product) до начала разработки. :::tip Когда у вас есть все пять, можно приступать к написанию кода. Скажите своему LLM: «Мой публичный SDK-ключ — X, мой ID плейсмента — Y», чтобы он сгенерировал корректный код инициализации и получения пейвола. ::: ### Настройте, когда будете готовы \{#set-up-when-ready\} Это необязательно для начала разработки, но пригодится по мере развития интеграции: - **A/B-тесты**: Настраиваются на странице **Placements**. Изменения в коде не нужны. [A/B-тесты](ab-tests) - **Дополнительные пейволы и плейсменты**: Добавьте больше вызовов `getPaywall` с разными идентификаторами плейсментов. - **Интеграции с аналитикой**: Настраиваются на странице **Integrations**. Процесс настройки зависит от интеграции. См. [интеграции с аналитикой](analytics-integration) и [интеграции с атрибуцией](attribution-integration). ## Передайте документацию Adapty своему LLM \{#feed-adapty-docs-to-your-llm\} ### Используйте Context7 (рекомендуется) \{#use-context7-recommended\} [Context7](https://context7.com) — это MCP-сервер, который даёт вашей LLM прямой доступ к актуальной документации Adapty. LLM автоматически находит нужные документы на основе вашего запроса — не нужно вручную вставлять ссылки. Context7 работает с **Cursor**, **Claude Code**, **Windsurf** и другими инструментами, поддерживающими MCP. Для настройки выполните: ``` npx ctx7 setup ``` Команда определит ваш редактор и настроит сервер Context7. Для ручной настройки смотрите [репозиторий Context7 на GitHub](https://github.com/upstash/context7). После настройки подключитесь к библиотеке Adapty в своих запросах: ``` Use the adaptyteam/adapty-docs library to look up how to install the Kotlin Multiplatform SDK ``` :::warning Несмотря на то что Context7 устраняет необходимость вставлять ссылки на документацию вручную, порядок реализации имеет значение. Следуйте [пошаговому руководству по реализации](#implementation-walkthrough) строго по шагам, чтобы всё работало корректно. ::: ### Используйте документацию в формате plain text Любую страницу документации Adapty можно открыть как plain text Markdown. Добавьте `.md` в конец URL или нажмите **Copy for LLM** под заголовком статьи. Например: [adapty-cursor-kmp.md](https://adapty.io/docs/ru/adapty-cursor-kmp.md). В каждом шаге [пошагового руководства по интеграции](#implementation-walkthrough) ниже есть блок «Отправьте это в ваш LLM» со ссылками `.md` для вставки. Чтобы получить сразу несколько страниц документации, см. [индексные файлы и платформо-специфичные подборки](#plain-text-doc-index-files) ниже. ## Пошаговое руководство по интеграции \{#implementation-walkthrough\} Этот гайд описывает интеграцию Adapty в порядке реализации. Для каждого этапа указаны документы для отправки в LLM, ожидаемый результат и типичные проблемы. ### Планируйте интеграцию заранее \{#plan-your-integration\} Прежде чем переходить к коду, попросите LLM проанализировать ваш проект и составить план реализации. Если ваш AI-инструмент поддерживает режим планирования (как в Cursor или Claude Code), воспользуйтесь им — LLM сможет изучить структуру вашего проекта и документацию Adapty до того, как начнёт писать код. Сообщите LLM, какой подход к покупкам вы используете — от этого зависит, какие гайды он должен применять: - [**Adapty Paywall Builder**](adapty-paywall-builder): Вы создаёте пейволы в визуальном редакторе Adapty, а SDK отображает их автоматически. - [**Паywalls, созданные вручную**](kmp-making-purchases): Вы строите собственный интерфейс пейвола в коде, но используете Adapty для получения продуктов и обработки покупок. - [**Observer mode**](observer-vs-full-mode): Вы сохраняете существующую инфраструктуру покупок и используете Adapty только для аналитики и интеграций. Не знаете, что выбрать? Прочитайте [таблицу сравнения в quickstart](kmp-quickstart-paywalls). ### Установка и настройка SDK \{#install-and-configure-the-sdk\} Добавьте зависимость Adapty SDK через Gradle и активируйте её с помощью вашего публичного ключа SDK. Это основа — без неё ничего не работает. **Гайд:** [Установка и настройка Adapty SDK](sdk-installation-kotlin-multiplatform) Отправьте это своему LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/sdk-installation-kotlin-multiplatform.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** Приложение собирается и запускается. Logcat (Android) или консоль Xcode (iOS) показывает лог активации Adapty. - **Частая ошибка:** «Public API key is missing» → убедитесь, что вы заменили плейсхолдер на реальный ключ из **App settings**. ::: ### Показывайте пейволы и обрабатывайте покупки \{#show-paywalls-and-handle-purchases\} Получите пейвол по ID плейсмента, отобразите его и обработайте события покупок. Нужные вам гайды зависят от того, как вы обрабатываете покупки. Тестируйте каждую покупку в песочнице по ходу работы — не откладывайте на конец. Инструкции по настройке см. в разделе [Тестирование покупок в песочнице](test-purchases-in-sandbox). <Tabs groupId="paywall-approach"> <TabItem value="builder" label="Paywall Builder" default> **Гайды:** - [Включение покупок через пейволы (быстрый старт)](kmp-quickstart-paywalls) - [Получение пейволов Paywall Builder и их конфигурации](kmp-get-pb-paywalls) - [Отображение пейволов](kmp-present-paywalls) - [Обработка событий пейвола](kmp-handling-events) - [Реакция на действия кнопок](kmp-handle-paywall-actions) Отправьте это своему LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/kmp-quickstart-paywalls.md - https://adapty.io/docs/ru/kmp-get-pb-paywalls.md - https://adapty.io/docs/ru/kmp-present-paywalls.md - https://adapty.io/docs/ru/kmp-handling-events.md - https://adapty.io/docs/ru/kmp-handle-paywall-actions.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** Пейвол отображается с настроенными продуктами. Нажатие на продукт вызывает диалог покупки в песочнице. - **Частая ошибка:** Пустой пейвол или ошибка `getPaywall` → убедитесь, что ID плейсмента точно совпадает с дашбордом и у плейсмента назначена аудитория. ::: </TabItem> <TabItem value="manual" label="Ручные пейволы"> **Гайды:** - [Включить покупки в вашем кастомном пейволе (quickstart)](kmp-quickstart-manual) - [Получить пейволы и продукты](fetch-paywalls-and-products-kmp) - [Отобразить пейвол, созданный через Remote Config](present-remote-config-paywalls-kmp) - [Совершить покупки](kmp-making-purchases) - [Восстановить покупки](kmp-restore-purchase) Read these Adapty docs before writing code: - https://adapty.io/docs/ru/kmp-quickstart-manual.md - https://adapty.io/docs/ru/fetch-paywalls-and-products-kmp.md - https://adapty.io/docs/ru/present-remote-config-paywalls-kmp.md - https://adapty.io/docs/ru/kmp-making-purchases.md - https://adapty.io/docs/ru/kmp-restore-purchase.md :::tip[Checkpoint] - **Ожидаемый результат:** Ваш кастомный пейвол отображает продукты, полученные из Adapty. Нажатие на продукт открывает диалог покупки в песочнице. - **Частая проблема:** Пустой массив продуктов → убедитесь, что продукты назначены пейволу на дашборде и для плейсмента задана аудитория. ::: </TabItem> <TabItem value="observer" label="Observer mode"> **Гайды:** - [Обзор Observer mode](observer-vs-full-mode) - [Реализация Observer mode](implement-observer-mode-kmp) - [Отчёт о транзакциях в Observer mode](report-transactions-observer-mode-kmp) Отправьте это в ваш LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/observer-vs-full-mode.md - https://adapty.io/docs/ru/implement-observer-mode-kmp.md - https://adapty.io/docs/ru/report-transactions-observer-mode-kmp.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После тестовой покупки в песочнице через ваш существующий флоу покупки транзакция появляется в **Event Feed** дашборда Adapty. - **Частая проблема:** Нет событий → убедитесь, что вы передаёте транзакции в Adapty и серверные уведомления настроены для обоих сторов. ::: </TabItem> </Tabs> ### Проверка статуса подписки \{#check-subscription-status\} После покупки проверьте профиль пользователя на наличие активного уровня доступа, чтобы ограничить доступ к премиум-контенту. **Гайд:** [Проверка статуса подписки](kmp-check-subscription-status) Отправьте это в вашу LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/kmp-check-subscription-status.md ``` :::tip[Контрольная точка] - **Ожидаемый результат:** После покупки в песочнице `profile.accessLevels["premium"]?.isActive` возвращает `true`. - **Частая ошибка:** Пустой `accessLevels` после покупки → проверьте, что продукту назначен уровень доступа в дашборде. ::: ### Идентифицируйте пользователей \{#identify-users\} Свяжите аккаунты пользователей вашего приложения с профилями Adapty, чтобы покупки сохранялись на всех устройствах. :::important Пропустите этот шаг, если в вашем приложении нет аутентификации. ::: **Гайд:** [Идентификация пользователей](kmp-quickstart-identify) Отправьте это в ваш LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/kmp-quickstart-identify.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После вызова `Adapty.identify("your-user-id")` в разделе **Profiles** дашборда отображается ваш пользовательский ID. - **Важно:** Вызывайте `identify` после активации SDK, но до получения пейволов — иначе события могут быть привязаны к анонимному профилю. ::: ### Подготовка к релизу Когда интеграция заработает в песочнице, пройдитесь по чек-листу релиза и убедитесь, что всё готово к продакшену. **Гайд:** [Чек-лист релиза](release-checklist) Отправьте это своему LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/ru/release-checklist.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** Все пункты чеклиста подтверждены: подключение сторов, серверные уведомления, флоу покупки, проверки уровня доступа и требования конфиденциальности. - **Возможная проблема:** Отсутствуют серверные уведомления — настройте App Store Server Notifications в **App settings → iOS SDK** и Google Play Real-Time Developer Notifications в **App settings → Android SDK**. ::: ## Файлы индекса документации в виде обычного текста \{#plain-text-doc-index-files\} Если вам нужно дать вашей языковой модели более широкий контекст помимо отдельных страниц, мы размещаем файлы индекса, которые содержат список или объединение всей документации Adapty: - [`llms.txt`](https://adapty.io/docs/ru/llms.txt): Список всех страниц со ссылками в формате `.md`. [Набирающий популярность стандарт](https://llmstxt.org/) для обеспечения доступа LLM к веб-сайтам. Обратите внимание, что для некоторых AI-агентов (например, ChatGPT) потребуется скачать `llms.txt` и загрузить файл в чат. - [`llms-full.txt`](https://adapty.io/docs/ru/llms-full.txt): Вся документация Adapty в одном файле. Очень большой объём — используйте только тогда, когда нужна полная картина. - Файлы для Kotlin Multiplatform: [`kmp-llms.txt`](https://adapty.io/docs/ru/kmp-llms.txt) и [`kmp-llms-full.txt`](https://adapty.io/docs/ru/kmp-llms-full.txt) — подмножества документации для конкретной платформы, которые экономят токены по сравнению с полным сайтом. --- # File: kmp-paywalls --- --- title: "Флоу и пейволы - Kotlin Multiplatform" description: "Отображайте флоу и пейволы, созданные с помощью Adapty Flow Builder или Paywall Builder, в вашем приложении на Kotlin Multiplatform." --- ## Отображение пейволов ### Adapty Flow Builder и Paywall Builder <CustomDocCardList ids={['kmp-get-pb-paywalls', 'kmp-present-paywalls', 'kmp-handling-events', 'kmp-handle-paywall-actions']} /> :::tip Чтобы быстро начать работу с пейволами Adapty Paywall Builder, ознакомьтесь с нашим [гайдом по быстрому старту](kmp-quickstart-paywalls). ::: ### Реализация пейволов вручную <CustomDocCardList ids={['kmp-quickstart-manual', 'fetch-paywalls-and-products-kmp', 'present-remote-config-paywalls-kmp', 'kmp-making-purchases']} /> Больше гайдов по реализации пейволов и обработке покупок вручную см. в [разделе](kmp-implement-paywalls-manually). ## Полезные возможности \{#useful-features\} <CustomDocCardList ids={['kmp-use-fallback-paywalls', 'kmp-web-paywalls']} /> --- # File: kmp-get-pb-paywalls --- --- title: "Получение флоу и пейволов - Kotlin Multiplatform" description: "Получение флоу и пейволов из Adapty в вашем приложении Kotlin Multiplatform." --- <SDKv4> <MethodPromo method="getFlow" /> После того как вы [создали флоу или пейвол в Paywall Builder](adapty-paywall-builder), его можно отобразить в мобильном приложении. Первый шаг — получить флоу или пейвол, привязанный к плейсменту, вместе с конфигурацией отображения, как описано ниже. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Прежде чем начать показывать флоу в мобильном приложении (нажмите, чтобы развернуть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте флоу/пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них флоу/пейвол](create-placement) в дашборде Adapty. 4. Установите [Adapty SDK](sdk-installation-kotlin-multiplatform) в своё мобильное приложение. </details> ## Получение флоу/пейвола \{#fetch-flowpaywall\} Если вы создали флоу или пейвол с помощью Flow Builder или Paywall Builder, вам не нужно беспокоиться о том, как отрисовать его в коде мобильного приложения — всё необходимое уже включено в сам флоу или пейвол: и что показывать, и как. Тем не менее вам нужно получить его ID через плейсмент, конфигурацию представления, а затем отобразить его в мобильном приложении. Чтобы обеспечить оптимальную производительность, важно получать флоу или пейвол и его [конфигурацию отображения](kmp-get-pb-paywalls#fetch-the-view-configuration) как можно раньше — это даёт достаточно времени для загрузки изображений до того, как они будут показаны пользователю. Чтобы получить флоу или пейвол, используйте метод `getFlow`: ```kotlin showLineNumbers Adapty.getFlow( placementId = "YOUR_PLACEMENT_ID", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { flow -> // запрошенный флоу/пейвол }.onError { error -> // обработка ошибки } ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `AdaptyPaywallFetchPolicy.Default` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают самые актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильный интернет, рассмотрите использование `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` для возврата кешированных данных, если они есть. В этом случае пользователи могут не получить самые последние данные, но загрузка будет быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии во избежание лишних сетевых запросов.</p><p></p><p>Обратите внимание, что кеш сохраняется после перезапуска приложения и очищается только при переустановке или ручной очистке.</p><p></p><p>Adapty SDK хранит флоу и пейволы локально в двух слоях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](fallback-paywalls). Для более быстрой загрузки используется CDN, а также отдельный резервный сервер на случай недоступности CDN. Эта система гарантирует получение последней версии данных и надёжную работу даже при слабом интернете.</p> | | **loadTimeout** | по умолчанию: 5 сек | <p>Это значение ограничивает таймаут метода. При его истечении будут возвращены кешированные данные или локальный резервный пейвол.</p><p>Обратите внимание, что в редких случаях метод может завершиться чуть позже указанного в `loadTimeout` значения, поскольку операция может включать несколько запросов под капотом.</p><p>Для Kotlin Multiplatform: вы можете создать `Duration` с помощью функций-расширений, например `5.seconds`, где `.seconds` — из `kotlin.time.Duration.Companion.seconds`.</p> | | Параметр | Описание | | :-------- | :---------- | | Flow | Объект `AdaptyFlow`, содержащий плейсмент, идентификаторы (`instanceIdentity`, `variationId`), название, варианты пейвола (`paywalls` — список `AdaptyFlowPaywall`) и Remote Config (`remoteConfigs` — список с одной записью на каждую локаль). Чтобы получить продукты для предзагрузки, кастомного UI или программных проверок, вызовите `getPaywallProducts(flow)`. | ## Получение конфигурации представления \{#fetch-the-view-configuration\} После получения флоу или пейвола загрузите конфигурацию представления и создайте само представление за один шаг с помощью метода `createFlowView`. Отдельного флага для проверки нет: если плейсмент был создан в **Flow Builder** (флоу) или **Paywall Builder** (пейвол), `createFlowView` возвращает представление, готовое к показу. Если плейсмент — это кастомный пейвол без UI в Builder, `createFlowView` возвращает `AdaptyResult.Error` — [обработайте его как пейвол с Remote Config](present-remote-config-paywalls-kmp). :::important Обязательно включите переключатель **Show on device** в Flow Builder. Если эта опция не включена, конфигурация представления не будет доступна для получения. ::: ```kotlin showLineNumbers AdaptyUI.createFlowView( flow = flow, loadTimeout = 5.seconds, preloadProducts = true ).onSuccess { view -> // use view }.onError { error -> // the flow has no view configured, or view creation failed } ``` | Параметр | Наличие | Описание | | :--------------------------- | :------------- | :----------------------------------------------------------- | | **flow** | обязательный | Объект `AdaptyFlow`, полученный через `Adapty.getFlow`. | | **loadTimeout** | необязательный | Ограничивает тайм-аут для этого метода. Если тайм-аут истёк, будут возвращены кэшированные данные или локальный резервный вариант. Обратите внимание, что в редких случаях метод может завершиться с тайм-аутом чуть позже указанного в `loadTimeout`, поскольку операция может включать несколько запросов под капотом. Можно использовать функции-расширения вроде `5.seconds` из `kotlin.time.Duration.Companion`. | | **preloadProducts** | необязательный | Установите `true`, чтобы заранее загрузить продукты для повышения производительности. При включении продукты загружаются заблаговременно, сокращая время отображения флоу или пейвола. | | **productPurchaseParams** | необязательный | Карта [`AdaptyProductIdentifier`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-product-identifier/) к [`AdaptyPurchaseParameters`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-purchase-parameters/). Используйте её для настройки параметров покупки — например, персонализированных предложений или параметров обновления подписки для отдельных продуктов во флоу или пейволе. | :::note Если вы используете несколько языков, узнайте, как добавить [локализацию в Builder](add-paywall-locale-in-adapty-paywall-builder). ::: После загрузки [отобразите флоу или пейвол](kmp-present-paywalls). ## Получите флоу или пейвол для аудитории по умолчанию, чтобы ускорить загрузку \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} Обычно флоу и пейволы загружаются почти мгновенно, так что беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и плейсментов, а соединение у пользователей слабое, загрузка флоу или пейвола может занять дольше, чем хотелось бы. В таких случаях имеет смысл показывать флоу или пейвол для аудитории по умолчанию — чтобы пользователь видел что-то, а не пустой экран. Чтобы решить эту задачу, можно использовать метод `getFlowForDefaultAudience`, который получает флоу или пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать флоу или пейвол через метод `getFlow`, как описано в разделе [Получение флоу/пейвола](#fetch-flowpaywall) выше. :::warning Почему мы рекомендуем использовать `getFlow` Метод `getFlowForDefaultAudience` имеет ряд существенных недостатков: - **Потенциальные проблемы обратной совместимости**: если вам нужно показывать разные флоу для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо разрабатывать флоу, совместимые с текущей (устаревшей) версией, либо смириться с тем, что пользователи этой версии могут столкнуться с проблемами при отображении флоу. - **Потеря таргетинга**: все пользователи будут видеть одинаковый флоу, настроенный для аудитории **All Users**, — это означает потерю персонализированного таргетинга (в том числе по странам, маркетинговой атрибуции или вашим собственным пользовательским атрибутам). Если вы готовы принять эти ограничения ради более быстрой загрузки флоу или пейвола, используйте метод `getFlowForDefaultAudience` следующим образом. В противном случае используйте `getFlow`, описанный [выше](#fetch-flowpaywall). ::: ```kotlin showLineNumbers Adapty.getFlowForDefaultAudience( placementId = "YOUR_PLACEMENT_ID", fetchPolicy = AdaptyPaywallFetchPolicy.Default, ).onSuccess { flow -> // the requested flow }.onError { error -> // handle the error } ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указываете при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `AdaptyPaywallFetchPolicy.Default` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае неудачи. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если вы считаете, что у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`, чтобы возвращать кешированные данные при их наличии. В этом случае пользователи могут не получать самые свежие данные, зато время загрузки будет быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание, что кеш сохраняется после перезапуска приложения и очищается только при его переустановке или ручной очистке.</p> | ## Настройка ресурсов \{#customize-assets\} Чтобы настроить изображения и видео во флоу или пейволе, используйте кастомные ресурсы. Hero-изображения и видео имеют предопределённые идентификаторы: `hero_image` и `hero_video`. В бандле кастомных ресурсов вы обращаетесь к этим элементам по их идентификаторам и настраиваете их поведение. Для остальных изображений и видео нужно [задать кастомный идентификатор](custom-media) в дашборде Adapty. Например, можно: - Показывать разные изображения или видео для разных пользователей. - Показывать локальное превью-изображение, пока загружается основное удалённое изображение. - Показывать превью-изображение перед воспроизведением видео. Вот пример того, как можно передавать кастомные ресурсы через map: :::info SDK для Kotlin Multiplatform поддерживает только локальные ресурсы. Для удалённого контента необходимо скачать и закэшировать ресурсы локально, прежде чем использовать их в кастомных ресурсах. ::: ```kotlin showLineNumbers // Import generated Res class for accessing resources viewModelScope.launch { // Get URIs for bundled resources using Res.getUri() val heroImagePath = Res.getUri("files/images/hero_image.png") val demoVideoPath = Res.getUri("files/videos/demo_video.mp4") // Or read image as byte data val imageByteData = Res.readBytes("files/images/avatar.png") // Create custom assets map val customAssets: Map<String, AdaptyCustomAsset> = mapOf( // Load image from app resources (bundled with the app) // Files should be placed in commonMain/composeResources/files/ "hero_image" to AdaptyCustomAsset.localImageResource( path = heroImagePath ), // Or use image byte data "avatar" to AdaptyCustomAsset.localImageData( data = imageByteData ), // Load video from app resources "demo_video" to AdaptyCustomAsset.localVideoResource( path = demoVideoPath ), // Or use a video file from device storage "intro_video" to AdaptyCustomAsset.localVideoFile( path = "/path/to/local/video.mp4" ), // Apply custom brand colors "brand_primary" to AdaptyCustomAsset.color( colorHex = "#FF6B35" ), // Create gradient background "card_gradient" to AdaptyCustomAsset.linearGradient( colors = listOf("#1E3A8A", "#3B82F6", "#60A5FA"), stops = listOf(0.0f, 0.5f, 1.0f) ) ) // Use custom assets when creating the flow view AdaptyUI.createFlowView( flow = flow, customAssets = customAssets ).onSuccess { view -> // Present the flow with custom assets view.present() }.onError { error -> // Handle the error - the flow will fall back to default appearance } } ``` :::note Если ресурс не найден или не загружается, флоу или пейвол вернётся к внешнему виду по умолчанию, настроенному в Builder. ::: </SDKv4> <SDKv3> После того как вы [создали визуальную часть пейвола](adapty-paywall-builder) с помощью нового Paywall Builder в дашборде Adapty, его можно отобразить в мобильном приложении. Первый шаг — получить пейвол, привязанный к плейсменту, и конфигурацию его отображения, как описано ниже. Пожалуйста, обратите внимание, что этот раздел посвящён пейволам, созданным с помощью Paywall Builder. Если вы реализуете пейволы вручную, обратитесь к разделу [Получение пейволов и продуктов для пейволов на Remote Config в вашем мобильном приложении](fetch-paywalls-and-products-kmp). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Перед тем как начать отображать пейволы в вашем мобильном приложении (нажмите, чтобы раскрыть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них пейвол](create-placement) в дашборде Adapty. 4. Установите [SDK Adapty](sdk-installation-kotlin-multiplatform) в своём мобильном приложении. </details> ## Получение пейвола, созданного в Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\} Если вы [создали пейвол с помощью Paywall Builder](adapty-paywall-builder), вам не нужно беспокоиться о его отрисовке в коде мобильного приложения. Такой пейвол содержит как то, что должно отображаться, так и то, как именно это должно выглядеть. Тем не менее вам нужно получить его ID через плейсмент, конфигурацию его отображения, а затем показать пейвол в мобильном приложении. Для обеспечения оптимальной производительности важно получать пейвол и его [конфигурацию отображения](kmp-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) как можно раньше, чтобы у изображений было достаточно времени для загрузки перед показом пользователю. Для получения пейвола используйте метод `getPaywall`: ```kotlin showLineNumbers Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указываете при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-paywall-locale-in-adapty-paywall-builder). Ожидается языковой код, состоящий из одного или двух субтегов, разделённых дефисом (**-**). Первый субтег — язык, второй — регион.</p><p></p><p>Пример: `en` — английский, `pt-br` — бразильский португальский.</p><p>Подробнее о кодах локализации и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `AdaptyPaywallFetchPolicy.Default` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` — оно вернёт кешированные данные, если они есть. В этом случае пользователи могут не получить самые последние данные, зато загрузка будет быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в рамках сессии, чтобы сократить количество сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.</p><p></p><p>Adapty SDK хранит пейволы локально в двух слоях: описанный выше регулярно обновляемый кеш и [резервные пейволы](fallback-paywalls). Мы также используем CDN для ускоренной загрузки пейволов и отдельный резервный сервер на случай недоступности CDN. Эта система гарантирует, что вы всегда получаете актуальную версию пейволов, обеспечивая надёжность даже при слабом интернет-соединении.</p> | | **loadTimeout** | по умолчанию: 5 сек | <p>Это значение ограничивает таймаут для данного метода. При достижении таймаута будут возвращены кешированные данные или локальный резервный вариант.</p><p>Обратите внимание: в редких случаях метод может завершиться с небольшим опозданием относительно значения `loadTimeout`, поскольку операция может включать несколько запросов внутри.</p><p>Для Kotlin Multiplatform: `TimeInterval` можно создать с помощью функций-расширений (например, `5.seconds`, где `.seconds` — из `import com.adapty.utils.seconds`) или `TimeInterval.seconds(5)`. Чтобы не ограничивать время ожидания, используйте `TimeInterval.INFINITE`.</p> | Параметры ответа: | Параметр | Описание | | :-------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------| | Paywall | Объект [`AdaptyPaywall`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall/) со списком идентификаторов продуктов, идентификатором пейвола, Remote Config и рядом других свойств. | ## Получение конфигурации отображения пейвола, созданного в Paywall Builder \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important Убедитесь, что в Paywall Builder включён тумблер **Show on device**. Если эта опция отключена, конфигурация отображения будет недоступна для получения. ::: После получения пейвола проверьте, содержит ли он `ViewConfiguration` — это указывает на то, что он был создан с помощью Paywall Builder. Это поможет вам определить, как отображать пейвол. Если `ViewConfiguration` присутствует, обрабатывайте его как пейвол Paywall Builder; если нет, [обработайте его как пейвол с Remote Config](present-remote-config-paywalls-kmp). Используйте метод `createPaywallView`, чтобы загрузить конфигурацию отображения. ```kotlin showLineNumbers if (paywall.hasViewConfiguration) { AdaptyUI.createPaywallView( paywall = paywall, loadTimeout = 5.seconds, preloadProducts = true ).onSuccess { paywallView -> // use paywallView }.onError { error -> // handle the error } } else { // use your custom logic } ``` | Parameter | Presence | Description | | :--------------------------- | :------------- | :----------------------------------------------------------- | | **paywall** | required | Объект `AdaptyPaywall` для получения контроллера нужного пейвола. | | **loadTimeout** | optional | Ограничивает таймаут для этого метода. Если таймаут истекает, возвращаются кешированные данные или локальный резервный вариант. Обратите внимание, что в редких случаях метод может завершиться чуть позже указанного в `loadTimeout` значения, поскольку операция может состоять из нескольких запросов. Можно использовать функции-расширения, например `5.seconds` из `kotlin.time.Duration.Companion`. | | **preloadProducts** | optional | Установите `true`, чтобы заранее загрузить продукты для повышения производительности. При включении продукты загружаются заблаговременно, сокращая время отображения пейвола. | | **productPurchaseParams** | optional | Словарь, сопоставляющий [`AdaptyProductIdentifier`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-product-identifier/) с [`AdaptyPurchaseParameters`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-purchase-parameters/). Используйте его для настройки параметров покупки — например, персонализированных офферов или параметров обновления подписки для отдельных продуктов пейвола. | :::note Если вы используете несколько языков, узнайте, как добавить [локализацию Paywall Builder](add-paywall-locale-in-adapty-paywall-builder). ::: После загрузки [откройте пейвол](kmp-present-paywalls). ## Получите пейвол для аудитории по умолчанию, чтобы ускорить загрузку \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} Как правило, пейволы загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и пейволов, а у пользователей слабое интернет-соединение, загрузка пейвола может занять больше времени, чем хотелось бы. В таких случаях стоит показывать пейвол по умолчанию — это обеспечит плавный пользовательский опыт вместо полного отсутствия пейвола. Чтобы решить эту проблему, используйте метод `getPaywallForDefaultAudience`, который загружает пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — загружать пейвол с помощью метода `getPaywall`, как описано в разделе [Получение информации о пейволе](#fetch-paywall-designed-with-paywall-builder) выше. :::warning Почему мы рекомендуем использовать `getPaywall` Метод `getPaywallForDefaultAudience` имеет ряд существенных недостатков: - **Возможные проблемы с обратной совместимостью**: если вам нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать пейволы с поддержкой текущей (устаревшей) версии, либо смириться с тем, что пользователи этой версии могут столкнуться с проблемами — пейволы не будут отображаться. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, настроенный для аудитории **All Users**, — это означает отказ от персонализированного таргетинга (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вы готовы принять эти ограничения ради более быстрой загрузки пейвола, используйте метод `getPaywallForDefaultAudience`, как описано ниже. В противном случае используйте `getPaywall`, описанный [выше](#fetch-paywall-designed-with-paywall-builder). ::: ```kotlin showLineNumbers Adapty.getPaywallForDefaultAudience( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается, что этот параметр будет языковым кодом, состоящим из одного или нескольких подтегов, разделённых символом минус (**-**). Первый подтег обозначает язык, второй — регион.</p><p></p><p>Пример: `en` означает английский язык, `pt-br` — бразильский португальский.</p><p></p><p>Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `AdaptyPaywallFetchPolicy.Default` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают самые актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` — это позволит возвращать кешированные данные при их наличии. В таком случае пользователи могут получать не самые последние данные, зато загрузка будет быстрее независимо от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание, что кеш сохраняется при перезапуске приложения и очищается только при его переустановке или принудительной очистке вручную.</p> | ## Настройка ресурсов \{#customize-assets\} Чтобы настроить изображения и видео в пейволе, используйте пользовательские ресурсы. Главные изображения и видео имеют предопределённые идентификаторы: `hero_image` и `hero_video`. В пользовательском наборе ресурсов вы обращаетесь к этим элементам по их идентификаторам и настраиваете их поведение. Для других изображений и видео нужно [задать пользовательский идентификатор](custom-media) в дашборде Adapty. Например, вы можете: - Показывать разные изображения или видео разным пользователям. - Показывать локальное превью-изображение, пока загружается основное удалённое изображение. - Показывать превью-изображение перед воспроизведением видео. :::important Чтобы использовать эту функцию, обновите SDK до версии 3.7.0 или выше. ::: Вот пример того, как можно передать пользовательские ресурсы через map: :::info Kotlin Multiplatform SDK поддерживает только локальные ресурсы. Для удалённого контента необходимо заранее скачать и кэшировать ресурсы локально, прежде чем использовать их в качестве пользовательских ресурсов. ::: ```kotlin showLineNumbers // Import generated Res class for accessing resources viewModelScope.launch { // Get URIs for bundled resources using Res.getUri() val heroImagePath = Res.getUri("files/images/hero_image.png") val demoVideoPath = Res.getUri("files/videos/demo_video.mp4") // Or read image as byte data val imageByteData = Res.readBytes("files/images/avatar.png") // Create custom assets map val customAssets: Map<String, AdaptyCustomAsset> = mapOf( // Load image from app resources (bundled with the app) // Files should be placed in commonMain/composeResources/files/ "hero_image" to AdaptyCustomAsset.localImageResource( path = heroImagePath ), // Or use image byte data "avatar" to AdaptyCustomAsset.localImageData( data = imageByteData ), // Load video from app resources "demo_video" to AdaptyCustomAsset.localVideoResource( path = demoVideoPath ), // Or use a video file from device storage "intro_video" to AdaptyCustomAsset.localVideoFile( path = "/path/to/local/video.mp4" ), // Apply custom brand colors "brand_primary" to AdaptyCustomAsset.color( colorHex = "#FF6B35" ), // Create gradient background "card_gradient" to AdaptyCustomAsset.linearGradient( colors = listOf("#1E3A8A", "#3B82F6", "#60A5FA"), stops = listOf(0.0f, 0.5f, 1.0f) ) ) // Use custom assets when creating paywall view AdaptyUI.createPaywallView( paywall = paywall, customAssets = customAssets ).onSuccess { paywallView -> // Present the paywall with custom assets paywallView.present() }.onError { error -> // Handle the error - paywall will fall back to default appearance } } ``` :::note Если ресурс не найден или не загружается, пейвол вернётся к внешнему виду по умолчанию, настроенному в Paywall Builder. ::: </SDKv3> --- # File: kmp-present-paywalls --- --- title: "Отображение флоу и пейволов - Kotlin Multiplatform" description: "Показывайте флоу и пейволы пользователям в вашем приложении Kotlin Multiplatform." --- <SDKv4> <MethodPromo method="getFlow" label="Display flows and paywalls" /> Если вы создали флоу или пейвол, вам не нужно беспокоиться об их отрисовке в коде мобильного приложения для отображения пользователю. Такой флоу или пейвол уже содержит всё необходимое: что именно показывать и как это показывать. :::warning Этот гайд охватывает флоу и **пейволы нового Paywall Builder**, которые отрисовывает Adapty. Для Remote Config пейволов и режима [Observer mode](observer-vs-full-mode) процесс отличается. - Для отображения **пейволов с Remote Config** см. [Отображение пейволов на основе Remote Config](present-remote-config-paywalls-kmp). - Для отображения флоу в **режиме Observer**, см. [Отображение флоу в режиме Observer](kmp-present-flows-in-observer-mode). ::: Чтобы получить объект `flow`, используемый далее, см. [Получение флоу и пейволов](kmp-get-pb-paywalls). Adapty Kotlin Multiplatform SDK предоставляет два способа отображения флоу и пейволов: - **С Compose Multiplatform** - **Без Compose Multiplatform** ## С Compose Multiplatform \{#with-compose-multiplatform\} Чтобы отобразить флоу или пейвол, используйте метод `view.present()` на объекте `view`, созданном методом [`createFlowView`](kmp-get-pb-paywalls#fetch-the-view-configuration). Каждый `view` можно использовать только один раз. Если нужно отобразить флоу повторно, вызовите `createFlowView` ещё раз, чтобы создать новый экземпляр `view`. :::warning Повторное использование одного и того же `view` без его пересоздания может привести к ошибке. ::: ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { AdaptyUI.createFlowView(flow = flow).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` ### Показ диалога \{#show-dialog\} Используйте этот метод вместо нативных диалоговых окон alert, когда флоу или пейвол отображается на Android. На Android обычные alert появляются позади слоя флоу, что делает их невидимыми для пользователей. Этот метод обеспечивает корректное отображение диалога поверх флоу на всех платформах. ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { view.showDialog( title = "Close this screen?", content = "You will lose access to exclusive offers.", primaryActionTitle = "Stay", secondaryActionTitle = "Close" ).onSuccess { action -> if (action == AdaptyUIDialogActionType.SECONDARY) { // User confirmed - close the flow view.dismiss() } // If primary - do nothing, user stays }.onError { error -> // handle the error } } ``` ### Настройка стиля отображения на iOS \{#configure-ios-presentation-style\} Настройте способ отображения флоу или пейвола на iOS, передав параметр `iosPresentationStyle` в метод `present()`. Параметр принимает значения `AdaptyUIIOSPresentationStyle.FULLSCREEN` (по умолчанию) или `AdaptyUIIOSPresentationStyle.PAGESHEET`. ```kotlin showLineNumbers viewModelScope.launch { val view = AdaptyUI.createFlowView(flow = flow).getOrNull() view?.present(iosPresentationStyle = AdaptyUIIOSPresentationStyle.PAGESHEET) } ``` ## Без Compose Multiplatform \{#without-compose-multiplatform\} :::note `createNativeFlowView` является частью основного модуля `io.adapty:adapty-kmp`. Если ваш проект не использует Compose Multiplatform, зависимость `io.adapty:adapty-kmp-ui` вам не нужна. ::: Чтобы встроить флоу или пейвол без Compose Multiplatform, вызовите `createNativeFlowView`. Метод возвращает `AdaptyNativeFlowView`, который нужно добавить в ваш лейаут: <Tabs> <TabItem value="android" label="Android"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val nativeView = AdaptyUI.createNativeFlowView( context = context, viewModelStoreOwner = activity, flow = flow, observer = myFlowObserver, ) // Embed in your Compose layout: AndroidView( factory = { nativeView.view }, modifier = Modifier.fillMaxSize() ) ``` По умолчанию встроенное представление не применяет отступы безопасной зоны — ваш макет должен самостоятельно обрабатывать вставки. Если вы хотите, чтобы представление применяло их само, передайте `androidEnableSafeArea = true` в `createNativeFlowView`. Этот параметр доступен только для Android. </TabItem> <TabItem value="ios" label="iOS"> Поскольку методы по умолчанию интерфейса KMP становятся `@required` в Swift, вы не можете реализовать `AdaptyUIFlowsEventsObserver` напрямую из Swift. Сначала объявите открытый базовый класс в `iosMain`: ```kotlin showLineNumbers title="iosMain (Kotlin)" open class BaseFlowObserver : AdaptyUIFlowsEventsObserver ``` Затем создайте подкласс в Swift, переопределив только то, что нужно: ```swift showLineNumbers title="Swift" class MyFlowObserver: BaseFlowObserver { override func flowViewDidPerformAction(view: AdaptyUIFlowView, action: any AdaptyUIAction) { if action is AdaptyUIActionCloseAction { // remove nativeView from your view hierarchy } } } let nativeView = AdaptyUI.shared.createNativeFlowView( flow: flow, observer: MyFlowObserver() ) // nativeView.viewController is a UIViewController. // Add it to your SwiftUI view or UIKit hierarchy. ``` </TabItem> </Tabs> ### Освобождение ресурсов представления \{#dispose-the-view\} Вызовите `dispose()` при удалении представления из макета. Это снимает регистрацию слушателя событий и освобождает внутренние ресурсы. ```kotlin showLineNumbers title="Kotlin Multiplatform" nativeView.dispose() ``` ## Кастомные теги \{#custom-tags\} Кастомные теги позволяют не создавать отдельные флоу или пейволы для разных сценариев. Представьте один флоу, который динамически адаптируется под данные пользователя. Например, вместо безликого «Привет!» можно приветствовать пользователей лично: «Привет, Иван!» или «Привет, Анна!» Вот несколько примеров использования кастомных тегов: - Отображать имя или email пользователя на флоу или пейволе. - Показывать текущий день недели для стимулирования продаж (например, «С четвергом!»). - Добавлять персонализированные детали о продаваемых продуктах (например, название фитнес-программы или номер телефона в VoIP-приложении). Пользовательские теги помогают создать гибкий флоу, который адаптируется к различным ситуациям, делая интерфейс вашего приложения более персонализированным и привлекательным. :::warning В некоторых случаях приложение может не знать, чем заменить пользовательский тег — особенно если пользователи работают на старой версии AdaptyUI SDK. Чтобы избежать этого, всегда добавляйте резервный текст, который будет подставляться вместо строк с неизвестными тегами. Без этого пользователи могут видеть теги в виде кода (`<USERNAME/>`). ::: Чтобы использовать пользовательские теги в вашем флоу или пейволе, передайте их при создании представления флоу: <Tabs> <TabItem value="standalone" label="With Compose Multiplatform" default> ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { val customTags = mapOf( "USERNAME" to "John", "DAY_OF_WEEK" to "Thursday" ) AdaptyUI.createFlowView( flow = flow, customTags = customTags ).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` </TabItem> <TabItem value="native" label="Without Compose Multiplatform"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val customTags = mapOf( "USERNAME" to "John", "DAY_OF_WEEK" to "Thursday" ) val nativeView = AdaptyUI.createNativeFlowView( context = context, viewModelStoreOwner = activity, flow = flow, observer = myFlowObserver, customTags = customTags, ) ``` ```kotlin showLineNumbers title="Kotlin Multiplatform (iOS)" val customTags = mapOf( "USERNAME" to "John", "DAY_OF_WEEK" to "Thursday" ) val nativeView = AdaptyUI.createNativeFlowView( flow = flow, observer = myFlowObserver, customTags = customTags, ) ``` </TabItem> </Tabs> ## Пользовательские таймеры \{#custom-timers\} Таймер — отличный инструмент для продвижения специальных и сезонных предложений с ограниченным сроком действия. Важно понимать, что этот таймер не связан со сроком действия предложения или продолжительностью кампании. Это просто самостоятельный обратный отсчёт, который стартует с заданного вами значения и уменьшается до нуля. Когда таймер достигает нуля, ничего не происходит — он просто остаётся на нуле. Вы можете настроить текст до и после таймера, чтобы сформировать нужное сообщение, например: «Предложение заканчивается через: 10:00 сек.» Чтобы использовать пользовательские таймеры в вашем флоу или пейволе, передайте их при создании представления флоу: <Tabs> <TabItem value="standalone" label="С Compose Multiplatform" default> ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { val customTimers = mapOf( "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0), "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59) ) AdaptyUI.createFlowView( flow = flow, customTimers = customTimers ).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` </TabItem> <TabItem value="native" label="Без Compose Multiplatform"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val customTimers = mapOf( "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0), "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59) ) val nativeView = AdaptyUI.createNativeFlowView( context = context, viewModelStoreOwner = activity, flow = flow, observer = myFlowObserver, customTimers = customTimers, ) ``` ```kotlin showLineNumbers title="Kotlin Multiplatform (iOS)" val customTimers = mapOf( "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0), "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59) ) val nativeView = AdaptyUI.createNativeFlowView( flow = flow, observer = myFlowObserver, customTimers = customTimers, ) ``` </TabItem> </Tabs> </SDKv4> <SDKv3> Если вы настроили пейвол с помощью Paywall Builder, вам не нужно беспокоиться о его рендеринге в коде мобильного приложения для отображения пользователю. Такой пейвол содержит как то, что должно быть показано, так и то, как это должно быть показано. :::warning Этот гайд предназначен только для пейволов, созданных с помощью **нового Paywall Builder**. Процесс отображения пейволов отличается для пейволов на основе Remote Config и [режима Observer](observer-vs-full-mode). Для отображения **пейволов на основе Remote Config** см. [Рендеринг пейвола, созданного с помощью Remote Config](present-remote-config-paywalls-kmp). ::: SDK Adapty Kotlin Multiplatform предоставляет два способа отображения пейволов: - **С Compose Multiplatform** - **Без Compose Multiplatform** ## С Compose Multiplatform \{#with-compose-multiplatform\} Чтобы отобразить пейвол, вызовите метод `view.present()` на объекте `view`, созданном методом [`createPaywallView`](kmp-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). Каждый объект `view` можно использовать только один раз. Если нужно показать пейвол повторно, вызовите `createPaywallView` ещё раз, чтобы создать новый экземпляр `view`. :::warning Повторное использование одного и того же `view` без его пересоздания может привести к ошибке. ::: ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { AdaptyUI.createPaywallView(paywall = paywall).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` ### Показ диалогов \{#show-dialog\} Используйте этот метод вместо нативных диалогов alert, когда на Android отображается пейвол. На Android стандартные алерты появляются позади пейвола и становятся невидимы для пользователей. Этот метод гарантирует корректное отображение диалога поверх пейвола на всех платформах. ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { view.showDialog( title = "Close paywall?", content = "You will lose access to exclusive offers.", primaryActionTitle = "Stay", secondaryActionTitle = "Close" ).onSuccess { action -> if (action == AdaptyUIDialogActionType.SECONDARY) { // User confirmed - close the paywall view.dismiss() } // If primary - do nothing, user stays }.onError { error -> // handle the error } } ``` ### Настройка стиля отображения на iOS \{#configure-ios-presentation-style\} Настройте способ отображения пейвола на iOS, передав параметр `iosPresentationStyle` в метод `present()`. Параметр принимает значения `AdaptyUIIOSPresentationStyle.FULLSCREEN` (по умолчанию) или `AdaptyUIIOSPresentationStyle.PAGESHEET`. ```kotlin showLineNumbers viewModelScope.launch { val view = AdaptyUI.createPaywallView(paywall = paywall).getOrNull() view?.present(iosPresentationStyle = AdaptyUIIOSPresentationStyle.PAGESHEET) } ``` ## Без Compose Multiplatform \{#without-compose-multiplatform\} :::note `createNativePaywallView` входит в основной модуль `io.adapty:adapty-kmp`. Если ваш проект не использует Compose Multiplatform, зависимость `io.adapty:adapty-kmp-ui` не нужна. ::: Чтобы встроить пейвол без Compose Multiplatform, вызовите `createNativePaywallView`. Метод возвращает `AdaptyNativePaywallView`, который добавляется в ваш layout: <Tabs> <TabItem value="android" label="Android"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val nativeView = AdaptyUI.createNativePaywallView( context = context, viewModelStoreOwner = activity, paywall = paywall, observer = myPaywallObserver, ) // Embed in your Compose layout: AndroidView( factory = { nativeView.view }, modifier = Modifier.fillMaxSize() ) ``` </TabItem> <TabItem value="ios" label="iOS"> Поскольку методы по умолчанию KMP-интерфейсов становятся `@required` в Swift, вы не можете реализовать `AdaptyUIPaywallsEventsObserver` напрямую из Swift. Сначала объявите открытый базовый класс в `iosMain`: ```kotlin showLineNumbers title="iosMain (Kotlin)" open class BasePaywallObserver : AdaptyUIPaywallsEventsObserver ``` Затем создайте подкласс в Swift, переопределяя только нужное: ```swift showLineNumbers title="Swift" class MyPaywallObserver: BasePaywallObserver { override func paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: any AdaptyUIAction) { if action is AdaptyUIActionCloseAction { // remove nativeView from your view hierarchy } } } let nativeView = AdaptyUI.shared.createNativePaywallView( paywall: paywall, observer: MyPaywallObserver() ) // nativeView.viewController is a UIViewController. // Add it to your SwiftUI view or UIKit hierarchy. ``` </TabItem> </Tabs> ### Освобождение ресурсов \{#dispose-the-view\} Вызовите `dispose()` при удалении представления из макета. Это отменяет регистрацию слушателя событий и освобождает внутренние ресурсы. ```kotlin showLineNumbers title="Kotlin Multiplatform" nativeView.dispose() ``` ## Пользовательские теги \{#custom-tags\} Пользовательские теги позволяют не создавать отдельные пейволы для разных сценариев. Представьте один пейвол, который динамически подстраивается под данные пользователя. Например, вместо безликого «Привет!» можно приветствовать пользователей лично: «Привет, Иван!» или «Привет, Анна!» Вот несколько способов применения пользовательских тегов: - Отображать имя или email пользователя на пейволе. - Показывать текущий день недели для повышения продаж (например, «Хорошего четверга»). - Добавлять персонализированные сведения о продаваемых продуктах (например, название фитнес-программы или номер телефона в VoIP-приложении). Пользовательские теги помогают создать гибкий пейвол, который адаптируется к различным ситуациям, делая интерфейс вашего приложения более персонализированным и привлекательным. :::warning В некоторых случаях приложение может не знать, чем заменить пользовательский тег — особенно если пользователи работают со старой версией AdaptyUI SDK. Чтобы избежать этого, всегда добавляйте резервный текст, который будет заменять строки с неизвестными пользовательскими тегами. Без него пользователи могут увидеть теги в виде кода (`<USERNAME/>`). ::: Чтобы использовать пользовательские теги в пейволе, передайте их при создании представления пейвола: <Tabs> <TabItem value="standalone" label="With Compose Multiplatform" default> ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { val customTags = mapOf( "USERNAME" to "John", "DAY_OF_WEEK" to "Thursday" ) AdaptyUI.createPaywallView( paywall = paywall, customTags = customTags ).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` </TabItem> <TabItem value="native" label="Without Compose Multiplatform"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val customTags = mapOf( "USERNAME" to "John", "DAY_OF_WEEK" to "Thursday" ) val nativeView = AdaptyUI.createNativePaywallView( context = context, viewModelStoreOwner = activity, paywall = paywall, observer = myPaywallObserver, customTags = customTags, ) ``` ```kotlin showLineNumbers title="Kotlin Multiplatform (iOS)" val customTags = mapOf( "USERNAME" to "John", "DAY_OF_WEEK" to "Thursday" ) val nativeView = AdaptyUI.createNativePaywallView( paywall = paywall, observer = myPaywallObserver, customTags = customTags, ) ``` </TabItem> </Tabs> ## Пользовательские таймеры \{#custom-timers\} Таймер на пейволе — отличный инструмент для продвижения специальных и сезонных предложений с ограниченным сроком действия. Важно учитывать, что этот таймер не связан ни со сроком действия предложения, ни с продолжительностью кампании. Это просто автономный обратный отсчёт, который начинается с заданного вами значения и уменьшается до нуля. Когда таймер достигает нуля, ничего не происходит — он просто остаётся на нуле. Вы можете настроить текст до и после таймера, чтобы сформировать нужное сообщение, например: «Предложение заканчивается через: 10:00 сек.» Чтобы использовать пользовательские таймеры на пейволе, передайте их при создании представления пейвола: <Tabs> <TabItem value="standalone" label="With Compose Multiplatform" default> ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { val customTimers = mapOf( "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0), "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59) ) AdaptyUI.createPaywallView( paywall = paywall, customTimers = customTimers ).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` </TabItem> <TabItem value="native" label="Without Compose Multiplatform"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val customTimers = mapOf( "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0), "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59) ) val nativeView = AdaptyUI.createNativePaywallView( context = context, viewModelStoreOwner = activity, paywall = paywall, observer = myPaywallObserver, customTimers = customTimers, ) ``` ```kotlin showLineNumbers title="Kotlin Multiplatform (iOS)" val customTimers = mapOf( "CUSTOM_TIMER_NY" to LocalDateTime(2025, 1, 1, 0, 0, 0), "CUSTOM_TIMER_SALE" to LocalDateTime(2024, 12, 31, 23, 59, 59) ) val nativeView = AdaptyUI.createNativePaywallView( paywall = paywall, observer = myPaywallObserver, customTimers = customTimers, ) ``` </TabItem> </Tabs> </SDKv3> --- # File: kmp-handle-paywall-actions --- --- title: "Реагирование на действия флоу - Kotlin Multiplatform" description: "Обрабатывайте действия кнопок из флоу и пейволов в вашем приложении Kotlin Multiplatform." --- <SDKv4> Если вы создаёте флоу или пейволы с помощью Adapty Flow Builder или Paywall Builder, важно правильно настроить кнопки: 1. Добавьте [кнопку в Builder](paywall-buttons) и назначьте ей существующее действие или создайте собственный ID действия. 2. Напишите код в приложении для обработки каждого назначенного действия. В этом гайде показано, как обрабатывать пользовательские и встроенные действия в коде. :::warning **Покупки, восстановление покупок, закрытие флоу/пейвола и открытие ссылок обрабатываются автоматически.** Все остальные действия кнопок, например пользовательские действия, требуют явной реализации в коде приложения. ::: ## Настройка AdaptyUIFlowsEventsObserver \{#set-up-the-adaptyuiflowseventsobs-erver\} Чтобы обрабатывать действия флоу, нужно реализовать интерфейс `AdaptyUIFlowsEventsObserver` и зарегистрировать его через `AdaptyUI.setFlowsEventsObserver()`. Это следует сделать как можно раньше в жизненном цикле приложения — как правило, в основной активности или при инициализации приложения. ```kotlin // In your app initialization AdaptyUI.setFlowsEventsObserver(MyAdaptyUIFlowsEventsObserver()) ``` Все действия кнопок поступают в колбэк `flowViewDidPerformAction(view, action)` в виде sealed-класса `AdaptyUIAction`: `CloseAction`, `AndroidSystemBackAction`, `OpenUrlAction` или `CustomAction`. :::warning Переопределение `flowViewDidPerformAction` заменяет обработку по умолчанию для **всех** действий, а не только для того, которое вас интересует. Оставляйте ветки по умолчанию для `CloseAction` (закрыть флоу) и `OpenUrlAction` (открыть URL), если вы не планируете их менять, — как показано в примерах ниже. ::: ## Закрытие флоу и пейволов \{#close-flows-and-paywalls\} Чтобы добавить кнопку закрытия флоу или пейвола: 1. В конструкторе добавьте кнопку и назначьте ей действие **Close**. 2. В коде приложения реализуйте обработчик действия `close`, который скрывает флоу. :::info В Kotlin Multiplatform SDK действие `CloseAction` по умолчанию закрывает флоу или пейвол. Однако при необходимости это поведение можно переопределить в коде — например, закрытие одного флоу может вызывать открытие другого. ::: ```kotlin class MyAdaptyUIFlowsEventsObserver : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CloseAction -> mainUiScope.launch { view.dismiss() } // default behavior is AdaptyUIAction.OpenUrlAction -> AdaptyUI.openWebUrl(action.url, action.openIn) // default behavior else -> Unit } } } // Set up the observer AdaptyUI.setFlowsEventsObserver(MyAdaptyUIFlowsEventsObserver()) ``` Если вы используете [`createNativeFlowView`](kmp-present-paywalls#without-compose-multiplatform), вызов `view.dismiss()` не даст никакого эффекта — представление встроено в ваш макет, а не отображается через стек KMP. Удалите представление из макета и вызовите вместо этого `dispose()`. ## Обработка системной кнопки «Назад» на Android \{#handle-the-android-system-back-button\} Нажатие системной кнопки «Назад» на Android (или соответствующий жест) генерирует `AdaptyUIAction.AndroidSystemBackAction`. По умолчанию это действие игнорируется — флоу остаётся открытым, и пользователь выходит из него через путь, который вы определяете, например кнопку **Close** или действие `on_device_back` в билдере. Если вы хотите, чтобы системная кнопка «Назад» закрывала флоу, обработайте это действие самостоятельно: ```kotlin class MyAdaptyUIFlowsEventsObserver : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CloseAction -> mainUiScope.launch { view.dismiss() } // default behavior is AdaptyUIAction.AndroidSystemBackAction -> mainUiScope.launch { view.dismiss() } // not handled by default is AdaptyUIAction.OpenUrlAction -> AdaptyUI.openWebUrl(action.url, action.openIn) // default behavior else -> Unit } } } ``` ## Открытие URL из флоу и пейволов \{#open-urls-from-flows-and-paywalls\} :::tip Если нужно добавить группу ссылок (например, условия использования и восстановление покупок), добавьте элемент **Link** в билдере и обработайте его так же, как кнопки с действием **Open URL**. ::: Чтобы добавить кнопку, открывающую ссылку из флоу или пейвола (например, **Terms of use** или **Privacy policy**), в билдере добавьте кнопку, назначьте ей действие **Open URL** и введите нужный URL. По умолчанию SDK открывает полученный URL нативно — во внешнем или встроенном браузере, в зависимости от `action.openIn` — так что никакого кода писать не нужно. Переопределите обработчик только если требуется кастомная логика, например показ диалога подтверждения перед открытием: ```kotlin class MyAdaptyUIFlowsEventsObserver( private val uriHandler: UriHandler ) : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.OpenUrlAction -> { // Show confirmation dialog before opening URL mainUiScope.launch { val selectedAction = view.showDialog( title = "Open URL?", content = action.url, primaryActionTitle = "Cancel", secondaryActionTitle = "Open" ).getOrNull() when (selectedAction) { AdaptyUIDialogActionType.PRIMARY -> { // User cancelled } AdaptyUIDialogActionType.SECONDARY -> { // User confirmed - open URL uriHandler.openUri(action.url) } else -> Unit } } } else -> Unit } } } // Set up the observer with UriHandler AdaptyUI.setFlowsEventsObserver(MyAdaptyUIFlowsEventsObserver(uriHandler)) ``` ## Войдите в приложение \{#log-into-the-app\} Чтобы добавить кнопку входа в приложение: 1. В конструкторе добавьте кнопку и назначьте ей действие **Custom** с ID «login». 2. В коде приложения реализуйте обработчик этого пользовательского действия, который идентифицирует пользователя. ```kotlin class MyAdaptyUIFlowsEventsObserver : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CustomAction -> { if (action.action == "login") { // Handle login action - navigate to login screen // This depends on your app's navigation system // For example, in Compose Multiplatform: // navController.navigate("login") } } else -> Unit } } } ``` ## Обработка пользовательских действий \{#handle-custom-actions\} Чтобы добавить кнопку для обработки любых других действий: 1. В конструкторе добавьте кнопку, назначьте ей действие **Custom** и задайте ей ID. 2. В коде приложения реализуйте обработчик для созданного вами ID действия. Например, если у вас есть другой набор предложений подписки или разовых покупок, вы можете добавить кнопку, которая будет отображать другой флоу или пейвол: ```kotlin class MyAdaptyUIFlowsEventsObserver : AdaptyUIFlowsEventsObserver { override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CustomAction -> { when (action.action) { "openNewFlow" -> { // Display another flow or paywall } } } else -> Unit } } } // Set up the observer AdaptyUI.setFlowsEventsObserver(MyAdaptyUIFlowsEventsObserver()) ``` </SDKv4> <SDKv3> :::warning **Только покупки и восстановления обрабатываются автоматически.** Все остальные действия кнопок, такие как закрытие пейволов или открытие ссылок, требуют реализации соответствующих обработчиков в коде приложения. ::: Если вы создаёте пейволы с помощью Paywall Builder в Adapty, важно правильно настроить кнопки: 1. Добавьте [кнопку в Paywall Builder](paywall-buttons) и назначьте ей готовое действие или создайте собственный ID действия. 2. Напишите код в своём приложении для обработки каждого назначенного действия. Этот гайд описывает, как обрабатывать пользовательские и встроенные действия в вашем коде. ## Настройка AdaptyUIPaywallsEventsObserver \{#set-up-the-adaptyuipaywallsevents-observer\} Чтобы обрабатывать действия на пейволе, нужно реализовать интерфейс `AdaptyUIPaywallsEventsObserver` и зарегистрировать его через `AdaptyUI.setPaywallsEventsObserver()`. Это следует делать как можно раньше в жизненном цикле приложения — обычно в главной активности или при инициализации приложения. ```kotlin // In your app initialization AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver()) ``` ## Закрытие пейволов \{#close-paywalls\} Чтобы добавить кнопку закрытия пейвола: 1. В Paywall Builder добавьте кнопку и назначьте ей действие **Close**. 2. В коде приложения реализуйте обработчик действия `close`, который закрывает пейвол. :::info В SDK для Kotlin Multiplatform `CloseAction` и `AndroidSystemBackAction` по умолчанию вызывают закрытие пейвола. При необходимости это поведение можно переопределить в коде. Например, закрытие одного пейвола может инициировать открытие другого. ::: ```kotlin class MyAdaptyUIPaywallsEventsObserver : AdaptyUIPaywallsEventsObserver { override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) { when (action) { AdaptyUIAction.CloseAction, AdaptyUIAction.AndroidSystemBackAction -> view.dismiss() } } } // Set up the observer AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver()) ``` Если вы используете [`createNativePaywallView`](kmp-present-paywalls#without-compose-multiplatform), вызов `view.dismiss()` не даст никакого эффекта — представление встроено в ваш макет, а не показано через стек KMP. Удалите представление из макета и вызовите на нём `dispose()`. ## Открытие URL с пейволов \{#open-urls-from-paywalls\} :::tip Если вы хотите добавить группу ссылок (например, пользовательское соглашение и восстановление покупок), добавьте элемент **Link** в Paywall Builder и обработайте его так же, как кнопки с действием **Open URL**. ::: Чтобы добавить кнопку, открывающую ссылку с вашего пейвола (например, **Terms of use** или **Privacy policy**): 1. В Paywall Builder добавьте кнопку, назначьте ей действие **Open URL** и введите URL, который нужно открыть. 2. В коде приложения реализуйте обработчик действия `openUrl`, который открывает полученный URL в браузере. :::info В Kotlin Multiplatform SDK `OpenUrlAction` предоставляет URL, который нужно открыть. Вы можете реализовать собственную логику обработки открытия URL — например, показать диалог подтверждения или использовать предпочтительный способ обработки URL в вашем приложении. ::: ```kotlin class MyAdaptyUIPaywallsEventsObserver( private val uriHandler: UriHandler ) : AdaptyUIPaywallsEventsObserver { override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.OpenUrlAction -> { // Show confirmation dialog before opening URL mainUiScope.launch { val selectedAction = view.showDialog( title = "Open URL?", content = action.url, primaryActionTitle = "Cancel", secondaryActionTitle = "Open" ).getOrNull() when (selectedAction) { AdaptyUIDialogActionType.PRIMARY -> { // User cancelled } AdaptyUIDialogActionType.SECONDARY -> { // User confirmed - open URL uriHandler.openUri(action.url) } else -> Unit } } } } } } // Set up the observer with UriHandler AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver(uriHandler)) ``` ## Войдите в приложение \{#log-into-the-app\} Чтобы добавить кнопку для входа пользователей в ваше приложение: 1. В Paywall Builder добавьте кнопку и назначьте ей действие **Custom** с ID "login". 2. В коде вашего приложения реализуйте обработчик этого действия, который идентифицирует пользователя. ```kotlin class MyAdaptyUIObserver : AdaptyUIObserver { override fun paywallViewDidPerformAction(view: AdaptyUIView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CustomAction -> { if (action.action == "login") { // Handle login action - navigate to login screen // This depends on your app's navigation system // For example, in Compose Multiplatform: // navController.navigate("login") } } } } } ``` ## Обработка пользовательских действий \{#handle-custom-actions\} Чтобы добавить кнопку, выполняющую произвольное действие: 1. В Paywall Builder добавьте кнопку, назначьте ей действие **Custom** и укажите идентификатор. 2. В коде приложения реализуйте обработчик для созданного идентификатора действия. Например, если у вас есть другой набор предложений по подписке или разовых покупок, можно добавить кнопку, которая откроет другой пейвол: ```kotlin class MyAdaptyUIPaywallsEventsObserver : AdaptyUIPaywallsEventsObserver { override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CustomAction -> { when (action.action) { "login" -> { // Handle login action - navigate to login screen // This depends on your app's navigation system // For example, in Compose Multiplatform: // navController.navigate("login") } } } } } } // Set up the observer AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver()) ``` </SDKv3> --- # File: kmp-handling-events --- --- title: "Обработка событий флоу и пейвола - Kotlin Multiplatform" description: "Обрабатывайте события флоу и пейвола в приложении на Kotlin Multiplatform." --- <SDKv4> :::important Этот гайд охватывает обработку событий покупок, восстановлений, выбора продуктов и рендеринга флоу. Вам также необходимо реализовать обработку кнопок (закрытие флоу, открытие ссылок и т.д.). Подробнее смотрите в нашем [гайде по обработке действий флоу](kmp-handle-paywall-actions). ::: Флоу и пейволы, настроенные с помощью [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder), не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют события, на которые ваше приложение может реагировать. К таким событиям относятся нажатия кнопок (кнопки закрытия, URL-адреса, выбор продуктов и т. д.), а также уведомления о действиях, связанных с покупками. Узнайте, как реагировать на эти события, ниже. Чтобы управлять процессами на экране флоу или отслеживать их в своём мобильном приложении, реализуйте методы интерфейса `AdaptyUIFlowsEventsObserver` и зарегистрируйте наблюдатель через `AdaptyUI.setFlowsEventsObserver()`. Некоторые методы имеют реализации по умолчанию, которые автоматически обрабатывают типичные сценарии, — переопределяйте только те из них, которые нужно изменить: ```kotlin showLineNumbers title="Kotlin" AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver { // override only the methods you want to change }) ``` :::note Здесь вы добавляете собственную логику для обработки событий флоу. Используйте `view.dismiss()`, чтобы закрыть флоу, или реализуйте любое другое нужное поведение. Обратите внимание: `dismiss()` — это suspend-функция, поэтому внутри колбэка запускайте её через `mainUiScope` обозревателя: `mainUiScope.launch { view.dismiss() }`. ::: ### События, сгенерированные пользователем #### Появление и исчезновение флоу Когда флоу появляется или исчезает, будут вызваны следующие методы: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidAppear(view: AdaptyUIFlowView) { // Handle flow appearance // You can track analytics or update UI here } override fun flowViewDidDisappear(view: AdaptyUIFlowView) { // Handle flow disappearance // You can track analytics or update UI here } ``` :::note - На iOS `flowViewDidAppear` также вызывается, когда пользователь нажимает [кнопку веб-пейвола](web-paywall#step-2a-add-a-web-purchase-button) внутри флоу, и веб-пейвол открывается во встроенном браузере. - На iOS `flowViewDidDisappear` также вызывается, когда [веб-пейвол](web-paywall#step-2a-add-a-web-purchase-button), открытый из флоу во встроенном браузере, исчезает с экрана. ::: <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```javascript // Flow appeared { // No additional data } // Flow disappeared { // No additional data } ``` </Details> #### Выбор продукта \{#product-selection\} If a user selects a product for purchase, this method will be invoked: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidSelectProduct(view: AdaptyUIFlowView, productId: String) { // Handle product selection // You can update UI or track analytics here } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "productId": "premium_monthly" } ``` </Details> #### Покупка начата \{#started-purchase\} If a user initiates the purchase process, this method will be invoked: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidStartPurchase(view: AdaptyUIFlowView, product: AdaptyPaywallProduct) { // Handle purchase start // You can show loading indicators or track analytics here } ``` :::note В [режиме Observer](kmp-present-flows-in-observer-mode), покупки, инициированные из флоу, передаются в ваш `AdaptyUIObserverModeResolver`. ::: <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ``` </Details> #### Успешная, отменённая или ожидающая покупка \{#successful-canceled-or-pending-purchase\} Этот метод вызывается после завершения покупки. По умолчанию он ничего не делает — флоу остаётся открытым после покупки, пока вы его явно не закроете, поэтому вызывайте `view.dismiss()` самостоятельно, как только пользователь получает доступ: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidFinishPurchase( view: AdaptyUIFlowView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { when (purchaseResult) { is AdaptyPurchaseResult.Success -> { // Check if user has access to premium features if (purchaseResult.profile.accessLevels["premium"]?.isActive == true) { mainUiScope.launch { view.dismiss() } } } AdaptyPurchaseResult.Pending -> { // Handle pending purchase (e.g., user will pay offline with cash) } AdaptyPurchaseResult.UserCanceled -> { // Handle user cancellation } } } ``` <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```javascript // Successful purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "Success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } } } // Pending purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "Pending" } } // User canceled purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "UserCanceled" } } ``` </Details> Рекомендуем закрывать экран флоу при успешной покупке. #### Неудачная покупка \{#failed-purchase\} Этот метод вызывается, если покупка завершается с ошибкой. Сюда входят ошибки StoreKit/Google Play Billing (ограничения платежей, недействительные продукты, сетевые сбои), ошибки верификации транзакций и системные ошибки. Обратите внимание: отмена покупки пользователем вызывает `flowViewDidFinishPurchase` с результатом отмены, а ожидающие платежи этот метод не вызывают. ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidFailPurchase( view: AdaptyUIFlowView, product: AdaptyPaywallProduct, error: AdaptyError ) { // Add your purchase failure handling logic here // For example: show error message, retry option, or custom error handling } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } } } ``` </Details> #### Восстановление начато \{#started-restore\} Если пользователь инициирует процесс восстановления покупок, будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidStartRestore(view: AdaptyUIFlowView) { // Handle restore start // You can show loading indicators or track analytics here } ``` #### Успешное восстановление \{#successful-restore\} Если восстановление покупки прошло успешно, будет вызван этот метод. По умолчанию он ничего не делает — флоу остаётся открытым после восстановления, пока вы его не закроете: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidFinishRestore(view: AdaptyUIFlowView, profile: AdaptyProfile) { // Add your successful restore handling logic here // For example: show success message, update UI, or dismiss the flow // Check if user has access to premium features if (profile.accessLevels["premium"]?.isActive == true) { mainUiScope.launch { view.dismiss() } } } ``` <Details> <summary>Пример события (нажмите, чтобы раскрыть)</summary> ```javascript { "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } ``` </Details> Рекомендуем закрывать экран, если у пользователя есть нужный `accessLevel`. О том, как это проверить, читайте в разделе [Статус подписки](subscription-status). #### Неудачное восстановление \{#failed-restore\} Если `Adapty.restorePurchases()` завершится с ошибкой, будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidFailRestore(view: AdaptyUIFlowView, error: AdaptyError) { // Add your restore failure handling logic here // For example: show error message, retry option, or custom error handling } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ``` </Details> #### Завершение навигации веб-оплаты \{#web-payment-navigation-completion\} Если пользователь инициирует процесс покупки через [веб-пейвол](web-paywall), будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidFinishWebPaymentNavigation( view: AdaptyUIFlowView, product: AdaptyPaywallProduct?, error: AdaptyError? ) { if (error != null) { // Handle web payment navigation error } else { // Handle successful web payment navigation } } ``` <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```javascript // Successful web payment navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": null } // Failed web payment navigation { "product": null, "error": { "code": "web_payment_failed", "message": "Web payment navigation failed", "details": { "underlyingError": "Network connection error" } } } ``` </Details> ### Загрузка данных и рендеринг \{#data-fetching-and-rendering\} #### Ошибки загрузки продуктов \{#product-loading-errors\} Если вы не передаёте продукты при инициализации, AdaptyUI самостоятельно получит необходимые объекты с сервера. Если эта операция завершится неудачей, AdaptyUI сообщит об ошибке, вызвав следующий метод: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidFailLoadingProducts(view: AdaptyUIFlowView, error: AdaptyError) { // Add your product loading failure handling logic here // For example: show error message, retry option, or custom error handling } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ``` </Details> #### Ошибки рендеринга и ошибки во время выполнения \{#rendering-and-runtime-errors\} Если в процессе рендеринга интерфейса или во время выполнения возникает ошибка (не связанная с покупкой), она будет передана через этот метод. По умолчанию флоу закрывается при ошибке — переопределите метод, чтобы оставить его открытым или добавить собственную обработку: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidReceiveError(view: AdaptyUIFlowView, error: AdaptyError) { // Handle the error // The default implementation dismisses the flow; // once you override this method, dismissal is up to you } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render flow interface", "details": { "underlyingError": "Invalid flow configuration" } } } ``` </Details> В штатной ситуации такие ошибки возникать не должны, поэтому, если вы с ними столкнулись, сообщите нам. #### События аналитики \{#analytics-events\} Коллбэк `flowViewDidReceiveAnalyticEvent` зарезервирован для пользовательских событий аналитики из флоу. Флоу пока не отправляют такие события в ваш код, поэтому реализовывать его не нужно: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidReceiveAnalyticEvent( view: AdaptyUIFlowView, name: String, paramsJsonString: String ) { // Reserved for custom analytic events from a flow } ``` ### Навигация \{#navigation\} #### Кнопка «Назад» на Android \{#android-system-back-button\} По умолчанию флоу нельзя закрыть системной кнопкой «Назад» или жестом назад на Android — стандартная реализация `flowViewDidPerformAction` закрывает флоу только по `CloseAction` и игнорирует `AndroidSystemBackAction`, поэтому пользователь покидает флоу тем путём, который вы определили: через кнопку **Close** или действие `on_device_back` в билдере. Если вы хотите, чтобы системная кнопка «Назад» закрывала флоу, обработайте это действие самостоятельно: ```kotlin showLineNumbers title="Kotlin" override fun flowViewDidPerformAction(view: AdaptyUIFlowView, action: AdaptyUIAction) { when (action) { is AdaptyUIAction.CloseAction -> mainUiScope.launch { view.dismiss() } // default behavior is AdaptyUIAction.AndroidSystemBackAction -> mainUiScope.launch { view.dismiss() } // not handled by default is AdaptyUIAction.OpenUrlAction -> AdaptyUI.openWebUrl(action.url, action.openIn) // default behavior else -> Unit } } ``` См. [гайд по обработке действий флоу](kmp-handle-paywall-actions) для полного списка действий. </SDKv4> <SDKv3> Пейволы, настроенные с помощью [Paywall Builder](adapty-paywall-builder), не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют события, на которые ваше приложение может реагировать. К ним относятся нажатия кнопок (кнопки закрытия, URL-адреса, выбор продуктов и т. д.), а также уведомления о действиях, связанных с покупками, на пейволе. Узнайте, как реагировать на эти события, ниже. :::warning Этот гайд предназначен **только для пейволов нового Paywall Builder**. ::: Для управления или отслеживания событий на экране пейвола в вашем мобильном приложении реализуйте методы интерфейса `AdaptyUIPaywallsEventsObserver`. Некоторые методы имеют реализации по умолчанию, которые автоматически обрабатывают типичные сценарии. :::note В этих методах вы добавляете собственную логику для реакции на события пейвола. Чтобы закрыть пейвол, используйте `view.dismiss()`, либо реализуйте любое другое необходимое поведение. ::: ## События, генерируемые пользователями \{#user-generated-events\} ### Появление и исчезновение пейвола \{#paywall-appearance-and-disappearance\} Когда пейвол появляется или исчезает, вызываются следующие методы: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidAppear(view: AdaptyUIPaywallView) { // Handle paywall appearance // You can track analytics or update UI here } override fun paywallViewDidDisappear(view: AdaptyUIPaywallView) { // Handle paywall disappearance // You can track analytics or update UI here } ``` :::note - На iOS `paywallViewDidAppear` также вызывается, когда пользователь нажимает на [кнопку веб-пейвола](web-paywall#step-2a-add-a-web-purchase-button) внутри пейвола, и веб-пейвол открывается во встроенном браузере. - На iOS `paywallViewDidDisappear` также вызывается, когда [веб-пейвол](web-paywall#step-2a-add-a-web-purchase-button), открытый из пейвола во встроенном браузере, исчезает с экрана. ::: <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```javascript // Paywall appeared { // No additional data } // Paywall disappeared { // No additional data } ``` </Details> ### Выбор продукта \{#product-selection\} Если пользователь выбирает продукт для покупки, будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidSelectProduct(view: AdaptyUIPaywallView, productId: String) { // Handle product selection // You can update UI or track analytics here } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "productId": "premium_monthly" } ``` </Details> ### Начало покупки \{#started-purchase\} Если пользователь начинает процесс покупки, будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidStartPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct) { // Handle purchase start // You can show loading indicators or track analytics here } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ``` </Details> ### Успешная, отменённая или ожидающая покупка \{#successful-canceled-or-pending-purchase\} Если покупка прошла успешно, будет вызван этот метод. По умолчанию он автоматически закрывает пейвол, если только покупка не была отменена пользователем: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFinishPurchase( view: AdaptyUIPaywallView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { when (purchaseResult) { is AdaptyPurchaseResult.Success -> { // Check if user has access to premium features if (purchaseResult.profile.accessLevels["premium"]?.isActive == true) { view.dismiss() } } AdaptyPurchaseResult.Pending -> { // Handle pending purchase (e.g., user will pay offline with cash) } AdaptyPurchaseResult.UserCanceled -> { // Handle user cancellation } } } ``` <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```javascript // Successful purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "Success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } } } // Pending purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "Pending" } } // User canceled purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "UserCanceled" } } ``` </Details> Рекомендуем закрывать экран пейвола после успешной покупки. ### Неудачная покупка \{#failed-purchase\} Если покупка завершилась ошибкой, будет вызван этот метод. Сюда входят ошибки StoreKit/Google Play Billing (ограничения оплаты, недействительные продукты, сбои сети), ошибки верификации транзакций и системные ошибки. Обратите внимание, что отмена покупки пользователем вызывает `paywallViewDidFinishPurchase` с результатом отмены, а ожидающие платежи этот метод не вызывают. ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFailPurchase( view: AdaptyUIPaywallView, product: AdaptyPaywallProduct, error: AdaptyError ) { // Add your purchase failure handling logic here // For example: show error message, retry option, or custom error handling } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } } } ``` </Details> ### Начало восстановления \{#started-restore\} Если пользователь инициирует процесс восстановления, будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidStartRestore(view: AdaptyUIPaywallView) { // Handle restore start // You can show loading indicators or track analytics here } ``` ### Успешное восстановление покупки \{#successful-restore\} Если восстановление покупки прошло успешно, будет вызван следующий метод: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFinishRestore(view: AdaptyUIPaywallView, profile: AdaptyProfile) { // Add your successful restore handling logic here // For example: show success message, update UI, or dismiss paywall // Check if user has access to premium features if (profile.accessLevels["premium"]?.isActive == true) { view.dismiss() } } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } ``` </Details> Рекомендуем закрывать экран, если у пользователя есть нужный `accessLevel`. Подробнее о том, как это проверить, читайте в разделе [Статус подписки](subscription-status). ### Ошибка восстановления покупок \{#failed-restore\} Если `Adapty.restorePurchases()` завершается с ошибкой, будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFailRestore(view: AdaptyUIPaywallView, error: AdaptyError) { // Add your restore failure handling logic here // For example: show error message, retry option, or custom error handling } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ``` </Details> ### Завершение навигации веб-оплаты \{#web-payment-navigation-completion\} Если пользователь инициирует процесс покупки через [веб-пейвол](web-paywall), будет вызван этот метод: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFinishWebPaymentNavigation( view: AdaptyUIPaywallView, product: AdaptyPaywallProduct?, error: AdaptyError? ) { if (error != null) { // Handle web payment navigation error } else { // Handle successful web payment navigation } } ``` <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```javascript // Successful web payment navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": null } // Failed web payment navigation { "product": null, "error": { "code": "web_payment_failed", "message": "Web payment navigation failed", "details": { "underlyingError": "Network connection error" } } } ``` </Details> ## Получение и отображение данных \{#data-fetching-and-rendering\} ### Ошибки загрузки продуктов \{#product-loading-errors\} Если вы не передаёте продукты при инициализации, AdaptyUI самостоятельно получит нужные объекты с сервера. Если эта операция завершится неудачей, AdaptyUI сообщит об ошибке, вызвав следующий метод: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFailLoadingProducts(view: AdaptyUIPaywallView, error: AdaptyError) { // Add your product loading failure handling logic here // For example: show error message, retry option, or custom error handling } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ``` </Details> ### Ошибки рендеринга \{#rendering-errors\} Если в процессе рендеринга интерфейса возникнет ошибка, она будет сообщена этим методом: ```kotlin showLineNumbers title="Kotlin" override fun paywallViewDidFailRendering(view: AdaptyUIPaywallView, error: AdaptyError) { // Handle rendering error // In a normal situation, such errors should not occur // If you come across one, please let us know } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } ``` </Details> В штатной ситуации такие ошибки возникать не должны, поэтому если вы столкнулись с одной из них, пожалуйста, сообщите нам. </SDKv3> --- # File: kmp-use-fallback-paywalls --- --- title: "Kotlin Multiplatform - Использование резервных пейволов" description: "Обработка случаев, когда пользователи офлайн или серверы Adapty недоступны" --- Чтобы поддерживать бесперебойный пользовательский опыт, важно настроить [резервные пейволы](/fallback-paywalls) для флоу, [пейволов](paywalls) и [онбордингов](onboardings). Это позволит приложению продолжить работу при частичной или полной потере интернет-соединения. * **Если приложение не может обратиться к серверам Adapty:** Оно сможет отобразить резервный флоу или пейвол, а также использовать локальную конфигурацию онбординга. * **Если приложение не может подключиться к интернету:** Оно сможет отобразить резервный флоу или пейвол. Онбординги содержат удалённый контент и требуют интернет-соединения для работы. :::important Прежде чем следовать шагам этого гайда, [скачайте](/local-fallback-paywalls) файлы резервной конфигурации из Adapty. ::: ## Настройка \{#configuration\} 1. Добавьте файл резервной конфигурации в ваше приложение. * Если целевая платформа — Android, переместите файл резервной конфигурации в папку `android/app/src/main/assets/`. * Если целевая платформа — iOS, добавьте резервный JSON-файл в бандл вашего проекта. (**File** -> **Add Files to YourProjectName**) 2. Вызовите метод `.setFallback` **до** того, как вы запросите целевой флоу, пейвол или онбординг. 3. Задайте параметр `assetId` в зависимости от целевой платформы. * Android: используйте путь к файлу относительно директории `assets`. * iOS: используйте полное имя файла. ```kotlin showLineNumbers Adapty.setFallback(assetId = "fallback.json") .onSuccess { // Fallback paywalls loaded successfully } .onError { error -> // Handle the error } ``` :::important `setFallback` должен быть вызван до того, как SDK запросит целевой флоу, пейвол или онбординг. ::: Параметры: | Parameter | Description | | :---------- |:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **assetId** | Имя файла резервной конфигурации (iOS). <br /> Путь к файлу резервной конфигурации относительно директории `assets` (Android). | :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: --- # File: kmp-localizations-and-locale-codes --- --- title: "Использование локализаций и кодов локали в Kotlin Multiplatform SDK" description: "Управляйте локализациями приложения и кодами локали для охвата глобальной аудитории в вашем Kotlin Multiplatform приложении." --- <SDKv4> ## Почему это важно \{#why-this-is-important\} Коды локалей используются, когда Adapty подбирает локализацию для флоу и когда вы читаете Remote Config для пользовательского пейвола. Коды локалей устроены непросто и могут различаться от платформы к платформе, поэтому Adapty опирается на единый внутренний стандарт для всех поддерживаемых платформ. Понимание этого стандарта поможет вам предсказать, какую локализацию получит пользователь. ## Стандарт кодов локалей в Adapty \{#locale-code-standard-at-adapty\} Для кодов локалей Adapty использует немного модифицированный [стандарт BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): каждый код состоит из строчных подтегов, разделённых дефисами. Примеры: `en` (английский), `pt-br` (португальский (Бразилия)), `zh` (упрощённый китайский), `zh-hant` (традиционный китайский). ## Сопоставление кодов локали \{#locale-code-matching\} Когда Adapty подбирает локализацию под локаль пользователя, происходит следующее: 1. Строка локали приводится к нижнему регистру, а все символы подчёркивания (`_`) заменяются дефисами (`-`) 2. Adapty ищет локализацию с полностью совпадающим кодом локали 3. Если совпадение не найдено, Adapty берёт подстроку до первого дефиса (`pt` для `pt-br`) и ищет соответствующую локализацию 4. Если совпадение снова не найдено, Adapty возвращает локализацию по умолчанию `en` Таким образом `'pt_BR'`, `pt-BR` и `pt-br` указывают на одну и ту же локализацию. ## Реализация локализаций \{#implementing-localizations\} В SDK v4 код локали не передаётся при получении флоу. - **Пейволы из Flow Builder и Paywall Builder**: Adapty автоматически определяет локализацию по настройкам устройства и локализациям, которые вы задали в билдере. Рендерьте флоу с помощью `createFlowView` — код локали не нужен. - **Кастомные пейволы (на базе Remote Config)**: `getFlow` возвращает все настроенные локализации в `flow.remoteConfigs`. Каждый элемент — это `AdaptyRemoteConfig` с кодом `locale` и `dataMap`. Выберите подходящий элемент для пользователя, реализовав собственный фолбэк: ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") .onSuccess { flow -> val config = flow.remoteConfigs.firstOrNull { it.locale == "en" } ?: flow.remoteConfigs.firstOrNull() // read your values from config?.dataMap } .onError { error -> // handle the error } ``` Правила сопоставления кода локали, описанные выше, определяют, как Adapty нормализует коды `locale`, хранящиеся в каждом Remote Config. </SDKv4> <SDKv3> ## Почему это важно \{#why-this-is-important\} Есть несколько ситуаций, когда коды локали имеют значение — например, когда вы запрашиваете правильный пейвол для текущей локализации приложения. Поскольку коды локали бывают сложными и могут различаться в зависимости от платформы, мы используем внутренний стандарт для всех поддерживаемых платформ. Именно из-за этой сложности важно понимать, что именно вы отправляете на наш сервер для получения правильной локализации и что происходит дальше — чтобы всегда получать ожидаемый результат. ## Стандарт кодов локалей в Adapty \{#locale-code-standard-at-adapty\} Для кодов локалей Adapty использует слегка изменённый [стандарт BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): каждый код состоит из подтегов в нижнем регистре, разделённых дефисами. Несколько примеров: `en` (английский), `pt-br` (португальский (Бразилия)), `zh` (упрощённый китайский), `zh-hant` (традиционный китайский). ## Сопоставление кодов локали \{#locale-code-matching\} Когда Adapty получает вызов от клиентского SDK с кодом локали и начинает искать соответствующую локализацию пейвола, происходит следующее: 1. Входящая строка локали приводится к нижнему регистру, а все символы подчёркивания (`_`) заменяются дефисами (`-`) 2. Выполняется поиск локализации с точным совпадением кода локали 3. Если совпадение не найдено, берётся подстрока до первого дефиса (`pt` для `pt-br`) и выполняется поиск по ней 4. Если совпадение снова не найдено, возвращается локализация по умолчанию — `en` Таким образом, устройство iOS, отправившее `'pt_BR'`, устройство Android, отправившее `pt-BR`, и другое устройство, отправившее `pt-br`, получат одинаковый результат. ## Реализация локализаций: рекомендуемый способ \{#implementing-localizations-recommended-way\} Если вы думаете о локализациях, скорее всего, вы уже работаете с локализованными строковыми ресурсами в своём проекте. В таком случае мы рекомендуем добавить пару ключ-значение с нужным кодом локали Adapty в каждый из ваших файлов ресурсов для соответствующих локализаций. А затем извлекать значение по этому ключу при вызове нашего SDK, например так: ```kotlin showLineNumbers // 1. Add the Adapty locale code to your Compose Multiplatform resources /* composeResources/values/strings.xml (default — English) */ <string name="adapty_paywalls_locale">en</string> /* composeResources/values-es/strings.xml (Spanish) */ <string name="adapty_paywalls_locale">es</string> /* composeResources/values-pt-rBR/strings.xml (Portuguese — Brazil) */ <string name="adapty_paywalls_locale">pt-br</string> // 2. Extract and use the locale code suspend fun fetchPaywall() { val locale = getString(Res.string.adapty_paywalls_locale) Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID", locale = locale ).onSuccess { paywall -> // запрошенный пейвол }.onError { error -> // обработка ошибки } } ``` Таким образом вы полностью контролируете, какая локализация будет получена для каждого пользователя вашего приложения. Если вы не используете ресурсы Compose Multiplatform, та же идея применима к любой другой библиотеке локализации (например, [moko-resources](https://github.com/icerockdev/moko-resources)) — сохраните код локали Adapty как строку в ресурсном бандле каждой локали и считайте его перед вызовом SDK. ## Реализация локализаций: альтернативный подход \{#implementing-localizations-the-other-way\} Можно получить похожий (но не идентичный) результат, не указывая явно коды локалей для каждой локализации. Для этого нужно извлекать код локали напрямую с устройства — но это потребует объявлений `expect`/`actual`, поскольку в `commonMain` нет общего API для работы с локалями: ```kotlin showLineNumbers // commonMain expect fun currentLocaleTag(): String // androidMain actual fun currentLocaleTag(): String = Locale.getDefault().toLanguageTag() // iosMain actual fun currentLocaleTag(): String = NSLocale.currentLocale.localeIdentifier // commonMain — pass the locale code to Adapty suspend fun fetchPaywall() { Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID", locale = currentLocaleTag() ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } } ``` Обратите внимание, что мы не рекомендуем этот подход по ряду причин: 1. На iOS предпочитаемый язык пользователя и региональная локаль устройства — не одно и то же. `NSLocale.currentLocale.localeIdentifier` возвращает региональную локаль, которая может отличаться от языка, на котором пользователь читает ваше приложение. iOS-приложения, использующие локализованные строковые файлы, опираются на логику разрешения Apple, которая объединяет оба параметра — это работает автоматически при использовании рекомендованного подхода выше. 2. Сложно предсказать, что именно вернёт устройство и совпадёт ли это с локализацией в Adapty. Локаль устройства может содержать расширения или региональные коды, которые вы не настроили в Adapty — в этом случае SDK откатывается до совпадения по первому подтегу или, в крайнем случае, до `en`. Should you decide to use this approach anyway — make sure you've covered all the relevant use cases. </SDKv3> --- # File: kmp-web-paywalls --- --- title: "Реализация веб-пейволов в Kotlin Multiplatform SDK" description: "Настройте веб-пейвол для приёма платежей без комиссий и проверок стора." --- :::important Перед началом убедитесь, что вы [настроили веб-пейвол в дашборде](web-paywall) и установили Adapty SDK версии 3.15 или выше. ::: ## Открытие веб-пейволов \{#open-web-paywalls\} Если вы работаете с пейволом, который разработали самостоятельно, вам нужно обрабатывать веб-пейволы с помощью метода SDK. Метод `openWebPaywall`: 1. Генерирует уникальный URL, позволяющий Adapty связать конкретный показанный пейвол с конкретным пользователем и веб-страницей, на которую тот перенаправляется. 2. Отслеживает возвращение пользователя в приложение и затем запрашивает `getProfile` с короткими интервалами, чтобы определить, обновились ли права доступа профиля. Таким образом, если платёж прошёл успешно и права доступа обновились, подписка активируется в приложении практически мгновенно. :::note После возвращения пользователей в приложение обновите UI, чтобы отразить изменения в профиле. Adapty получит и обработает события обновления профиля. ::: ```kotlin showLineNumbers viewModelScope.launch { Adapty.openWebPaywall(product = product).onSuccess { // the web paywall was opened successfully }.onError { error -> // handle the error } } ``` :::note Есть две версии метода `openWebPaywall`: 1. `openWebPaywall(product = product)` — генерирует URL по пейволу и добавляет данные о продукте в URL. 2. `openWebPaywall(paywall = paywall)` — генерирует URL по пейволу без добавления данных о продукте в URL. Используйте этот вариант, если продукты в пейволе Adapty отличаются от продуктов в веб-пейволе. В SDK v4 параметр `paywall` заменён параметром `flowPaywall`, принимающим `AdaptyFlowPaywall` — один из вариантов пейвола в `flow.paywalls`. См. [гайд по миграции](migration-to-kmp-sdk-v4). ::: ## Открытие веб-пейволов во встроенном браузере \{#open-web-paywalls-in-an-in-app-browser\} По умолчанию веб-пейволы открываются во внешнем браузере. Чтобы обеспечить бесшовный пользовательский опыт, вы можете открывать веб-пейволы во встроенном браузере. Страница покупки будет отображаться прямо в приложении, и пользователям не придётся переключаться между приложениями для завершения транзакции. Чтобы включить это, установите параметр `openIn` в значение `AdaptyWebPresentation.IN_APP_BROWSER`: ```kotlin showLineNumbers viewModelScope.launch { Adapty.openWebPaywall( product = product, openIn = AdaptyWebPresentation.IN_APP_BROWSER // default – EXTERNAL_BROWSER ).onSuccess { // the web paywall was opened successfully }.onError { error -> // handle the error } } ``` --- # File: kmp-present-flows-in-observer-mode --- --- title: "Показ флоу в режиме Observer — Kotlin Multiplatform" description: "Показывайте флоу и пейволы Paywall Builder в режиме Observer в вашем приложении на Kotlin Multiplatform, обрабатывая покупки собственным кодом." --- Если вы настроили флоу или пейвол с помощью билдера, вам не нужно беспокоиться о его отрисовке в коде мобильного приложения для показа пользователю. Такой флоу или пейвол содержит и то, что должно быть показано, и то, как это должно быть показано. :::warning Этот раздел относится только к [режиму Observer](observer-vs-full-mode). Если вы не работаете в режиме Observer, обратитесь к разделу [Отображение флоу и пейволов](kmp-present-paywalls). ::: :::info Эта возможность требует Adapty Kotlin Multiplatform SDK 4.0 (бета) или более поздней версии — ранее она была доступна только в нативных SDK для iOS и Android. Смотрите [руководство по миграции](migration-to-kmp-sdk-v4) для обновления. ::: <details> <summary>Перед тем как начать отображать флоу (нажмите, чтобы развернуть)</summary> 1. Настройте первоначальную интеграцию Adapty [с App Store](initial_ios) и [с Google Play](initial-android). 2. Установите и настройте SDK. Убедитесь, что в конфигурационном билдере вызывается `withObserverMode(true)`. Обратитесь к [руководству по установке Kotlin Multiplatform SDK](sdk-installation-kotlin-multiplatform#activate-adapty-sdk). 3. [Создайте продукты](create-product) в дашборде Adapty. 4. [Настройте флоу или пейволы в билдерах](create-paywall) и привяжите к ним продукты. 5. [Создайте плейсменты и назначьте им флоу или пейволы](create-placement). 6. [Получите флоу и их конфигурацию](kmp-get-pb-paywalls) в коде мобильного приложения. </details> В режиме Observer SDK не выполняет покупки самостоятельно. Когда пользователь нажимает кнопку покупки или восстановления во флоу или пейволе, отрисованном Adapty, SDK вызывает ваш `AdaptyUIObserverModeResolver` — выполните покупку или восстановление с помощью собственного кода там. 1. Реализуйте интерфейс `AdaptyUIObserverModeResolver`: ```kotlin showLineNumbers import com.adapty.kmp.AdaptyUIObserverModeResolver import com.adapty.kmp.models.AdaptyPaywallProduct import com.adapty.kmp.models.AdaptyUIFlowView class MyObserverModeResolver : AdaptyUIObserverModeResolver { override fun observerModeDidInitiatePurchase( view: AdaptyUIFlowView, product: AdaptyPaywallProduct, onStartPurchase: () -> Unit, onFinishPurchase: () -> Unit ) { onStartPurchase() // the view shows its loading indicator // make the purchase with your own code, // then report the transaction to Adapty and call: onFinishPurchase() // the view hides the loading indicator } override fun observerModeDidInitiateRestore( view: AdaptyUIFlowView, onStartRestore: () -> Unit, onFinishRestore: () -> Unit ) { onStartRestore() // restore purchases with your own code, then: onFinishRestore() } } ``` `observerModeDidInitiatePurchase` уведомляет вас о том, что пользователь инициировал покупку, а `observerModeDidInitiateRestore` — что пользователь инициировал восстановление. В ответ запустите ваш собственный флоу покупки или восстановления. Также не забудьте вызвать следующие колбэки, чтобы уведомить AdaptyUI о процессе покупки или восстановления. Это необходимо для корректного поведения флоу, например для отображения загрузчика: | Callback | Description | | :----------------- | :-------------------------------------------------------------------------------------------------- | | onStartPurchase() | Колбэк необходимо вызвать, чтобы уведомить AdaptyUI о начале покупки. | | onFinishPurchase() | Колбэк необходимо вызвать, чтобы уведомить AdaptyUI о завершении покупки. | | onStartRestore() | Колбэк необходимо вызвать, чтобы уведомить AdaptyUI о начале восстановления. | | onFinishRestore() | Колбэк необходимо вызвать, чтобы уведомить AdaptyUI о завершении восстановления. | Флоу остаётся открытым, пока выполняется ваш код — закройте его самостоятельно после успешной покупки или восстановления. 2. Зарегистрируйте resolver до показа любого экрана: ```kotlin showLineNumbers import com.adapty.kmp.AdaptyUI AdaptyUI.setObserverModeResolver(MyObserverModeResolver()) ``` Без зарегистрированного resolver флоу не может передать покупку вашему коду, и нажатие кнопки покупки ни к чему не приводит. 3. Создайте и отобразите флоу как обычно: [получите флоу и создайте его view](kmp-get-pb-paywalls), затем [отобразите его](kmp-present-paywalls). Дополнительные параметры не нужны — как только резолвер зарегистрирован, все флоу и пейволы, отображаемые через Adapty, маршрутизируют покупки и восстановления через него. :::warning Не забудьте [сообщить о транзакции и связать её с пейволом](report-transactions-observer-mode-kmp). Иначе Adapty не распознает транзакцию и не определит, с какого пейвола была совершена покупка. ::: --- # File: kmp-troubleshoot-paywall-builder --- --- title: "Устранение неполадок в Paywall Builder для Kotlin Multiplatform SDK" description: "Устранение неполадок в Paywall Builder для Kotlin Multiplatform SDK" --- Этот гайд поможет устранить распространённые проблемы при использовании пейволов, созданных в Adapty Paywall Builder, в Kotlin Multiplatform SDK. ## Не удаётся получить конфигурацию пейвола \{#getting-a-paywall-configuration-fails\} **Проблема**: Метод `createPaywallView` не создаёт представление пейвола, или у пейвола отсутствует конфигурация представления. **Причина**: Пейвол не настроен для отображения на устройстве в Paywall Builder. **Решение**: Включите переключатель **Show on device** в Paywall Builder. Также можно проверить, есть ли у пейвола конфигурация представления, с помощью свойства `hasViewConfiguration` объекта `AdaptyPaywall`. <img src="/assets/shared/img/show-on-device.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Слишком большое количество просмотров пейвола \{#the-paywall-view-number-is-too-big\} **Проблема**: Счётчик просмотров пейвола показывает вдвое больше ожидаемого значения. **Причина**: Возможно, в коде вызывается `logShowPaywall`, что дублирует счётчик просмотров при использовании Paywall Builder. Для пейволов, созданных с помощью Paywall Builder, аналитика отслеживается автоматически — использовать этот метод не нужно. **Решение**: Убедитесь, что в коде не вызывается `logShowPaywall`, если вы используете Paywall Builder. --- # File: kmp-implement-paywalls-manually --- --- title: "Реализация пейволов вручную в Kotlin Multiplatform SDK" description: "Узнайте, как реализовать пейволы вручную в вашем приложении на Kotlin Multiplatform с помощью Adapty SDK." --- ## Приём платежей \{#accept-purchases\} Если вы работаете с пейволами собственной реализации, вы можете делегировать обработку покупок в Adapty с помощью метода `makePurchase`. Adapty возьмёт на себя все пользовательские сценарии, а вам останется только обрабатывать результаты покупок. :::important `makePurchase` работает с продуктами, созданными в дашборде Adapty. Убедитесь, что продукты и способы их получения настроены в дашборде — следуйте [quickstart guide](quickstart). ::: <CustomDocCardList ids={['kmp-quickstart-manual', 'fetch-paywalls-and-products-kmp', 'present-remote-config-paywalls-kmp', 'kmp-making-purchases', 'kmp-restore-purchase', 'kmp-troubleshoot-purchases']} /> ## Режим наблюдателя \{#observer-mode\} Если вы хотите реализовать собственную логику обработки покупок с нуля, но при этом воспользоваться расширенной аналитикой Adapty, вы можете использовать режим наблюдателя. :::important Ознакомьтесь с ограничениями режима наблюдателя [здесь](observer-vs-full-mode). ::: <CustomDocCardList ids={['implement-observer-mode-kmp', 'report-transactions-observer-mode-kmp', 'kmp-troubleshoot-purchases']} /> --- # File: kmp-quickstart-manual --- --- title: "Включение покупок в вашем кастомном пейволе в Kotlin Multiplatform SDK" description: "Интегрируйте Adapty SDK в ваши кастомные пейволы Kotlin Multiplatform, чтобы включить встроенные покупки." --- В этом гайде описано, как интегрировать Adapty в ваши кастомные пейволы. Сохраняйте полный контроль над реализацией пейвола, пока Adapty SDK получает продукты, обрабатывает новые покупки и восстанавливает предыдущие. В этом гайде используются API Adapty Kotlin Multiplatform SDK v4 (beta) — если вы используете v3, смотрите [руководство по миграции](migration-to-kmp-sdk-v4) с соответствующими названиями методов. :::important **Это руководство для разработчиков, которые реализуют пользовательские пейволы.** Если вы хотите самый простой способ подключить покупки, используйте [Adapty Flow Builder](kmp-quickstart-paywalls). С Flow Builder вы создаёте флоу в визуальном редакторе без кода, Adapty автоматически обрабатывает всю логику покупок, а вы можете тестировать разные дизайны без повторной публикации приложения. ::: ## Прежде чем начать \{#before-you-start\} ### Настройка продуктов \{#set-up-products\} Чтобы подключить встроенные покупки, нужно разобраться с тремя ключевыми понятиями: - [**Продукты**](product) – всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ) - [**Пейволы**](paywalls) – конфигурации, определяющие, какие продукты предлагать. В Adapty пейволы — единственный способ получить продукты, но такой подход позволяет изменять продукты, цены и офферы без обновления кода приложения. В SDK v4 варианты пейволов для плейсмента передаются через объект **flow** — вы получаете флоу и запрашиваете его продукты. - [**Плейсменты**](placements) – где и когда показывать пейволы в приложении (например, `main`, `onboarding`, `settings`). Пейволы для плейсментов настраиваются в дашборде, а затем запрашиваются по ID плейсмента в коде. Это упрощает проведение A/B-тестов и показ разных пейволов разным пользователям. Убедитесь, что вы понимаете эти концепции, даже если используете собственный пейвол. По сути, это просто способ управлять продуктами, которые вы продаёте в приложении. Чтобы реализовать собственный пейвол, нужно создать **пейвол** и добавить его в **плейсмент**. Это позволит получать ваши продукты. Чтобы разобраться, что именно нужно сделать в дашборде, следуйте [этому](quickstart) руководству по быстрому старту. ### Управление пользователями \{#manage-users\} Вы можете работать как с бэкенд-аутентификацией, так и без неё. При этом SDK по-разному обрабатывает анонимных и идентифицированных пользователей. Прочитайте [гайд по идентификации пользователей](kmp-quickstart-identify), чтобы разобраться в деталях и правильно работать с пользователями. ## Шаг 1. Получите продукты \{#step-1-get-products\} Чтобы получить продукты для вашего кастомного пейвола, нужно: 1. Получить объект `flow`, передав ID [плейсмента](placements) в метод `getFlow`. 2. Получить массив продуктов для этого флоу с помощью метода `getPaywallProducts`. ```kotlin showLineNumbers fun loadPaywall() { Adapty.getFlow(placementId = "YOUR_PLACEMENT_ID") .onSuccess { flow -> Adapty.getPaywallProducts(flow = flow) .onSuccess { products -> // Use products to build your custom paywall UI } .onError { error -> // Handle the error } } .onError { error -> // Handle the error } } ``` ## Шаг 2. Принимайте покупки \{#step-2-accept-purchases\} Когда пользователь нажимает на продукт в вашем кастомном пейволе, вызовите метод `makePurchase` с выбранным продуктом. Это запустит флоу покупки и вернёт обновлённый профиль. ```kotlin showLineNumbers fun purchaseProduct(product: AdaptyPaywallProduct) { Adapty.makePurchase(product = product) .onSuccess { purchaseResult -> when (purchaseResult) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile // Purchase successful, profile updated } is AdaptyPurchaseResult.UserCanceled -> { // User canceled the purchase } is AdaptyPurchaseResult.Pending -> { // Purchase is pending (e.g., user will pay offline with cash) } } } .onError { error -> // Handle the error } } ``` ## Шаг 3. Восстановление покупок \{#step-3-restore-purchases\} Сторы требуют, чтобы все приложения с подписками предоставляли пользователям возможность восстановить покупки. Вызывайте метод `restorePurchases`, когда пользователь нажимает кнопку восстановления. Это синхронизирует историю покупок с Adapty и вернёт обновлённый профиль. ```kotlin showLineNumbers fun restorePurchases() { Adapty.restorePurchases() .onSuccess { profile -> // Restore successful, profile updated } .onError { error -> // Handle the error } } ``` ## Шаг 4. Проверьте статус подписки \{#step-4-check-the-subscription-status\} После покупки или восстановления проверьте [уровень доступа](access-level) пользователя, чтобы решить — показывать пейвол или открыть платные функции. Методы `makePurchase` и `restorePurchases` уже возвращают обновлённый профиль; когда нужно получить текущий статус в другом месте приложения, используйте метод `getProfile`: ```kotlin showLineNumbers fun checkPremiumAccess() { Adapty.getProfile() .onSuccess { profile -> val hasPremiumAccess = profile.accessLevels["premium"]?.isActive == true // Grant access to paid features if hasPremiumAccess is true } .onError { error -> // Handle the error } } ``` Подробнее о способах проверки и мониторинга статуса подписки, включая прослушивание обновлений в реальном времени, см. в разделе [Проверка статуса подписки](kmp-check-subscription-status). ## Следующие шаги \{#next-steps\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Ваш пейвол готов к отображению в приложении. Протестируйте покупки в [песочнице App Store](test-purchases-in-sandbox) или в [Google Play Store](testing-on-android), чтобы убедиться, что тестовая покупка с пейвола проходит успешно. Чтобы увидеть, как это работает в production-ready реализации, посмотрите на [AppViewModel.kt](https://github.com/adaptyteam/AdaptySDK-KMP/blob/main/example/composeMultiplatformApp/composeApp/src/commonMain/kotlin/com/adapty/exampleapp/AppViewModel.kt) в нашем примере приложения — там показана обработка покупок с корректной обработкой ошибок и управлением состоянием. --- # File: fetch-paywalls-and-products-kmp --- --- title: "Получение пейволов и продуктов для пейволов с Remote Config в Kotlin Multiplatform SDK" description: "Получайте пейволы и продукты в Adapty Kotlin Multiplatform SDK для улучшения монетизации пользователей." --- <SDKv4> Прежде чем работать с Remote Config и кастомными пейволами, нужно получить информацию о них. Обратите внимание: этот раздел посвящён Remote Config и кастомным пейволам. Инструкции по получению флоу или пейволов, созданных в **Flow Builder** или **Paywall Builder**, см. в разделе [Получение флоу и пейволов](kmp-get-pb-paywalls). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Прежде чем начать получать флоу и продукты в мобильном приложении (нажмите, чтобы развернуть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте флоу или пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них флоу или пейвол](create-placement) в дашборде Adapty. 4. [Установите SDK Adapty](sdk-installation-kotlin-multiplatform) в своём мобильном приложении. </details> ## Получение информации о флоу \{#fetch-flow-information\} В Adapty [продукт](product) — это комбинация продуктов из App Store и Google Play. Эти кросс-платформенные продукты интегрированы во флоу и пейволы, что позволяет отображать их в определённых плейсментах мобильного приложения. Чтобы отобразить продукты, необходимо получить `AdaptyFlow` из одного из ваших [плейсментов](placements) с помощью метода `getFlow`. :::important **Не прописывайте ID продуктов в коде.** В коде нужно хардкодить только ID плейсмента. Флоу настраиваются удалённо, поэтому количество продуктов и доступных офферов может меняться в любой момент. Ваше приложение должно обрабатывать эти изменения динамически — если сегодня флоу возвращает два продукта, а завтра три, отображайте все из них без изменений в коде. ::: ```kotlin showLineNumbers Adapty.getFlow( placementId = "YOUR_PLACEMENT_ID", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { flow -> // the requested flow }.onError { error -> // handle the error } ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указываете при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `AdaptyPaywallFetchPolicy.Default` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кэшированные данные в случае сбоя. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` — оно возвращает кэшированные данные, если они есть. В этом случае пользователи могут получить не самые свежие данные, зато загрузка будет быстрее независимо от качества соединения. Кэш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание: кэш сохраняется при перезапуске приложения и очищается только при его переустановке или принудительной очистке.</p><p></p><p>Adapty SDK хранит флоу и пейволы в двух слоях: регулярно обновляемый кэш, описанный выше, и [резервные пейволы](kmp-use-fallback-paywalls). Для ускоренной загрузки флоу и пейволов используется CDN, а также отдельный резервный сервер на случай недоступности CDN. Такая система гарантирует, что вы всегда получаете актуальные версии флоу и пейволов, сохраняя надёжность даже при нестабильном интернете.</p> | | **loadTimeout** | по умолчанию: 5 сек | <p>Это значение ограничивает таймаут метода. Если таймаут истекает, возвращаются кэшированные данные или локальный резервный вариант.</p><p></p><p>Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения, указанного в `loadTimeout`, поскольку операция может состоять из нескольких внутренних запросов.</p> | Не указывайте идентификаторы продуктов в коде! Поскольку флоу настраиваются удалённо, набор доступных продуктов, их количество и специальные предложения (например, бесплатные пробные периоды) могут меняться со временем. Убедитесь, что ваш код учитывает эти сценарии. Например, если изначально вы получаете 2 продукта, приложение должно отображать именно 2. Но если позднее вы получите 3 продукта, приложение должно показать все 3 без каких-либо изменений в коде. Единственное, что нужно зафиксировать в коде, — это идентификатор плейсмента. Параметры ответа: | Параметр | Описание | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | Объект `AdaptyFlow` с: идентификатором флоу, вариантами пейвола (`paywalls` — каждый со своими идентификаторами продуктов), списком `remoteConfigs` (по одной записи на каждую настроенную локаль) и рядом других свойств. Чтобы получить продукты для флоу, вызовите `getPaywallProducts(flow)`. | :::note В v4 у `getFlow` нет параметра `locale`. При рендеринге флоу с помощью `createFlowView` локализация определяется автоматически. Для кастомных пейволов все доступные локали возвращаются вместе в `flow.remoteConfigs` — выберите локаль, соответствующую устройству пользователя или настройкам вашего приложения. Подробнее см. в разделе [Локализации и коды локалей](kmp-localizations-and-locale-codes). ::: ## Получение продуктов \{#fetch-products\} Получив флоу, можно запросить массив продуктов, соответствующих ему: ```kotlin showLineNumbers Adapty.getPaywallProducts(flow).onSuccess { products -> // the requested products }.onError { error -> // handle the error } ``` Параметры ответа: | Параметр | Описание | | :-------- |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | Список объектов [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/) с идентификатором продукта, названием продукта, ценой, валютой, длительностью подписки и рядом других свойств. | При реализации собственного дизайна флоу вам, скорее всего, потребуется доступ к этим свойствам объекта [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/). Ниже представлены наиболее часто используемые свойства; полный список доступных свойств см. в документации по ссылке. | Свойство | Описание | |-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Чтобы отобразить название продукта, используйте `product.localizedTitle`. Локализация определяется страной стора, выбранной пользователем, а не локалью устройства. | | **Price** | Чтобы отобразить цену в локализованном виде, используйте `product.price.localizedString`. Локализация определяется локалью устройства. Цену в виде числа можно получить через `product.price.amount` — значение будет в местной валюте. Чтобы получить символ валюты, используйте `product.price.currencySymbol`. | | **Subscription Period** | Чтобы отобразить период (например, неделя, месяц, год и т. д.), используйте `product.subscriptionDetails?.localizedSubscriptionPeriod`. Локализация определяется локалью устройства. Чтобы получить период подписки программно, используйте `product.subscriptionDetails?.subscriptionPeriod`. Из него можно получить enum `unit`, определяющий длину периода (DAY, WEEK, MONTH, YEAR или UNKNOWN). Значение `numberOfUnits` содержит количество единиц периода. Например, для квартальной подписки в свойстве `unit` будет `MONTH`, а в `numberOfUnits` — `3`. | | **Introductory Offer** | Чтобы отобразить значок или другой индикатор наличия introductory offer у подписки, проверьте свойство `product.subscriptionDetails?.introductoryOfferPhases`. Это список, который может содержать до двух фаз скидки: фазу бесплатного пробного периода и фазу вводной цены. В каждом объекте фазы доступны следующие полезные свойства:<br/>• `paymentMode` — enum со значениями `FREE_TRIAL`, `PAY_AS_YOU_GO`, `PAY_UPFRONT` и `UNKNOWN`. Бесплатные пробные периоды имеют тип `FREE_TRIAL`.<br/>• `price` — цена со скидкой в виде числа. Для бесплатных пробных периодов здесь будет `0`.<br/>• `localizedNumberOfPeriods` — строка, локализованная по локали устройства и описывающая длину предложения. Например, для трёхдневного пробного периода в этом поле отобразится `3 days`.<br/>• `subscriptionPeriod` — позволяет получить детали периода предложения по отдельности. Работает так же, как описано в предыдущем разделе.<br/>• `localizedSubscriptionPeriod` — отформатированный период подписки по скидке для локали пользователя. | ## Ускорение загрузки флоу с помощью флоу для аудитории по умолчанию \{#speed-up-flow-fetching-with-default-audience-flow\} Как правило, флоу загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и плейсментов, а пользователи находятся в условиях слабого интернет-соединения, загрузка флоу может занять больше времени, чем хотелось бы. В таких ситуациях имеет смысл показывать флоу по умолчанию — это обеспечит плавный пользовательский опыт вместо пустого экрана. Чтобы решить эту проблему, используйте метод `getFlowForDefaultAudience`, который загружает флоу указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — загружать флоу методом `getFlow`, как описано в разделе [Загрузка информации о флоу](fetch-paywalls-and-products-kmp#fetch-flow-information) выше. :::warning Почему мы рекомендуем использовать `getFlow` Метод `getFlowForDefaultAudience` имеет ряд существенных недостатков: - **Возможные проблемы с обратной совместимостью**: если нужно показывать разные флоу для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать флоу с учётом текущей (legacy) версии, либо смириться с тем, что у пользователей с текущей (legacy) версией флоу могут не отображаться. - **Потеря таргетинга**: все пользователи будут видеть один флоу, настроенный для аудитории **All Users**, — то есть вы теряете персонализированный таргетинг (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вы готовы принять эти недостатки ради более быстрого получения флоу, используйте метод `getFlowForDefaultAudience`, как описано ниже. В противном случае используйте метод `getFlow`, описанный [выше](fetch-paywalls-and-products-kmp#fetch-flow-information). ::: ```kotlin showLineNumbers Adapty.getFlowForDefaultAudience( placementId = "YOUR_PLACEMENT_ID", fetchPolicy = AdaptyPaywallFetchPolicy.Default ).onSuccess { flow -> // the requested flow }.onError { error -> // handle the error } ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `AdaptyPaywallFetchPolicy.Default` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если вы считаете, что у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad`, чтобы возвращать кешированные данные при их наличии. В этом случае пользователи могут не получить самые последние данные, зато загрузка будет быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии для уменьшения количества сетевых запросов.</p><p></p><p>Обратите внимание, что кеш сохраняется после перезапуска приложения и очищается только при его переустановке или при ручной очистке.</p> | </SDKv4> <SDKv3> Прежде чем работать с Remote Config и кастомными пейволами, необходимо получить информацию о них. Обратите внимание, что этот раздел посвящён Remote Config и кастомным пейволам. Для получения пейволов, созданных в Paywall Builder, см. [Получение пейволов Paywall Builder и их конфигурации](kmp-get-pb-paywalls). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Прежде чем начать получать пейволы и продукты в мобильном приложении (нажмите, чтобы раскрыть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте пейвол и добавьте продукты в него](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте пейвол в плейсмент](create-placement) в дашборде Adapty. 4. [Установите SDK Adapty](sdk-installation-kotlin-multiplatform) в своё мобильное приложение. </details> ## Получение информации о пейволе \{#fetch-paywall-information\} В Adapty [продукт](product) объединяет в себе продукты из App Store и Google Play. Эти кросс-платформенные продукты интегрируются в пейволы, позволяя отображать их в нужных плейсментах мобильного приложения. Чтобы показать продукты, необходимо получить [Paywall](paywalls) из одного из ваших [плейсментов](placements) с помощью метода `getPaywall`. :::important **Не прописывайте ID продуктов в коде.** Единственный ID, который нужно хардкодить, — это ID плейсмента. Пейволы настраиваются удалённо, поэтому количество продуктов и доступных предложений может меняться в любой момент. Приложение должно обрабатывать эти изменения динамически: если сегодня пейвол возвращает два продукта, а завтра три — отображайте все без изменений в коде. ::: ```kotlin showLineNumbers Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>необязательный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается, что этот параметр будет языковым кодом, состоящим из одного или нескольких подтегов, разделённых символом минус (**-**). Первый подтег — язык, второй — регион.</p><p></p><p>Пример: `en` означает английский, `pt-br` — бразильский португальский.</p> | | **fetchPolicy** | по умолчанию: `AdaptyPaywallFetchPolicy.Default` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кэшированные данные в случае ошибки. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают самые актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` — оно возвращает кэшированные данные, если они есть. В этом случае пользователи могут не получить самые последние данные, зато загрузка будет быстрее вне зависимости от качества соединения. Кэш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание, что кэш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.</p><p></p><p>Adapty SDK хранит пейволы в двух слоях: регулярно обновляемый кэш, описанный выше, и [резервные пейволы](kmp-use-fallback-paywalls). Также используется CDN для более быстрой загрузки пейволов и отдельный резервный сервер на случай недоступности CDN. Система спроектирована так, чтобы вы всегда получали актуальную версию пейволов даже при нестабильном интернет-соединении.</p> | | **loadTimeout** | по умолчанию: 5 сек | <p>Это значение ограничивает таймаут метода. По истечении таймаута будут возвращены кэшированные данные или локальный резервный пейвол.</p><p></p><p>Обратите внимание, что в редких случаях метод может завершиться с небольшой задержкой относительно значения, указанного в `loadTimeout`, поскольку операция может включать несколько внутренних запросов.</p> | Не прописывайте идентификаторы продуктов в коде! Поскольку пейволы настраиваются удалённо, список доступных продуктов, их количество и специальные предложения (например, бесплатные пробные периоды) могут меняться со временем. Убедитесь, что ваш код учитывает эти сценарии. Например, если изначально вы получаете 2 продукта, приложение должно показывать 2 продукта. Но если позже вы получите 3 продукта, приложение должно отображать все 3 без каких-либо изменений в коде. Единственное, что нужно прописать в коде, — это идентификатор плейсмента. Параметры ответа: | Параметр | Описание | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Объект [`AdaptyPaywall`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall/) со списком идентификаторов продуктов, идентификатором пейвола, Remote Config и рядом других свойств. | ## Получение продуктов \{#fetch-products\} Получив пейвол, вы можете запросить массив продуктов, соответствующих ему: ```kotlin showLineNumbers Adapty.getPaywallProducts(paywall).onSuccess { products -> // the requested products }.onError { error -> // handle the error } ``` Параметры ответа: | Параметр | Описание | | :-------- |:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | Список объектов [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/) с: идентификатором продукта, названием продукта, ценой, валютой, длительностью подписки и рядом других свойств. | При реализации собственного дизайна пейвола вам, скорее всего, понадобятся следующие свойства объекта [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/). Ниже приведены наиболее часто используемые свойства; полный список доступных свойств см. в связанной документации. | Свойство | Описание | |-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Чтобы отобразить название продукта, используйте `product.localizedTitle`. Локализация основана на стране стора, выбранной пользователем, а не на локали устройства. | | **Price** | Чтобы отобразить локализованную цену, используйте `product.price.localizedString`. Локализация основана на локали устройства. Цену также можно получить как число через `product.price.amount` — значение будет в местной валюте. Символ валюты доступен через `product.price.currencySymbol`. | | **Subscription Period** | Чтобы отобразить период подписки (например, неделя, месяц, год и т. д.), используйте `product.subscriptionDetails?.localizedSubscriptionPeriod`. Локализация основана на локали устройства. Для программного получения периода подписки используйте `product.subscriptionDetails?.subscriptionPeriod`. Оттуда можно обратиться к enum `unit`, чтобы узнать длину периода (DAY, WEEK, MONTH, YEAR или UNKNOWN). Значение `numberOfUnits` содержит количество единиц периода. Например, для квартальной подписки в свойстве `unit` будет `MONTH`, а в `numberOfUnits` — `3`. | | **Introductory Offer** | Чтобы показать значок или другой индикатор наличия introductory offer у подписки, проверьте свойство `product.subscriptionDetails?.introductoryOfferPhases`. Это список, который может содержать до двух фаз скидки: фазу бесплатного пробного периода и фазу вступительной цены. Каждый объект фазы содержит следующие полезные свойства:<br/>• `paymentMode`: enum со значениями `FREE_TRIAL`, `PAY_AS_YOU_GO`, `PAY_UPFRONT` и `UNKNOWN`. Бесплатный пробный период соответствует типу `FREE_TRIAL`.<br/>• `price`: сниженная цена как число. Для бесплатных пробных периодов здесь будет `0`.<br/>• `localizedNumberOfPeriods`: строка, локализованная по локали устройства, описывающая длительность предложения. Например, для трёхдневного пробного периода в этом поле будет `3 days`.<br/>• `subscriptionPeriod`: позволяет получить отдельные детали периода предложения — работает так же, как описано в предыдущем разделе.<br/>• `localizedSubscriptionPeriod`: отформатированный период подписки скидки для локали пользователя. | ## Ускорьте загрузку пейвола с помощью пейвола для аудитории по умолчанию \{#speed-up-paywall-fetching-with-default-audience-paywall\} Как правило, пейволы загружаются практически мгновенно, и беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и пейволов, а пользователи работают при слабом интернете, загрузка пейвола может занять больше времени, чем хотелось бы. В таких случаях имеет смысл показывать пейвол по умолчанию, чтобы обеспечить комфортный пользовательский опыт вместо отсутствия пейвола. Чтобы решить эту задачу, можно использовать метод `getPaywallForDefaultAudience`, который получает пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать пейвол через метод `getPaywall`, как описано в разделе [Получение информации о пейволе](fetch-paywalls-and-products-kmp#fetch-paywall-information) выше. :::warning Почему мы рекомендуем использовать `getPaywall` Метод `getPaywallForDefaultAudience` имеет ряд существенных недостатков: - **Возможные проблемы с обратной совместимостью**: если вам нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать пейволы с поддержкой текущей (устаревшей) версии, либо смириться с тем, что пользователи на текущей (устаревшей) версии будут сталкиваться с проблемами из-за неотображаемых пейволов. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, настроенный для аудитории **All Users**, — это означает отказ от персонализированного таргетинга (в том числе по странам, маркетинговой атрибуции или собственным кастомным атрибутам). Если вы готовы принять эти недостатки ради более быстрого получения пейвола, используйте метод `getPaywallForDefaultAudience`, как описано ниже. В противном случае используйте `getPaywall`, описанный [выше](fetch-paywalls-and-products-kmp#fetch-paywall-information). ::: ```kotlin showLineNumbers Adapty.getPaywallForDefaultAudience( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указывали при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается языковой код, состоящий из одного или нескольких подтегов, разделённых символом минус (**-**). Первый подтег — язык, второй — регион.</p><p></p><p>Пример: `en` — английский, `pt-br` — бразильский португальский.</p><p></p> | | **fetchPolicy** | по умолчанию: `AdaptyPaywallFetchPolicy.Default` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Мы рекомендуем этот вариант: он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильный интернет, рассмотрите вариант `AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` — он возвращает кешированные данные, если они есть. В этом случае пользователи могут получить не самые свежие данные, зато загрузка будет быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы сократить количество сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.</p> | </SDKv3> --- # File: present-remote-config-paywalls-kmp --- --- title: "Отображение пейвола на основе Remote Config в Kotlin Multiplatform SDK" description: "Узнайте, как показывать пейволы с Remote Config в Adapty Kotlin Multiplatform SDK для персонализации пользовательского опыта." --- <SDKv4> Если вы настроили пейвол с помощью Remote Config, вам нужно реализовать отрисовку в коде мобильного приложения, чтобы показывать его пользователям. Поскольку Remote Config гибко подстраивается под ваши задачи, вы сами решаете, что включить и как будет выглядеть пейвол. Adapty предоставляет метод для получения Remote Config, позволяя вам полностью управлять отображением пользовательского пейвола. ## Получение Remote Config флоу и его отображение \{#get-flow-remote-config-and-present-it\} В v4 флоу содержит по одному объекту `AdaptyRemoteConfig` на каждую настроенную локаль в списке `remoteConfigs`. Выберите локаль, соответствующую предпочтениям пользователя, и считайте нужные значения. ```kotlin showLineNumbers Adapty.getFlow("YOUR_PLACEMENT_ID") .onSuccess { flow -> val config = flow.remoteConfigs.firstOrNull { it.locale == "en" } ?: flow.remoteConfigs.firstOrNull() val headerText = config?.dataMap?.get("header_text") as? String // use the remote config values } .onError { error -> // handle the error } ``` На этом этапе, получив все необходимые значения, можно приступать к отрисовке и сборке визуально привлекательного экрана. Убедитесь, что дизайн адаптирован под разные экраны и ориентации мобильных устройств — это обеспечит комфортный пользовательский опыт на любом девайсе. :::warning Обязательно [зафиксируйте событие просмотра пейвола](present-remote-config-paywalls-kmp#track-paywall-view-events), как описано ниже, чтобы аналитика Adapty могла собирать данные для воронок и A/B-тестов. ::: После того как пейвол отображён, переходите к настройке флоу покупки. Когда пользователь совершает покупку, просто вызовите `.makePurchase()` с продуктом из вашего флоу. Подробнее о методе `.makePurchase()` читайте в разделе [Совершение покупок](kmp-making-purchases). Рекомендуем [создать резервный пейвол](kmp-use-fallback-paywalls). Он будет показан пользователю при отсутствии интернета или кэша, обеспечивая бесперебойную работу даже в таких ситуациях. ## Отслеживание событий просмотра пейвола \{#track-paywall-view-events\} Adapty помогает измерять эффективность ваших флоу и пейволов. Данные о покупках мы собираем автоматически, а вот просмотры нужно логировать вручную — только вы знаете, когда пользователь видит флоу. Чтобы залогировать событие просмотра, вызовите `.logShowFlow(flow)` — и оно отобразится в ваших метриках воронок и A/B-тестов. :::important Вызывать `.logShowFlow(flow)` не нужно, если вы отображаете флоу или пейволы, созданные с помощью [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder). В этих случаях Adapty отслеживает просмотры автоматически. ::: ```kotlin showLineNumbers Adapty.logShowFlow(flow) .onSuccess { // flow view logged successfully } .onError { error -> // handle the error } ``` Параметры запроса: | Параметр | Наличие | Описание | | :-------- | :------- |:-----------------------------------------------------------------| | **flow** | обязательный | Объект `AdaptyFlow`, полученный через `Adapty.getFlow`. | </SDKv4> <SDKv3> Если вы настроили пейвол с помощью Remote Config, вам нужно реализовать его отображение в коде мобильного приложения. Поскольку Remote Config предоставляет гибкость под ваши нужды, вы сами решаете, что включить и как будет выглядеть пейвол. Мы предоставляем метод для получения Remote Config — вы самостоятельно управляете тем, как отображать пейвол, настроенный через Remote Config. ## Получение Remote Config пейвола и его отображение \{#get-paywall-remote-config-and-present-it\} Чтобы получить Remote Config пейвола, обратитесь к свойству `remoteConfig` и извлеките нужные значения. ```kotlin showLineNumbers Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { paywall -> val headerText = paywall.remoteConfig?.dataMap?.get("header_text") as? String // use the remote config values }.onError { error -> // handle the error } ``` На этом этапе, получив все необходимые значения, можно приступать к рендерингу и сборке визуально привлекательной страницы. Убедитесь, что дизайн адаптирован под различные экраны и ориентации мобильных телефонов — это обеспечит удобный пользовательский опыт на любых устройствах. :::warning Обязательно [зафиксируйте событие просмотра пейвола](present-remote-config-paywalls-kmp#track-paywall-view-events-1), как описано ниже, чтобы аналитика Adapty могла собирать данные для воронок и A/B-тестов. ::: После того как пейвол отображён, переходите к настройке процесса покупки. Когда пользователь совершает покупку, просто вызовите `.makePurchase()` с продуктом из вашего пейвола. Подробнее о методе `.makePurchase()` читайте в разделе [Совершение покупок](kmp-making-purchases). Рекомендуем [создать резервный пейвол](kmp-use-fallback-paywalls). Он будет отображаться пользователю при отсутствии интернет-соединения или кэша, обеспечивая бесперебойную работу даже в таких ситуациях. ## Отслеживание событий просмотра пейвола \{#track-paywall-view-events\} Adapty помогает измерять эффективность ваших пейволов. Данные о покупках собираются автоматически, но логирование просмотров пейвола требует вашего участия — только вы знаете, когда пользователь видит пейвол. Чтобы зафиксировать событие просмотра пейвола, вызовите `.logShowPaywall(paywall)` — это отразится в метриках пейвола в воронках и A/B-тестах. :::important Вызов `.logShowPaywall(paywall)` не нужен, если вы показываете пейволы, созданные в [Paywall Builder](adapty-paywall-builder). ::: ```kotlin showLineNumbers Adapty.logShowPaywall(paywall = paywall) .onSuccess { // paywall view logged successfully } .onError { error -> // handle the error } ``` Параметры запроса: | Параметр | Наличие | Описание | | :---------- | :------- |:-------------------------------------------------------------------------------------------------------| | **paywall** | required | Объект [`AdaptyPaywall`](https://kmp.adapty.io//////adapty/com.adapty.kmp.models/-adapty-paywall/). | </SDKv3> --- # File: kmp-making-purchases --- --- title: "Совершение покупок в мобильном приложении с помощью Kotlin Multiplatform SDK" description: "Руководство по обработке встроенных покупок и подписок с помощью Adapty." --- Отображение пейволов в мобильном приложении — обязательный шаг для предоставления пользователям доступа к премиум-контенту или сервисам. Однако само по себе отображение пейволов достаточно для поддержки покупок только в том случае, если вы используете [Paywall Builder](adapty-paywall-builder) для их настройки. Если вы не используете Paywall Builder, для совершения покупки и разблокировки нужного контента необходимо вызвать отдельный метод `.makePurchase()`. Он служит точкой входа для пользователей, которые взаимодействуют с пейволами и совершают нужные им транзакции. Если для продукта, который пользователь пытается купить, активен promotional offer, Adapty автоматически применит его в момент покупки. :::warning Обратите внимание: introductory offer применяется автоматически только в том случае, если вы используете пейволы, созданные с помощью Paywall Builder. В остальных случаях вам нужно [проверить право пользователя на получение introductory offer на iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios). Пропуск этого шага может привести к отклонению приложения при релизе. Кроме того, пользователи, имеющие право на introductory offer, могут быть списаны по полной цене. ::: Убедитесь, что вы выполнили [начальную настройку](quickstart), не пропустив ни одного шага. Без неё мы не сможем валидировать покупки. ## Совершение покупки \{#make-purchase\} :::note **Используете [Paywall Builder](adapty-paywall-builder)?** Покупки обрабатываются автоматически — этот шаг можно пропустить. **Нужны пошаговые инструкции?** Ознакомьтесь с [гайдом по быстрому старту](kmp-implement-paywalls-manually) — там описана полная реализация с подробным контекстом. ::: ```kotlin showLineNumbers Adapty.makePurchase(product = product).onSuccess { purchaseResult -> when (purchaseResult) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // Grant access to the paid features } } is AdaptyPurchaseResult.UserCanceled -> { // Handle the case where the user canceled the purchase } is AdaptyPurchaseResult.Pending -> { // Handle deferred purchases (e.g., the user will pay offline with cash) } } }.onError { error -> // Handle the error } ``` | Параметр | Наличие | Описание | | :---------- | :------- |:---------------------------------------------------------------------------------------------------------------------------------------------------| | **Product** | required | Объект [`AdaptyPaywallProduct`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-paywall-product/), полученный из пейвола. | Параметры ответа: | Параметр | Описание | |---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>Если запрос выполнен успешно, ответ содержит этот объект. Объект [AdaptyProfile](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-profile/) предоставляет исчерпывающую информацию об уровнях доступа пользователя, подписках и разовых покупках внутри приложения.</p><p>Проверьте статус уровня доступа, чтобы определить, есть ли у пользователя необходимый доступ к приложению.</p> | :::warning **Примечание:** если вы используете Apple StoreKit версии ниже v2.0 и Adapty SDK версии ниже v2.9.0, вам необходимо указать [общий секрет Apple App Store](app-store-connection-configuration#step-5-enter-app-store-shared-secret) вместо этого. Этот метод в настоящее время устарел по решению Apple. ::: ## Изменение подписки при совершении покупки \{#change-subscription-when-making-a-purchase\} Когда пользователь выбирает новую подписку вместо продления текущей, поведение зависит от стора. В Google Play подписка не обновляется автоматически. Вам нужно управлять переключением в коде мобильного приложения, как описано ниже. Чтобы заменить подписку другой в Android, вызовите метод `.makePurchase()` с дополнительным параметром: ```kotlin showLineNumbers val subscriptionUpdateParams = AdaptyAndroidSubscriptionUpdateParameters( oldSubVendorProductId = "old_subscription_product_id", replacementMode = AdaptyAndroidSubscriptionUpdateReplacementMode.CHARGE_FULL_PRICE ) val purchaseParams = AdaptyPurchaseParameters.Builder() .setSubscriptionUpdateParams(subscriptionUpdateParams) .build() Adapty.makePurchase( product = product, parameters = purchaseParams ).onSuccess { purchaseResult -> when (purchaseResult) { is AdaptyPurchaseResult.Success -> { val profile = purchaseResult.profile // successful cross-grade } is AdaptyPurchaseResult.UserCanceled -> { // user canceled the purchase flow } is AdaptyPurchaseResult.Pending -> { // the purchase has not been finished yet, e.g. user will pay offline by cash } } }.onError { error -> // Handle the error } ``` Дополнительный параметр запроса: | Параметр | Наличие | Описание | |:---------------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **parameters** | опционально | объект [`AdaptyAndroidSubscriptionUpdateParameters`](https://kmp.adapty.io/////adapty/com.adapty.kmp.models/-adapty-android-subscription-update-parameters/), передаваемый через [`AdaptyPurchaseParameters`](https://kmp.adapty.io/adapty/com.adapty.kmp.models/-adapty-purchase-parameters/). | Подробнее о подписках и режимах замены можно прочитать в документации Google для разработчиков: - [О режимах замены](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Рекомендации Google по режимам замены](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - Режим замены [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE()). Примечание: этот метод доступен только для апгрейда подписки. Даунгрейд не поддерживается. - Режим замены [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED()). Примечание: фактическая смена подписки произойдёт только по окончании текущего расчётного периода. ## Погашение промокодов в iOS \{#redeem-offer-codes-in-ios\} <Details> <summary>Об офферных кодах</summary> Офферные коды позволяют предоставлять скидки или бесплатные пробные периоды конкретным пользователям. В отличие от обычных офферов, которые применяются автоматически, офферные коды распространяются за пределами приложения — через email-рассылки, социальные сети или печатные материалы. Пользователи активируют их, вводя код в App Store, переходя по ссылке для активации или через диалог внутри приложения. Чтобы настроить офферные коды, откройте подписку в App Store Connect и перейдите в раздел **Offer Codes**. Вы можете создать [три вида](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes) офферных кодов: - **Free** — подписка бесплатна на заданный период, следующее продление — по полной цене. - **Pay as you go** — пользователь платит сниженную цену в каждом расчётном периоде на протяжении заданного срока, после чего подписка продлевается по полной цене. - **Pay up front** — пользователь единовременно платит сниженную цену за весь срок оффера, после чего подписка продлевается по полной цене. Добавлять офферные коды в Adapty не нужно. Apple помечает каждую транзакцию в период действия оффера категорией офферного кода. Это касается как первоначальной активации, так и всех последующих продлений со скидкой. Adapty обнаруживает метку и записывает каждую транзакцию с категорией оффера `offer_code`. Как только период оффера заканчивается и подписка продлевается по полной цене, метка исчезает. Вы можете фильтровать аналитику по типу оффера **Offer Code** в [дашборде Adapty](controls-filters-grouping-compare-proceeds). #### Устранение расхождений в выручке \{#revenue-discrepancy-troubleshooting\} Если транзакция по офферному коду отображается в Adapty по полной цене продукта вместо сниженной цены оффера, проверьте следующее в App Store Connect: - Для офферного кода настроены корректные цены для всех регионов, где пользователи могут его активировать. - Цена оффера задана для конкретной страны или региона пользователя. Apple передаёт региональную цену в транзакции. Если для оффера не настроена региональная цена, Apple может передать полную цену продукта. Вы можете фильтровать и проверять транзакции по офферным кодам в [дашборде Adapty](controls-filters-grouping-compare-proceeds) по фильтрам типа оффера **Offer Code** и **Offer Discount Type**. #### Устаревшие промокоды (deprecated) \{#legacy-promo-codes-deprecated\} :::warning Apple прекратила поддержку промокодов для встроенных покупок в марте 2026 года. Офферные коды заменяют их с расширенными возможностями: настраиваемые условия применения, сроки действия и до 1 миллиона кодов в квартал. Если вы ранее использовали промокоды для встроенных покупок, перейдите на офферные коды в App Store Connect. ::: Устаревшие промокоды (не более 100 на приложение на версию) предоставляли бесплатный доступ к подписке. В отличие от офферных кодов, Apple не включала информацию о скидке в транзакции по промокодам — в чеке указывалась полная цена продукта. В результате Adapty записывал эти транзакции по полной цене, что приводило к расхождениям в выручке между аналитикой Adapty и App Store Connect. Если вы видите исторические транзакции по полной цене, которые должны были быть бесплатными, скорее всего, они связаны с устаревшими промокодами. Поскольку эти коды больше не поддерживаются, перейдите на офферные коды для точного учёта выручки. </Details> Чтобы показать в приложении экран погашения промокодов: ```kotlin showLineNumbers Adapty.presentCodeRedemptionSheet() .onSuccess { // code redemption sheet presented successfully } .onError { error -> // handle the error } ``` :::danger По нашим наблюдениям, экран погашения промокодов в некоторых приложениях может работать нестабильно. Рекомендуем перенаправлять пользователя напрямую в App Store. Чтобы сделать это, нужно открыть URL следующего формата: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: ## Управление предоплаченными планами (Android) \{#manage-prepaid-plans-android\} Если пользователи вашего приложения могут приобретать [предоплаченные планы](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (например, неавтоматически возобновляемую подписку на несколько месяцев), вы можете включить [отложенные транзакции](https://developer.android.com/google/play/billing/subscriptions#pending) для предоплаченных планов. ```kotlin showLineNumbers Adapty.activate( AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withGoogleEnablePendingPrepaidPlans(true) .build() ).onSuccess { // successful activation }.onError { error -> // handle the error } ``` --- # File: kmp-restore-purchase --- --- title: "Восстановление покупок в мобильном приложении с помощью Kotlin Multiplatform SDK" description: "Узнайте, как восстанавливать покупки в Adapty для бесперебойного пользовательского опыта." --- Восстановление покупок — это функция, которая позволяет пользователям снова получить доступ к ранее приобретённому контенту (подпискам или встроенным покупкам) без повторного списания средств. Она особенно полезна для тех, кто удалил и переустановил приложение или перешёл на новое устройство и хочет вернуть доступ к уже купленному контенту. :::note В пейволах, созданных с помощью [Paywall Builder](adapty-paywall-builder), покупки восстанавливаются автоматически без дополнительного кода с вашей стороны. Если это ваш случай — данный шаг можно пропустить. ::: Чтобы восстановить покупку без использования [Paywall Builder](adapty-paywall-builder) для настройки пейвола, вызовите метод `.restorePurchases()`: ```kotlin showLineNumbers Adapty.restorePurchases().onSuccess { profile -> if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) { // successful access restore } }.onError { error -> // handle the error } ``` Параметры ответа: | Параметр | Описание | |---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>Объект [`AdaptyProfile`](https://kmp.adapty.io//////adapty/com.adapty.kmp.models/-adapty-profile/). Эта модель содержит информацию об уровнях доступа, подписках и разовых покупках.</p><p>Проверьте **статус уровня доступа**, чтобы определить, есть ли у пользователя доступ к приложению.</p> | :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: --- # File: implement-observer-mode-kmp --- --- title: "Реализация режима Observer в Kotlin Multiplatform SDK" description: "Реализуйте режим observer в Adapty для отслеживания событий подписок пользователей в Kotlin Multiplatform SDK." --- Если у вас уже есть собственная инфраструктура покупок и вы не готовы полностью переходить на Adapty, вы можете рассмотреть [Observer mode](observer-vs-full-mode). В базовом варианте Observer Mode предоставляет расширенную аналитику и бесшовную интеграцию с системами атрибуции и аналитики. Если это соответствует вашим потребностям, вам нужно лишь: 1. Включить его при настройке Adapty SDK, установив параметр `observerMode` в значение `true`. Следуйте инструкциям по настройке для [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform). 2. [Передавать транзакции](report-transactions-observer-mode-kmp) из вашей существующей инфраструктуры покупок в Adapty. :::tip В SDK v4 вы также можете отображать флоу и пейволы, отрисованные Adapty, в Observer mode: когда пользователь нажимает кнопку покупки или восстановления, SDK передаёт действие вашему коду, чтобы вы могли самостоятельно выполнить покупку или восстановление. См. [Отображение флоу в Observer mode](kmp-present-flows-in-observer-mode). ::: ## Настройка режима наблюдателя \{#observer-mode-setup\} Включите режим наблюдателя, если вы самостоятельно обрабатываете покупки и статус подписки, а Adapty используете только для отправки событий подписки и аналитики. :::important В режиме наблюдателя Adapty SDK не закрывает транзакции — убедитесь, что вы обрабатываете их самостоятельно. ::: ```kotlin showLineNumbers val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") .withObserverMode(true) // default false .build() Adapty.activate(configuration = config) .onSuccess { Log.d("Adapty", "SDK initialised in observer mode") } .onError { error -> Log.e("Adapty", "Adapty init error: ${error.message}") } ``` Параметры: | Параметр | Описание | | --------------------------- | ------------------------------------------------------------ | | observerMode | Булево значение, которое управляет [режимом Observer](observer-vs-full-mode). Значение по умолчанию — `false`. | ## Использование пейволов Adapty в режиме Observer \{#using-adapty-paywalls-in-observer-mode\} Если вы также хотите использовать пейволы Adapty и функции A/B-тестирования, это возможно — но в режиме Observer потребуется дополнительная настройка. Помимо шагов выше, вам нужно будет сделать следующее: 1. Отображайте пейволы как обычно для [пейволов на основе Remote Config](present-remote-config-paywalls-kmp). 3. [Связывайте пейволы](report-transactions-observer-mode-kmp) с транзакциями покупок. --- # File: report-transactions-observer-mode-kmp --- --- title: "Передача транзакций в Observer Mode в Kotlin Multiplatform SDK" description: "Передавайте транзакции покупок в Adapty Observer Mode для отслеживания пользовательских данных и доходов в Kotlin Multiplatform SDK." --- В Observer Mode SDK Adapty не может самостоятельно отслеживать покупки, совершённые через вашу существующую систему. Вам нужно передавать транзакции из стора вручную. Важно настроить это **до** выпуска приложения, чтобы избежать ошибок в аналитике. Используйте `reportTransaction`, чтобы явно сообщать Adapty о каждой транзакции. :::warning **Не пропускайте передачу транзакций!** Если вы не вызываете `reportTransaction`, Adapty не распознает транзакцию, она не появится в аналитике и не будет отправлена в интеграции. ::: Если вы используете пейволы Adapty, передавайте `variationId` при отчёте о транзакции. Это связывает покупку с пейволом, который её инициировал, и обеспечивает точную аналитику пейволов. ```kotlin showLineNumbers Adapty.reportTransaction( transactionId = "your_transaction_id", variationId = paywall.variationId ).onSuccess { profile -> // Transaction reported successfully // profile contains updated user data }.onError { error -> // handle the error } ``` Параметры: | Параметр | Обязательность | Описание | | --------------- | -------------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | transactionId | обязательный | Идентификатор транзакции из стора. Как правило, это токен покупки или идентификатор транзакции, возвращаемый стором. | | variationId | необязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://kmp.adapty.io//////adapty/com.adapty.kmp.models/-adapty-paywall/). | --- # File: kmp-troubleshoot-purchases --- --- title: "Устранение неполадок с покупками в Kotlin Multiplatform SDK" description: "Устранение неполадок с покупками в Kotlin Multiplatform SDK" --- Этот гайд поможет вам решить распространённые проблемы при ручной реализации покупок в Kotlin Multiplatform SDK. ## makePurchase выполняется успешно, но профиль не обновляется \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\} **Проблема**: Метод `makePurchase` завершается успешно, но профиль пользователя и статус подписки в Adapty не обновляются. **Причина**: Как правило, это указывает на неполную настройку Google Play Store или ошибки конфигурации. **Решение**: Убедитесь, что вы выполнили все [шаги настройки Google Play](initial-android). ## makePurchase вызывается дважды \{#makepurchase-is-invoked-twice\} **Проблема**: Метод `makePurchase` вызывается несколько раз для одной и той же покупки. **Причина**: Обычно это происходит из-за того, что процесс покупки запускается несколько раз вследствие проблем с управлением состоянием UI или быстрых повторных действий пользователя. **Решение**: Убедитесь, что вы выполнили все [шаги настройки Google Play](initial-android). ## AdaptyError.cantMakePayments в режиме Observer \{#adaptyelrorcantmakepayments-in-observer-mode\} **Проблема**: При использовании `makePurchase` в режиме Observer вы получаете ошибку `AdaptyError.cantMakePayments`. **Причина**: В режиме Observer покупки должны обрабатываться на вашей стороне — использовать метод `makePurchase` от Adapty не следует. **Решение**: Если вы используете `makePurchase` для покупок, отключите режим Observer. Нужно либо использовать `makePurchase`, либо обрабатывать покупки самостоятельно в режиме Observer. Подробнее см. в разделе [Реализация режима Observer](implement-observer-mode-kmp). ## Ошибка Adapty: (code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\} **Проблема**: Вы получаете ошибку недоступности биллинга от Google Play Store. **Причина**: Эта ошибка не связана с Adapty. Это ошибка Google Play Billing Library, указывающая на то, что биллинг недоступен на устройстве. **Решение**: Эта ошибка не связана с Adapty. Подробнее о ней можно узнать в документации Play Store: [Handle BillingResult response codes](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers. ## makePurchasesCompletionHandlers не найден \{#not-found-makepurchasescompletionhandlers\} **Проблема**: Вы сталкиваетесь с тем, что `makePurchasesCompletionHandlers` не удаётся найти. **Причина**: Как правило, это связано с проблемами при тестировании в песочнице. **Решение**: Создайте нового пользователя в песочнице и повторите попытку. Это обычно решает проблемы с обработчиком завершения покупки в песочнице. --- # File: kmp-user --- --- title: "Пользователи и доступ в Kotlin Multiplatform SDK" description: "Узнайте, как работать с пользователями и уровнями доступа в приложении Kotlin Multiplatform с помощью Adapty SDK." --- На этой странице собраны все гайды по работе с пользователями и уровнями доступа в приложении Kotlin Multiplatform. Выберите нужный раздел: - **[Идентификация пользователей](kmp-identifying-users)** — узнайте, как идентифицировать пользователей в приложении - **[Обновление данных пользователя](kmp-setting-user-attributes)** — задайте атрибуты пользователя и данные профиля - **[Отслеживание изменений статуса подписки](kmp-listen-subscription-changes)** — отслеживайте изменения подписки в режиме реального времени - **[Режим Kids Mode](kids-mode-kmp)** — реализуйте Kids Mode в своём приложении --- # File: kmp-identifying-users --- --- title: "Идентификация пользователей в Kotlin Multiplatform SDK" description: "Идентифицируйте пользователей в Adapty для улучшения персонализированного опыта с подписками." --- Adapty создаёт внутренний ID профиля для каждого пользователя. Однако если у вас есть собственная система аутентификации, вам следует задать свой Customer User ID. Вы можете найти пользователей по их Customer User ID в разделе [Профили](profiles-crm) и использовать его в [серверном API](getting-started-with-server-side-api) — он будет передаваться во все интеграции. ### Указание идентификатора пользователя при настройке \{#setting-customer-user-id-on-configuration\} Если идентификатор пользователя известен в момент настройки, передайте его в параметре `customerUserId` метода `.activate()`: ```kotlin showLineNumbers Adapty.activate( AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId("YOUR_USER_ID") .build() ).onSuccess { // successful activation }.onError { error -> // handle the error } } ``` :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Указание customer user ID после инициализации \{#setting-customer-user-id-after-configuration\} Если при настройке SDK у вас не было user ID, его можно задать позже в любой момент с помощью метода `.identify()`. Чаще всего этот метод используется после регистрации или авторизации — когда пользователь переходит из анонимного состояния в аутентифицированное. ```kotlin showLineNumbers Adapty.identify("YOUR_USER_ID").onSuccess { // successful identify }.onError { error -> // handle the error } ``` Параметры запроса: - **Customer User ID** (обязательный): строковый идентификатор пользователя. :::warning Повторная отправка важных данных пользователя В некоторых случаях, например когда пользователь снова входит в аккаунт, серверы Adapty уже располагают информацией об этом пользователе. В таких ситуациях Adapty SDK автоматически переключится на работу с новым пользователем. Если вы передавали какие-либо данные анонимному пользователю — например, пользовательские атрибуты или атрибуцию из сторонних сетей — необходимо повторно отправить эти данные для идентифицированного пользователя. Также важно учитывать, что после идентификации пользователя следует заново запросить все пейволы и продукты, поскольку данные нового пользователя могут отличаться. ::: ### Выход и вход в систему \{#logging-out-and-logging-in\} Вы можете выйти из системы в любой момент, вызвав метод `.logout()`: ```kotlin showLineNumbers Adapty.logout().onSuccess { // successful logout }.onError { error -> // handle the error } ``` После этого можно войти снова с помощью метода `.identify()`. ## Назначение `appAccountToken` (iOS) \{#assign-appaccounttoken-ios\} [`iosAppAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) — это **UUID**, который позволяет связать транзакции App Store с внутренним идентификатором пользователя. StoreKit прикрепляет этот токен к каждой транзакции, чтобы ваш бэкенд мог сопоставить данные App Store с конкретными пользователями. Используйте стабильный UUID, сгенерированный для каждого пользователя, и повторно применяйте его для одного и того же аккаунта на всех устройствах. Это гарантирует, что покупки и уведомления App Store будут корректно связаны с нужным пользователем. Токен можно передать двумя способами — при активации SDK или при идентификации пользователя. :::important Необходимо всегда передавать `iosAppAccountToken` вместе с `customerUserId`. Если передать только токен, он не будет включён в транзакцию. ::: ```kotlin showLineNumbers // During configuration: Adapty.activate( AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId( id = "YOUR_USER_ID", iosAppAccountToken = "YOUR_IOS_APP_ACCOUNT_TOKEN" ) .build() ).onSuccess { // successful activation }.onError { error -> // handle the error } // Or when identifying users Adapty.identify( customerUserId = "YOUR_USER_ID", iosAppAccountToken = "YOUR_IOS_APP_ACCOUNT_TOKEN" ).onSuccess { // successful identify }.onError { error -> // handle the error } ``` ## Установка обфусцированных идентификаторов аккаунта (Android) \{#set-obfuscated-account-ids-android\} Google Play требует обфусцированные идентификаторы аккаунта в ряде сценариев — для защиты конфиденциальности и безопасности пользователей. Эти идентификаторы позволяют Google Play отслеживать покупки, не раскрывая личные данные пользователей, что особенно важно для предотвращения мошенничества и аналитики. Задавать такие идентификаторы может потребоваться, если приложение обрабатывает чувствительные пользовательские данные или должно соответствовать определённым требованиям по защите персональных данных. Обфусцированные идентификаторы позволяют Google Play отслеживать покупки, не раскрывая реальные идентификаторы пользователей. :::important Вы всегда должны передавать `androidObfuscatedAccountId` вместе с `customerUserId`. Если передать только obfuscated account ID, он не будет включён в транзакцию. ::: ```kotlin showLineNumbers // During configuration: Adapty.activate( AdaptyConfig.Builder("PUBLIC_SDK_KEY") .withCustomerUserId( id = "YOUR_USER_ID", androidObfuscatedAccountId = "YOUR_OBFUSCATED_ACCOUNT_ID" ) .build() ).onSuccess { // successful activation }.onError { error -> // handle the error } // Or when identifying users Adapty.identify( customerUserId = "YOUR_USER_ID", androidObfuscatedAccountId = "YOUR_OBFUSCATED_ACCOUNT_ID" ).onSuccess { // successful identify }.onError { error -> // handle the error } ``` ## Определение пользователей на разных устройствах \{#detect-users-across-devices\} При активации SDK он автоматически считывает существующие права пользователя из StoreKit (iOS) или Google Play Billing (Android) и синхронизирует их с бэкендом Adapty. Активная подписка появляется в профиле Adapty без вызова `restorePurchases` со стороны приложения. Что **не** происходит автоматически — так это распознавание того, что профиль на новом устройстве принадлежит тому же пользователю, что и профиль на исходном устройстве. Adapty сопоставляет профили по Customer User ID, поэтому непрерывность идентификации зависит от того, что вы используете в качестве CUID. **Что Adapty может определить между устройствами** | Ваша настройка | Что Adapty определяет | Что нужно сделать | | --- | --- | --- | | Customer User ID = `device_id` (без входа в аккаунт) | Новое устройство получает другой CUID и, следовательно, другой профиль. Подписка синхронизируется с новым профилем через событие **Access level updated**, но `subscription_started` не срабатывает — новый профиль считается наследником исходной покупки. Аналитика, основанная на `subscription_started`, будет занижать количество возвращающихся пользователей. | Используйте стабильный идентификатор аккаунта в качестве Customer User ID, чтобы вернувшийся пользователь соответствовал существующему профилю на разных устройствах. | | Customer User ID = стабильный идентификатор аккаунта (вход на каждом устройстве) | SDK автоматически синхронизирует подписку при `activate()`, а `identify()` сопоставляет существующий профиль по CUID. | Никаких дополнительных действий не требуется — и идентификация, и подписка разрешаются автоматически. | | Наследник Apple Family Sharing | Член семьи получает подписку только через событие **Access level updated** — `subscription_started` не срабатывает. | Отслеживайте событие **Access level updated**. Полную матрицу событий см. в разделе [Apple Family Sharing](apple-family-sharing). | | Один аккаунт Apple/Google, разные пользователи внутри приложения | Первый профиль, зафиксировавший покупку, становится родительским. Последующие профили видят подписку через цепочку наследования с одним событием **Access level updated**. | Требуйте входа в аккаунт, затем выберите [режим совместного использования](sharing-paid-access-between-user-accounts), подходящий для вашей модели. | **Восстановление покупок на новом устройстве** Добавьте кнопку «Восстановить покупки», инициируемую пользователем, на свой пейвол. Apple App Review (руководство 3.1.1) её требует, и она служит запасным вариантом на случай, если автоматическая синхронизация пропустит граничный случай. Кнопка должна вызывать `restorePurchases` в вашем SDK. Программный вызов `restorePurchases` при первом запуске не нужен для обычного использования — SDK уже выполняет аналогичную операцию при `activate()`. Программные вызовы стоит использовать только для принудительной проверки чека, например при отладке отсутствующего доступа после завершения `activate()`. --- # File: kmp-setting-user-attributes --- --- title: "Установка атрибутов пользователя в Kotlin Multiplatform SDK" description: "Узнайте, как устанавливать атрибуты пользователя в Adapty для улучшенной сегментации аудитории." --- Вы можете задавать пользователям приложения необязательные атрибуты: email, номер телефона и т.д. Атрибуты можно использовать для создания [сегментов](segments) пользователей или просматривать их в CRM. ### Установка атрибутов пользователя \{#setting-user-attributes\} Чтобы установить атрибуты пользователя, вызовите метод `.updateProfile()`: ```kotlin showLineNumbers val builder = AdaptyProfileParameters.Builder() .withEmail("email@email.com") .withPhoneNumber("+18888888888") .withFirstName("John") .withLastName("Appleseed") .withGender(AdaptyProfile.Gender.FEMALE) .withBirthday(AdaptyProfile.Date(1970, 1, 3)) Adapty.updateProfile(builder.build()) .onSuccess { // profile updated successfully } .onError { error -> // handle the error } ``` Обратите внимание: атрибуты, ранее установленные с помощью метода `updateProfile`, не сбрасываются. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Список допустимых ключей \{#the-allowed-keys-list\} Допустимые ключи `<Key>` для `AdaptyProfileParameters.Builder` и соответствующие значения `<Value>` перечислены ниже: | Ключ | Значение | |---|-----| | <p>email</p><p>phoneNumber</p><p>firstName</p><p>lastName</p> | String | | gender | Enum, допустимые значения: `AdaptyProfile.Gender.FEMALE`, `AdaptyProfile.Gender.MALE`, `AdaptyProfile.Gender.OTHER` | | birthday | Date | ### Пользовательские атрибуты \{#custom-user-attributes\} Вы можете задавать собственные пользовательские атрибуты — как правило, они связаны с использованием приложения. Например, для фитнес-приложений это может быть количество тренировок в неделю, для приложений для изучения языков — уровень знаний пользователя и т.д. Их можно использовать в сегментах для создания целевых пейволов и предложений, а также в аналитике для выявления продуктовых метрик, которые сильнее всего влияют на выручку. ```kotlin showLineNumbers val builder = AdaptyProfileParameters.Builder() builder.withCustomAttribute("key1", "value1") ``` Чтобы удалить существующий ключ, используйте метод `.withRemovedCustomAttribute()`: ```kotlin showLineNumbers val builder = AdaptyProfileParameters.Builder() builder.withRemovedCustomAttribute("key2") ``` Иногда нужно узнать, какие пользовательские атрибуты уже установлены. Для этого используйте поле `customAttributes` объекта `AdaptyProfile`. :::warning Имейте в виду, что значение `customAttributes` может быть устаревшим: атрибуты пользователя могут отправляться с разных устройств в любое время, поэтому данные на сервере могут измениться после последней синхронизации. ::: ### Ограничения \{#limits\} - До 30 пользовательских атрибутов на пользователя - Длина имени ключа — до 30 символов. Имя ключа может содержать буквенно-цифровые символы, а также: `_` `-` `.` - Значение может быть строкой или числом с плавающей точкой длиной не более 50 символов. --- # File: kmp-listen-subscription-changes --- --- title: "Проверка статуса подписки в Kotlin Multiplatform SDK" description: "Отслеживайте и управляйте статусом подписки пользователей в Adapty для повышения удержания клиентов в вашем приложении на Kotlin Multiplatform." --- Adapty упрощает отслеживание статуса подписки. Вам не нужно вручную прописывать ID продуктов в коде — достаточно проверить наличие активного [уровня доступа](access-level), чтобы убедиться, что у пользователя есть подписка. Прежде чем приступить к проверке статуса подписки, настройте [Real-time Developer Notifications (RTDN)](enable-real-time-developer-notifications-rtdn). ## Уровень доступа и объект AdaptyProfile \{#access-level-and-the-adaptyprofile-object\} Уровни доступа — это свойства объекта [AdaptyProfile](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-profile/). Рекомендуем получать профиль при запуске приложения, например когда вы [идентифицируете пользователя](android-identifying-users#setting-customer-user-id-on-configuration), и обновлять его при каждом изменении. Так вы сможете использовать объект профиля без повторных запросов. Чтобы получать уведомления об обновлениях профиля, подпишитесь на изменения профиля, как описано в разделе [Подписка на обновления профиля, включая уровни доступа](android-listen-subscription-changes) ниже. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ## Получение уровня доступа с сервера \{#retrieving-the-access-level-from-the-server\} Чтобы получить уровень доступа с сервера, используйте метод `.getProfile()`: ```kotlin showLineNumbers Adapty.getProfile().onSuccess { profile -> // check the access }.onError { error -> // handle the error } ``` Параметры ответа: | Параметр | Описание | | --------- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Profile | <p>Объект [AdaptyProfile](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-profile/). Как правило, достаточно проверить статус уровня доступа профиля, чтобы определить, есть ли у пользователя премиум-доступ к приложению.</p><p></p><p>Метод `.getProfile` возвращает наиболее актуальные данные, поскольку всегда пытается запросить API. Если по какой-либо причине (например, при отсутствии интернета) Adapty SDK не может получить информацию с сервера, возвращаются данные из кэша. Также важно учитывать, что Adapty SDK регулярно обновляет кэш `AdaptyProfile`, чтобы поддерживать данные в актуальном состоянии.</p> | Метод `.getProfile()` возвращает профиль пользователя, из которого можно получить статус уровня доступа. В одном приложении может быть несколько уровней доступа. Например, в приложении-газете с независимыми подписками на разные рубрики можно создать уровни доступа «sports» и «science». Однако в большинстве случаев достаточно одного уровня доступа — тогда можно просто использовать стандартный уровень «premium». Вот пример проверки стандартного уровня доступа «premium»: ```kotlin showLineNumbers Adapty.getProfile().onSuccess { profile -> if (profile.accessLevels["premium"]?.isActive == true) { // grant access to premium features } }.onError { error -> // handle the error } ``` ### Подписка на обновления статуса подписки \{#listening-for-subscription-status-updates\} При каждом изменении подписки пользователя Adapty генерирует событие. Чтобы получать сообщения от Adapty, необходима дополнительная настройка: ```kotlin showLineNumbers Adapty.setOnProfileUpdatedListener { profile -> // handle any changes to subscription state } ``` Adapty также генерирует событие при запуске приложения. В этом случае передаётся кэшированный статус подписки. ### Кэш статуса подписки \{#subscription-status-cache\} Кэш, реализованный в Adapty SDK, хранит статус подписки профиля. Это означает, что даже при недоступности сервера кэшированные данные позволяют получить информацию о статусе подписки профиля. Следует учитывать, что напрямую запрашивать данные из кэша невозможно. SDK периодически опрашивает сервер каждую минуту, проверяя наличие обновлений или изменений в профиле. При обнаружении изменений — например, новых транзакций или других обновлений — они отправляются в кэшированные данные для синхронизации с сервером. --- # File: kmp-deal-with-att --- --- title: "Работа с ATT в Kotlin Multiplatform SDK" description: "Начните работу с Adapty на Kotlin Multiplatform для упрощения настройки подписок и управления ими." --- Если ваше приложение использует фреймворк AppTrackingTransparency и показывает пользователю запрос на авторизацию отслеживания, необходимо отправить [статус авторизации](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/) в Adapty. ```kotlin showLineNumbers val profileParameters = AdaptyProfileParameters.Builder() .withAttStatus(3) // 3 = ATTrackingManagerAuthorizationStatusAuthorized .build() Adapty.updateProfile(profileParameters) .onSuccess { // ATT status updated successfully } .onError { error -> // handle AdaptyError } ``` :::warning Настоятельно рекомендуем передавать это значение как можно раньше при его изменении — только в этом случае данные будут своевременно отправлены в настроенные вами интеграции. ::: --- # File: kids-mode-kmp --- --- title: "Детский режим в Kotlin Multiplatform SDK" description: "Легко включите детский режим для соблюдения политик Google. GAID и рекламные данные не собираются в Kotlin Multiplatform SDK." --- Если ваше приложение на Kotlin Multiplatform предназначено для детей, вы обязаны соблюдать политики [Google](https://support.google.com/googleplay/android-developer/answer/9893335). Если вы используете Adapty SDK, несколько простых шагов помогут настроить его в соответствии с этими политиками и пройти проверку в сторах. ## Что нужно сделать? \{#whats-required\} Необходимо настроить Adapty SDK так, чтобы отключить сбор: - [IDFA (Identifier for Advertisers)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) (iOS) - [Android Advertising ID (AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248) (Android) - [IP-адреса](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Кроме того, рекомендуем осторожно использовать пользовательский идентификатор (customer user ID). Идентификатор в формате `<FirstName.LastName>` однозначно будет расценён как сбор персональных данных — так же, как и использование email. Для детского режима рекомендуется использовать случайные или анонимизированные идентификаторы (например, хэшированные ID или UUID, сгенерированные на устройстве). ## Включение детского режима \{#enabling-kids-mode\} ### Настройки в дашборде Adapty \{#updates-in-the-adapty-dashboard\} В дашборде Adapty необходимо отключить сбор IP-адресов. Перейдите в [App settings](https://app.adapty.io/settings/general) и нажмите **Disable IP address collection** в разделе **Collect users' IP address**. ### Изменения в коде приложения \{#updates-in-your-mobile-app-code\} Для соблюдения политик нужно отключить сбор Android Advertising ID (AAID/GAID) и IP-адреса при инициализации Adapty SDK: ```kotlin showLineNumbers override fun onCreate() { super.onCreate() val config = AdaptyConfig .Builder("PUBLIC_SDK_KEY") // highlight-start .withGoogleAdvertisingIdCollectionDisabled(true) // set to `true` .withIpAddressCollectionDisabled(true) // set to `true` // highlight-end .build() Adapty.activate(configuration = config) .onSuccess { Log.d("Adapty", "SDK initialised with privacy settings") } .onError { error -> Log.e("Adapty", "Adapty init error: ${error.message}") } } ``` --- # File: kmp-onboardings --- --- title: "Онбординги в Kotlin Multiplatform SDK" description: "Узнайте, как работать с онбордингами в приложении Kotlin Multiplatform с помощью Adapty SDK." --- :::warning **Онбординги устарели в SDK v4 и будут удалены в одном из следующих релизов.** Они больше не получают исправлений и улучшений. Используйте [флоу](kmp-get-pb-paywalls) вместо них: в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает плавные анимации, единый нативный внешний вид, быструю загрузку и отсутствие зависимости от WebView. Смотрите [Получение флоу и пейволов](kmp-get-pb-paywalls) и [Отображение флоу и пейволов](kmp-present-paywalls), чтобы начать. ::: <CustomDocCardList /> --- # File: kmp-get-onboardings --- --- title: "Получение онбордингов в Kotlin Multiplatform SDK" description: "Узнайте, как получать онбординги в Adapty для Kotlin Multiplatform." --- :::warning **Онбординги устарели в SDK v4 и будут удалены в одном из следующих релизов.** Они больше не получают исправлений и улучшений. Используйте [флоу](kmp-get-pb-paywalls) вместо них: в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает плавную анимацию, единообразный нативный внешний вид, быструю загрузку и отсутствие зависимости от WebView. См. [Получение флоу и пейволов](kmp-get-pb-paywalls) и [Отображение флоу и пейволов](kmp-present-paywalls), чтобы начать работу. ::: После того как вы [разработали визуальную часть онбординга](design-onboarding) с помощью конструктора в дашборде Adapty, его можно отобразить в вашем Kotlin Multiplatform приложении. Первый шаг — получить онбординг, связанный с плейсментом, и его конфигурацию отображения, как описано ниже. Прежде чем начать, убедитесь, что: 1. Вы установили [Adapty Kotlin Multiplatform SDK](sdk-installation-kotlin-multiplatform) версии 3.15.0 или выше. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). ## Получение онбординга \{#fetch-onboarding\} Когда вы создаёте [онбординг](onboardings) в нашем no-code конструкторе, он сохраняется как контейнер с конфигурацией, которую ваше приложение должно загрузить и отобразить. Этот контейнер управляет всем процессом: какой контент показывается, как он отображается и как обрабатываются действия пользователя (например, ответы на вопросы или ввод данных в форму). Контейнер также автоматически отслеживает аналитические события, поэтому реализовывать отдельное отслеживание просмотров не нужно. Для наилучшей производительности загружайте конфигурацию онбординга заблаговременно, чтобы изображения успели скачаться до того, как их увидит пользователь. Чтобы получить онбординг, используйте метод `getOnboarding`: ```kotlin showLineNumbers Adapty.getOnboarding( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, loadTimeout = 5.seconds ).onSuccess { paywall -> // the requested paywall }.onError { error -> // handle the error } ``` Параметры: | Параметр | Обязательность | Описание | |---------|--------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указывали при создании плейсмента в дашборде Adapty. | | **locale** | <p>необязательный</p><p>по умолчанию: `en`</p> | Идентификатор локализации онбординга. Ожидается языковой код, состоящий из одного или двух подтегов, разделённых символом минус (**-**). Первый подтег — язык, второй — регион.<p>Например: `en` — английский, `pt-br` — бразильский португальский.</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера, а в случае ошибки возвращает кешированные данные. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите вариант `.returnCacheDataElseLoad` — он возвращает кешированные данные при их наличии. В этом случае пользователи могут получать не самые последние данные, зато загрузка будет быстрее независимо от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.</p><p></p><p>Adapty SDK хранит онбординги локально в двух слоях: регулярно обновляемый кеш, описанный выше, и резервные онбординги. Также используется CDN для ускоренной загрузки онбордингов и отдельный резервный сервер на случай недоступности CDN. Эта система гарантирует, что вы всегда получаете актуальную версию онбордингов даже при нестабильном интернет-соединении.</p> | | **loadTimeout** | по умолчанию: 5 сек | <p>Это значение ограничивает тайм-аут для данного метода. По истечении тайм-аута будут возвращены кешированные данные или локальный резервный вариант.</p><p>Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения, заданного в `loadTimeout`, поскольку операция может включать несколько внутренних запросов.</p> | ## Параметры ответа \{#response-parameters\} | Параметр | Описание | |:----------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Объект [`AdaptyOnboarding`](https://kmp.adapty.io///adapty/com.adapty.kmp.models/-adapty-onboarding/) с: идентификатором и конфигурацией онбординга, Remote Config и рядом других свойств. | ## Ускорьте загрузку онбординга с помощью онбординга для аудитории по умолчанию \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} Как правило, онбординги загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и онбордингов, а пользователи находятся при слабом интернет-соединении, загрузка онбординга может занять больше времени, чем хотелось бы. В таких ситуациях имеет смысл отображать онбординг по умолчанию — чтобы обеспечить плавный пользовательский опыт вместо того, чтобы не показывать ничего. Чтобы решить эту проблему, вы можете использовать метод `getOnboardingForDefaultAudience`, который загружает онбординг указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — загружать онбординг с помощью метода `getOnboarding`, как описано в разделе [Загрузка онбординга](#fetch-onboarding) выше. :::warning Используйте `getOnboarding` вместо `getOnboardingForDefaultAudience`, так как у последнего есть существенные ограничения: - **Проблемы совместимости**: могут возникать трудности при поддержке нескольких версий приложения — придётся либо делать дизайн обратно совместимым, либо мириться с некорректным отображением в старых версиях. - **Отсутствие персонализации**: показывается только контент для аудитории «Все пользователи», без таргетинга по стране, атрибуции или пользовательским атрибутам. Если для вашего сценария скорость загрузки важнее этих недостатков, используйте `getOnboardingForDefaultAudience`, как показано ниже. В противном случае используйте `getOnboarding`, как описано [выше](#fetch-onboarding). ::: ```kotlin showLineNumbers Adapty.getOnboardingForDefaultAudience( placementId = "YOUR_PLACEMENT_ID", locale = "en", fetchPolicy = AdaptyPaywallFetchPolicy.Default, ).onSuccess { paywall -> // запрошенный онбординг }.onError { error -> // обработайте ошибку } ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | Идентификатор локализации онбординга. Ожидается языковой код, состоящий из одного или двух подтегов, разделённых знаком минус (**-**). Первый подтег обозначает язык, второй — регион.<br/>Например: `en` означает английский, `pt-br` — бразильский португальский. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите вариант `.returnCacheDataElseLoad` — он возвращает кешированные данные, если они есть. В этом случае пользователи могут получать не самые свежие данные, но загрузка будет быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы сократить количество сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется после перезапуска приложения и очищается только при его переустановке или ручной очистке.</p><p></p><p>Adapty SDK хранит онбординги локально в двух слоях: регулярно обновляемый кеш, описанный выше, и резервные онбординги. Мы также используем CDN для более быстрой загрузки онбордингов и отдельный резервный сервер на случай недоступности CDN. Система спроектирована так, чтобы вы всегда получали актуальную версию онбордингов, даже при нестабильном интернет-соединении.</p> | --- # File: kmp-present-onboardings --- --- title: "Показ онбординга в Kotlin Multiplatform SDK" description: "Узнайте, как эффективно показывать онбординги и повышать конверсию." --- :::warning **Онбординги устарели в SDK v4 и будут удалены в одном из следующих релизов.** Они больше не получают исправлений и улучшений. Используйте [флоу](kmp-get-pb-paywalls) вместо них: в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает более плавные анимации, единый нативный внешний вид, быструю загрузку и отсутствие зависимости от WebView. Подробнее — в разделах [Получение флоу и пейволов](kmp-get-pb-paywalls) и [Отображение флоу и пейволов](kmp-present-paywalls). ::: Если вы создали онбординг с помощью билдера, вам не нужно беспокоиться о его отрисовке в коде вашего Kotlin Multiplatform приложения для показа пользователю. Такой онбординг содержит как то, что должно быть показано, так и то, как именно это должно быть показано. Прежде чем начать, убедитесь, что: 1. Вы установили [Adapty Kotlin Multiplatform SDK](sdk-installation-kotlin-multiplatform) версии 3.16.1 или выше. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). Adapty Kotlin Multiplatform SDK предоставляет два способа отображения онбординга: - **С Compose Multiplatform** - **Без Compose Multiplatform** ## С Compose Multiplatform \{#with-compose-multiplatform\} Чтобы отобразить онбординг, вызовите метод `view.present()` на объекте `view`, созданном методом `createOnboardingView`. Каждый `view` можно использовать только один раз. Если нужно показать онбординг снова, вызовите `createOnboardingView` ещё раз, чтобы создать новый экземпляр `view`. :::warning Повторное использование одного и того же `view` без его пересоздания может привести к ошибке. ::: ```kotlin showLineNumbers title="Kotlin Multiplatform" viewModelScope.launch { AdaptyUI.createOnboardingView(onboarding = onboarding).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` ### Настройка стиля представления для iOS \{#configure-ios-presentation-style\} Настройте способ отображения онбординга на iOS, передав параметр `iosPresentationStyle` в метод `present()`. Параметр принимает значения `AdaptyUIIOSPresentationStyle.FULLSCREEN` (по умолчанию) или `AdaptyUIIOSPresentationStyle.PAGESHEET`. ```kotlin showLineNumbers viewModelScope.launch { val view = AdaptyUI.createOnboardingView(onboarding = onboarding).getOrNull() view?.present(iosPresentationStyle = AdaptyUIIOSPresentationStyle.PAGESHEET) } ``` ### Настройка открытия ссылок в онбордингах \{#customize-how-links-open-in-onboardings\} По умолчанию ссылки в онбордингах открываются во встроенном браузере. Это обеспечивает удобный пользовательский опыт: веб-страницы отображаются прямо внутри приложения, и пользователю не нужно переключаться между приложениями. Если вы хотите открывать ссылки во внешнем браузере, задайте параметру `externalUrlsPresentation` значение `AdaptyWebPresentation.EXTERNAL_BROWSER`: ```kotlin showLineNumbers viewModelScope.launch { AdaptyUI.createOnboardingView( onboarding = onboarding, externalUrlsPresentation = AdaptyWebPresentation.EXTERNAL_BROWSER // default – IN_APP_BROWSER ).onSuccess { view -> view.present() }.onError { error -> // handle the error } } ``` ## Без Compose Multiplatform :::note `createNativeOnboardingView` входит в состав основного модуля `io.adapty:adapty-kmp`. Если ваш проект не использует Compose Multiplatform, зависимость `io.adapty:adapty-kmp-ui` не нужна. ::: Чтобы встроить онбординг без Compose Multiplatform, вызовите `createNativeOnboardingView`. Метод возвращает `AdaptyNativeOnboardingView`, который вы добавляете в свой лейаут: <Tabs> <TabItem value="android" label="Android"> ```kotlin showLineNumbers title="Kotlin Multiplatform (Android)" val nativeView = AdaptyUI.createNativeOnboardingView( context = context, viewModelStoreOwner = activity, onboarding = onboarding, observer = myOnboardingObserver, ) // Embed in your Compose layout: AndroidView( factory = { nativeView.view }, modifier = Modifier.fillMaxSize() ) ``` </TabItem> <TabItem value="ios" label="iOS"> Поскольку методы по умолчанию в KMP-интерфейсах становятся `@required` в Swift, вы не можете напрямую реализовать `AdaptyUIOnboardingsEventsObserver` из Swift. Сначала объявите открытый базовый класс в `iosMain`: ```kotlin showLineNumbers title="iosMain (Kotlin)" open class BaseOnboardingObserver : AdaptyUIOnboardingsEventsObserver ``` Затем создайте подкласс в Swift, переопределив только нужное: ```swift showLineNumbers title="Swift" class MyOnboardingObserver: BaseOnboardingObserver { override func onboardingViewOnCloseAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, actionId: String ) { // remove nativeView from your view hierarchy } } let nativeView = AdaptyUI.shared.createNativeOnboardingView( onboarding: onboarding, observer: MyOnboardingObserver() ) // nativeView.viewController is a UIViewController. // Add it to your SwiftUI view or UIKit hierarchy. ``` </TabItem> </Tabs> ### Освобождение ресурсов \{#dispose-the-view\} Вызывайте `dispose()` при удалении view из макета. Это отписывает обработчик событий и освобождает внутренние ресурсы. ```kotlin showLineNumbers title="Kotlin Multiplatform" nativeView.dispose() ``` --- # File: kmp-handling-onboarding-events --- --- title: "Обработка событий онбординга в Kotlin Multiplatform SDK" description: "Обработка событий, связанных с онбордингом, в Kotlin Multiplatform с использованием Adapty." --- :::warning **Онбординги устарели в SDK v4 и будут удалены в одном из следующих релизов.** Они больше не получают исправлений и улучшений. Используйте [флоу](kmp-get-pb-paywalls) вместо них: в отличие от онбордингов, работающих внутри WebView, флоу рендерятся нативно на устройстве — обеспечивая более плавные анимации, единый нативный внешний вид, быструю загрузку и отсутствие зависимости от WebView. Подробнее читайте в разделах [Получение флоу и пейволов](kmp-get-pb-paywalls) и [Отображение флоу и пейволов](kmp-present-paywalls). ::: Прежде чем начать, убедитесь, что: 1. Вы установили [Adapty Kotlin Multiplatform SDK](sdk-installation-kotlin-multiplatform) версии 3.15.0 или выше. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). Онбординги, настроенные в билдере, генерируют события, на которые ваше приложение может реагировать. Ниже описано, как с ними работать. ## Настройка обозревателя событий онбординга \{#set-up-the-onboarding-event-observer\} Чтобы обрабатывать события онбординга, нужно реализовать интерфейс `AdaptyUIOnboardingsEventsObserver` и зарегистрировать его через `AdaptyUI.setOnboardingsEventsObserver()`. Это следует делать на ранних этапах жизненного цикла приложения — как правило, в главной activity или при инициализации приложения. ```kotlin // In your app initialization AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` ## Пользовательские действия \{#custom-actions\} В конструкторе вы можете добавить к кнопке действие **custom** и назначить ему идентификатор. Затем этот идентификатор можно использовать в коде и обрабатывать как пользовательское действие. <img src={require('./img/ios-events-1.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Например, если пользователь нажимает кастомную кнопку — **Login** или **Allow notifications** — вызывается метод делегата `onCustomAction` с идентификатором действия из билдера. Идентификаторы задаёте вы сами, например `"allowNotifications"`. ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnCustomAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, actionId: String ) { when (actionId) { "openPaywall" -> { // Показ пейвола из онбординга // Обычно здесь нужно получить и отобразить новый пейвол mainUiScope.launch { // Пример: получение пейвола по ID плейсмента // val paywallResult = Adapty.getPaywall("your_placement_id") // paywallResult.onSuccess { paywall -> // val paywallViewResult = AdaptyUI.createPaywallView(paywall) // paywallViewResult.onSuccess { paywallView -> // paywallView.present() // } // } } } "allowNotifications" -> { // Обработка разрешений на уведомления } else -> { // Обработка других пользовательских действий } } } } // Настройка наблюдателя AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ``` </Details> ## Закрытие онбординга \{#closing-onboarding\} Онбординг считается закрытым, когда пользователь нажимает кнопку с назначенным действием **Close**. Вам нужно управлять тем, что происходит при закрытии онбординга. Например: :::important Вам нужно управлять тем, что происходит при закрытии онбординга. Например, нужно прекратить отображение самого онбординга. ::: Если вы используете [`createNativeOnboardingView`](kmp-present-onboardings#without-compose-multiplatform), `view.isStandaloneView` имеет значение `false` — реализация по умолчанию не вызывает `view.dismiss()`. Вместо этого удалите представление из разметки и вызовите `dispose()` в данном колбэке. ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnCloseAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, actionId: String ) { // Dismiss the onboarding screen mainUiScope.launch { view.dismiss() } // Additional cleanup or navigation logic can be added here // For example, navigate back or show main app content } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> ## Открытие пейвола \{#opening-a-paywall\} :::tip Обработайте это событие, чтобы открыть пейвол внутри онбординга. Если вы хотите открыть пейвол после его закрытия, есть более простой способ — обработайте [`onboardingViewOnCloseAction`](#closing-onboarding) и откройте пейвол, не опираясь на данные события. ::: Самый удобный подход — задать ID действия равным ID плейсмента пейвола. Тогда можно сразу использовать ID плейсмента, чтобы получить и открыть нужный пейвол: ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnPaywallAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, actionId: String ) { // Get the paywall using the placement ID from the action mainUiScope.launch { val paywallResult = Adapty.getPaywall(placementId = actionId) paywallResult.onSuccess { paywall -> val paywallViewResult = AdaptyUI.createPaywallView(paywall) paywallViewResult.onSuccess { paywallView -> paywallView.present() }.onError { error -> // handle the error } }.onError { error -> // handle the error } } } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ``` </Details> ## Завершение загрузки онбординга \{#finishing-loading-onboarding\} Когда онбординг завершает загрузку, вызывается этот метод: ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewDidFinishLoading( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta ) { // Handle loading completion // You can add any initialization logic here } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ``` </Details> ## События навигации \{#navigation-events\} Метод `onboardingViewOnAnalyticsEvent` вызывается при возникновении различных аналитических событий во время флоу онбординга. Объект `event` может быть одного из следующих типов: |Тип | Описание | |------------|-------------| | `AdaptyOnboardingsAnalyticsEventOnboardingStarted` | Онбординг загружен | | `AdaptyOnboardingsAnalyticsEventScreenPresented` | Показан любой экран | | `AdaptyOnboardingsAnalyticsEventScreenCompleted` | Экран завершён. Включает необязательный `elementId` (идентификатор завершённого элемента) и необязательный `reply` (ответ пользователя). Срабатывает, когда пользователь выполняет любое действие для выхода с экрана. | | `AdaptyOnboardingsAnalyticsEventSecondScreenPresented` | Показан второй экран | | `AdaptyOnboardingsAnalyticsEventUserEmailCollected` | Срабатывает, когда email пользователя собран через поле ввода | | `AdaptyOnboardingsAnalyticsEventOnboardingCompleted` | Срабатывает, когда пользователь достигает экрана с идентификатором `final`. Если вам нужно это событие, присвойте идентификатор `final` последнему экрану. | | `AdaptyOnboardingsAnalyticsEventUnknown` | Для любого нераспознанного типа события. Включает `name` (название неизвестного события) и `meta` (дополнительные метаданные) | Каждое событие содержит мета-информацию `meta` со следующими полями: | Поле | Описание | |------------|-------------| | `onboardingId` | Уникальный идентификатор флоу онбординга | | `screenClientId` | Идентификатор текущего экрана | | `screenIndex` | Позиция текущего экрана во флоу | | `screensTotal` | Общее количество экранов во флоу | Вот пример того, как можно использовать события аналитики для отслеживания: ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnAnalyticsEvent( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, event: AdaptyOnboardingsAnalyticsEvent ) { when (event) { is AdaptyOnboardingsAnalyticsEventOnboardingStarted -> { // Track onboarding start trackEvent("onboarding_started", event.meta) } is AdaptyOnboardingsAnalyticsEventScreenPresented -> { // Track screen presentation trackEvent("screen_presented", event.meta) } is AdaptyOnboardingsAnalyticsEventScreenCompleted -> { // Track screen completion with user response trackEvent("screen_completed", event.meta, event.elementId, event.reply) } is AdaptyOnboardingsAnalyticsEventOnboardingCompleted -> { // Track successful onboarding completion trackEvent("onboarding_completed", event.meta) } is AdaptyOnboardingsAnalyticsEventUnknown -> { // Handle unknown events trackEvent(event.name, event.meta) } // Handle other cases as needed } } private fun trackEvent(eventName: String, meta: AdaptyUIOnboardingMeta, elementId: String? = null, reply: String? = null) { // Implement your analytics tracking here // For example, send to your analytics service } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```javascript // OnboardingStarted { "meta": { "onboardingId": "onboarding_123", "screenClientId": "welcome_screen", "screenIndex": 0, "screensTotal": 4 } } // ScreenPresented { "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "screensTotal": 4 } } // ScreenCompleted { "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 1, "screensTotal": 4 }, "elementId": "profile_form", "reply": "success" } // SecondScreenPresented { "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 1, "screensTotal": 4 } } // UserEmailCollected { "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 1, "screensTotal": 4 } } // OnboardingCompleted { "meta": { "onboardingId": "onboarding_123", "screenClientId": "final_screen", "screenIndex": 3, "screensTotal": 4 } } ``` </Details> --- # File: kmp-onboarding-input --- --- title: "Обработка данных из онбординга в Kotlin Multiplatform SDK" description: "Сохраняйте и используйте данные из онбордингов в вашем Kotlin Multiplatform приложении с помощью Adapty SDK." --- :::warning **Онбординги устарели в SDK v4 и будут удалены в одном из следующих релизов.** Они больше не получают исправлений и улучшений. Используйте [флоу](kmp-get-pb-paywalls): в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает плавную анимацию, единый нативный стиль, быструю загрузку и отсутствие зависимости от WebView. Подробнее: [Получение флоу и пейволов](kmp-get-pb-paywalls) и [Отображение флоу и пейволов](kmp-present-paywalls). ::: Когда ваши пользователи отвечают на вопрос викторины или вводят данные в поле ввода, вызывается метод `onboardingViewOnStateUpdatedAction`. Вы можете сохранять или обрабатывать тип поля в своём коде. Например: ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnStateUpdatedAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, elementId: String, params: AdaptyOnboardingsStateUpdatedParams ) { // Store user preferences or responses when (params) { is AdaptyOnboardingsSelectParams -> { // Handle single selection val id = params.id val value = params.value val label = params.label AppLogger.d("Selected option: $label (id: $id, value: $value)") } is AdaptyOnboardingsMultiSelectParams -> { // Handle multiple selections } is AdaptyOnboardingsInputParams -> { // Handle text input } is AdaptyOnboardingsDatePickerParams -> { // Handle date selection } } } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` <Details> <summary>Примеры сохранённых данных (формат может отличаться в вашей реализации)</summary> ```javascript // Example of a saved select action { "id": "onboarding_on_state_updated_action", "view": { /* AdaptyUI.OnboardingView object */ }, "meta": { "onboarding_id": "onboarding_123", "screen_cid": "preferences_screen", "screen_index": 1, "total_screens": 3 }, "action": { "element_id": "preference_selector", "element_type": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" } } } // Example of a saved multi-select action { "id": "onboarding_on_state_updated_action", "view": { /* AdaptyUI.OnboardingView object */ }, "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 3 }, "action": { "element_id": "interests_selector", "element_type": "multi_select", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ] } } // Example of a saved input action { "id": "onboarding_on_state_updated_action", "view": { /* AdaptyUI.OnboardingView object */ }, "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 0, "total_screens": 3 }, "action": { "element_id": "name_input", "element_type": "input", "value": { "type": "text", "value": "John Doe" } } } // Example of a saved date picker action { "id": "onboarding_on_state_updated_action", "view": { /* AdaptyUI.OnboardingView object */ }, "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 0, "total_screens": 3 }, "action": { "element_id": "birthday_picker", "element_type": "date_picker", "value": { "day": 15, "month": 6, "year": 1990 } } } ``` </Details> ## Сценарии использования \{#use-cases\} ### Дополнение профилей пользователей данными \{#enrich-user-profiles-with-data\} Если вы хотите сразу связать введённые данные с профилем пользователя и не спрашивать одно и то же дважды, нужно [обновить профиль пользователя](kmp-setting-user-attributes) с этими данными при обработке действия. Например, вы просите пользователей ввести имя в текстовое поле с ID `name`, и хотите установить это значение как имя пользователя. Также вы просите ввести email в поле `email`. В коде приложения это может выглядеть так: ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnStateUpdatedAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, elementId: String, params: AdaptyOnboardingsStateUpdatedParams ) { // Store user preferences or responses when (params) { is AdaptyOnboardingsInputParams -> { // Handle text input val builder = AdaptyProfileParameters.Builder() // Map elementId to appropriate profile field when (elementId) { "name" -> { when (val input = params.input) { is AdaptyOnboardingsTextInput -> { builder.withFirstName(input.value) } } } "email" -> { when (val input = params.input) { is AdaptyOnboardingsEmailInput -> { builder.withEmail(input.value) } } } } // Update profile asynchronously mainUiScope.launch { val profileParams = builder.build() val result = Adapty.updateProfile(profileParams) result.onSuccess { profile -> // Profile updated successfully AppLogger.d("Profile updated: ${profile.email}") }.onError { error -> // Handle the error AppLogger.e("Failed to update profile: ${error.message}") } } } } } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` ### Настройте пейволы на основе ответов \{#customize-paywalls-based-on-answers\} Используя квизы в онбординге, вы также можете настраивать пейволы, которые показываете пользователям после завершения онбординга. Например, можно спросить пользователей об их опыте занятий спортом и показывать разные призывы к действию и продукты разным группам пользователей. 1. [Добавьте квиз](onboarding-quizzes) в конструкторе онбординга и назначьте значимые ID его вариантам ответов. 2. Обработайте ответы на квиз по их ID и [задайте пользовательские атрибуты](kmp-setting-user-attributes) для пользователей. ```kotlin class MyAdaptyUIOnboardingsEventsObserver : AdaptyUIOnboardingsEventsObserver { override fun onboardingViewOnStateUpdatedAction( view: AdaptyUIOnboardingView, meta: AdaptyUIOnboardingMeta, elementId: String, params: AdaptyOnboardingsStateUpdatedParams ) { // Handle quiz responses and set custom attributes when (params) { is AdaptyOnboardingsSelectParams -> { // Handle quiz selection val builder = AdaptyProfileParameters.Builder() // Map quiz responses to custom attributes when (elementId) { "experience" -> { // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.withCustomAttribute("experience", params.value) } } // Update profile asynchronously mainUiScope.launch { val profileParams = builder.build() val result = Adapty.updateProfile(profileParams) result.onSuccess { profile -> // Profile updated successfully AppLogger.d("Custom attribute 'experience' set to: ${params.value}") }.onError { error -> // Handle the error AppLogger.e("Failed to update profile: ${error.message}") } } } } } } // Set up the observer AdaptyUI.setOnboardingsEventsObserver(MyAdaptyUIOnboardingsEventsObserver()) ``` 3. [Создайте сегменты](segments) для каждого значения кастомного атрибута. 4. Создайте [плейсмент](placements) и добавьте [аудитории](audience) для каждого созданного сегмента. 5. [Отобразите пейвол](kmp-paywalls) для плейсмента в коде вашего приложения. Если в онбординге есть кнопка, открывающая пейвол, реализуйте код пейвола как [реакцию на действие этой кнопки](kmp-handling-onboarding-events#opening-a-paywall). --- # File: kmp-best-practices --- --- title: "Лучшие практики в Kotlin Multiplatform SDK" description: "Справочные паттерны для интеграции Adapty SDK на Kotlin Multiplatform — порядок вызовов, обработка ошибок и другие правила для продакшн-готовности." --- <CustomDocCardList /> --- # File: kmp-sdk-call-order --- --- title: "Порядок вызовов в Kotlin Multiplatform SDK" description: "Избегайте потери премиального доступа, отсутствия атрибуции и случайных ошибок активации, вызывая методы Adapty SDK в правильном порядке." --- `Adapty.activate()` должен завершиться до того, как вы вызовете любой другой метод Adapty SDK. Пока он не выполнен, SDK не имеет состояния. Любой вызов, сделанный до или параллельно с `activate()`, завершится ошибкой активации. См. [Обработка ошибок в Kotlin Multiplatform SDK](kmp-handle-errors). Если ваше приложение аутентифицирует пользователей и вы получаете customer user ID после запуска, вызовите `Adapty.identify()` в этот момент. Не вызывайте методы, зависящие от действий пользователя, до завершения `identify`. Вызовы, конкурирующие с ним, либо вернут ошибку, либо попадут в анонимный профиль, созданный при активации. В этом случае атрибуция, MMP ID вроде `appsflyer_id` и принадлежность установки не всегда переносятся в идентифицированный профиль. Если ваше приложение не аутентифицирует пользователей, пропустите `identify` и продолжайте работать с анонимным профилем. MMP и аналитические SDK (AppsFlyer, Adjust, Branch, PostHog) подчиняются тому же правилу. Инициализируйте их первыми и дождитесь колбэков с UID, прежде чем вызывать `Adapty.activate`. Иначе MMP ID привяжется к короткоживущему анонимному профилю и не всегда перенесётся на идентифицированный. Подробнее об особенностях AppsFlyer — в разделе [AppsFlyer](appsflyer). ## Правильный порядок \{#the-correct-order\} Ваш путь зависит от двух вещей: когда вы узнаёте customer user ID и используете ли вы MMP или аналитический SDK. - **Шаги 2 и 5**: Обязательны для каждого приложения. Активируйте SDK, затем вызовите методы SDK. - **Шаги 1 и 3**: Нужны только если вы интегрируете MMP или аналитический SDK (AppsFlyer, Adjust, Branch, PostHog). - **Шаг 4**: Нужен только если ваше приложение аутентифицирует пользователей и получает customer user ID после запуска. Если у вас есть customer user ID в момент запуска приложения, передайте его в `AdaptyConfig.Builder` до вызова `activate()` (шаг 2a). В этом случае анонимный профиль никогда не создаётся, поэтому шаг 4 не нужен. | Шаг | Вызов | Когда | Примечания | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | Инициализируйте ваш MMP или аналитический SDK (AppsFlyer, Adjust, PostHog, Branch) | Запуск приложения, первым делом | Дождитесь коллбэка с UID от MMP, например `getAppsFlyerUID`. | | 2a | `Adapty.activate(configuration = AdaptyConfig.Builder("KEY").withCustomerUserId(...).build())` | Запуск приложения, после шага 1, если у вас есть customer user ID | Рекомендуется. Анонимный профиль не создаётся. | | 2b | `Adapty.activate(configuration = AdaptyConfig.Builder("KEY").build())` без `withCustomerUserId` | Запуск приложения, после шага 1, если у вас нет customer user ID (или вы его не собираете) | Adapty создаёт анонимный профиль. | | 3 | `Adapty.setIntegrationIdentifier("appsflyer_id", uid)` для каждого MMP | После шага 2, до любого вызова, связанного с действием пользователя | Необходимо, чтобы идентификаторы MMP попали на нужный профиль. | | 4 | `Adapty.identify("YOUR_USER_ID").onSuccess { ... }.onError { ... }` | После шага 3 (или шага 2, если нет MMP), до шага 5 — только на пути 2b с аутентификацией | Дождитесь `onSuccess` перед любым вызовом, связанным с действием пользователя. Параллельные вызовы во время `identify` могут попасть на анонимный профиль. | | 5 | `getPaywall` (`getFlow` в SDK v4), `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution`, `updateProfile` | После шага 4, если вызывали `identify`; иначе после шага 3 (или шага 2, если нет MMP) | Эти вызовы требуют стабильного профиля. | :::important Пропуск этих шагов приведёт к тому, что вернувшиеся пользователи потеряют премиум-доступ, в профилях не будет `appsflyer_id`, а пейволы будут отображаться не той аудитории. ::: ## Установки через web2app и веб-воронки \{#web2app-and-web-funnel-installs\} Если пользователи совершают покупку на веб-чекауте (Stripe, Paddle) и затем устанавливают нативное приложение, первый вызов `activate()` на устройстве создаёт новый анонимный профиль. Этот профиль не связан с веб-профилем. Если вы можете определить customer user ID до запуска приложения (из потока авторизации или install referrer), передайте его напрямую в `AdaptyConfig.Builder`. В противном случае веб-покупка остаётся невидимой на устройстве, пока вы не вызовете `identify("YOUR_USER_ID")`, а затем `restorePurchases`. Метаданные, которые нужно передавать при каждом веб-чекауте, описаны здесь: - [Stripe](stripe) - [Paddle](paddle) --- # File: kmp-optimize-paywall-fetching --- --- title: "Оптимизация загрузки пейвола в Kotlin Multiplatform SDK" description: "Надёжная загрузка пейволов Adapty: тайминг, кэширование и резервные паттерны для Kotlin Multiplatform." --- Надёжная загрузка пейвола в Kotlin Multiplatform решает три задачи: быстрый рендеринг, отображение пейвола с учётом аудитории и корректный откат при медленной сети. Правила ниже охватывают тайминг, кэширование и резервные паттерны для достижения этой цели. :::tip Предполагается, что `Adapty.activate()` и `Adapty.identify()` уже выполнены. См. [Порядок вызовов в Kotlin Multiplatform SDK](kmp-sdk-call-order). ::: Советы ниже используют названия методов v3. В SDK v4 метод `getPaywall` переименован в `getFlow` (см. [руководство по миграции](migration-to-kmp-sdk-v4)) — все правила применяются без изменений. ## Правила и подводные камни \{#rules-and-pitfalls\} | Делайте так | Не делайте так | Почему | |---|---|---| | Загружайте только тот плейсмент, который собираетесь показать. | Не загружайте все плейсменты одновременно при запуске. | Массовая загрузка блокирует главный поток и вызывает чёрный экран во время всплеска запросов. | | Вызывайте `getPaywall` после того, как атрибуция успела разрешиться — например, через 1–2 секунды после `activate` или после срабатывания `setOnProfileUpdatedListener`. | Не вызывайте `getPaywall` при запуске приложения. | Атрибуция ещё не пришла. Пейвол разрешается для аудитории по умолчанию и незаметно обходит сегменты и персонализацию ASA. | | Задайте `loadTimeout` и настройте [резервный пейвол](fallback-paywalls) для каждого плейсмента. | Не ждите ответа `getPaywall` бесконечно. | Без таймаута пользователи с плохим соединением видят пустой экран до тех пор, пока сеть не ответит — или закрывают приложение. | Смотрите [Получение пейволов и продуктов](fetch-paywalls-and-products-kmp) для справки по параметрам `fetchPolicy` и `loadTimeout`, а также [Плейсменты](placements) для выбора подходящего плейсмента. ## Настройка для слабого соединения \{#tune-for-poor-connectivity\} Для рынков с постоянно слабым интернетом (сельская местность, транспорт, регионы с проблемами маршрутизации): - Устанавливайте `fetchPolicy = AdaptyPaywallFetchPolicy.ReturnCacheDataElseLoad` при каждом запросе, кроме самого первого. - Настройте [резервный пейвол](fallback-paywalls) для каждого плейсмента в дашборде Adapty. - Задайте `loadTimeout` в 3–5 секунд и используйте резервный пейвол, когда таймаут срабатывает. - Не блокируйте отображение пейвола вызовом `Adapty.getProfile()`. Вызывайте `getPaywall` независимо, чтобы медленная загрузка профиля не тормозила интерфейс. --- # File: kmp-test --- --- title: "Тестирование и релиз в Kotlin Multiplatform SDK" description: "Узнайте, как проверить статус подписки в вашем приложении на Kotlin Multiplatform с помощью Adapty." --- Если вы уже интегрировали Adapty SDK в своё приложение на Kotlin Multiplatform, стоит убедиться, что всё настроено правильно и покупки работают как ожидается. Для этого нужно протестировать как саму интеграцию SDK, так и реальный флоу покупок в среде песочницы. ## Тестирование приложения \{#test-your-app\} Для полноценного тестирования встроенных покупок воспользуйтесь нашими платформенными гайдами: [гайд по тестированию на iOS](test-purchases-in-sandbox) и [гайд по тестированию на Android](testing-on-android). ## Подготовка к релизу \{#prepare-for-release\} Перед отправкой приложения в стор пройдитесь по [чеклисту релиза](release-checklist) и убедитесь, что: - Подключение к стору и серверные уведомления настроены - Покупки выполняются и передаются в Adapty - Уровень доступа корректно открывается и восстанавливается - Выполнены требования к конфиденциальности и модерации --- # File: kmp-reference --- --- title: "Справочник по Kotlin Multiplatform SDK" description: "Справочная документация по Adapty Kotlin Multiplatform SDK." --- На этой странице собрана справочная документация по Adapty Kotlin Multiplatform SDK. Выберите нужный раздел: - **[Модели SDK](https://kmp.adapty.io/adapty/)** — модели данных и структуры, используемые SDK - **[Обработка ошибок](kmp-handle-errors)** — обработка ошибок и решение проблем --- # File: kmp-handle-errors --- --- title: "Обработка ошибок в Kotlin Multiplatform SDK" description: "Узнайте, как обрабатывать ошибки в приложении на Kotlin Multiplatform с помощью Adapty." --- На этой странице описана обработка ошибок в Adapty Kotlin Multiplatform SDK. ## Основы обработки ошибок \{#error-handling-basics\} Все методы Adapty SDK возвращают результат, который может быть либо успешным, либо содержать ошибку. Всегда обрабатывайте оба случая: <Tabs groupId="current-os" queryString> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers Adapty.getProfile { result -> when (result) { is AdaptyResult.Success -> { val profile = result.value // Handle success } is AdaptyResult.Error -> { val error = result.error // Handle error Log.e("Adapty", "Error: ${error.message}") } } } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers Adapty.getProfile(result -> { if (result instanceof AdaptyResult.Success) { AdaptyProfile profile = ((AdaptyResult.Success<AdaptyProfile>) result).getValue(); // Handle success } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); // Handle error Log.e("Adapty", "Error: " + error.getMessage()); } }); ``` </TabItem> </Tabs> ## Распространённые коды ошибок \{#common-error-codes\} | Код ошибки | Описание | Решение | |------------|----------|---------| | 1000 | Идентификаторы продуктов не найдены | Проверьте настройки продуктов в дашборде | | 1001 | Ошибка сети | Проверьте подключение к интернету | | 1002 | Неверный ключ SDK | Убедитесь, что ключ SDK указан верно | | 1003 | Невозможно совершить платёж | Устройство не поддерживает платежи | | 1004 | Продукт недоступен | Продукт не настроен в сторе | ## Обработка конкретных ошибок \{#handle-specific-errors\} ### Ошибки сети \{#network-errors\} <Tabs groupId="current-os" queryString> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers Adapty.getPaywall("main") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // Use paywall } is AdaptyResult.Error -> { val error = result.error when (error.code) { 1001 -> { // Network error - show offline message showOfflineMessage() } else -> { // Other errors showErrorMessage(error.message) } } } } } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers Adapty.getPaywall("main", result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success<AdaptyPaywall>) result).getValue(); // Use paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); switch (error.getCode()) { case 1001: // Network error - show offline message showOfflineMessage(); break; default: // Other errors showErrorMessage(error.getMessage()); break; } } }); ``` </TabItem> </Tabs> ### Ошибки покупки \{#purchase-errors\} <Tabs groupId="current-os" queryString> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers product.makePurchase { result -> when (result) { is AdaptyResult.Success -> { val purchase = result.value // Purchase successful showSuccessMessage() } is AdaptyResult.Error -> { val error = result.error when (error.code) { 1003 -> { // Can't make payments showPaymentNotAvailableMessage() } 1004 -> { // Product not available showProductNotAvailableMessage() } else -> { // Other purchase errors showPurchaseErrorMessage(error.message) } } } } } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers product.makePurchase(result -> { if (result instanceof AdaptyResult.Success) { AdaptyPurchase purchase = ((AdaptyResult.Success<AdaptyPurchase>) result).getValue(); // Purchase successful showSuccessMessage(); } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); switch (error.getCode()) { case 1003: // Can't make payments showPaymentNotAvailableMessage(); break; case 1004: // Product not available showProductNotAvailableMessage(); break; default: // Other purchase errors showPurchaseErrorMessage(error.getMessage()); break; } } }); ``` </TabItem> </Tabs> ## Стратегии восстановления после ошибок \{#error-recovery-strategies\} ### Повтор запроса при ошибках сети \{#retry-on-network-errors\} <Tabs groupId="current-os" queryString> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers fun getPaywallWithRetry(placementId: String, maxRetries: Int = 3) { var retryCount = 0 fun attemptGetPaywall() { Adapty.getPaywall(placementId) { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value // Use paywall } is AdaptyResult.Error -> { val error = result.error if (error.code == 1001 && retryCount < maxRetries) { // Network error - retry retryCount++ Handler(Looper.getMainLooper()).postDelayed({ attemptGetPaywall() }, 1000 * retryCount) // Exponential backoff } else { // Max retries reached or other error showErrorMessage(error.message) } } } } } attemptGetPaywall() } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers public void getPaywallWithRetry(String placementId, int maxRetries) { AtomicInteger retryCount = new AtomicInteger(0); Runnable attemptGetPaywall = new Runnable() { @Override public void run() { Adapty.getPaywall(placementId, result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success<AdaptyPaywall>) result).getValue(); // Use paywall } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); if (error.getCode() == 1001 && retryCount.get() < maxRetries) { // Network error - retry retryCount.incrementAndGet(); new Handler(Looper.getMainLooper()).postDelayed(this, 1000 * retryCount.get()); } else { // Max retries reached or other error showErrorMessage(error.getMessage()); } } }); } }; attemptGetPaywall.run(); } ``` </TabItem> </Tabs> ### Использование кешированных данных как запасного варианта \{#fallback-to-cached-data\} <Tabs groupId="current-os" queryString> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers class PaywallManager { private var cachedPaywall: AdaptyPaywall? = null fun getPaywall(placementId: String) { Adapty.getPaywall(placementId) { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value cachedPaywall = paywall showPaywall(paywall) } is AdaptyResult.Error -> { val error = result.error if (error.code == 1001 && cachedPaywall != null) { // Network error - use cached paywall showPaywall(cachedPaywall!!) showOfflineIndicator() } else { // No cache available or other error showErrorMessage(error.message) } } } } } } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers public class PaywallManager { private AdaptyPaywall cachedPaywall; public void getPaywall(String placementId) { Adapty.getPaywall(placementId, result -> { if (result instanceof AdaptyResult.Success) { AdaptyPaywall paywall = ((AdaptyResult.Success<AdaptyPaywall>) result).getValue(); cachedPaywall = paywall; showPaywall(paywall); } else if (result instanceof AdaptyResult.Error) { AdaptyError error = ((AdaptyResult.Error) result).getError(); if (error.getCode() == 1001 && cachedPaywall != null) { // Network error - use cached paywall showPaywall(cachedPaywall); showOfflineIndicator(); } else { // No cache available or other error showErrorMessage(error.getMessage()); } } }); } } ``` </TabItem> </Tabs> ## Дальнейшие шаги \{#next-steps\} - [Исправление ошибки Code-1000 noProductIDsFound](InvalidProductIdentifiers-kmp) - [Исправление ошибки Code-1003 cantMakePayments](cantMakePayments-kmp) - [Полный справочник API](https://android.adapty.io) — полная документация SDK --- # File: InvalidProductIdentifiers-kmp --- --- title: "Исправление ошибки Code-1000 noProductIDsFound в Kotlin Multiplatform SDK" description: "Решение проблемы с недействительными идентификаторами продуктов при управлении подписками в Adapty." --- Ошибка с кодом 1000 — `noProductIDsFound` — означает, что ни один из продуктов, запрошенных на пейволе, недоступен для покупки в App Store, хотя они там и перечислены. Иногда эта ошибка сопровождается предупреждением `InvalidProductIdentifiers`. Если предупреждение появляется без ошибки — можно его игнорировать. Если вы столкнулись с ошибкой `noProductIDsFound`, выполните следующие шаги для её устранения: ## Шаг 1. Проверьте Bundle ID \{#step-2-check-bundle-id\} 1. Откройте [App Store Connect](https://appstoreconnect.apple.com/apps). Выберите своё приложение и перейдите в раздел **General** → **App Information**. 2. Скопируйте **Bundle ID** в подразделе **General Information**. <Zoom> <img src="/docs/img/afd5012-bundle_id_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. Откройте вкладку [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) в верхнем меню Adapty и вставьте скопированное значение в поле **Bundle ID**. <Zoom> <img src="/docs/img/2d64163-bundle_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 4. Вернитесь на страницу **App information** в App Store Connect и скопируйте **Apple ID**. 5. На странице [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) в дашборде Adapty вставьте этот ID в поле **Apple app ID**. ## Шаг 2. Проверьте продукты \{#step-3-check-products\} 1. Перейдите в **App Store Connect** и откройте раздел [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions) в левом меню. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите на название группы подписок. Вы увидите свои продукты в разделе **Subscriptions**. 3. Убедитесь, что тестируемый продукт имеет статус **Ready to Submit**. Если нет, следуйте инструкциям на странице [Продукт в App Store](app-store-products). <img src="/assets/shared/img/ready-to-submit.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Сравните ID продукта из таблицы с тем, что указан на вкладке [**Products**](https://app.adapty.io/products) в дашборде Adapty. Если ID не совпадают, скопируйте ID продукта из таблицы и [создайте продукт](create-product) с этим ID в дашборде Adapty. <img src="/assets/shared/img/product-id-copy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 3. Проверьте доступность продуктов \{#step-4-check-product-availability\} 1. Вернитесь в **App Store Connect** и откройте тот же раздел **Subscriptions**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите на название группы подписок, чтобы просмотреть свои продукты. 3. Выберите продукт, который тестируете. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Прокрутите вниз до раздела **Availability** и убедитесь, что все нужные страны и регионы указаны. <img src="/assets/shared/img/product-availability.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 4. Проверка цен продуктов \{#step-5-check-product-prices\} 1. Снова откройте раздел **Monetization** → **Subscriptions** в **App Store Connect**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите на название группы подписок. 3. Выберите продукт, который хотите протестировать. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Прокрутите вниз до раздела **Subscription Pricing** и разверните раздел **Current Pricing for New Subscribers**. <img src="/assets/shared/img/check-prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Убедитесь, что все необходимые цены указаны. <img src="/assets/shared/img/product-pricing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 5. Проверьте статус платёжного аккаунта, банковских реквизитов и налоговых форм \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\} 1. На главной странице [**App Store Connect**](https://appstoreconnect.apple.com/) нажмите **Business**. <img src="/assets/shared/img/business.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Выберите название вашей компании. 3. Прокрутите вниз и убедитесь, что **Paid Apps Agreement**, **Bank Account** и **Tax forms** отображаются как **Active**. <img src="/assets/shared/img/appstore-connect-status.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Выполнив эти шаги, вы сможете устранить предупреждение `InvalidProductIdentifiers` и запустить продукты в сторе ## Шаг 6. Пересоздайте продукт, если он завис \{#step-6-recreate-the-product-if-its-stuck\} Шаги 1–5 могут пройти успешно — статус `Approved`, совпадающий Bundle ID, корректный API-ключ — а SDK всё равно возвращает `1000 noProductIDsFound`. В таком случае продукт может быть завис в реестре Apple. Реестр продуктов Apple иногда входит в состояние, при котором продукт существует в интерфейсе App Store Connect, но недоступен по пути поиска StoreKit. Удалите продукт в App Store Connect и пересоздайте его с тем же идентификатором. После пересоздания подождите до 24 часов для распространения изменений. --- # File: cantMakePayments-kmp --- --- title: "Исправление ошибки Code-1003 cantMakePayment в Kotlin Multiplatform SDK" description: "Устранение ошибки проведения платежей при управлении подписками в Adapty." --- Ошибка 1003, `cantMakePayments`, означает, что на этом устройстве нельзя совершать встроенные покупки. Если вы столкнулись с ошибкой `cantMakePayments`, обычно это происходит по одной из следующих причин: - Ограничения устройства: ошибка не связана с Adapty. Способы решения описаны ниже. - Настройка Observer mode: метод `makePurchase` и Observer mode нельзя использовать одновременно. Подробнее — в соответствующем разделе ниже. ## Проблема: ограничения устройства \{#issue-device-restrictions\} | Проблема | Решение | |---------------------------------|-------------------------------------------------------------------------------------------------------------------| | Ограничения Screen Time | Отключите ограничения встроенных покупок в [Screen Time](https://support.apple.com/en-us/102470) | | Аккаунт заблокирован | Обратитесь в службу поддержки Apple для решения проблем с аккаунтом | | Региональные ограничения | Используйте аккаунт App Store из поддерживаемого региона | ## Проблема: одновременное использование Observer mode и makePurchase \{#issue-using-both-observer-mode-and-makepurchase\} Если вы используете `makePurchase` для обработки покупок, Observer mode не нужен. [Observer mode](observer-vs-full-mode) требуется только в том случае, если логику покупок вы реализуете самостоятельно. Таким образом, если вы используете `makePurchase`, можно смело убрать активацию Observer mode из кода инициализации SDK. --- # File: kmp-sdk-migration-guides --- --- title: "Руководства по миграции Kotlin Multiplatform SDK" description: "Руководства по миграции для версий Adapty Kotlin Multiplatform SDK." --- На этой странице собраны все руководства по миграции для Adapty Kotlin Multiplatform SDK. Выберите версию, на которую хотите перейти, чтобы получить подробные инструкции: - **[Миграция на v4.0 (beta)](migration-to-kmp-sdk-v4)** - **[Миграция на v3.15](migration-to-kmp-315)** --- # File: migration-to-kmp-sdk-v4 --- --- title: "Миграция Adapty Kotlin Multiplatform SDK на v. 4.0" description: "Мигрируйте на Adapty Kotlin Multiplatform SDK v4.0 (beta): замените API пейволов на API флоу, совместимые как с Flow Builder, так и с Paywall Builder." --- Adapty Kotlin Multiplatform SDK 4.0 (beta) вводит флоу и соответственно переименовывает API пейволов. Новые API работают как с новым Flow Builder, так и с существующим Paywall Builder — никаких изменений в настройках дашборда Adapty не требуется. ## Краткий справочник \{#quick-reference\} | v3 | v4 | |---|---| | `Adapty.getPaywall(placementId, locale)` | `Adapty.getFlow(placementId)` | | `Adapty.getPaywallForDefaultAudience(placementId, locale)` | `Adapty.getFlowForDefaultAudience(placementId)` | | `Adapty.getPaywallProducts(paywall)` | `Adapty.getPaywallProducts(flow)` | | `Adapty.logShowPaywall(paywall)` | `Adapty.logShowFlow(flow)` | | `AdaptyPaywall` | `AdaptyFlow` | | `AdaptyUI.createPaywallView(paywall, ...)` | `AdaptyUI.createFlowView(flow, ...)` | | `AdaptyUI.createNativePaywallView(...)` → `AdaptyNativePaywallView` | `AdaptyUI.createNativeFlowView(...)` → `AdaptyNativeFlowView` | | `AdaptyUIPaywallView` | `AdaptyUIFlowView` | | `AdaptyUI.presentPaywallView(view)` / `dismissPaywallView(view)` | `AdaptyUI.presentFlowView(view)` / `dismissFlowView(view)` | | `AdaptyUI.setPaywallsEventsObserver(observer)` | `AdaptyUI.setFlowsEventsObserver(observer)` | | `AdaptyUI.registerPaywallEventsListener` / `unregisterPaywallEventsListener` | `AdaptyUI.registerFlowEventsListener` / `unregisterFlowEventsListener` | | `AdaptyUIPaywallsEventsObserver` | `AdaptyUIFlowsEventsObserver` | | `AdaptyUIPaywallPlatformView(paywall, ...)` | `AdaptyUIFlowPlatformView(flow, ...)` | | `paywallViewDidPerformAction`, `paywallViewDidAppear` и другие колбэки `paywallView...` | `flowViewDidPerformAction`, `flowViewDidAppear` и другие колбэки `flowView...` | | `paywallViewDidFailRendering` | `flowViewDidReceiveError` | `AdaptyPaywallProduct` сохраняет своё название — продукты по-прежнему принадлежат флоу, и `getPaywallProducts` тоже сохраняет название, теперь принимая `AdaptyFlow`. Методы `getFlow` и `getFlowForDefaultAudience` больше не принимают параметр `locale`. API покупок и профиля (`makePurchase`, `restorePurchases`, `getProfile`, `identify`, `updateProfile`) и резервные пейволы через `setFallback` остаются без изменений. Методы онбординга по-прежнему работают, но помечены как устаревшие — см. [Устаревание Onboarding API](#onboarding-api-deprecation). Некоторые стандартные настройки поведения изменились — см. [Изменения поведения по умолчанию](#default-behavior-changes). ## Установка \{#installation\} v4.0 — это предрелизная версия, поэтому указывайте точный номер версии: Gradle не выбирает предрелизные версии через динамические диапазоны: ```toml showLineNumbers title="libs.versions.toml" [versions] adapty-kmp = "4.0.0-beta.1" [libraries] adapty-kmp = { module = "io.adapty:adapty-kmp", version.ref = "adapty-kmp" } adapty-kmp-ui = { module = "io.adapty:adapty-kmp-ui", version.ref = "adapty-kmp" } ``` Модуль `adapty-kmp-ui` нужен только если вы отображаете флоу и пейволы через слой Compose Multiplatform (`view.present()`). Подробная инструкция по настройке — в разделе [Установка Adapty SDK](sdk-installation-kotlin-multiplatform). Нативные SDK Adapty обновлены до версии 4.x на обеих платформах и подтягиваются автоматически — изменений в сборке не требуется. Минимальная версия iOS остаётся **15.0**, это не изменилось в данном релизе. ## Получение флоу \{#fetching-flows\} ### getPaywall → getFlow Возвращаемый тип изменяется с `AdaptyPaywall` на `AdaptyFlow`, а параметр `locale` удалён — при отображении флоу локаль определяется автоматически; для кастомных пейволов все локали возвращаются в `flow.remoteConfigs`: ```diff showLineNumbers - Adapty.getPaywall("YOUR_PLACEMENT_ID", locale = "en") - .onSuccess { paywall -> - // use the paywall + Adapty.getFlow("YOUR_PLACEMENT_ID") + .onSuccess { flow -> + // use the flow } .onError { error -> // handle the error } ``` `getPaywallForDefaultAudience` переименован аналогичным образом: ```diff showLineNumbers - Adapty.getPaywallForDefaultAudience("YOUR_PLACEMENT_ID", locale = "en") + Adapty.getFlowForDefaultAudience("YOUR_PLACEMENT_ID") ``` ### getPaywallProducts(paywall) → getPaywallProducts(flow) `getPaywallProducts` сохраняет своё название, но теперь принимает `AdaptyFlow`: ```diff showLineNumbers - Adapty.getPaywallProducts(paywall) + Adapty.getPaywallProducts(flow) .onSuccess { products -> // use the products } ``` ## Модель данных \{#data-model\} `getFlow` возвращает `AdaptyFlow` вместо `AdaptyPaywall`, и структура объекта изменилась: | Свойство v3 `AdaptyPaywall` | Свойство v4 `AdaptyFlow` | Действие | |---|---|---| | `remoteConfig: AdaptyRemoteConfig?` (одиночное) | `remoteConfigs: List<AdaptyRemoteConfig>` | Флоу содержит один Remote Config на каждый настроенный язык. Читайте тот, который соответствует пользователю: `flow.remoteConfigs.firstOrNull { it.locale == "en" }`. | | _(новое)_ | `paywalls: List<AdaptyFlowPaywall>` | Каждый элемент — один вариант пейвола во флоу со своими `name`, `variationId` и `productIdentifiers`. Методы web paywall принимают `AdaptyFlowPaywall` — см. [Методы web paywall](#web-paywall-methods). | | `productIdentifiers` | перемещено | Идентификаторы продуктов теперь хранятся в каждом варианте: `flow.paywalls[i].productIdentifiers`. Для получения продуктов по-прежнему используйте `getPaywallProducts(flow)`. | | `hasViewConfiguration` | удалено | Удалите все проверки `hasViewConfiguration` из кода — вместо этого `createFlowView` возвращает ошибку (см. [Отображение флоу](#displaying-flows)). | `hasViewConfiguration` остаётся в `AdaptyOnboarding` — только модель флоу его убирает. ## Методы веб-пейвола \{#web-paywall-methods\} `openWebPaywall` и `createWebPaywallUrl` сохраняют свои названия, но параметр `paywall` заменяется параметром `flowPaywall`, принимающим `AdaptyFlowPaywall` — одним из вариантов в `flow.paywalls`. Вместо него по-прежнему можно передать `AdaptyPaywallProduct`: ```diff showLineNumbers - Adapty.openWebPaywall(paywall = paywall) + flow.paywalls.firstOrNull()?.let { flowPaywall -> + Adapty.openWebPaywall(flowPaywall = flowPaywall) + } ``` ## Отслеживание просмотров флоу \{#tracking-flow-views\} ### logShowPaywall → logShowFlow `logShowPaywall` переименован в `logShowFlow` и теперь принимает `AdaptyFlow`. Событие по-прежнему логируется для того же варианта, поэтому существующие метрики воронок и A/B-тестов продолжают работать без изменений на дашборде. ```diff showLineNumbers - Adapty.logShowPaywall(paywall) + Adapty.logShowFlow(flow) ``` Как и в v3, вызывать этот метод при отображении флоу или пейволов, отрисованных [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder), не нужно — Adapty отслеживает такие просмотры автоматически. ## Отображение флоу \{#displaying-flows\} ### createPaywallView → createFlowView Переименуйте фабричный метод и передайте `AdaptyFlow`. Тип возвращаемого представления переименован с `AdaptyUIPaywallView` на `AdaptyUIFlowView`, но его методы (`present`, `dismiss`) и необязательные параметры (`loadTimeout`, `preloadProducts`, `customTags`, `customTimers`, `customAssets`, `productPurchaseParams`) остались без изменений: ```diff showLineNumbers - AdaptyUI.createPaywallView(paywall) + AdaptyUI.createFlowView(flow) .onSuccess { view -> view.present() } .onError { error -> // handle the error } ``` Если вы не используете Compose Multiplatform, нативный фабричный метод переименован аналогично: ```diff showLineNumbers - AdaptyUI.createNativePaywallView(paywall) + AdaptyUI.createNativeFlowView(flow) ``` `createFlowView` возвращает `AdaptyResult.Error`, если для флоу не настроен вид — это заменяет проверку `hasViewConfiguration` из v3: ```diff showLineNumbers - if (paywall.hasViewConfiguration) { - AdaptyUI.createPaywallView(paywall) - .onSuccess { view -> view.present() } - } + AdaptyUI.createFlowView(flow) + .onSuccess { view -> view.present() } + .onError { error -> + // the flow has no view configured, or view creation failed + } ``` :::note Представление флоу одноразовое: после вызова `dismiss()` оно уничтожается, поэтому для повторного отображения флоу вызовите `createFlowView` снова. ::: ## Обработка событий \{#handling-events\} Наблюдатель событий переименован с `AdaptyUIPaywallsEventsObserver` на `AdaptyUIFlowsEventsObserver`, а его колбэки меняют префикс `paywallView` на `flowView`. Тела существующих обработчиков менять не нужно — достаточно переименовать тип и переопределения: ```diff showLineNumbers - AdaptyUI.setPaywallsEventsObserver(object : AdaptyUIPaywallsEventsObserver { - override fun paywallViewDidFinishPurchase( - view: AdaptyUIPaywallView, + AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver { + override fun flowViewDidFinishPurchase( + view: AdaptyUIFlowView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult ) { // custom logic after purchase } }) ``` Один колбэк также переименован: `paywallViewDidFailRendering` становится `flowViewDidReceiveError`. Он срабатывает для тех же ошибок рендеринга, что и раньше, плюс других runtime-ошибок, не связанных с покупкой: ```diff showLineNumbers - override fun paywallViewDidFailRendering(view: AdaptyUIPaywallView, error: AdaptyError) {} + override fun flowViewDidReceiveError(view: AdaptyUIFlowView, error: AdaptyError) {} ``` См. [Обработка событий флоу и пейвола](kmp-handling-events) — полный список колбэков. ### Compose platform view Если вы встраиваете представления через Compose Multiplatform composable, `AdaptyUIPaywallPlatformView(paywall, ...)` переименовывается в `AdaptyUIFlowPlatformView(flow, ...)`. Колбэки событий сохраняют свои имена `onDid...`, за исключением `onDidFailRendering`, который становится `onDidReceiveError`: ```diff showLineNumbers - AdaptyUIPaywallPlatformView( - paywall = paywall, + AdaptyUIFlowPlatformView( + flow = flow, onDidFinishPurchase = { view, product, result -> /* ... */ }, ) ``` Как и в v3, коллбэки, которые вы передаёте здесь (и любой наблюдатель, зарегистрированный через `registerFlowEventsListener`), выполняются **в дополнение к** глобальному наблюдателю, а не вместо него — ваш коллбэк наблюдает за событием, но не заменяет глобальное поведение по умолчанию. Учитывайте [изменения поведения по умолчанию](#default-behavior-changes): например, глобальное поведение по умолчанию больше не закрывает экран после покупки. ### Новые API \{#new-apis\} - `AdaptyUI.setObserverModeResolver(...)` с `AdaptyUIObserverModeResolver` — управляет покупками и восстановлениями, инициированными из флоу, когда SDK работает в [режиме Observer](implement-observer-mode-kmp). Ранее это было доступно только в нативных SDK для iOS и Android. Подробнее — в разделе [Отображение флоу в режиме Observer](kmp-present-flows-in-observer-mode). - `AdaptyUI.setSystemRequestsHandler(...)` с `AdaptyUISystemRequestsHandler` — зарезервировано для системных запросов из флоу (запросы разрешений ОС и запросы на оценку приложения). Флоу пока не инициируют такие запросы, поэтому регистрировать обработчик не нужно. - Новый необязательный коллбэк `flowViewDidReceiveAnalyticEvent` зарезервирован для пользовательских аналитических событий из флоу. Флоу пока не отправляют их в ваш код, так что реализовывать его не обязательно. - `AdaptyUI.openWebUrl(url, openIn)` и `AdaptyUI.requestAppReview()` — обеспечивают стандартную обработку `OpenUrlAction` и `handleAppReviewRequest` по умолчанию, поэтому URL и запросы на оценку приложения обрабатываются нативно «из коробки». Вызывайте их напрямую только при переопределении этих настроек по умолчанию. - `AdaptyConfig.ServerCluster.CN` — новая опция серверного кластера наряду с `DEFAULT` и `EU`, для подключения приложения к [серверам Adapty в Китае](china-cluster). ## Изменения поведения по умолчанию \{#default-behavior-changes\} Эти изменения не вызывают ошибок компиляции, поэтому проверяйте их в runtime: - **Завершение покупки**: В v3 дефолтный `paywallViewDidFinishPurchase` закрывал вью после любого результата покупки, кроме `AdaptyPurchaseResult.UserCanceled`. В v4 дефолтный `flowViewDidFinishPurchase` ничего не делает, поэтому **флоу остаётся открытым после покупки, пока вы его не закроете** — аналогично поведению на iOS. Если вы рассчитывали на автоматическое закрытие, вызовите `view.dismiss()` самостоятельно после завершения покупки. - **Системная кнопка «Назад» на Android**: В v3 дефолтный `paywallViewDidPerformAction` закрывал вью как по `CloseAction`, так и по `AndroidSystemBackAction`. В v4 дефолтный обработчик реагирует только на `CloseAction` — **системная кнопка «Назад» больше не закрывает флоу автоматически**, что соответствует поведению iOS, где флоу нельзя закрыть системным жестом. Дайте пользователям явный способ выйти (кнопка **Close** или действие `on_device_back`) или закройте вью самостоятельно в `flowViewDidPerformAction`. - **Ошибки вью**: В v3 дефолтный `paywallViewDidFailRendering` ничего не делал. В v4 дефолтный `flowViewDidReceiveError` **закрывает вью** — переопределите его, если хотите оставить вью открытым или обработать ошибку иначе. - **Вью одноразовые**: После вызова `dismiss()` вью уничтожается. Чтобы показать флоу повторно, вызовите `createFlowView` заново. ## Устаревшее API онбординга \{#onboarding-api-deprecation\} Устаревшее API онбординга объявлено устаревшим в v4.0 в пользу [Flow Builder](adapty-flow-builder). Оно по-прежнему работает, но будет удалено в одном из следующих релизов, поэтому запланируйте миграцию своих онбордингов во Flow Builder. Устаревшие символы: `getOnboarding`, `getOnboardingForDefaultAudience`, `AdaptyUI.createOnboardingView`, `AdaptyUI.createNativeOnboardingView` и `AdaptyUIOnboardingsEventsObserver`. --- # File: migration-to-kmp-315 --- --- title: "Руководство по миграции на Adapty Kotlin Multiplatform SDK 3.15.0" description: "Шаги миграции для Adapty Kotlin Multiplatform SDK 3.15.0" --- Adapty Kotlin Multiplatform SDK 3.15.0 — это мажорный релиз с новыми возможностями и улучшениями, которые, однако, могут потребовать от вас нескольких шагов миграции. 1. Обновите названия класса и метода наблюдателя. 2. Обновите название метода для резервных пейволов. 3. Обновите название класса представления в методах обработки событий. ## Обновление названий класса и метода наблюдателя \{#update-observer-class-and-method-names\} Класс наблюдателя и его метод регистрации были переименованы: ```diff - import com.adapty.kmp.AdaptyUIObserver + import com.adapty.kmp.AdaptyUIPaywallsEventsObserver - import com.adapty.kmp.models.AdaptyUIView + import com.adapty.kmp.models.AdaptyUIPaywallView - class MyAdaptyUIObserver : AdaptyUIObserver { - override fun paywallViewDidPerformAction(view: AdaptyUIView, action: AdaptyUIAction) { + class MyAdaptyUIPaywallsEventsObserver : AdaptyUIPaywallsEventsObserver { + override fun paywallViewDidPerformAction(view: AdaptyUIPaywallView, action: AdaptyUIAction) { // handle actions } } // Set up the observer - AdaptyUI.setObserver(MyAdaptyUIObserver()) + AdaptyUI.setPaywallsEventsObserver(MyAdaptyUIPaywallsEventsObserver()) ``` ## Обновление названия метода для резервных пейволов \{#update-fallback-paywalls-method-name\} Название метода для установки резервных пейволов было изменено: ```diff showLineNumbers - Adapty.setFallbackPaywalls(assetId = "fallback.json") + Adapty.setFallback(assetId = "fallback.json") .onSuccess { // Fallback paywalls loaded successfully } .onError { error -> // Handle the error } ``` ## Обновление названия класса представления в методах обработки событий \{#update-view-class-name-in-event-handling-methods\} Все методы обработки событий теперь используют новый класс `AdaptyUIPaywallView` вместо `AdaptyUIView`: ```diff - override fun paywallViewDidAppear(view: AdaptyUIView) { + override fun paywallViewDidAppear(view: AdaptyUIPaywallView) { // Handle paywall appearance } - override fun paywallViewDidDisappear(view: AdaptyUIView) { + override fun paywallViewDidDisappear(view: AdaptyUIPaywallView) { // Handle paywall disappearance } - override fun paywallViewDidSelectProduct(view: AdaptyUIPaywallView, productId: String) { + override fun paywallViewDidSelectProduct(view: AdaptyUIView, productId: String) { // Handle product selection } - override fun paywallViewDidStartPurchase(view: AdaptyUIView, product: AdaptyPaywallProduct) { + override fun paywallViewDidStartPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct) { // Handle purchase start } - override fun paywallViewDidFinishPurchase(view: AdaptyUIView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult) { + override fun paywallViewDidFinishPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct, purchaseResult: AdaptyPurchaseResult) { // Handle purchase result } - override fun paywallViewDidFailPurchase(view: AdaptyUIView, product: AdaptyPaywallProduct, error: AdaptyError) { + override fun paywallViewDidFailPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct, error: AdaptyError) { // Add your purchase failure handling logic here } - override fun paywallViewDidFinishRestore(view: AdaptyUIView, profile: AdaptyProfile) { + override fun paywallViewDidFinishRestore(view: AdaptyUIPaywallView, profile: AdaptyProfile) { // Add your successful restore handling logic here } - override fun paywallViewDidFailRestore(view: AdaptyUIView, error: AdaptyError) { + override fun paywallViewDidFailRestore(view: AdaptyUIPaywallView, error: AdaptyError) { // Add your restore failure handling logic here } - override fun paywallViewDidFinishWebPaymentNavigation(view: AdaptyUIView, product: AdaptyPaywallProduct?, error: AdaptyError?) { + override fun paywallViewDidFinishWebPaymentNavigation(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct?, error: AdaptyError?) { // Handle web payment navigation result } - override fun paywallViewDidFailLoadingProducts(view: AdaptyUIView, error: AdaptyError) { + override fun paywallViewDidFailLoadingProducts(view: AdaptyUIPaywallView, error: AdaptyError) { // Add your product loading failure handling logic here } - override fun paywallViewDidFailRendering(view: AdaptyUIView, error: AdaptyError) { + override fun paywallViewDidFailRendering(view: AdaptyUIPaywallView, error: AdaptyError) { // Handle rendering error } ``` --- # End of Documentation _Generated on: 2026-07-24T13:01:12.808Z_ _Successfully processed: 48/48 files_ # REACT-NATIVE - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: ru Generated on: 2026-07-24T13:01:12.810Z Total files: 45 --- # File: sdk-installation-react-native-expo --- --- title: "Install & configure Adapty React Native SDK in an Expo project" description: "Step-by-step guide on installing Adapty React Native SDK in an Expo project for subscription-based apps." --- :::important Этот гайд охватывает установку и настройку Adapty SDK для React Native **в проекте Expo**. Если вы используете **чистый React Native (без Expo)**, следуйте [гайду по установке для React Native](sdk-installation-react-native-pure). ::: Adapty SDK включает два ключевых модуля для интеграции в ваше React Native приложение: - **Core Adapty**: Этот модуль необходим для корректной работы Adapty в вашем приложении. - **AdaptyUI**: Этот модуль нужен, если вы используете [Adapty Paywall Builder](adapty-paywall-builder) — удобный инструмент без кода для создания кроссплатформенных пейволов. AdaptyUI активируется автоматически вместе с основным модулем. Если вам нужен полный туториал по реализации встроенных покупок в React Native-приложении, ознакомьтесь с [этим материалом](https://adapty.io/blog/react-native-in-app-purchases-tutorial/). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в Expo-приложение? Посмотрите наши примеры: - [Пример Expo dev build](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/FocusJournalExpo) — полная функциональность, включая реальные покупки и Paywall Builder - [Пример Expo Go & Web](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/ExpoGoWebMock) — тестирование в режиме мока ::: Полное пошаговое руководство по реализации также доступно в виде видео: <div style={{ textAlign: 'center' }}> <iframe width="560" height="315" src="https://www.youtube.com/embed/TtCJswpt2ms?si=FlFJGvpj-U33yoNK" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> </div> ## Требования \{#requirements\} Adapty React Native SDK требует iOS 15.0+. Для сборки под iOS необходим **Swift 6.0** или более новая версия. [Kids Mode](kids-mode-react-native) требует **Swift 6.1** или более новой версии. :::info Начиная с SDK v3.17, Adapty SDK использует Google Play Billing Library v8.0.0 по умолчанию. ::: :::info Установка SDK — это шаг 5 настройки Adapty. Прежде чем покупки заработают в вашем приложении, вам также нужно подключить приложение к сторам, а затем создать продукты, пейвол и плейсмент в дашборде Adapty. [Гайд по быстрому старту](quickstart) описывает все необходимые шаги. ::: ## Установка Adapty SDK \{#install-adapty-sdk\} :::important Начиная с версии v4, Adapty React Native SDK больше не поддерживает установку нативных зависимостей через CocoaPods. Если вам нужна версия v4 или выше (для [Flow Builder](adapty-flow-builder)), воспользуйтесь инструкцией [Adapty SDK 4.0: подключение Swift Package Manager](#adapty-sdk-40-enable-swift-package-manager) ниже. ::: [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-React-Native.svg?style=flat&logo=react)](https://github.com/adaptyteam/AdaptySDK-React-Native/releases) :::important Для использования Adapty в проекте Expo требуется [Expo Dev Client](https://docs.expo.dev/versions/latest/sdk/dev-client/) (кастомная сборка для разработки). Expo Go не поддерживает кастомные нативные модули, поэтому его можно использовать только в [**режиме mock**](#set-up-mock-mode-for-expo-go--expo-web) для разработки UI/логики (без реальных покупок и без рендеринга AdaptyUI/Paywall Builder). ::: 1. Установите Adapty SDK (при этом автоматически устанавливается `@adapty/core`): ```sh npx expo install react-native-adapty npx expo prebuild ``` 2. Соберите приложение для разработки с помощью EAS или локальной сборки: <Tabs> <TabItem value="eas" label="EAS build" default> ```sh # For iOS eas build --profile development --platform ios # For Android eas build --profile development --platform android ``` </TabItem> <TabItem value="local" label="Local build"> ```sh # For iOS npx expo run:ios # For Android npx expo run:android ``` </TabItem> </Tabs> 3. Запустите dev-сервер: ```sh npx expo start --dev-client ``` ### Adapty SDK 4.0: включение Swift Package Manager \{#adapty-sdk-40-enable-swift-package-manager\} React Native SDK 4.0 — который добавляет поддержку [Flow Builder](adapty-flow-builder) — требует **React Native 0.75 или выше**. Установите SDK: ```sh npx expo install react-native-adapty@^4.0.0 ``` v4 подключает нативные iOS SDK (`Adapty`, `AdaptyUI`, `AdaptyPlugin`) через Swift Package Manager вместо CocoaPods sub-dependencies ([репозиторий спецификаций CocoaPods переходит в режим «только чтение» в декабре 2026 года](https://blog.cocoapods.org/CocoaPods-Specs-Repo/)). SPM требует динамических фреймворков, которые в Expo включаются через плагин [`expo-build-properties`](https://docs.expo.dev/versions/latest/sdk/build-properties/). Добавьте его в `app.json` (или `app.config.js`): ```json showLineNumbers title="app.json" { "expo": { "plugins": [ [ "expo-build-properties", { "ios": { "useFrameworks": "dynamic" } } ] ] } } ``` Затем установите плагин и пересоздайте нативный проект: ```sh npx expo install expo-build-properties npx expo prebuild --clean ``` См. [Миграция Adapty React Native SDK на v4](migration-to-react-native-sdk-v4) для полного руководства по миграции. ## Активация модуля Adapty SDK \{#activate-adapty-module-of-adapty-sdk\} Чтобы получить **Public SDK Key**: 1. Откройте дашборд Adapty и перейдите в [**App settings → General**](https://app.adapty.io/settings/general). 2. В разделе **Api keys** скопируйте **Public SDK Key** (НЕ Secret Key). 3. Замените `"YOUR_PUBLIC_SDK_KEY"` в коде. Или получите его программно с помощью [Adapty CLI](developer-cli): ``` npm install -g adapty adapty auth login adapty apps list ``` Или напрямую: ``` npx adapty auth login adapty apps list ``` - Убедитесь, что для инициализации Adapty вы используете **Public SDK key** — **Secret key** предназначен только для [серверного API](getting-started-with-server-side-api). - **SDK-ключи** уникальны для каждого приложения, поэтому если у вас несколько приложений, выберите нужный ключ. Скопируйте следующий код в `App.tsx`, чтобы активировать Adapty: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY'); ``` :::important Дождитесь завершения `activate`, прежде чем вызывать другие методы Adapty SDK. Полная последовательность описана в разделе [Порядок вызовов в React Native SDK](react-native-sdk-call-order). ::: Теперь настройте пейволы в вашем приложении: - Если вы используете [Adapty Paywall Builder](adapty-paywall-builder), следуйте [быстрому старту для Paywall Builder](react-native-quickstart-paywalls). - Если вы создаёте собственный интерфейс пейвола, см. [быстрый старт для кастомных пейволов](react-native-quickstart-manual). :::tip Чтобы избежать ошибок активации в среде разработки, воспользуйтесь [советами](#development-environment-tips). ::: ## Активация модуля AdaptyUI в SDK Adapty \{#activate-adaptyui-module-of-adapty-sdk\} Если вы планируете использовать [Paywall Builder](adapty-paywall-builder), вам понадобится модуль AdaptyUI. Он активируется автоматически вместе с основным модулем — никаких дополнительных действий не требуется. ## Дополнительная настройка \{#optional-setup\} ### Логирование \{#logging\} #### Настройка системы логирования \{#set-up-the-logging-system\} Adapty записывает ошибки и другую важную информацию, чтобы вы понимали, что происходит. Доступны следующие уровни логирования: | Level | Description | | ---------- | ------------------------------------------------------------ | | `error` | Будут записываться только ошибки | | `warn` | Будут записываться ошибки и сообщения от SDK, которые не вызывают критических ошибок, но заслуживают внимания | | `info` | Будут записываться ошибки, предупреждения и различные информационные сообщения | | `verbose` | Будет записываться любая дополнительная информация, которая может быть полезна при отладке: вызовы функций, API-запросы и т. д. | Вы можете задать уровень логирования до или во время инициализации Adapty: ```typescript showLineNumbers title="App.tsx" // Set log level before activation // 'verbose' is recommended for development and the first production release adapty.setLogLevel('verbose'); // Or set it during configuration adapty.activate('YOUR_PUBLIC_SDK_KEY', { logLevel: 'verbose', }); ``` ### Политики обработки данных \{#data-policies\} Adapty не хранит персональные данные пользователей, если вы явно их не передаёте. При необходимости можно настроить дополнительные политики безопасности данных для соответствия требованиям стора или законодательства конкретной страны. #### Отключение сбора и передачи IP-адресов \{#disable-ip-address-collection-and-sharing\} При активации модуля Adapty установите `ipAddressCollectionDisabled` в значение `true`, чтобы отключить сбор и передачу IP-адресов пользователей. Значение по умолчанию — `false`. Используйте этот параметр для защиты конфиденциальности пользователей, соблюдения региональных законов о защите данных (например, GDPR или CCPA) или сокращения избыточного сбора данных в случаях, когда функции на основе IP-адреса вашему приложению не нужны. ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { ipAddressCollectionDisabled: true, }); ``` #### Отключение сбора и передачи рекламного идентификатора \{#disable-advertising-id-collection-and-sharing\} При активации модуля Adapty установите `ios.idfaCollectionDisabled` (iOS) или `android.adIdCollectionDisabled` (Android) в значение `true`, чтобы отключить сбор рекламных идентификаторов. Значение по умолчанию — `false`. Используйте этот параметр для соответствия требованиям App Store/Play Store, чтобы не вызывать запрос App Tracking Transparency, или если ваше приложение не требует атрибуции рекламы или аналитики на основе рекламных идентификаторов. ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { ios: { idfaCollectionDisabled: true, }, android: { adIdCollectionDisabled: true, }, }); ``` #### Настройка кэша медиафайлов для AdaptyUI \{#set-up-media-cache-configuration-for-adaptyui\} По умолчанию AdaptyUI кэширует медиафайлы (например, изображения и видео), чтобы повысить производительность и сократить использование сети. Вы можете настроить параметры кэша, передав собственную конфигурацию. Используйте `mediaCache`, чтобы переопределить настройки кэша по умолчанию: ```typescript adapty.activate('YOUR_PUBLIC_SDK_KEY', { mediaCache: { memoryStorageTotalCostLimit: 200 * 1024 * 1024, // Optional: memory cache size in bytes memoryStorageCountLimit: 2147483647, // Optional: max number of items in memory diskStorageSizeLimit: 200 * 1024 * 1024, // Optional: disk cache size in bytes }, }); ``` | Параметр | Обязательный | Описание | |-----------|----------|-------------| | memoryStorageTotalCostLimit | необязательный | Общий размер кэша в памяти в байтах. По умолчанию используется значение, зависящее от платформы. | | memoryStorageCountLimit | необязательный | Максимальное количество элементов в кэше в памяти. По умолчанию используется значение, зависящее от платформы. | | diskStorageSizeLimit | необязательный | Максимальный размер файлов на диске в байтах. По умолчанию используется значение, зависящее от платформы. | ### Включите локальные уровни доступа (Android) \{#enable-local-access-levels-android\} По умолчанию [локальные уровни доступа](local-access-levels) включены на iOS и отключены на Android. Чтобы включить их на Android, установите `localAccessLevelAllowed` в `true`: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { android: { localAccessLevelAllowed: true, }, }); ``` ### Очистка данных при восстановлении из резервной копии \{#clear-data-on-backup-restore\} Если `clearDataOnBackup` установлен в `true`, SDK определяет, что приложение было восстановлено из резервной копии iCloud, и удаляет все локально сохранённые данные SDK, включая кэшированную информацию профиля, сведения о продуктах и пейволы. После этого SDK инициализируется с чистым состоянием. Значение по умолчанию — `false`. :::note Удаляется только локальный кэш SDK. История транзакций в Apple и данные пользователя на серверах Adapty остаются без изменений. ::: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { ios: { clearDataOnBackup: true }, }); ``` ## Советы по разработке \{#development-environment-tips\} #### Настройка режима заглушки для Expo Go / Expo Web \{#set-up-mock-mode-for-expo-go--expo-web\} В Expo Go и Expo Web нет доступа к нативным модулям Adapty. Чтобы избежать ошибок во время выполнения и при этом иметь возможность собирать и тестировать UI приложения и логику пейвола, Adapty предоставляет **режим заглушки**. ::::important Режим заглушки — это **не** инструмент для тестирования реальных покупок: - Он **не открывает** потоки покупок App Store / Google Play и **не создаёт** реальные транзакции. - Он **не отображает** пейволы/онбординги, созданные с помощью **Adapty Paywall Builder (AdaptyUI)**. - Нативные модули Adapty **полностью обходятся** — даже отсутствующие файлы нативного SDK в сборке Xcode/Android или недействительный API-ключ не вызовут ошибок. Для тестирования реальных покупок и пейволов Paywall Builder используйте Expo Dev Client или production-сборку, в которых режим мока отключён автоматически. :::: **По умолчанию** SDK автоматически определяет среды Expo Go и web и включает режим mock. Никакой дополнительной настройки не требуется — если только вы не хотите изменить mock-данные. Когда активен режим mock: - Все методы Adapty возвращают mock-данные, не отправляя запросы на серверы Adapty. - По умолчанию исходный mock-профиль не имеет активных подписок. - По умолчанию `makePurchase(...)` симулирует успешную покупку и предоставляет премиум-доступ. Вы можете настроить мок-данные с помощью `mockConfig` при активации. Формат конфига и поддерживаемые параметры описаны [здесь](https://react-native.adapty.io/interfaces/adaptymockconfig). ```typescript showLineNumbers title="App.tsx" try { await adapty.activate('YOUR_PUBLIC_SDK_KEY', { mockConfig: { // Customize the initial mock profile (optional) }, }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); } ``` Если вам нужно вызывать методы SDK до активации (например, `isActivated()` или `setLogLevel()`), используйте `enableMock()` перед `activate()`. Если бридж уже инициализирован, этот метод ничего не делает. ```typescript showLineNumbers title="App.tsx" adapty.enableMock(); // Optional: pass mockConfig to customize mock data // Now you can call methods before activation await adapty.activate('YOUR_PUBLIC_SDK_KEY'); ``` #### Отложить активацию SDK в целях разработки \{#delay-sdk-activation-for-development-purposes\} Adapty заранее загружает все необходимые данные пользователя при активации SDK, что обеспечивает быстрый доступ к актуальным данным. Однако в симуляторе iOS это может вызывать проблемы: во время разработки симулятор часто запрашивает аутентификацию. Adapty не может управлять флоу аутентификации StoreKit, но может откладывать запросы SDK на получение свежих данных пользователя. Если включить свойство `__debugDeferActivation`, вызов активации будет удерживаться до следующего обращения к Adapty SDK. Это позволяет избежать лишних запросов аутентификационных данных, если они не нужны. Важно учитывать, что **эта функция предназначена только для разработки** — она не охватывает все возможные пользовательские сценарии. В продакшене не следует откладывать активацию, так как реальные устройства обычно запоминают данные аутентификации и не запрашивают учётные данные повторно. Рекомендуемый подход к использованию: ```typescript showLineNumbers title="Typescript" try { adapty.activate('PUBLIC_SDK_KEY', { __debugDeferActivation: isSimulator(), // 'isSimulator' from any 3rd party library }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); // Handle the error appropriately for your app } ``` #### Устранение ошибок активации SDK при Fast Refresh в React Native \{#troubleshoot-sdk-activation-errors-on-react-natives-fast-refresh\} При разработке с Adapty SDK в React Native вы можете столкнуться с ошибкой: `Adapty can only be activated once. Ensure that the SDK activation call is not made more than once.` Это происходит потому, что функция быстрого обновления (fast refresh) в React Native вызывает несколько обращений к активации во время разработки. Чтобы этого избежать, используйте опцию `__ignoreActivationOnFastRefresh` со значением `__DEV__` (флаг режима разработки в React Native). ```typescript showLineNumbers title="Typescript" try { adapty.activate('PUBLIC_SDK_KEY', { __ignoreActivationOnFastRefresh: __DEV__, }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); // Handle the error appropriately for your app } ``` ## Устранение неполадок \{#troubleshooting\} #### Ошибка минимальной версии iOS \{#minimum-ios-version-error\} При сборке для iOS может появиться ошибка о **минимальной версии iOS** или deployment target. Adapty требует **iOS 15.0+**. Поскольку Expo генерирует iOS-проект (включая `Podfile`) во время выполнения `expo prebuild`, **не редактируйте `Podfile` напрямую**. Вместо этого настройте deployment target через config-плагин `expo-build-properties`. 1. Установите плагин: ```sh npx expo install expo-build-properties ``` 2. Обновите конфигурацию Expo (`app.json` или `app.config.js`), указав iOS deployment target: ``` { "expo": { // ...other Expo config... "plugins": [ [ "expo-build-properties", { "ios": { // Adapty requires iOS 15.0+. "deploymentTarget": "15.0" } } ], ] } } ``` 3. Пересоздайте нативный iOS-проект и выполните сборку: ``` npx expo prebuild --clean npx expo run:ios # или `eas build -p ios` на вашем CI ``` #### Конфликт Android Auto Backup в манифесте \{#android-auto-backup-manifest-conflict\} Когда вы используете Expo с несколькими SDK, которые настраивают Android Auto Backup (например, Adapty, AppsFlyer или expo-secure-store), может возникнуть конфликт при слиянии манифестов. Типичная ошибка выглядит так: `Manifest merger failed : Attribute application@fullBackupContent value=(@xml/secure_store_backup_rules) from AndroidManifest.xml:24:248-306 is also present at [io.adapty:android-sdk:3.12.0] AndroidManifest.xml:9:18-70 value=(@xml/adapty_backup_rules).` Чтобы решить этот конфликт, нужно разрешить плагину Adapty управлять конфигурацией резервного копирования Android. Если в вашем проекте также используется `expo-secure-store`, отключите его собственную настройку резервного копирования, чтобы избежать конфликта. Вот как настроить `app.json`: ```json title="app.json" { "expo": { "plugins": [ ["react-native-adapty", { "replaceAndroidBackupConfig": true }], ["expo-secure-store", { "configureAndroidBackup": false }] ] } } ``` Опция `replaceAndroidBackupConfig` по умолчанию имеет значение `false`. При включении она позволяет плагину Adapty управлять правилами резервного копирования Android. Добавьте `"configureAndroidBackup": false`, если вы используете `expo-secure-store`, чтобы избежать предупреждений — настройка резервного копирования SecureStore теперь будет выполняться через Adapty. :::important Эта настройка учитывает требования к резервному копированию только для Adapty, AppsFlyer и expo-secure-store. Если другие библиотеки в вашем проекте определяют собственные правила резервного копирования, вам нужно настроить их вручную. ::: --- # File: sdk-installation-react-native-pure --- --- title: "Install & configure Adapty SDK in a pure React Native project" description: "Step-by-step guide on installing Adapty SDK on React Native for subscription-based apps." --- :::important Это руководство применимо только к **чистым проектам React Native (без Expo)**. Если вы используете **Expo**, следуйте [инструкции по установке для Expo](sdk-installation-react-native-expo). ::: Adapty SDK включает два ключевых модуля для интеграции в ваше приложение React Native: - **Core Adapty**: Этот модуль необходим для корректной работы Adapty в вашем приложении. - **AdaptyUI**: Этот модуль нужен, если вы используете [Adapty Paywall Builder](adapty-paywall-builder) — удобный no-code инструмент для создания кросс-платформенных пейволов. AdaptyUI активируется автоматически вместе с основным модулем. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ## Требования \{#requirements\} Для работы Adapty React Native SDK требуется iOS 15.0+. Для сборки под iOS необходим **Swift 6.0** или новее. [Kids Mode](kids-mode-react-native) требует **Swift 6.1** или новее. :::info Начиная с SDK v3.17, Adapty SDK использует Google Play Billing Library v8.0.0 по умолчанию. ::: :::info Установка SDK — это шаг 5 настройки Adapty. Прежде чем покупки заработают в вашем приложении, вам также нужно подключить приложение к сторам, а затем создать продукты, пейвол и плейсмент в дашборде Adapty. [Гайд по быстрому старту](quickstart) описывает все необходимые шаги. ::: ## Установка Adapty SDK \{#install-adapty-sdk\} :::important Начиная с v4, Adapty React Native SDK больше не поддерживает установку нативных зависимостей через CocoaPods. Если вам нужна v4 или более поздняя версия (для [Flow Builder](adapty-flow-builder)), воспользуйтесь инструкцией [Adapty SDK 4.0: enable Swift Package Manager](#adapty-sdk-40-enable-swift-package-manager) ниже. ::: [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-React-Native.svg?style=flat&logo=react)](https://github.com/adaptyteam/AdaptySDK-React-Native/releases) 1. Установите Adapty SDK (это также автоматически установит `@adapty/core`): ```sh showLineNumbers title="Shell" # using npm npm install react-native-adapty # or using yarn yarn add react-native-adapty ``` 2. Для iOS установите поды: ```sh showLineNumbers title="Shell" cd ios && pod install ``` <details> <summary>Для Android, если ваша версия React Native ниже 0.73.0 (нажмите для раскрытия)</summary> Обновите файл `/android/build.gradle`. Убедитесь, что присутствует зависимость `kotlin-gradle-plugin:1.8.0` или более новая: ```groovy showLineNumbers title="/android/build.gradle" ... buildscript { ... dependencies { ... classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.0" } } ... ``` </details> ### Adapty SDK 4.0: включите Swift Package Manager \{#adapty-sdk-40-enable-swift-package-manager\} React Native SDK 4.0 — который добавляет поддержку [Flow Builder](adapty-flow-builder) — требует **React Native 0.75 или новее**. Установите SDK: ```sh showLineNumbers title="Shell" npm install react-native-adapty@^4.0.0 # or using yarn yarn add react-native-adapty@^4.0.0 ``` v4 подключает нативные iOS SDK (`Adapty`, `AdaptyUI`, `AdaptyPlugin`) через Swift Package Manager вместо зависимостей CocoaPods ([спецификации CocoaPods переходят в режим только чтения в декабре 2026](https://blog.cocoapods.org/CocoaPods-Specs-Repo/)). SPM требует динамических фреймворков — добавьте следующее в таргет вашего `ios/Podfile`, затем переустановите поды: ```ruby showLineNumbers title="ios/Podfile" use_frameworks! :linkage => :dynamic ``` ```sh showLineNumbers title="Shell" cd ios && pod install --repo-update ``` Если ранее вы подключали `Adapty`, `AdaptyUI` или `AdaptyPlugin` как CocoaPods sub-dependencies, сначала удалите все явные строки `pod 'Adapty'`, `pod 'AdaptyUI'` или `pod 'AdaptyPlugin'` из вашего `Podfile`. :::warning Переход со статической линковки по умолчанию на динамические фреймворки может конфликтовать с библиотеками, которые ещё не поддерживают modular headers, а также несовместим с Flipper. Подробнее см. в разделе [Миграция React Native SDK Adapty на v4](migration-to-react-native-sdk-v4). ::: ## Активация модуля Adapty в SDK \{#activate-adapty-module-of-adapty-sdk\} Чтобы получить **Public SDK Key**: 1. Откройте дашборд Adapty и перейдите в [**App settings → General**](https://app.adapty.io/settings/general). 2. В разделе **Api keys** скопируйте **Public SDK Key** (НЕ Secret Key). 3. Замените `"YOUR_PUBLIC_SDK_KEY"` в коде. Или получите его программно с помощью [Adapty CLI](developer-cli): ``` npm install -g adapty adapty auth login adapty apps list ``` Или напрямую: ``` npx adapty auth login adapty apps list ``` - Убедитесь, что для инициализации Adapty вы используете **Public SDK key** — **Secret key** предназначен только для [серверного API](getting-started-with-server-side-api). - **SDK-ключи** уникальны для каждого приложения, поэтому если у вас несколько приложений, выберите нужный ключ. Скопируйте следующий код в `App.tsx`, чтобы активировать Adapty: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY'); ``` :::important Дождитесь завершения `activate` перед вызовом любых других методов SDK. Подробная последовательность — в статье [Порядок вызовов в React Native SDK](react-native-sdk-call-order). ::: Теперь настройте пейволы в вашем приложении: - Если вы используете [Adapty Paywall Builder](adapty-paywall-builder), следуйте [быстрому старту с Paywall Builder](react-native-quickstart-paywalls). - Если вы создаёте собственный интерфейс пейвола, см. [быстрый старт для кастомных пейволов](react-native-quickstart-manual). :::tip Чтобы избежать ошибок активации в среде разработки, воспользуйтесь [советами](#development-environment-tips). ::: ## Активация модуля AdaptyUI в Adapty SDK \{#activate-adaptyui-module-of-adapty-sdk\} Если вы планируете использовать [Paywall Builder](adapty-paywall-builder), вам понадобится модуль AdaptyUI. Он активируется автоматически вместе с основным модулем — никаких дополнительных действий не требуется. ## Дополнительная настройка \{#optional-setup\} ### Логирование \{#logging\} #### Настройка системы логирования \{#set-up-the-logging-system\} Adapty записывает ошибки и другую важную информацию, чтобы вы понимали, что происходит. Доступны следующие уровни логирования: | Level | Description | | ---------- | ------------------------------------------------------------ | | `error` | Будут записываться только ошибки | | `warn` | Будут записываться ошибки и сообщения от SDK, которые не вызывают критических сбоев, но заслуживают внимания | | `info` | Будут записываться ошибки, предупреждения и различные информационные сообщения | | `verbose` | Будет записываться любая дополнительная информация, которая может быть полезна при отладке: вызовы функций, запросы к API и т. д. | Вы можете задать уровень логирования в приложении до или во время конфигурации Adapty: ```typescript showLineNumbers title="App.tsx" // Set log level before activation // 'verbose' is recommended for development and the first production release adapty.setLogLevel('verbose'); // Or set it during configuration adapty.activate('YOUR_PUBLIC_SDK_KEY', { logLevel: 'verbose', }); ``` ### Политики данных \{#data-policies\} Adapty не хранит личные данные пользователей, если вы явно их не передаёте. Однако вы можете настроить дополнительные политики безопасности данных для соответствия требованиям стора или законодательству конкретной страны. #### Отключение сбора и передачи IP-адресов \{#disable-ip-address-collection-and-sharing\} При активации модуля Adapty установите `ipAddressCollectionDisabled` в значение `true`, чтобы отключить сбор и передачу IP-адресов пользователей. Значение по умолчанию — `false`. Используйте этот параметр для защиты конфиденциальности пользователей, соблюдения региональных требований законодательства о защите данных (например, GDPR или CCPA), а также для сокращения избыточного сбора данных, когда функции на основе IP-адреса не нужны в вашем приложении. ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { ipAddressCollectionDisabled: true, }); ``` #### Отключить сбор и передачу рекламного идентификатора \{#disable-advertising-id-collection-and-sharing\} При активации модуля Adapty установите `ios.idfaCollectionDisabled` (iOS) или `android.adIdCollectionDisabled` (Android) в значение `true`, чтобы отключить сбор рекламных идентификаторов. Значение по умолчанию — `false`. Используйте этот параметр для соблюдения требований App Store/Play Store, чтобы не вызывать запрос App Tracking Transparency, или если ваше приложение не использует атрибуцию рекламы или аналитику на основе рекламных идентификаторов. ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { ios: { idfaCollectionDisabled: true, }, android: { adIdCollectionDisabled: true, }, }); ``` #### Настройте конфигурацию медиакеша для AdaptyUI \{#set-up-media-cache-configuration-for-adaptyui\} По умолчанию AdaptyUI кэширует медиафайлы (изображения и видео) для повышения производительности и снижения сетевой нагрузки. Вы можете настроить параметры кеширования, передав собственную конфигурацию. Используйте `mediaCache`, чтобы переопределить настройки кеша по умолчанию: ```typescript adapty.activate('YOUR_PUBLIC_SDK_KEY', { mediaCache: { memoryStorageTotalCostLimit: 200 * 1024 * 1024, // Optional: memory cache size in bytes memoryStorageCountLimit: 2147483647, // Optional: max number of items in memory diskStorageSizeLimit: 200 * 1024 * 1024, // Optional: disk cache size in bytes }, }); ``` | Параметр | Обязателен | Описание | |-----------|----------|-------------| | memoryStorageTotalCostLimit | нет | Общий размер кэша в памяти в байтах. По умолчанию используется значение, зависящее от платформы. | | memoryStorageCountLimit | нет | Максимальное количество элементов в кэше памяти. По умолчанию используется значение, зависящее от платформы. | | diskStorageSizeLimit | нет | Максимальный размер файлов на диске в байтах. По умолчанию используется значение, зависящее от платформы. | ### Включение локальных уровней доступа (Android) \{#enable-local-access-levels-android\} По умолчанию [локальные уровни доступа](local-access-levels) включены на iOS и отключены на Android. Чтобы включить их на Android, установите `localAccessLevelAllowed` в `true`: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { android: { localAccessLevelAllowed: true, }, }); ``` ### Очистка данных при восстановлении из резервной копии \{#clear-data-on-backup-restore\} Если `clearDataOnBackup` установлен в `true`, SDK определяет, когда приложение восстановлено из резервной копии iCloud, и удаляет все локально сохранённые данные SDK: кешированную информацию профиля, данные о продуктах и пейволы. После этого SDK инициализируется с чистого состояния. Значение по умолчанию — `false`. :::note Удаляется только локальный кеш SDK. История транзакций Apple и данные пользователя на серверах Adapty остаются без изменений. ::: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { ios: { clearDataOnBackup: true }, }); ``` ## Советы для среды разработки \{#development-environment-tips\} #### Отложите активацию SDK в целях разработки \{#delay-sdk-activation-for-development-purposes\} При активации SDK Adapty заранее загружает все необходимые данные о пользователе, чтобы обеспечить быстрый доступ к актуальной информации. Однако в симуляторе iOS это может создавать неудобства: в процессе разработки симулятор часто запрашивает аутентификацию. Adapty не управляет процессом аутентификации StoreKit, но может отложить запросы SDK на получение свежих данных о пользователе. Включив свойство `__debugDeferActivation`, вы откладываете вызов активации до следующего обращения к Adapty SDK. Это позволяет избежать лишних запросов аутентификационных данных, если они не нужны. Важно учитывать, что **эта функция предназначена только для разработки**, поскольку не охватывает все возможные сценарии использования. В продакшене откладывать активацию не следует: реальные устройства, как правило, сохраняют аутентификационные данные и не запрашивают учётные данные повторно. Рекомендуемый подход к использованию: ```typescript showLineNumbers title="Typescript" try { adapty.activate('PUBLIC_SDK_KEY', { __debugDeferActivation: isSimulator(), // 'isSimulator' from any 3rd party library }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); // Handle the error appropriately for your app } ``` #### Устранение ошибок активации SDK при Fast Refresh в React Native \{#troubleshoot-sdk-activation-errors-on-react-natives-fast-refresh\} При разработке с Adapty SDK в React Native вы можете столкнуться с ошибкой: `Adapty can only be activated once. Ensure that the SDK activation call is not made more than once.` Это происходит потому, что функция быстрого обновления (fast refresh) в React Native вызывает несколько активаций в процессе разработки. Чтобы этого избежать, используйте опцию `__ignoreActivationOnFastRefresh` со значением `__DEV__` (флаг режима разработки в React Native). ```typescript showLineNumbers title="Typescript" try { adapty.activate('PUBLIC_SDK_KEY', { __ignoreActivationOnFastRefresh: __DEV__, }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); // Handle the error appropriately for your app } ``` #### Настройка mock-режима для локального тестирования \{#set-up-mock-mode-for-local-testing\} Для локальной разработки и тестирования можно включить mock-режим — это позволяет обойтись без аккаунтов в песочнице App Store/Google Play и ускорить итерации. Mock-режим полностью обходит нативные модули Adapty и возвращает симулированные данные. :::important Mock-режим **не** предназначен для тестирования реальных покупок: - **Не открывает** флоу покупок App Store / Google Play и **не создаёт** реальных транзакций. - **Не рендерит** пейволы/онбординги, созданные с помощью **Adapty Paywall Builder (AdaptyUI)**. - Нативные модули Adapty **полностью обходятся** — даже отсутствие нативных файлов SDK в сборке Xcode/Android или неверный API-ключ не вызовут ошибок. - Данные на серверы Adapty не отправляются. Чтобы протестировать реальные покупки и пейволы Paywall Builder, отключите режим мока и используйте аккаунты песочницы. ::: Чтобы включить режим мока, установите `enableMock` в `true`: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { enableMock: true, }); ``` Когда активен режим mock: - Все методы Adapty возвращают mock-данные без сетевых запросов к серверам Adapty. - По умолчанию исходный mock-профиль не имеет активных подписок. - По умолчанию `makePurchase(...)` симулирует успешную покупку и предоставляет премиум-доступ. Вы можете настроить mock-данные с помощью `mockConfig` при активации. Формат конфига и поддерживаемые параметры описаны [здесь](https://react-native.adapty.io/interfaces/adaptymockconfig). ```typescript showLineNumbers title="App.tsx" try { await adapty.activate('YOUR_PUBLIC_SDK_KEY', { mockConfig: { // Customize the initial mock profile (optional) }, }); } catch (error) { console.error('Failed to activate Adapty SDK:', error); } ``` Если вам нужно вызвать методы SDK до активации (например, `isActivated()` или `setLogLevel()`), используйте `enableMock()` перед `activate()`. Если мост уже инициализирован, этот метод ничего не делает. ```typescript showLineNumbers title="App.tsx" adapty.enableMock(); // Optional: pass mockConfig to customize mock data // Now you can call methods before activation await adapty.activate('YOUR_PUBLIC_SDK_KEY'); ``` ## Устранение неполадок \{#troubleshooting\} #### Ошибка минимальной версии iOS \{#minimum-ios-version-error\} Если вы получаете ошибку минимальной версии iOS, обновите Podfile: ```diff -platform :ios, min_ios_version_supported +platform :ios, '15.0' ``` #### Конфликт манифеста Android Auto Backup \{#android-auto-backup-manifest-conflict\} Некоторые SDK (включая Adapty) поставляются с собственной конфигурацией Android Auto Backup. Если вы используете несколько SDK, каждый из которых определяет правила резервного копирования, слияние манифестов Android может завершиться ошибкой, связанной с `android:fullBackupContent`, `android:dataExtractionRules` или `android:allowBackup`. Типичные симптомы ошибки: `Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/your_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` :::note Эти изменения нужно вносить в директорию Android-платформы (обычно находится в папке `android/` вашего проекта). ::: Чтобы решить проблему, необходимо: - Указать механизму слияния манифестов использовать значения вашего приложения для атрибутов, связанных с резервным копированием. - Создать файлы правил резервного копирования, объединяющие правила Adapty с правилами других SDK. #### 1. Добавьте пространство имён `tools` в манифест В файле `AndroidManifest.xml` убедитесь, что корневой тег `<manifest>` включает tools: ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.app"> ... </manifest> ``` #### 2. Переопределите атрибуты резервного копирования в `<application>` В том же файле `AndroidManifest.xml` обновите тег `<application>`, чтобы ваше приложение предоставляло итоговые значения и указывало механизму слияния манифестов заменять значения библиотек: ```xml <application android:name=".App" android:allowBackup="true" android:fullBackupContent="@xml/sample_backup_rules" android:dataExtractionRules="@xml/sample_data_extraction_rules" tools:replace="android:fullBackupContent,android:dataExtractionRules"> ... </application> ``` Если какой-либо SDK также задаёт `android:allowBackup`, включите его в `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. Создайте объединённые файлы правил резервного копирования Создайте XML-файлы в директории `res/xml/` вашего Android-проекта, объединяющие правила Adapty с правилами других SDK. Android использует разные форматы правил резервного копирования в зависимости от версии ОС, поэтому создание обоих файлов обеспечивает совместимость со всеми версиями Android, которые поддерживает ваше приложение. :::note В примерах ниже в качестве стороннего SDK используется AppsFlyer. Замените или добавьте правила для других SDK, которые используются в вашем приложении. ::: **Для Android 12 и выше** (используется новый формат правил извлечения данных): ```xml title="sample_data_extraction_rules.xml" <?xml version="1.0" encoding="utf-8"?> <data-extraction-rules> <cloud-backup> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </cloud-backup> <device-transfer> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </device-transfer> </data-extraction-rules> ``` **Для Android 11 и ниже** (используется устаревший формат полного резервного копирования): ```xml title="sample_backup_rules.xml" <?xml version="1.0" encoding="utf-8"?> <full-backup-content> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> #### Покупки не завершаются после возврата из другого приложения на Android \{#purchases-fail-after-returning-from-another-app-in-android\} Если Activity, запускающий флоу покупки, использует нестандартный `launchMode`, Android может некорректно пересоздать или повторно использовать его, когда пользователь возвращается из Google Play, банковского приложения или браузера. Это может привести к тому, что результат покупки будет потерян или расценён как отменённый. Чтобы покупки работали корректно, используйте только режимы запуска `standard` или `singleTop` для Activity, из которой начинается флоу покупки — любые другие режимы недопустимы. В файле `AndroidManifest.xml` убедитесь, что Activity, из которой запускается флоу покупки, имеет режим `standard` или `singleTop`: ```xml <activity android:name=".MainActivity" android:launchMode="standard" /> ``` #### Ошибки сборки Swift 6 из-за переопределения SWIFT_VERSION в Podfile \{#swift-6-build-errors-caused-by-podfile-swift-version-override\} При сборке React Native-приложения для iOS на pod-таргетах Adapty могут возникать ошибки компиляции Swift 6. Типичные симптомы: несоответствия `@Sendable` в `AdaptyUIBuilderLogic`, отсутствие соответствия `Sendable` в типах Adapty или ошибки изоляции актора. Pod'ы Adapty объявляют `s.swift_version = '6.0'` и требуют Swift 6 для сборки. Ваш собственный код приложения может оставаться на Swift 5 — только pod-таргеты Adapty (`Adapty`, `AdaptyUI`, `AdaptyUIBuilder`, `AdaptyLogger`, `AdaptyPlugin`) должны собираться с Swift 6. Наиболее распространённая причина — хук `post_install` в `ios/Podfile`, который перезаписывает `SWIFT_VERSION` для каждого pod-таргета: ```ruby showLineNumbers title="ios/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` **Исправление**: исключите pod-таргеты Adapty из переопределения: ```ruby showLineNumbers title="ios/Podfile" post_install do |installer| installer.pods_project.targets.each do |target| next if %w[Adapty AdaptyUI AdaptyUIBuilder AdaptyLogger AdaptyPlugin].include?(target.name) target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '5.9' end end end ``` Затем выполните `pod install` из директории `ios/` и пересоберите проект. Чтобы проверить, откройте `ios/Pods/Pods.xcodeproj`, выберите pod-таргет `Adapty` → **Build Settings** → **Swift Language Version**. Там должно быть указано **Swift 6**. --- # File: react-native-quickstart-paywalls --- --- title: "Включение покупок с помощью Flow Builder в React Native SDK" description: "Краткое руководство по подключению встроенных покупок с помощью Adapty Flow Builder." --- Чтобы подключить встроенные покупки, нужно разобраться с тремя ключевыми понятиями: - [**Продукты**](product) – всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ) - [**Флоу**](adapty-flow-builder) – последовательности экранов для представления продуктов пользователям, созданные в no-code Flow Builder. SDK получает их через `getFlow`. Если вы предпочитаете строить UI в собственном коде, используйте пейвол — см. [Ручная реализация пейволов](react-native-quickstart-manual). - [**Плейсменты**](placements) – где и когда вы показываете флоу в приложении (например, `main`, `onboarding`, `settings`). Вы привязываете флоу к плейсментам на дашборде, а затем запрашиваете их по ID плейсмента в коде. Это упрощает проведение A/B-тестов и показ разных флоу разным пользователям. Adapty предлагает три способа подключить покупки в вашем приложении. Выберите подходящий в зависимости от требований: | Реализация | Сложность | Когда использовать | |---|---|---| | Adapty Flow Builder | ✅ Просто | Вы [создаёте готовый к покупкам флоу в no-code конструкторе](quickstart-paywalls). Adapty автоматически отображает его и берёт на себя весь сложный процесс покупки, валидацию чеков и управление подписками. | | Пейволы, созданные вручную | 🟡 Средне | Вы реализуете UI пейвола в коде приложения, но всё равно получаете объект флоу от Adapty, сохраняя гибкость в настройке продуктов. См. [гайд](react-native-quickstart-manual). | | Observer mode | 🔴 Сложно | У вас уже есть собственная инфраструктура для обработки покупок и вы хотите её сохранить. Обратите внимание, что observer mode имеет ограничения в Adapty. См. [статью](observer-vs-full-mode). | :::important **Шаги ниже показывают, как реализовать флоу, созданное в Adapty Flow Builder.** Если вы предпочитаете создавать UI пейвола самостоятельно, см. [Реализация пейволов вручную](react-native-quickstart-manual). ::: Чтобы отобразить флоу, созданное в Adapty Flow Builder, в коде приложения нужно выполнить всего несколько шагов: 1. **Получите флоу**: Запросите его из Adapty. 2. **Отобразите его — Adapty возьмёт покупки на себя**: Покажите view в приложении. 3. **Обработайте действия кнопок**: Свяжите действия пользователя с реакцией вашего приложения на них. Например, открывайте ссылки или закрывайте флоу при нажатии кнопок. ## Перед началом работы \{#before-you-start\} Выполните следующие шаги: 1. Подключите приложение к [App Store](initial_ios) и/или [Google Play](initial-android) в дашборде Adapty. 2. [Создайте продукты](create-product) в Adapty. 3. [Создайте пейвол и добавьте в него продукты](create-paywall). 4. [Создайте плейсмент и добавьте пейвол в него](create-placement). 5. [Установите и активируйте Adapty SDK](sdk-installation-reactnative) в коде приложения. :::tip Быстрее всего выполнить эти шаги можно с помощью [quickstart-гайда](quickstart) или создав пейволы и плейсменты через [Developer CLI](developer-cli-quickstart). ::: ## 1. Получите флоу \{#1-get-the-flow\} Флоу привязаны к плейсментам, настроенным в дашборде. Плейсменты позволяют показывать разные флоу разным аудиториям или запускать [A/B-тесты](ab-tests). Чтобы получить флоу, созданный в Adapty Flow Builder, получите объект `flow` по идентификатору [плейсмента](placements) с помощью метода `getFlow`. Флоу содержит элементы интерфейса и стили, необходимые для его отображения. ```typescript showLineNumbers title="React Native" try { const flow = await adapty.getFlow('YOUR_PLACEMENT_ID'); // the requested flow } catch (error) { // handle the error } ``` ## 2. Отображение флоу \{#display-the-flow\} Теперь, когда у вас есть флоу, достаточно добавить несколько строк для его отображения. <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Чтобы встроить флоу в существующее дерево компонентов, используйте компонент `AdaptyFlowView` непосредственно в иерархии компонентов React Native: ```typescript showLineNumbers title="React Native (TSX)" function MyFlow({ flow }) { const onPurchaseCompleted = useCallback<FlowEventHandlers['onPurchaseCompleted']>( (result, product) => result.type !== 'user_cancelled', [], ); return ( <AdaptyFlowView flow={flow} style={{ flex: 1 }} onPurchaseCompleted={onPurchaseCompleted} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> Чтобы отобразить флоу как отдельный экран, создайте `view` с помощью метода `createFlowView`, задайте обработчики событий, затем вызовите `view.present()`. Каждый `view` можно использовать только один раз. Если вам нужно снова показать флоу, вызовите `createFlowView` ещё раз, чтобы создать новый экземпляр `view`. ```typescript showLineNumbers title="React Native" try { const view = await createFlowView(flow); view.setEventHandlers({ onPurchaseCompleted(result, product) { return result.type !== 'user_cancelled'; }, }); await view.present(); } catch (error) { // handle the error } ``` </TabItem> </Tabs> :::tip Подробнее о том, как отобразить флоу, читайте в нашем [гайде](react-native-present-paywalls). ::: ## 3. Обработка действий кнопок \{#handle-button-actions\} Когда пользователи нажимают кнопки во флоу, React Native SDK автоматически обрабатывает покупки, восстановление, закрытие флоу и открытие URL. Однако у других кнопок есть пользовательские или предопределённые идентификаторы, требующие обработки действий в вашем коде. Или вы можете захотеть переопределить их поведение по умолчанию. Например, ниже показано поведение по умолчанию для кнопки закрытия. Добавлять это в код не нужно, но здесь вы можете увидеть, как это делается при необходимости. <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> For the React component, handle actions directly in the `AdaptyFlowView` component: ```typescript showLineNumbers title="React Native (TSX)" function MyFlow({ flow }) { const onCloseButtonPress = useCallback<FlowEventHandlers['onCloseButtonPress']>( () => true, // allow the flow to close [], ); const onCustomAction = useCallback<FlowEventHandlers['onCustomAction']>( (actionId) => false, [], ); return ( <AdaptyFlowView flow={flow} style={{ flex: 1 }} onCloseButtonPress={onCloseButtonPress} onCustomAction={onCustomAction} /> ); } ``` </TabItem> <TabItem value="standalone" label="Модальное представление"> Для модального представления реализуйте обработчики событий с помощью `setEventHandlers`: ```typescript showLineNumbers title="React Native" const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { return true; // allow the flow to close }, }); ``` </TabItem> </Tabs> :::tip Прочитайте наши гайды по обработке [действий](react-native-handle-paywall-actions) и [событий](react-native-handling-events-1) кнопок. ::: ## Следующие шаги \{#next-steps\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Пейвол готов к отображению в приложении. Протестируйте покупки в [песочнице App Store](test-purchases-in-sandbox) или в [Google Play Store](testing-on-android), чтобы убедиться, что тестовая покупка через пейвол проходит успешно. Теперь нужно [проверить уровень доступа пользователей](react-native-check-subscription-status), чтобы показывать пейвол или предоставлять доступ к платным функциям только нужным пользователям. ## Полный пример \{#full-example\} Вот как все шаги из этого гайда можно объединить в вашем приложении. <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React компонент" default> ```javascript showLineNumbers title="React Native (TSX)" export default function FlowScreen() { const [flow, setFlow] = useState(null); const loadFlow = async () => { try { const flowData = await adapty.getFlow('YOUR_PLACEMENT_ID'); setFlow(flowData); } catch (error) { console.warn('Error loading flow:', error); } }; const onCloseButtonPress = useCallback<FlowEventHandlers['onCloseButtonPress']>( () => true, [], ); const onPurchaseCompleted = useCallback<FlowEventHandlers['onPurchaseCompleted']>( (result, product) => result.type !== 'user_cancelled', [], ); useEffect(() => { loadFlow(); }, []); return ( <View style={{ flex: 1 }}> {flow ? ( <AdaptyFlowView flow={flow} style={{ flex: 1 }} onCloseButtonPress={onCloseButtonPress} onPurchaseCompleted={onPurchaseCompleted} /> ) : ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Button title="Load Flow" onPress={loadFlow} /> </View> )} </View> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> ```javascript showLineNumbers title="React Native" export default function FlowScreen() { const showFlow = async () => { try { const flow = await adapty.getFlow('YOUR_PLACEMENT_ID'); const view = await createFlowView(flow); view.setEventHandlers({ onCloseButtonPress() { return true; }, onPurchaseCompleted(result, product) { return result.type !== 'user_cancelled'; }, }); await view.present(); } catch (error) { // handle any error that may occur during the process console.warn('Error showing flow:', error); } }; // you can add a button to manually trigger the flow for testing purposes return ( <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Button title="Show Flow" onPress={showFlow} /> </View> ); } ``` </TabItem> </Tabs> --- # File: react-native-check-subscription-status --- --- title: "Проверка статуса подписки в React Native SDK" description: "Узнайте, как проверить статус подписки в вашем React Native приложении с помощью Adapty." --- Чтобы решить, может ли пользователь получить доступ к платному контенту или должен увидеть пейвол, нужно проверить его [уровень доступа](access-level) в профиле. В этой статье показано, как обращаться к состоянию профиля, чтобы понять, что показать пользователю — пейвол или платный контент. ## Получение статуса подписки \{#get-subscription-status\} Когда вы решаете, показать пользователю пейвол или платный контент, вы проверяете его [уровень доступа](access-level) в профиле. Есть два варианта: - Вызовите `getProfile`, если нужны актуальные данные профиля прямо сейчас (например, при запуске приложения) или если хотите принудительно обновить их. - Настройте **автоматические обновления профиля**, чтобы хранить локальную копию, которая автоматически обновляется при изменении статуса подписки. ### Получить профиль \{#get-profile\} Самый простой способ узнать статус подписки — использовать метод `getProfile` для обращения к профилю: ```typescript showLineNumbers try { const profile = await adapty.getProfile(); } catch (error) { // handle the error } ``` ### Подписка на обновления подписки \{#listen-to-subscription-updates\} Чтобы автоматически получать обновления профиля в приложении: 1. Используйте `adapty.addEventListener('onLatestProfileLoad')` для отслеживания изменений профиля — Adapty будет автоматически вызывать этот метод при каждом изменении статуса подписки пользователя. 2. Сохраняйте обновлённые данные профиля при вызове этого метода, чтобы использовать их в любом месте приложения без дополнительных сетевых запросов. ```javascript class SubscriptionManager { private currentProfile: any = null; constructor() { // Listen for profile updates adapty.addEventListener('onLatestProfileLoad', (profile) => { this.currentProfile = profile; // Update UI, unlock content, etc. }); } // Use stored profile instead of calling getProfile() hasAccess(): boolean { return this.currentProfile?.accessLevels?.['premium']?.isActive ?? false; } } ``` :::note Adapty автоматически вызывает обработчик события `onLatestProfileLoad` при запуске приложения, предоставляя кешированные данные подписки даже при отсутствии интернета. ::: ## Связь профиля с логикой пейвола \{#connect-profile-with-paywall-logic\} Когда нужно принять немедленное решение о показе пейвола или предоставлении доступа к платным функциям, можно проверить профиль пользователя напрямую. Этот подход удобен при запуске приложения, при переходе в премиум-разделы или перед отображением определённого контента. ```javascript const checkAccessLevel = async () => { try { const profile = await adapty.getProfile(); return profile.accessLevels['YOUR_ACCESS_LEVEL']?.isActive === true; } catch (error) { console.warn('Error checking access level:', error); return false; // Show paywall if access check fails } }; const initializePaywall = async () => { try { await loadPaywall(); const hasAccess = await checkAccessLevel(); if (!hasAccess) { // Show paywall if no access } } catch (error) { console.warn('Error initializing paywall:', error); } }; ``` ## Дальнейшие шаги \{#next-steps\} Теперь, когда вы знаете, как отслеживать статус подписки, узнайте, как [работать с профилями пользователей](react-native-quickstart-identify), чтобы они могли получить доступ к тому, за что заплатили. --- # File: react-native-quickstart-identify --- --- title: "Идентификация пользователей в React Native SDK" description: "Быстрый старт по настройке Adapty для управления встроенными покупками в React Native." --- :::important Этот гайд для вас, если у вас есть собственная система аутентификации. Здесь вы узнаете, как работать с профилями пользователей в Adapty, чтобы они соответствовали вашей существующей системе аутентификации. ::: То, как вы управляете покупками пользователей, зависит от модели аутентификации вашего приложения: - Если приложение не использует бэкенд-аутентификацию и не хранит данные пользователей, смотрите [раздел об анонимных пользователях](#anonymous-users). - Если в приложении есть (или будет) бэкенд-аутентификация, смотрите [раздел об идентифицированных пользователях](#identified-users). **Ключевые концепции**: - **Профили** — это сущности, необходимые для работы SDK. Adapty создаёт их автоматически. - Они могут быть анонимными **(без customer user ID)** или идентифицированными **(с customer user ID)**. - Вы передаёте **customer user ID**, чтобы сопоставить профили в Adapty с вашей внутренней системой аутентификации. Вот чем отличаются анонимные и идентифицированные пользователи: | | Анонимные пользователи | Идентифицированные пользователи | |-------------------------|-------------------------------------------------------------|--------------------------------------------------------------------------------------------------------| | **Управление покупками** | Восстановление покупок на уровне стора | История покупок сохраняется на всех устройствах через customer user ID | | **Управление профилем** | Новый профиль при каждой переустановке | Один и тот же профиль на всех сессиях и устройствах | | **Хранение данных** | Данные анонимных пользователей привязаны к установке приложения | Данные идентифицированных пользователей сохраняются между установками приложения | ## Анонимные пользователи \{#anonymous-users\} Если у вас нет серверной аутентификации, **вам не нужно обрабатывать аутентификацию в коде приложения**: 1. Когда SDK активируется при первом запуске приложения, Adapty **создаёт новый профиль для пользователя**. 2. Когда пользователь совершает покупку в приложении, она **привязывается к его профилю Adapty и аккаунту в сторе**. 3. Когда пользователь **переустанавливает** приложение или устанавливает его на **новом устройстве**, Adapty **создаёт новый анонимный профиль при активации**. 4. Если пользователь ранее совершал покупки в вашем приложении, по умолчанию они автоматически синхронизируются из App Store при активации SDK. Таким образом, для анонимных пользователей при каждой установке будет создаваться новый профиль, но это не проблема: в аналитике Adapty можно [настроить, что считать новой установкой](general#4-installs-definition-for-analytics). :::note Восстановление из резервной копии работает иначе, чем переустановка. По умолчанию при восстановлении из бэкапа SDK сохраняет кешированные данные и не создаёт новый профиль. Это поведение можно настроить с помощью параметра `clearDataOnBackup`. [Подробнее](sdk-installation-react-native-pure#clear-data-on-backup-restore). ::: Для анонимных пользователей установки считаются по **идентификаторам устройств**. При этом каждая установка приложения на устройство считается отдельной установкой, включая повторные. ## Идентификация пользователей \{#identified-users\} Есть два способа идентифицировать пользователей в приложении: - [**При входе/регистрации:**](#during-loginsignup) Если пользователи входят уже после запуска приложения, вызовите `identify()` с customer user ID в момент аутентификации. - [**При активации SDK:**](#during-the-sdk-activation) Если customer user ID уже сохранён на момент запуска приложения, передайте его при вызове `activate()`. :::important По умолчанию, когда Adapty получает покупку от Customer User ID, который уже связан с другим Customer User ID, уровень доступа распределяется между обоими профилями — то есть оба получают платный доступ. Вы можете настроить это поведение так, чтобы платный доступ переносился с одного профиля на другой, или полностью отключить распределение. Подробнее — в [статье](general#6-sharing-paid-access-between-user-accounts). ::: <img src="/assets/shared/img/identify-diagram.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### В процессе входа/регистрации \{#during-loginsignup\} Если вы идентифицируете пользователей после запуска приложения (например, после входа в аккаунт или регистрации), используйте метод `identify`, чтобы задать их customer user ID. - Если вы **не использовали этот customer user ID раньше**, Adapty автоматически свяжет его с текущим профилем. - Если вы **уже использовали этот customer user ID для идентификации пользователя**, Adapty переключится на работу с профилем, связанным с этим customer user ID. :::important Идентификаторы пользователей должны быть уникальными для каждого пользователя. Если захардкодить значение параметра, все пользователи будут считаться одним. ::: Всегда используйте `await` для `identify` перед вызовом других методов SDK. Параллельные вызовы приводят к ошибке `#3006 profileWasChanged` или работают с анонимным профилем. См. [Порядок вызовов в React Native SDK](react-native-sdk-call-order). ```typescript showLineNumbers try { await adapty.identify("YOUR_USER_ID"); // Unique for each user // successfully identified } catch (error) { // handle the error } ``` ### При активации SDK \{#during-the-sdk-activation\} Если вы знаете customer user ID ещё до активации SDK, его можно передать прямо в метод `activate` — и не вызывать `identify` отдельно. Если же вы знаете customer user ID, но устанавливаете его только после активации, то при активации Adapty создаст новый анонимный профиль и переключится на существующий лишь после вызова `identify`. Вы можете передать как существующий customer user ID (который вы уже использовали ранее), так и новый. Если передать новый, профиль, созданный при активации, будет автоматически привязан к этому customer user ID. :::note По умолчанию создание анонимных профилей не влияет на аналитические дашборды, поскольку установки считаются на основе идентификаторов устройств. Идентификатор устройства соответствует одной установке приложения из стора и обновляется только при переустановке приложения. Он не зависит от того, является ли установка первой или повторной, а также от того, используется ли существующий пользовательский ID. Создание профиля (при активации SDK или выходе из системы), вход в систему или обновление приложения без его переустановки не генерируют дополнительных событий установки. Если вы хотите считать установки на основе уникальных пользователей, а не устройств, перейдите в **App settings** и настройте [**Installs definition for analytics**](general#4-installs-definition-for-analytics). ::: ```typescript showLineNumbers adapty.activate("PUBLIC_SDK_KEY", { customerUserId: "YOUR_USER_ID" // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. }); ``` ### Выход пользователей из аккаунта \{#log-users-out\} Если в приложении есть кнопка выхода, используйте метод `logout`. :::important Выход из аккаунта создаёт новый анонимный профиль для пользователя. ::: ```typescript showLineNumbers try { await adapty.logout(); // successful logout } catch (error) { // handle the error } ``` :::info Чтобы снова войти в аккаунт, используйте метод `identify`. ::: ### Разрешите покупки без входа в систему \{#allow-purchases-without-login\} Если пользователи могут совершать покупки как до, так и после входа в приложение, необходимо убедиться, что после входа они сохранят доступ: 1. Когда неавторизованный пользователь совершает покупку, Adapty привязывает её к анонимному ID профиля. 2. Когда пользователь входит в аккаунт, Adapty переключается на работу с идентифицированным профилем. - Если это новый customer user ID (например, покупка была совершена до регистрации), Adapty присваивает customer user ID текущему профилю, сохраняя всю историю покупок. - Если customer user ID уже существует (он уже привязан к профилю), после переключения профиля нужно получить актуальный уровень доступа. Для этого можно вызвать [`getProfile`](react-native-check-subscription-status) сразу после идентификации или [подписаться на обновления профиля](react-native-check-subscription-status), чтобы данные синхронизировались автоматически. ## Следующие шаги \{#next-steps\} Поздравляем! Вы реализовали логику встроенных покупок в своём приложении! Желаем вам всего наилучшего в монетизации вашего приложения! Чтобы получить от Adapty ещё больше, изучите эти темы: - [**Тестирование**](troubleshooting-test-purchases): Убедитесь, что всё работает как ожидается - [**Онбординги**](react-native-onboardings): Вовлекайте пользователей с помощью онбордингов и повышайте удержание - [**Интеграции**](configuration): Интегрируйтесь с сервисами маркетинговой атрибуции и аналитики буквально в одну строку кода - [**Настройка атрибутов профиля**](react-native-setting-user-attributes): Добавляйте пользовательские атрибуты к профилям и создавайте сегменты — чтобы запускать A/B-тесты или показывать разные пейволы разным пользователям --- # File: adapty-sdk-integration-skill-react-native --- --- title: "Интеграция Adapty в приложение React Native с помощью навыка SDK integration" description: "Используйте навык adapty-sdk-integration для сквозной интеграции Adapty SDK в приложение React Native с помощью вашего AI-инструмента для кодирования." --- [Скилл adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) автоматизирует интеграцию Adapty от начала до конца: настройку дашборда, установку SDK, пейвол и проверку на каждом этапе. Он автоматически определяет вашу платформу и подгружает нужную документацию Adapty на каждом шаге. **Поддерживаемые инструменты**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Для установки выберите команду для своего инструмента. Полный список — в [README скилла](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex или любой другой инструмент** — используйте [skills CLI](https://skills.sh) (обратите внимание, что скиллы, установленные таким способом, не обновляются автоматически): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` Либо клонируйте репозиторий и скопируйте `skills/adapty-sdk-integration/` в директорию скиллов вашего инструмента. После установки запустите скилл в вашем проекте: ``` /adapty-sdk-integration ``` Скилл задаст несколько вопросов по настройке, а затем проведёт через настройку дашборда, установку SDK, пейвол и проверку. :::important Этот навык находится в бета-версии. Если он зависает или ведёт себя непредсказуемо, воспользуйтесь [пошаговым руководством по интеграции](adapty-cursor-react-native) — оно проведёт ваш AI-инструмент через каждый этап с нужной документацией. ::: [Скилл adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) автоматизирует интеграцию Adapty от начала до конца: настройку дашборда, установку SDK, пейвол и проверку на каждом этапе. Он автоматически определяет вашу платформу и подгружает нужную документацию Adapty на каждом шаге. **Поддерживаемые инструменты**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Для установки выберите команду для своего инструмента. Полный список — в [README скилла](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex или любой другой инструмент** — используйте [skills CLI](https://skills.sh) (обратите внимание, что скиллы, установленные таким способом, не обновляются автоматически): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` Либо клонируйте репозиторий и скопируйте `skills/adapty-sdk-integration/` в директорию скиллов вашего инструмента. После установки запустите скилл в вашем проекте: ``` /adapty-sdk-integration ``` Скилл задаст несколько вопросов по настройке, а затем проведёт через настройку дашборда, установку SDK, пейвол и проверку. --- # File: adapty-cursor-react-native --- --- title: "Интеграция Adapty в ваше React Native приложение с помощью ИИ" description: "Пошаговое руководство по интеграции Adapty в ваше React Native приложение с использованием Cursor, Context7, ChatGPT, Claude или других ИИ-инструментов." --- Это руководство шаг за шагом проведёт вас через интеграцию Adapty в ваше приложение на React Native с помощью AI-инструмента для написания кода — вы даёте ему нужную документацию Adapty в правильном порядке. For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## Прежде чем начать: настройка дашборда \{#before-you-start-dashboard-setup\} Adapty требует предварительной настройки в дашборде — до того как вы начнёте писать код с использованием SDK. Это можно сделать с помощью интерактивного LLM-скилла или вручную через дашборд. ### Подход через skill (рекомендуется) \{#skill-approach-recommended\} Skill Adapty CLI позволяет вашему LLM настраивать приложение, продукты, уровни доступа, пейволы и плейсменты напрямую — без необходимости открывать дашборд на каждом шаге. Нужно только [подключить сторы](integrate-payments) в дашборде. ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` После добавления skill запустите `/adapty-cli` в своём агенте. Он проведёт вас через каждый шаг — включая момент, когда нужно открыть дашборд для подключения сторов. ### Подход через дашборд Если вы предпочитаете настраивать всё вручную, вот что нужно сделать до написания кода. Ваш LLM не может самостоятельно получить значения из дашборда — вам придётся предоставить их самостоятельно. 1. **Подключите сторы**: В дашборде Adapty перейдите в **App settings → General**. Подключите App Store и Google Play, если ваше приложение поддерживает обе платформы. Это обязательное условие для работы покупок. [Подключите сторы](integrate-payments) 2. **Скопируйте публичный SDK-ключ**: В дашборде Adapty перейдите в **App settings → General** и найдите раздел **API keys**. В коде это строка, которую вы передаёте в `adapty.activate("YOUR_PUBLIC_SDK_KEY")`. 3. **Создайте хотя бы один продукт**: В дашборде Adapty перейдите на страницу **Products**. В коде ссылаться на продукты напрямую не нужно — Adapty передаёт их через пейволы. [Добавить продукты](quickstart-products) 4. **Создайте пейвол и плейсмент**: В дашборде Adapty создайте пейвол на странице **Paywalls**, затем привяжите его к плейсменту на странице **Placements**. В коде идентификатор плейсмента — это строка, которую вы передаёте в `adapty.getPaywall("YOUR_PLACEMENT_ID")`. [Создать пейвол](quickstart-paywalls) 5. **Настройте уровни доступа**: в дашборде Adapty настройте каждый продукт на странице **Products**. В коде проверяйте строку `profile.accessLevels['premium']?.isActive`. Уровень доступа `premium` по умолчанию подходит большинству приложений. Если платящие пользователи получают доступ к разным функциям в зависимости от продукта (например, план `basic` и план `pro`), [создайте дополнительные уровни доступа](assigning-access-level-to-a-product) до начала разработки. :::tip Как только у вас есть все пять — можно писать код. Скажите LLM: «Мой публичный SDK-ключ — X, мой идентификатор плейсмента — Y», чтобы она сгенерировала правильный код инициализации и получения пейвола. ::: ### Настройте по мере готовности \{#set-up-when-ready\} Это не обязательно для начала разработки, но пригодится по мере развития интеграции: - **A/B-тесты**: настраиваются на странице **Placements**. Изменений в коде не требуется. [A/B-тесты](ab-tests) - **Дополнительные пейволы и плейсменты**: добавьте больше вызовов `getPaywall` с разными идентификаторами плейсментов. - **Интеграции с аналитикой**: настраиваются на странице **Integrations**. Процесс настройки зависит от конкретной интеграции. См. [интеграции с аналитикой](analytics-integration) и [интеграции с атрибуцией](attribution-integration). ## Загрузите документацию Adapty в свой LLM \{#feed-adapty-docs-to-your-llm\} ### Используйте Context7 (рекомендуется) \{#use-context7-recommended\} [Context7](https://context7.com) — это MCP-сервер, который даёт вашей LLM прямой доступ к актуальной документации Adapty. LLM автоматически находит нужные доки, исходя из вашего запроса, — никакого ручного копирования ссылок. Context7 работает с **Cursor**, **Claude Code**, **Windsurf** и другими MCP-совместимыми инструментами. Для настройки выполните: ``` npx ctx7 setup ``` Команда определит ваш редактор и настроит сервер Context7. Для ручной настройки см. [репозиторий Context7 на GitHub](https://github.com/upstash/context7). После настройки обращайтесь к библиотеке Adapty в своих промптах: ``` Use the adaptyteam/adapty-docs library to look up how to install the React Native SDK ``` :::warning Несмотря на то что Context7 избавляет от необходимости вручную вставлять ссылки на документацию, порядок реализации важен. Следуйте [пошаговому руководству](#implementation-walkthrough) ниже строго по шагам, чтобы всё работало корректно. ::: ### Используйте документацию в формате обычного текста \{#use-plain-text-docs\} Любой документ Adapty доступен в формате Markdown. Добавьте `.md` в конец URL или нажмите **Copy for LLM** под заголовком статьи. Например: [adapty-cursor-react-native.md](https://adapty.io/docs/ru/adapty-cursor-react-native.md). Каждый шаг [пошагового руководства по интеграции](#implementation-walkthrough) ниже содержит блок «Отправьте это своему LLM» со ссылками `.md` для вставки. Чтобы получить сразу несколько документов, смотрите [индексные файлы и подборки по платформам](#plain-text-doc-index-files) ниже. ## Пошаговое руководство по интеграции \{#implementation-walkthrough\} Этот гайд проведёт вас через интеграцию Adapty в порядке реализации. Каждый этап включает документацию для передачи в LLM, описание ожидаемого результата и типичные проблемы. ### Планирование интеграции \{#plan-your-integration\} Прежде чем браться за код, попросите LLM проанализировать ваш проект и составить план реализации. Если ваш AI-инструмент поддерживает режим планирования (например, Cursor или Claude Code), используйте его — тогда LLM сможет изучить структуру вашего проекта и документацию Adapty ещё до написания кода. Укажите LLM, какой подход к покупкам вы используете — от этого зависит, какие гайды ей нужно будет применять: - [**Adapty Paywall Builder**](adapty-paywall-builder): Вы создаёте пейволы в визуальном редакторе Adapty, а SDK отображает их автоматически. - [**Пейволы, созданные вручную**](react-native-making-purchases): Вы строите собственный интерфейс пейвола в коде, но используете Adapty для получения продуктов и обработки покупок. - [**Режим наблюдателя**](observer-vs-full-mode): Вы сохраняете существующую инфраструктуру покупок и используете Adapty только для аналитики и интеграций. Не знаете, что выбрать? Прочитайте [сравнительную таблицу в руководстве по быстрому старту](react-native-quickstart-paywalls). ### Установка и настройка SDK \{#install-and-configure-the-sdk\} Добавьте зависимость Adapty SDK через npm (или yarn) и активируйте её с помощью вашего публичного ключа SDK. Это основа — без неё ничего не работает. У нас есть отдельные гайды по установке для проектов на Expo и bare React Native — выберите тот, что подходит для вашего случая. **Гайды:** - [Установка с Expo](sdk-installation-react-native-expo) - [Установка с bare React Native](sdk-installation-react-native-pure) :::tip[Checkpoint] - **Ожидаемый результат:** Приложение собирается и запускается на iOS и Android. В логах Metro bundler видна запись об активации Adapty. - **Частая ошибка:** "Public API key is missing" → убедитесь, что заменили заглушку на реальный ключ из **App settings**. ::: ### Показ пейволов и обработка покупок \{#show-paywalls-and-handle-purchases\} Получите пейвол по идентификатору плейсмента, отобразите его и обработайте события покупок. Нужные вам гайды зависят от того, как вы обрабатываете покупки. Тестируйте каждую покупку в песочнице по ходу работы — не откладывайте на конец. Инструкции по настройке смотрите в разделе [Тестирование покупок в песочнице](test-purchases-in-sandbox). <Tabs groupId="paywall-approach"> <TabItem value="builder" label="Paywall Builder" default> **Гайды:** - [Включение покупок через пейволы (быстрый старт)](react-native-quickstart-paywalls) - [Получение пейволов Paywall Builder и их конфигурации](react-native-get-pb-paywalls) - [Отображение пейволов](react-native-present-paywalls) - [Обработка событий пейвола](react-native-handling-events-1) - [Реакция на действия кнопок](react-native-handle-paywall-actions) Read these Adapty docs before writing code: - https://adapty.io/docs/ru/react-native-quickstart-paywalls.md - https://adapty.io/docs/ru/react-native-get-pb-paywalls.md - https://adapty.io/docs/ru/react-native-present-paywalls.md - https://adapty.io/docs/ru/react-native-handling-events-1.md - https://adapty.io/docs/ru/react-native-handle-paywall-actions.md :::tip[Checkpoint] - **Ожидается:** Пейвол отображается с настроенными продуктами. Нажатие на продукт вызывает диалог покупки в песочнице. - **Проблема:** Пустой пейвол или ошибка `getPaywall` → убедитесь, что ID плейсмента точно совпадает с дашбордом и плейсменту назначена аудитория. ::: </TabItem> <TabItem value="manual" label="Ручные пейволы"> **Гайды:** - [Включение покупок в вашем кастомном пейволе (быстрый старт)](react-native-quickstart-manual) - [Получение пейволов и продуктов](fetch-paywalls-and-products-react-native) - [Отображение пейвола на основе Remote Config](present-remote-config-paywalls-react-native) - [Совершение покупок](react-native-making-purchases) - [Восстановление покупок](react-native-restore-purchase) Read these Adapty docs before writing code: - https://adapty.io/docs/ru/react-native-quickstart-manual.md - https://adapty.io/docs/ru/fetch-paywalls-and-products-react-native.md - https://adapty.io/docs/ru/present-remote-config-paywalls-react-native.md - https://adapty.io/docs/ru/react-native-making-purchases.md - https://adapty.io/docs/ru/react-native-restore-purchase.md :::tip[Checkpoint] - **Ожидаемый результат:** Ваш кастомный пейвол отображает продукты, загруженные из Adapty. Нажатие на продукт вызывает диалог покупки в песочнице. - **Возможная проблема:** Пустой массив продуктов → убедитесь, что на пейволе назначены продукты в дашборде и у плейсмента задана аудитория. ::: </TabItem> <TabItem value="observer" label="Observer mode"> **Гайды:** - [Обзор Observer mode](observer-vs-full-mode) - [Реализация Observer mode](implement-observer-mode-react-native) - [Передача транзакций в Observer mode](report-transactions-observer-mode-react-native) Отправьте это своему LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/observer-vs-full-mode.md - https://adapty.io/docs/ru/implement-observer-mode-react-native.md - https://adapty.io/docs/ru/report-transactions-observer-mode-react-native.md ``` :::tip[Checkpoint] - **Ожидается:** После покупки в песочнице через существующий флоу покупки транзакция появляется в **Event Feed** дашборда Adapty. - **Важно:** Нет событий → убедитесь, что вы отправляете транзакции в Adapty и серверные уведомления настроены для обоих сторов. ::: </TabItem> </Tabs> ### Проверка статуса подписки \{#check-subscription-status\} После покупки проверьте профиль пользователя на наличие активного уровня доступа, чтобы открыть доступ к премиум-контенту. **Гайд:** [Проверка статуса подписки](react-native-check-subscription-status) Отправьте это в свой LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/react-native-check-subscription-status.md ``` :::tip[Контрольная точка] - **Ожидаемый результат:** После покупки в песочнице `profile.accessLevels['premium']?.isActive` возвращает `true`. - **Частая ошибка:** Пустой `accessLevels` после покупки → проверьте, что продукту назначен уровень доступа в дашборде. ::: ### Идентификация пользователей \{#identify-users\} Привяжите аккаунты пользователей вашего приложения к профилям Adapty, чтобы покупки сохранялись на всех устройствах. :::important Пропустите этот шаг, если в вашем приложении нет аутентификации. ::: **Гайд:** [Идентификация пользователей](react-native-quickstart-identify) Отправьте это в ваш LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/react-native-quickstart-identify.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После вызова `adapty.identify("your-user-id")` в разделе **Profiles** дашборда появляется ваш пользовательский ID. - **Важно:** Вызывайте `identify` после активации, но до получения пейволов — иначе действия могут быть привязаны к анонимному профилю. ::: ### Подготовка к релизу \{#prepare-for-release\} Когда интеграция заработает в песочнице, пройдитесь по чеклисту релиза и убедитесь, что всё готово к продакшену. **Гайд:** [Чеклист релиза](release-checklist) Отправьте это своему LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/ru/release-checklist.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** Все пункты чеклиста подтверждены: подключение сторов, серверные уведомления, флоу покупки, проверки уровня доступа и требования конфиденциальности. - **Частая проблема:** Отсутствуют серверные уведомления → настройте App Store Server Notifications в **App settings → iOS SDK** и Google Play Real-Time Developer Notifications в **App settings → Android SDK**. ::: ## Индексные файлы в виде обычного текста \{#plain-text-doc-index-files\} Если вам нужно дать вашему LLM более широкий контекст помимо отдельных страниц, у нас есть индексные файлы, которые перечисляют или объединяют всю документацию Adapty: - [`llms.txt`](https://adapty.io/docs/ru/llms.txt): Список всех страниц со ссылками `.md`. [Формирующийся стандарт](https://llmstxt.org/) для обеспечения доступа LLM к сайтам. Обратите внимание, что для некоторых AI-агентов (например, ChatGPT) потребуется скачать `llms.txt` и загрузить файл в чат. - [`llms-full.txt`](https://adapty.io/docs/ru/llms-full.txt): Вся документация Adapty в одном файле. Очень большой — используйте только когда нужна полная картина. - Файлы для React Native: [`react-native-llms.txt`](https://adapty.io/docs/ru/react-native-llms.txt) и [`react-native-llms-full.txt`](https://adapty.io/docs/ru/react-native-llms-full.txt): подмножества документации для конкретной платформы, которые экономят токены по сравнению с полным сайтом. --- # File: react-native-get-pb-paywalls --- --- title: "Получение флоу и пейволов - React Native" description: "Получайте флоу и пейволы из Adapty в вашем React Native приложении." --- <SDKv4> <MethodPromo method="getFlow" /> После того как вы [создали флоу или пейвол в Paywall Builder](adapty-paywall-builder), его можно отобразить в мобильном приложении. Первый шаг — получить флоу или пейвол, связанный с плейсментом, и конфигурацию его отображения, как описано ниже. Обратите внимание, что этот раздел посвящён флоу и пейволам, созданным в Paywall Builder. Если вы реализуете пейволы вручную, обратитесь к разделу [Получение пейволов и продуктов для пейволов с Remote Config в мобильном приложении](fetch-paywalls-and-products-react-native). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Прежде чем начать показывать флоу и пейволы в своём мобильном приложении (нажмите, чтобы развернуть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте флоу/пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них флоу/пейвол](create-placement) в дашборде Adapty. 4. Установите [SDK Adapty](sdk-installation-reactnative) в своём мобильном приложении. </details> ## Получение флоу/пейвола \{#fetch-flowpaywall\} Если вы создали флоу или пейвол с помощью Flow Builder или Paywall Builder, вам не нужно беспокоиться о том, как отрендерить его в коде мобильного приложения для показа пользователю. Такой флоу или пейвол уже содержит и то, что должно отображаться, и то, как именно это должно выглядеть. Тем не менее вам нужно получить его ID через плейсмент, получить конфигурацию представления и затем показать его в мобильном приложении. Получите флоу или пейвол и создайте его [представление](react-native-get-pb-paywalls#fetch-the-view-configuration) как можно раньше — в идеале задолго до его показа. Метод `createFlowView` загружает конфигурацию представления и запускает фоновое скачивание и кэширование изображений. Чем раньше вы его вызовете, тем больше времени есть на завершение загрузки. К моменту показа флоу или пейвола конфигурация и изображения уже могут быть закэшированы и готовы к отображению. Чтобы получить флоу или пейвол, используйте метод `getFlow`: ```typescript showLineNumbers try { const placementId = 'YOUR_PLACEMENT_ID'; const flow = await adapty.getFlow(placementId); // запрошенный флоу/пейвол } catch (error) { // обработка ошибки } ``` Параметры: | Параметр | Наличие | Описание | |-------------------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указывали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Рекомендуем этот вариант — он гарантирует, что пользователи всегда получают самые актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильный интернет, рассмотрите использование `.returnCacheDataElseLoad` — оно возвращает кешированные данные, если они есть. В этом случае пользователи могут не получить самые последние данные, зато время загрузки будет меньше независимо от качества соединения. Кеш обновляется регулярно, поэтому его безопасно использовать в рамках сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при переустановке или ручной очистке.</p><p></p><p>Adapty SDK хранит пейволы локально на двух уровнях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](fallback-paywalls). Для более быстрой загрузки пейволов мы также используем CDN и отдельный резервный сервер на случай недоступности CDN. Эта система обеспечивает получение актуальных версий пейволов и надёжную работу даже при слабом интернет-соединении.</p> | | **loadTimeoutMs** | по умолчанию: 5 сек | <p>Это значение ограничивает таймаут для данного метода. По истечении таймаута будут возвращены кешированные данные или локальный резервный вариант.</p><p>Обратите внимание: в редких случаях метод может завершиться по таймауту немного позже, чем указано в `loadTimeout`, поскольку операция может состоять из нескольких запросов под капотом.</p><p>Для Android: можно создать `TimeInterval` с помощью функций-расширений (например, `5.seconds`, где `.seconds` — из `import com.adapty.utils.seconds`) или `TimeInterval.seconds(5)`. Чтобы убрать ограничение, используйте `TimeInterval.INFINITE`.</p> | Response parameters: | Параметр | Описание | | :-------- |:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Flow | Объект `AdaptyFlow` с идентификаторами флоу (`id`, `variationId`), названием, плейсментом, вариантами пейвола (`paywalls`) и Remote Config (`remoteConfigs`). | ## Получение конфигурации представления \{#fetch-the-view-configuration\} :::important Убедитесь, что в билдере включён переключатель **Show on device**. Если этот параметр не включён, конфигурация представления не будет доступна для получения. ::: Если плейсмент был создан в **Flow Builder** или **Paywall Builder**, Adapty отрисовывает UI самостоятельно. Создайте представление с помощью `createFlowView`, затем [отобразите флоу или пейвол](react-native-present-paywalls). Если плейсмент — это кастомный пейвол без UI билдера, [обрабатывайте его как пейвол с Remote Config](present-remote-config-paywalls-react-native). В React Native SDK вызывайте `createFlowView` напрямую — предварительно получать конфигурацию не нужно. :::warning Результат метода `createFlowView` можно использовать только один раз. Если вам нужно использовать его повторно, вызовите метод `createFlowView` заново. Повторный вызов без пересоздания может привести к ошибке `AdaptyUIError.viewAlreadyPresented`. ::: ```typescript showLineNumbers try { const view = await createFlowView(flow); } catch (error) { // handle the error } ``` Параметры: | Параметр | Наличие | Описание | | :------------------- | :------- | :----------------------------------------------------------- | | **flow** | обязательный | Объект `AdaptyFlow` для получения контроллера нужного флоу/пейвола. | | **customTags** | необязательный | Словарь кастомных тегов и их значений. Кастомные теги служат плейсхолдерами в контенте и динамически заменяются на конкретные строки для персонализации контента во флоу/пейволе. Подробнее см. в разделе «Кастомные теги в Paywall Builder». | | **prefetchProducts** | необязательный | Включите для оптимизации времени отображения продуктов на экране. При значении `true` AdaptyUI автоматически загружает необходимые продукты. По умолчанию: `false`. | | **android.enableSafeArea** | необязательный | Только для Android (игнорируется на iOS). Передаётся как вложенный объект: `android: { enableSafeArea: true }`. При значении `true` к представлению флоу применяются отступы для безопасной области. По умолчанию `true` для модального представления (`createFlowView` + `present()`) и `false` для встраиваемого компонента `AdaptyFlowView`. Значение по умолчанию подходит для большинства случаев. | :::note Если вы используете несколько языков, узнайте, как добавить [локализацию флоу](add-paywall-locale-in-adapty-paywall-builder) и как правильно использовать коды локалей [здесь](react-native-localizations-and-locale-codes). ::: Когда у вас есть представление, [откройте флоу/пейвол](react-native-present-paywalls). ## Получение флоу или пейвола для аудитории по умолчанию для более быстрой загрузки \{#get-a-flow-or-paywall-for-a-default-audience-to-fetch-it-faster\} Как правило, флоу и пейволы загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и плейсментов, а у пользователей слабое интернет-соединение, загрузка флоу или пейвола может занять больше времени, чем хотелось бы. В таких случаях можно показывать флоу или пейвол для аудитории по умолчанию — это обеспечит плавный пользовательский опыт вместо пустого экрана. Чтобы решить эту задачу, можно использовать метод `getFlowForDefaultAudience`, который получает флоу или пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать флоу или пейвол через метод `getFlow`, как описано в разделе [Получение флоу/пейвола](#fetch-flowpaywall) выше. :::warning Почему мы рекомендуем использовать `getFlow` Метод `getFlowForDefaultAudience` имеет ряд существенных недостатков: - **Потенциальные проблемы с обратной совместимостью**: если вам нужно показывать разные пейволы для разных версий приложения (текущей и будущих), вы можете столкнуться с трудностями. Придётся либо проектировать пейволы с поддержкой текущей (устаревшей) версии, либо мириться с тем, что пользователи этой версии могут видеть нерендерящиеся пейволы. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, настроенный для аудитории **All Users**, что означает отказ от персонализированного таргетинга (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вы готовы принять эти недостатки ради более быстрой загрузки флоу или пейвола, используйте метод `getFlowForDefaultAudience` следующим образом. В противном случае используйте `getFlow`, описанный [выше](#fetch-flowpaywall). ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const flow = await adapty.getFlowForDefaultAudience(id); // the requested flow/paywall } catch (error) { // handle the error } ``` | Параметр | Наличие | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают самые актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `.returnCacheDataElseLoad` — оно вернёт кешированные данные, если они есть. В этом случае пользователи могут получить не самые свежие данные, зато загрузка будет быстрее вне зависимости от качества связи. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы сократить количество сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или принудительной очистке вручную.</p> | ## Кастомизация ресурсов \{#customize-assets\} Чтобы кастомизировать изображения и видео в своём флоу или пейволе, используйте пользовательские ресурсы. У hero-изображений и видео есть предопределённые ID: `hero_image` и `hero_video`. В пользовательском наборе ресурсов вы обращаетесь к этим элементам по их ID и настраиваете их поведение. Для остальных изображений и видео нужно [задать пользовательский ID](custom-media) в дашборде Adapty. Например, вы можете: - Показывать разные изображения или видео разным пользователям. - Показывать локальное превью, пока загружается основное удалённое изображение. - Показывать превью перед запуском видео. :::important Чтобы использовать эту функцию, обновите Adapty React Native SDK до версии 3.8.0 или выше. ::: Вот пример того, как можно передать кастомные ресурсы через простой словарь: ```javascript const customAssets: Record<string, AdaptyCustomAsset> = { 'custom_image': { type: 'image', relativeAssetPath: 'custom_image.png' }, 'hero_video': { type: 'video', fileLocation: { ios: { fileName: 'custom_video.mp4' }, android: { relativeAssetPath: 'videos/custom_video.mp4' } } } }; view = await createFlowView(flow, { customAssets }) ``` :::note Если ресурс не найден, флоу/пейвол отобразится в виде по умолчанию. ::: </SDKv4> <SDKv3> После того как вы [создали визуальную часть пейвола](adapty-paywall-builder) с помощью нового Paywall Builder в дашборде Adapty, его можно отобразить в мобильном приложении. Первый шаг — получить пейвол, связанный с плейсментом, и конфигурацию его отображения, как описано ниже. :::warning Новый Paywall Builder работает с React Native SDK версии 3.0 и выше. ::: Обратите внимание: эта тема относится к пейволам, настроенным с помощью Paywall Builder. Если вы реализуете пейволы вручную, обратитесь к теме [Получение пейволов и продуктов для пейволов с Remote Config в мобильном приложении](fetch-paywalls-and-products-react-native). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Прежде чем начать отображать пейволы в мобильном приложении (нажмите, чтобы развернуть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них пейвол](create-placement) в дашборде Adapty. 4. Установите [Adapty SDK](sdk-installation-reactnative) в своём мобильном приложении. </details> ## Получение пейвола, созданного в Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\} Если вы [создали пейвол в Paywall Builder](adapty-paywall-builder), вам не нужно беспокоиться о его отрисовке в коде мобильного приложения для отображения пользователю. Такой пейвол содержит как то, что должно быть показано, так и то, как именно это должно быть показано. Тем не менее, вам нужно получить его ID через плейсмент, конфигурацию отображения и затем показать пейвол в мобильном приложении. Чтобы обеспечить оптимальную производительность, важно получить пейвол и его [конфигурацию отображения](react-native-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) как можно раньше — это даст достаточно времени для загрузки изображений до того, как пейвол будет показан пользователю. Для получения пейвола используйте метод `getPaywall`: ```typescript showLineNumbers try { const placementId = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const paywall = await adapty.getPaywall(placementId, locale); // the requested paywall } catch (error) { // handle the error } ``` Параметры: | Параметр | Наличие | Описание | |-------------------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указываете при создании плейсмента в дашборде Adapty. | | **locale** | <p>необязательный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-paywall-locale-in-adapty-paywall-builder). Ожидается языковой код из одного или двух подтегов, разделённых дефисом (**-**). Первый подтег — язык, второй — регион.</p><p></p><p>Пример: `en` — английский, `pt-br` — бразильский португальский.</p><p>Подробнее о кодах локализации и рекомендациях по их использованию — в разделе [Локализации и коды локалей](localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера, а при ошибке возвращает кешированные данные. Мы рекомендуем именно этот вариант: он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Если вы считаете, что ваши пользователи часто сталкиваются с нестабильным интернетом, рассмотрите `.returnCacheDataElseLoad` — возвращает кеш, если он есть. В этом случае данные могут быть не самыми свежими, зато загрузка будет быстрее при любом качестве соединения. Кеш обновляется регулярно, поэтому использовать его в течение сессии безопасно — это позволяет избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется между перезапусками приложения и очищается только при переустановке или вручную.</p><p></p><p>Adapty SDK хранит пейволы локально на двух уровнях: регулярно обновляемый кеш (описан выше) и [резервные пейволы](fallback-paywalls). Для быстрой загрузки используется CDN, а при его недоступности — отдельный резервный сервер. Такая система гарантирует, что вы всегда получаете актуальную версию пейволов, даже при нестабильном интернете.</p> | | **loadTimeoutMs** | по умолчанию: 5 сек | <p>Ограничивает таймаут этого метода. По истечении таймаута возвращаются кешированные данные или локальный фолбэк.</p><p>Обратите внимание: в редких случаях метод может завершиться чуть позже указанного в `loadTimeout` значения, так как операция может включать несколько запросов под капотом.</p><p>Для Android: `TimeInterval` можно создать с помощью функций-расширений (например, `5.seconds`, где `.seconds` — из `import com.adapty.utils.seconds`) или `TimeInterval.seconds(5)`. Чтобы снять ограничение, используйте `TimeInterval.INFINITE`.</p> | ## Параметры ответа \{#response-parameters\} | Параметр | Описание | | :-------- |:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Paywall | Объект [`AdaptyPaywall`](https://react-native.adapty.io/interfaces/adaptypaywall) со списком ID продуктов, идентификатором пейвола, Remote Config и рядом других свойств. | ## Получение конфигурации отображения пейвола, созданного в Paywall Builder \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important Убедитесь, что в Paywall Builder включён переключатель **Show on device**. Если он не активирован, конфигурация отображения не будет доступна для получения. ::: После получения пейвола проверьте, содержит ли он `ViewConfiguration` — это означает, что пейвол был создан в Paywall Builder. Это подскажет вам, как отображать пейвол. Если `ViewConfiguration` присутствует, обработайте его как пейвол Paywall Builder; если нет, [обработайте его как пейвол Remote Config](present-remote-config-paywalls-react-native). В React Native SDK напрямую вызовите метод `createPaywallView`, не получая предварительно конфигурацию представления вручную. :::warning Результат метода `createPaywallView` можно использовать только один раз. Если он нужен повторно, вызовите метод `createPaywallView` заново. Повторный вызов без пересоздания может привести к ошибке `AdaptyUIError.viewAlreadyPresented`. ::: ```typescript showLineNumbers // for the Adapty SDK < 3.14 – import {createPaywallView} from 'react-native-adapty/dist/ui'; if (paywall.hasViewConfiguration) { try { const view = await createPaywallView(paywall); } catch (error) { // handle the error } } else { //use your custom logic } ``` Параметры: | Параметр | Обязательность | Описание | | :------------------- | :------------- | :----------------------------------------------------------- | | **paywall** | обязательный | Объект `AdaptyPaywall` для получения контроллера нужного пейвола. | | **customTags** | необязательный | Словарь пользовательских тегов и их значений. Пользовательские теги служат плейсхолдерами в содержимом пейвола и динамически заменяются конкретными строками для персонализации контента. Подробнее см. в разделе о пользовательских тегах в Paywall Builder. | | **prefetchProducts** | необязательный | Включите для оптимизации времени отображения продуктов на экране. При значении `true` AdaptyUI автоматически загрузит необходимые продукты. По умолчанию: `false`. | :::note Если вы используете несколько языков, узнайте, как добавить [локализацию Paywall Builder](add-paywall-locale-in-adapty-paywall-builder) и как правильно использовать коды локалей [здесь](react-native-localizations-and-locale-codes). ::: Получив объект представления, [покажите пейвол](react-native-present-paywalls). ## Получите пейвол для аудитории по умолчанию, чтобы загрузить его быстрее \{#get-a-paywall-for-a-default-audience-to-fetch-it-faster\} Как правило, пейволы загружаются почти мгновенно, и беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и пейволов, а у пользователей слабое интернет-соединение, загрузка пейвола может занять больше времени, чем хотелось бы. В таких ситуациях имеет смысл показывать пейвол по умолчанию — это обеспечит плавный пользовательский опыт вместо полного отсутствия пейвола. Чтобы решить эту задачу, используйте метод `getPaywallForDefaultAudience`, который получает пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать пейвол через метод `getPaywall`, как описано в разделе [Получение информации о пейволе](#fetch-paywall-designed-with-paywall-builder) выше. :::warning Почему мы рекомендуем использовать `getPaywall` Метод `getPaywallForDefaultAudience` имеет ряд существенных недостатков: - **Возможные проблемы с обратной совместимостью**: если нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать пейволы с поддержкой текущей (устаревшей) версии, либо смириться с тем, что у пользователей этой версии пейволы могут не отображаться. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, настроенный для аудитории **All Users**, — это означает отказ от персонализированного таргетинга (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вас устраивают эти ограничения ради более быстрой загрузки пейвола, используйте метод `getPaywallForDefaultAudience` следующим образом. В противном случае используйте `getPaywall`, описанный [выше](#fetch-paywall-designed-with-paywall-builder). ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const paywall = await adapty.getPaywallForDefaultAudience(id, locale); // the requested paywall } catch (error) { // handle the error } ``` :::note Метод `getPaywallForDefaultAudience` доступен начиная с версии React Native SDK 2.11.2. ::: | Параметр | Наличие | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается в виде языкового кода, состоящего из одного или нескольких подтегов, разделённых символом минус (**-**). Первый подтег — язык, второй — регион.</p><p></p><p>Пример: `en` означает английский, `pt-br` — бразильский португальский.</p><p></p><p>Подробнее о кодах локалей и рекомендациях по их использованию — в разделе [Локализации и коды локалей](react-native-localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите вариант `.returnCacheDataElseLoad` — он возвращает кешированные данные, если они есть. В таком случае пользователи могут получить не самые свежие данные, зато загрузка будет быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.</p> | ## Настройка ресурсов \{#customize-assets\} Чтобы настроить изображения и видео на пейволе, используйте пользовательские ресурсы. У hero-изображений и видео есть предопределённые идентификаторы: `hero_image` и `hero_video`. В бандле пользовательских ресурсов вы обращаетесь к этим элементам по их идентификаторам и настраиваете их поведение. Для остальных изображений и видео необходимо [задать пользовательский идентификатор](custom-media) в дашборде Adapty. Например, вы можете: - Показывать разные изображения или видео разным пользователям. - Показывать локальное превью-изображение, пока загружается основное удалённое изображение. - Показывать превью-изображение перед запуском видео. :::important Для использования этой функции обновите Adapty React Native SDK до версии 3.8.0 или выше. ::: Вот пример того, как можно передать кастомные ресурсы через простой словарь: ```javascript const customAssets: Record<string, AdaptyCustomAsset> = { 'custom_image': { type: 'image', relativeAssetPath: 'custom_image.png' }, 'hero_video': { type: 'video', fileLocation: { ios: { fileName: 'custom_video.mp4' }, android: { relativeAssetPath: 'videos/custom_video.mp4' } } } }; view = await createPaywallView(paywall, { customAssets }) ``` :::note Если ресурс не найден, пейвол вернётся к внешнему виду по умолчанию. ::: </SDKv3> --- # File: react-native-present-paywalls --- --- title: "Отображение флоу и пейволов — React Native" description: "Показывайте флоу и пейволы пользователям вашего React Native приложения с помощью Adapty." --- <SDKv4> <MethodPromo method="getFlow" label="Display flows and paywalls" /> Если вы создали флоу или пейвол в Flow Builder, вам не нужно беспокоиться о том, как отрендерить его в коде мобильного приложения для отображения пользователю. Такой флоу содержит и то, что должно быть показано, и то, как именно это должно быть показано. Прежде чем начать, убедитесь, что: 1. Вы [создали флоу или пейвол](create-paywall). 2. Вы добавили его в [плейсмент](placements). 3. Вы [получили флоу и подготовили представление](react-native-get-pb-paywalls). :::warning Этот гайд предназначен только для **флоу и пейволов Paywall Builder**, которые требуют SDK v4.0 или выше. Процесс отображения флоу отличается для пейволов на Remote Config. - Для отображения **пейволов на Remote Config** см. [Отображение пейвола, созданного через Remote Config](present-remote-config-paywalls). ::: Adapty React Native SDK предоставляет два способа отображения флоу и пейволов: - **React-компонент**: встраиваемый компонент, который можно интегрировать в архитектуру и систему навигации вашего приложения. - **Модальное представление** ## React-компонент \{#react-component\} Чтобы встроить флоу в существующее дерево компонентов, используйте компонент `AdaptyFlowView` напрямую в иерархии React Native-компонентов. Встроенный компонент позволяет интегрировать его в архитектуру приложения и систему навигации. :::tip Компонент `AdaptyFlowView` создаёт своё представление в момент рендеринга — когда загружаются конфигурация и изображения. Чтобы предзагрузить их, вызовите [`createFlowView`](react-native-get-pb-paywalls#fetch-the-view-configuration) для того же флоу раньше в приложении. Тогда компонент использует кэшированные данные и рендерится без ожидания загрузки. ::: ```typescript showLineNumbers title="React Native (TSX)" function MyFlow({ flow }) { const flowParams = useMemo(() => ({ loadTimeoutMs: 3000, }), []); const onCloseButtonPress = useCallback<FlowEventHandlers['onCloseButtonPress']>(() => {}, []); const onProductSelected = useCallback<FlowEventHandlers['onProductSelected']>((productId) => {}, []); const onPurchaseStarted = useCallback<FlowEventHandlers['onPurchaseStarted']>((product) => {}, []); const onPurchaseCompleted = useCallback<FlowEventHandlers['onPurchaseCompleted']>((purchaseResult, product) => {}, []); const onPurchaseFailed = useCallback<FlowEventHandlers['onPurchaseFailed']>((error, product) => {}, []); const onRestoreStarted = useCallback<FlowEventHandlers['onRestoreStarted']>(() => {}, []); const onRestoreCompleted = useCallback<FlowEventHandlers['onRestoreCompleted']>((profile) => {}, []); const onRestoreFailed = useCallback<FlowEventHandlers['onRestoreFailed']>((error) => {}, []); const onAppeared = useCallback<FlowEventHandlers['onAppeared']>(() => {}, []); const onError = useCallback<FlowEventHandlers['onError']>((error) => {}, []); const onLoadingProductsFailed = useCallback<FlowEventHandlers['onLoadingProductsFailed']>((error) => {}, []); const onUrlPress = useCallback<FlowEventHandlers['onUrlPress']>((url) => {}, []); const onCustomAction = useCallback<FlowEventHandlers['onCustomAction']>((actionId) => {}, []); const onWebPaymentNavigationFinished = useCallback<FlowEventHandlers['onWebPaymentNavigationFinished']>(() => {}, []); return ( <AdaptyFlowView flow={flow} params={flowParams} style={styles.flow} onCloseButtonPress={onCloseButtonPress} onProductSelected={onProductSelected} onPurchaseStarted={onPurchaseStarted} onPurchaseCompleted={onPurchaseCompleted} onPurchaseFailed={onPurchaseFailed} onRestoreStarted={onRestoreStarted} onRestoreCompleted={onRestoreCompleted} onRestoreFailed={onRestoreFailed} onAppeared={onAppeared} onError={onError} onLoadingProductsFailed={onLoadingProductsFailed} onCustomAction={onCustomAction} onUrlPress={onUrlPress} onWebPaymentNavigationFinished={onWebPaymentNavigationFinished} /> ); } ``` ## Модальное отображение \{#modal-presentation\} Чтобы показать флоу как отдельный экран, используйте метод `view.present()` на объекте `view`, созданном методом [`createFlowView`](react-native-get-pb-paywalls#fetch-the-view-configuration). Каждый `view` можно использовать только один раз. Если нужно показать флоу повторно, вызовите `createFlowView` ещё раз, чтобы создать новый экземпляр `view`. :::warning Повторное использование одного и того же `view` без его пересоздания запрещено. Это приведёт к ошибке `AdaptyUIError.viewAlreadyPresented`. ::: ```typescript showLineNumbers title="React Native (TSX)" const view = await createFlowView(flow); // Optional: handle flow events (close, purchase, restore, etc) // view.setEventHandlers({ ... }); try { await view.present(); } catch (error) { // handle the error } ``` :::important Каждый повторный вызов `setEventHandlers` перезаписывает обработчики: заменяются как дефолтные, так и ранее установленные обработчики для указанных событий. ::: ### Настройка стиля отображения на iOS \{#configure-ios-presentation-style\} Настройте способ отображения флоу на iOS, передав параметр `iosPresentationStyle` в метод `present()`. Параметр принимает значения `'full_screen'` (по умолчанию) или `'page_sheet'`. ```typescript showLineNumbers try { await view.present({ iosPresentationStyle: 'page_sheet' }); } catch (error) { // handle the error } ``` ## Использование таймеров, определённых разработчиком \{#use-developer-defined-timer\} Чтобы использовать таймеры, определённые разработчиком, в мобильном приложении, используйте `timerId` — в данном примере `CUSTOM_TIMER_NY`, **Timer ID** таймера, заданного в дашборде Adapty. Это обеспечивает динамическое обновление таймера с правильным значением — например, `13d 09h 03m 34s` (вычисляется как время окончания таймера, например Новый год, минус текущее время). <Tabs> <TabItem value="component" label="React component"> ```typescript showLineNumbers title="React Native (TSX)" const flowParams = { customTimers: { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) } }; <AdaptyFlowView flow={flow} params={flowParams} // ... your event handlers /> ``` </TabItem> <TabItem value="modal" label="Modal presentation"> ```typescript showLineNumbers title="React Native (TSX)" const customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) }; const view = await createFlowView(flow, { customTimers }); ``` </TabItem> </Tabs> В этом примере `CUSTOM_TIMER_NY` — это **Timer ID** таймера, заданного разработчиком в дашборде Adapty. `timerResolver` обеспечивает динамическое обновление таймера с нужным значением — например, `13d 09h 03m 34s` (вычисляется как разница между временем окончания таймера, например Новым годом, и текущим временем). ## Показ диалогов \{#show-dialog\} Используйте этот метод вместо нативных диалогов предупреждений, когда на Android отображается флоу. На Android обычные RN-алерты появляются позади флоу и становятся невидимыми для пользователей. Этот метод обеспечивает корректное отображение диалога поверх флоу на всех платформах. ```typescript showLineNumbers title="React Native (TSX)" try { const action = await view.showDialog({ title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', }); if (action === 'secondary') { // User confirmed - close the flow await view.dismiss(); } // If primary - do nothing, user stays } catch (error) { // handle error } ``` ## Замена одной подписки на другую \{#replace-one-subscription-with-another\} Когда пользователь пытается приобрести новую подписку, пока на Android активна другая, вы можете управлять тем, как должна обрабатываться новая покупка, — для этого передайте параметры обновления подписки при создании представления флоу. Чтобы заменить текущую подписку новой, используйте `productPurchaseParams` в `createFlowView` с параметрами `oldSubVendorProductId` и `prorationMode`. ```typescript showLineNumbers title="React Native (TSX)" const productPurchaseParams = flow.paywalls .flatMap((variation) => variation.productIdentifiers) .map((productId) => { let params = {}; if (Platform.OS === 'android') { params.android = { subscriptionUpdateParams: { oldSubVendorProductId: 'PRODUCT_ID_OF_THE_CURRENT_ACTIVE_SUBSCRIPTION', prorationMode: 'with_time_proration', }, }; } return { productId, params }; }); const view = await createFlowView(flow, { productPurchaseParams }); ``` </SDKv4> <SDKv3> Если вы настроили пейвол с помощью Paywall Builder, вам не нужно беспокоиться о его отображении в коде мобильного приложения — всё уже задано внутри самого пейвола: и содержимое, и способ отображения. Прежде чем начать, убедитесь, что: 1. Вы [создали пейвол](create-paywall). 2. Вы добавили пейвол в [плейсмент](placements). 3. Вы [получили пейвол и подготовили представление](react-native-get-pb-paywalls). :::warning Это руководство предназначено только для **пейволов, созданных в новом Paywall Builder**, которые требуют SDK v3.0 или выше. Процесс отображения пейволов различается для пейволов, созданных в разных версиях Paywall Builder, и для Remote Config пейволов. - Чтобы отобразить **Remote Config пейволы**, см. [Отображение пейвола на основе Remote Config](present-remote-config-paywalls). ::: Adapty React Native SDK предоставляет два способа отображения пейволов: - **React-компонент**: встроенный компонент, который можно интегрировать в архитектуру и систему навигации вашего приложения. - **Модальное окно** ## React-компонент \{#react-component\} :::note Подход на основе **React-компонента** требует SDK версии 3.14.0 или выше. ::: Чтобы встроить пейвол в существующее дерево компонентов, используйте компонент `AdaptyPaywallView` непосредственно в иерархии React Native компонентов. Встроенный компонент позволяет интегрировать его в архитектуру приложения и систему навигации. :::note На Android, если пейвол не перекрывает строку состояния, вверху может появиться визуальный оверлей. Рекомендуем отключить его для своих пейволов. См. [Визуальный оверлей в верхней части пейвола (Android)](#visual-overlay-at-the-top-of-the-paywall-android). ::: ```typescript showLineNumbers title="React Native (TSX)" function MyPaywall({ paywall }) { const paywallParams = useMemo(() => ({ loadTimeoutMs: 3000, }), []); const onCloseButtonPress = useCallback<EventHandlers['onCloseButtonPress']>(() => {}, []); const onProductSelected = useCallback<EventHandlers['onProductSelected']>((productId) => {}, []); const onPurchaseStarted = useCallback<EventHandlers['onPurchaseStarted']>((product) => {}, []); const onPurchaseCompleted = useCallback<EventHandlers['onPurchaseCompleted']>((purchaseResult, product) => {}, []); const onPurchaseFailed = useCallback<EventHandlers['onPurchaseFailed']>((error, product) => {}, []); const onRestoreStarted = useCallback<EventHandlers['onRestoreStarted']>(() => {}, []); const onRestoreCompleted = useCallback<EventHandlers['onRestoreCompleted']>((profile) => {}, []); const onRestoreFailed = useCallback<EventHandlers['onRestoreFailed']>((error) => {}, []); const onPaywallShown = useCallback<EventHandlers['onPaywallShown']>(() => {}, []); const onRenderingFailed = useCallback<EventHandlers['onRenderingFailed']>((error) => {}, []); const onLoadingProductsFailed = useCallback<EventHandlers['onLoadingProductsFailed']>((error) => {}, []); const onUrlPress = useCallback<EventHandlers['onUrlPress']>((url) => {}, []); const onCustomAction = useCallback<EventHandlers['onCustomAction']>((actionId) => {}, []); const onWebPaymentNavigationFinished = useCallback<EventHandlers['onWebPaymentNavigationFinished']>(() => {}, []); return ( <AdaptyPaywallView paywall={paywall} params={paywallParams} style={styles.paywall} onCloseButtonPress={onCloseButtonPress} onProductSelected={onProductSelected} onPurchaseStarted={onPurchaseStarted} onPurchaseCompleted={onPurchaseCompleted} onPurchaseFailed={onPurchaseFailed} onRestoreStarted={onRestoreStarted} onRestoreCompleted={onRestoreCompleted} onRestoreFailed={onRestoreFailed} onPaywallShown={onPaywallShown} onRenderingFailed={onRenderingFailed} onLoadingProductsFailed={onLoadingProductsFailed} onCustomAction={onCustomAction} onUrlPress={onUrlPress} onWebPaymentNavigationFinished={onWebPaymentNavigationFinished} /> ); } ``` ## Модальное отображение \{#modal-presentation\} Чтобы показать пейвол как отдельный экран, используйте метод `view.present()` на объекте `view`, созданном методом [`createPaywallView`](react-native-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). Каждый `view` можно использовать только один раз. Если нужно показать пейвол повторно, вызовите `createPaywallView` ещё раз, чтобы создать новый экземпляр `view`. :::warning Повторное использование одного и того же `view` без его пересоздания запрещено. Это приведёт к ошибке `AdaptyUIError.viewAlreadyPresented`. ::: ```typescript showLineNumbers title="React Native (TSX)" const view = await createPaywallView(paywall); // Optional: handle paywall events (close, purchase, restore, etc) // view.setEventHandlers({ ... }); try { await view.present(); } catch (error) { // handle the error } ``` :::important Каждый вызов `setEventHandlers` перезаписывает ранее установленные обработчики — включая дефолтные и те, что вы задали раньше, для указанных событий. ::: ### Настройка стиля отображения на iOS \{#configure-ios-presentation-style\} Настройте способ отображения пейвола на iOS, передав параметр `iosPresentationStyle` в метод `present()`. Параметр принимает значения `'full_screen'` (по умолчанию) или `'page_sheet'`. ```typescript showLineNumbers try { await view.present({ iosPresentationStyle: 'page_sheet' }); } catch (error) { // handle the error } ``` ## Использование таймера, заданного разработчиком \{#use-developer-defined-timer\} Чтобы использовать таймеры, заданные разработчиком, в мобильном приложении, используйте `timerId` — в данном примере `CUSTOM_TIMER_NY`, **Timer ID** таймера, заданного разработчиком, который вы установили в дашборде Adapty. Это позволяет приложению динамически обновлять таймер с правильным значением — например, `13d 09h 03m 34s` (рассчитывается как время окончания таймера, например Новый год, минус текущее время). <Tabs> <TabItem value="component" label="React component"> ```typescript showLineNumbers title="React Native (TSX)" const paywallParams = { customTimers: { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) } }; <AdaptyPaywallView paywall={paywall} params={paywallParams} // ... your event handlers /> ``` </TabItem> <TabItem value="modal" label="Modal presentation"> ```typescript showLineNumbers title="React Native (TSX)" const customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) }; const view = await createPaywallView(paywall, { customTimers }); ``` </TabItem> </Tabs> В этом примере `CUSTOM_TIMER_NY` — это **Timer ID** таймера, заданного разработчиком в дашборде Adapty. `timerResolver` обеспечивает динамическое обновление таймера с нужным значением — например, `13d 09h 03m 34s` (вычисляется как время окончания таймера, например Новый год, минус текущее время). ## Отображение диалога \{#show-dialog\} Используйте этот метод вместо нативных диалогов оповещений, когда на Android отображается пейвол. На Android обычные RN-алерты появляются позади пейвола и становятся невидимы для пользователей. Этот метод обеспечивает корректное отображение диалога поверх пейвола на всех платформах. ```typescript showLineNumbers title="React Native (TSX)" try { const action = await view.showDialog({ title: 'Close paywall?', content: 'You will lose access to exclusive offers.', primaryActionTitle: 'Stay', secondaryActionTitle: 'Close', }); if (action === 'secondary') { // User confirmed - close the paywall await view.dismiss(); } // If primary - do nothing, user stays } catch (error) { // handle error } ``` ## Замена одной подписки на другую \{#replace-one-subscription-with-another\} Когда пользователь пытается купить новую подписку при наличии активной подписки на Android, вы можете управлять тем, как должная обрабатываться новая покупка, передав параметры обновления подписки при создании вью пейвола. Чтобы заменить текущую подписку на новую, используйте `productPurchaseParams` в `createPaywallView` с параметрами `oldSubVendorProductId` и `prorationMode`. ```typescript showLineNumbers title="React Native (TSX)" const productPurchaseParams = paywall.productIdentifiers.map((productId) => { let params = {}; if (Platform.OS === 'android') { params.android = { subscriptionUpdateParams: { oldSubVendorProductId: 'PRODUCT_ID_OF_THE_CURRENT_ACTIVE_SUBSCRIPTION', prorationMode: 'with_time_proration', }, }; } return { productId, params }; }); const view = await createPaywallView(paywall, { productPurchaseParams }); ``` ## Устранение неполадок \{#troubleshooting\} ### Визуальный оверлей в верхней части пейвола (Android) \{#visual-overlay-at-the-top-of-the-paywall-android\} :::note Этот параметр поддерживается начиная с React Native SDK 3.15.5 и доступен только в bare React Native-проектах. Если вы используете управляемый воркфлоу Expo, добавить этот Android-ресурс напрямую не получится. Чтобы применить этот параметр, необходимо создать кастомный Expo config plugin, который добавляет соответствующий Android-ресурс, и зарегистрировать его в app.config.js. Это обязательно, поскольку Expo управляет нативным Android-проектом за вас. ::: Если `AdaptyPaywallView` не растягивается за пределы строки состояния, над ней всё равно может появляться визуальный оверлей. Чтобы убрать его, добавьте следующий булев ресурс в приложение: 1. Перейдите в `android/app/src/main/res/values`. Если файла `bools.xml` нет — создайте его. 2. Добавьте следующий ресурс: ```xml <resources> <bool name="adapty_paywall_enable_safe_area_paddings">false</bool> </resources> ``` Обратите внимание: изменения применяются глобально ко всем пейволам в приложении. </SDKv3> --- # File: react-native-handle-paywall-actions --- --- title: "Реакция на действия флоу - React Native" description: "Обрабатывайте действия кнопок из флоу и пейволов в React Native с помощью Adapty для улучшения монетизации приложения." --- <SDKv4> Если вы создаёте флоу или пейволы с помощью Adapty Flow Builder или Paywall Builder, важно правильно настроить кнопки: 1. Добавьте [кнопку во флоу/Paywall Builder](paywall-buttons) и назначьте ей уже существующее действие или создайте пользовательский ID действия. 2. Напишите в приложении код для обработки каждого назначенного действия. В этом гайде показано, как обрабатывать пользовательские и встроенные действия в коде. :::warning **Покупки, восстановления, закрытие флоу/пейвола и открытие URL обрабатываются автоматически.** Вы можете настроить их поведение по умолчанию или реализовать реакцию на пользовательские действия. ::: :::note SDK предоставляет обработчик флоу `onRequestPermission` для системных запросов разрешений, например на push-уведомления или доступ к камере. Флоу пока не инициируют такие запросы, поэтому реализовывать его сейчас не нужно. ::: ## Закрытие флоу и пейволов \{#close-flows-and-paywalls\} Чтобы добавить кнопку для закрытия флоу или пейвола: 1. В билдере добавьте кнопку и назначьте ей действие **Close**. 2. В коде приложения реализуйте обработчик действия `close`, который закрывает флоу или пейвол. :::info В React Native SDK действие `close` по умолчанию закрывает флоу или пейвол. При необходимости это поведение можно переопределить в коде. Например, закрытие одного флоу может запускать открытие другого. ::: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Для React-компонента обработка действия закрытия выполняется через отдельные пропсы обработчиков событий: ```javascript function MyPaywall({ flow }) { const onCloseButtonPress = useCallback<FlowEventHandlers['onCloseButtonPress']>(() => { // Handle close button press - navigate away or hide component navigation.goBack(); }, [navigation]); return ( <AdaptyFlowView flow={flow} style={styles.container} onCloseButtonPress={onCloseButtonPress} /> ); } ``` </TabItem> <TabItem value="standalone" label="Модальное представление"> Для модального представления реализуйте обработчик закрытия: ```javascript const view = await createFlowView(flow); const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { return true; // allow flow or paywall closing } }); ``` </TabItem> </Tabs> ## Открытие URL из флоу и пейволов \{#open-urls-from-flows-and-paywalls\} :::tip Если вы хотите добавить группу ссылок (например, пользовательское соглашение и восстановление покупок), добавьте элемент **Link** в билдере и обработайте его так же, как кнопки с действием **Open URL**. ::: Чтобы добавить кнопку, открывающую ссылку из флоу или пейвола (например, **Terms of use** или **Privacy policy**): 1. В билдере добавьте кнопку, назначьте ей действие **Open URL** и укажите нужный URL. 2. В коде приложения реализуйте обработчик действия `openUrl`, который открывает полученный URL в браузере. :::info В React Native SDK действие `openUrl` по умолчанию открывает URL. Однако при необходимости вы можете переопределить это поведение в своём коде. ::: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Для React-компонента обработайте открытие URL через проп обработчика события: ```javascript function MyPaywall({ flow }) { const onUrlPress = useCallback<FlowEventHandlers['onUrlPress']>((url) => { Linking.openURL(url); }, []); return ( <AdaptyFlowView flow={flow} style={styles.container} onUrlPress={onUrlPress} /> ); } ``` </TabItem> <TabItem value="standalone" label="Модальное представление"> For modal presentation, implement the URL handler: ```javascript const view = await createFlowView(flow); const unsubscribe = view.setEventHandlers({ onUrlPress(url) { Linking.openURL(url); return false; // Keep flow or paywall open }, }); ``` </TabItem> </Tabs> ## Обработка кастомных действий \{#handle-custom-actions\} Чтобы добавить кнопку с любым другим действием: 1. В конструкторе добавьте кнопку, назначьте ей действие **Custom** и задайте ID. 2. В коде приложения реализуйте обработчик для созданного вами ID действия. Например, если у вас есть другой набор предложений по подписке или разовых покупок, можно добавить кнопку, которая откроет другой флоу или пейвол: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Для React-компонента обрабатывайте кастомные действия через проп обработчика событий: ```javascript function MyPaywall({ flow }) { const onCustomAction = useCallback<FlowEventHandlers['onCustomAction']>((actionId) => { if (actionId === 'openNewPaywall') { // Display another flow or paywall } }, []); return ( <AdaptyFlowView flow={flow} style={styles.container} onCustomAction={onCustomAction} /> ); } ``` </TabItem> <TabItem value="standalone" label="Модальное представление"> Для модальной презентации реализуйте обработчики пользовательских действий: ```javascript const unsubscribe = view.setEventHandlers({ onCustomAction(actionId) { if (actionId === 'openNewPaywall') { // Display another flow or paywall } }, }); ``` </TabItem> </Tabs> </SDKv4> <SDKv3> Если вы создаёте пейволы с помощью Paywall Builder, важно правильно настроить кнопки: 1. Добавьте [кнопку в Paywall Builder](paywall-buttons) и назначьте ей готовое действие или создайте пользовательский ID действия. 2. Напишите код в своём приложении для обработки каждого назначенного действия. В этом гайде показано, как обрабатывать пользовательские и готовые действия в коде. :::warning **Только покупки, восстановления, закрытие пейвола и открытие URL обрабатываются автоматически.** Все остальные действия кнопок требуют явной реализации обработки в коде приложения. ::: ## Закрытие пейвола \{#close-paywalls\} Чтобы добавить кнопку закрытия пейвола: 1. В Paywall Builder добавьте кнопку и назначьте ей действие **Close**. 2. В коде приложения реализуйте обработчик действия `close`, который скрывает пейвол. :::info В React Native SDK действие `close` по умолчанию закрывает пейвол. Однако при необходимости это поведение можно переопределить в коде. Например, закрытие одного пейвола может инициировать открытие другого. ::: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Для React-компонента закрытие обрабатывается через отдельные пропсы-обработчики событий: ```javascript function MyPaywall({ paywall }) { const onCloseButtonPress = useCallback<EventHandlers['onCloseButtonPress']>(() => { // Handle close button press - navigate away or hide component navigation.goBack(); }, [navigation]); return ( <AdaptyPaywallView paywall={paywall} style={styles.container} onCloseButtonPress={onCloseButtonPress} /> ); } ``` </TabItem> <TabItem value="standalone" label="Модальное представление"> Для модального представления реализуйте обработчик закрытия: ```javascript const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { return true; // allow paywall closing } }); ``` </TabItem> </Tabs> ## Открытие URL-адресов из пейволов \{#open-urls-from-paywalls\} :::tip Если вы хотите добавить группу ссылок (например, условия использования и восстановление покупок), добавьте элемент **Link** в Paywall Builder и обработайте его так же, как кнопки с действием **Open URL**. ::: Чтобы добавить кнопку, открывающую ссылку из пейвола (например, **Terms of use** или **Privacy policy**): 1. В Paywall Builder добавьте кнопку, назначьте ей действие **Open URL** и введите URL, который нужно открыть. 2. В коде приложения реализуйте обработчик действия `openUrl`, который открывает полученный URL в браузере. :::info В React Native SDK действие `openUrl` по умолчанию открывает URL. Однако при необходимости вы можете переопределить это поведение в своём коде. ::: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Для React-компонента обработайте открытие URL через проп обработчика событий: ```javascript function MyPaywall({ paywall }) { const onUrlPress = useCallback<EventHandlers['onUrlPress']>((url) => { Linking.openURL(url); }, []); return ( <AdaptyPaywallView paywall={paywall} style={styles.container} onUrlPress={onUrlPress} /> ); } ``` </TabItem> <TabItem value="standalone" label="Модальное представление"> Для модального представления реализуйте обработчик URL: ```javascript const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onUrlPress(url) { Linking.openURL(url); return false; // Keep paywall open }, }); ``` </TabItem> </Tabs> ## Войдите в приложение \{#log-into-the-app\} Чтобы добавить кнопку входа пользователей в приложение: 1. В Paywall Builder добавьте кнопку и назначьте ей действие **Login**. 2. В коде приложения реализуйте обработчик для действия `login`, который идентифицирует пользователя. <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Для React-компонента обработайте вход через пропс обработчика событий: ```javascript function MyPaywall({ paywall }) { const onCustomAction = useCallback<EventHandlers['onCustomAction']>((actionId) => { if (actionId === 'login') { navigation.navigate('Login'); } }, [navigation]); return ( <AdaptyPaywallView paywall={paywall} style={styles.container} onCustomAction={onCustomAction} /> ); } ``` </TabItem> <TabItem value="standalone" label="Модальное представление"> Для модального представления реализуйте обработчик входа: ```javascript const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onCustomAction(actionId) { if (actionId === 'login') { navigation.navigate('Login'); } } }); ``` </TabItem> </Tabs> ## Обработка пользовательских действий \{#handle-custom-actions\} Чтобы добавить кнопку для обработки произвольных действий: 1. В Paywall Builder добавьте кнопку, назначьте ей действие **Custom** и задайте идентификатор. 2. В коде приложения реализуйте обработчик для созданного вами идентификатора действия. Например, если у вас есть другой набор предложений по подписке или разовых покупок, вы можете добавить кнопку, которая откроет другой пейвол: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Для React-компонента обработайте пользовательские действия через проп обработчика событий: ```javascript function MyPaywall({ paywall }) { const onCustomAction = useCallback<EventHandlers['onCustomAction']>((actionId) => { if (actionId === 'openNewPaywall') { // Display another paywall } }, []); return ( <AdaptyPaywallView paywall={paywall} style={styles.container} onCustomAction={onCustomAction} /> ); } ``` </TabItem> <TabItem value="standalone" label="Модальное представление"> Для модального представления реализуйте пользовательские обработчики действий: ```javascript const unsubscribe = view.setEventHandlers({ onCustomAction(actionId) { if (actionId === 'openNewPaywall') { // Display another paywall } }, }); ``` </TabItem> </Tabs> </SDKv3> --- # File: react-native-handling-events-1 --- --- title: "Обработка событий флоу и пейвола — React Native" description: "Обрабатывайте события флоу и пейвола в вашем React Native приложении с помощью SDK Adapty." --- <SDKv4> :::important Этот гайд охватывает обработку событий для покупок, восстановлений, выбора продукта и отображения флоу. Вы также можете настроить обработку кнопок (закрытие флоу, открытие ссылок, пользовательские действия и т. д.). Подробнее см. в нашем [гайде по обработке действий кнопок](react-native-handle-paywall-actions). ::: Флоу и пейволы, созданные с помощью Flow Builder, не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют ряд событий, на которые ваше приложение может реагировать. К таким событиям относятся нажатия кнопок (кнопок закрытия, URL-ссылок, выбора продуктов и т. д.), а также уведомления о действиях, связанных с покупками во флоу. Ниже описано, как реагировать на эти события. Чтобы контролировать или отслеживать процессы, происходящие на экране флоу в вашем мобильном приложении, реализуйте обработчики событий: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Для React-компонента события обрабатываются через отдельные пропсы обработчиков событий в компоненте `AdaptyFlowView`: ```typescript showLineNumbers title="React Native (TSX)" function MyFlow({ flow }) { const onCloseButtonPress = useCallback<FlowEventHandlers['onCloseButtonPress']>(() => {}, []); const onProductSelected = useCallback<FlowEventHandlers['onProductSelected']>((productId) => {}, []); const onPurchaseStarted = useCallback<FlowEventHandlers['onPurchaseStarted']>((product) => {}, []); const onPurchaseCompleted = useCallback<FlowEventHandlers['onPurchaseCompleted']>((purchaseResult, product) => {}, []); const onPurchaseFailed = useCallback<FlowEventHandlers['onPurchaseFailed']>((error, product) => {}, []); const onRestoreStarted = useCallback<FlowEventHandlers['onRestoreStarted']>(() => {}, []); const onRestoreCompleted = useCallback<FlowEventHandlers['onRestoreCompleted']>((profile) => {}, []); const onRestoreFailed = useCallback<FlowEventHandlers['onRestoreFailed']>((error) => {}, []); const onAppeared = useCallback<FlowEventHandlers['onAppeared']>(() => {}, []); const onError = useCallback<FlowEventHandlers['onError']>((error) => {}, []); const onLoadingProductsFailed = useCallback<FlowEventHandlers['onLoadingProductsFailed']>((error) => {}, []); const onUrlPress = useCallback<FlowEventHandlers['onUrlPress']>((url, openIn) => { adapty.openWebUrl(url, openIn); return false; }, []); const onCustomAction = useCallback<FlowEventHandlers['onCustomAction']>((actionId) => {}, []); const onWebPaymentNavigationFinished = useCallback<FlowEventHandlers['onWebPaymentNavigationFinished']>(() => {}, []); return ( <AdaptyFlowView flow={flow} style={styles.container} onCloseButtonPress={onCloseButtonPress} onProductSelected={onProductSelected} onPurchaseStarted={onPurchaseStarted} onPurchaseCompleted={onPurchaseCompleted} onPurchaseFailed={onPurchaseFailed} onRestoreStarted={onRestoreStarted} onRestoreCompleted={onRestoreCompleted} onRestoreFailed={onRestoreFailed} onAppeared={onAppeared} onError={onError} onLoadingProductsFailed={onLoadingProductsFailed} onUrlPress={onUrlPress} onCustomAction={onCustomAction} onWebPaymentNavigationFinished={onWebPaymentNavigationFinished} /> ); } ``` </TabItem> <TabItem value="standalone" label="Модальное представление"> Для модального представления реализуйте метод обработчиков событий. :::important Повторный вызов `setEventHandlers` перезапишет ранее установленные обработчики, заменив как стандартные, так и ранее заданные для этих конкретных событий. ::: ```javascript showLineNumbers title="React Native (TSX)" const view = await createFlowView(flow); const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { return true; }, onAndroidSystemBack() { return true; }, onPurchaseCompleted(purchaseResult, product) { return purchaseResult.type !== 'user_cancelled'; }, onPurchaseStarted(product) { /***/}, onPurchaseFailed(error, product) { /***/ }, onRestoreCompleted(profile) { /***/ }, onRestoreFailed(error) { /***/ }, onProductSelected(productId) { /***/}, onError(error) { /***/ }, onLoadingProductsFailed(error) { /***/ }, onUrlPress(url, openIn) { adapty.openWebUrl(url, openIn); return false; // Keep flow open }, onAppeared() { /***/ }, onDisappeared() { /***/ }, onWebPaymentNavigationFinished() { /***/ }, }); ``` </TabItem> </Tabs> <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```javascript // onCloseButtonPress { //Record the event } // onAndroidSystemBack { //Record the event } // onUrlPress { "url": "https://example.com/terms" } // onCustomAction { "actionId": "login" } // onProductSelected { "productId": "premium_monthly" } // onPurchaseStarted { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onPurchaseCompleted - Success { "purchaseResult": { "type": "success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onPurchaseCompleted - Cancelled { "purchaseResult": { "type": "user_cancelled" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onPurchaseFailed { "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onRestoreCompleted { "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } // onRestoreFailed { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } // onError { "error": { "code": "rendering_failed", "message": "Failed to render flow interface", "details": { "underlyingError": "Invalid flow configuration" } } } // onLoadingProductsFailed { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } // onAppeared { //Record the event } // onDisappeared { //Record the event } // onWebPaymentNavigationFinished { //Record the event } ``` </Details> Вы можете зарегистрировать только нужные обработчики событий, а ненужные — пропустить. В этом случае лишние слушатели событий не создаются. Обязательных обработчиков событий нет. Обработчики событий возвращают булево значение. Если возвращается `true`, процесс отображения считается завершённым: экран флоу закрывается, а слушатели событий для этого представления удаляются. Некоторые обработчики событий имеют поведение по умолчанию, которое можно переопределить при необходимости: - `onCloseButtonPress`: закрывает флоу при нажатии кнопки закрытия. - `onUrlPress`: открывает нажатую ссылку и оставляет флоу открытым. - `onAndroidSystemBack` (только для модального отображения): оставляет флоу открытым при нажатии кнопки **Back**. Верните `true`, чтобы закрыть флоу. - `onRestoreCompleted`: оставляет флоу открытым после успешного восстановления покупок. Верните `true`, чтобы закрыть флоу. - `onPurchaseCompleted`: оставляет флоу открытым после завершения покупки. Верните `true`, чтобы закрыть флоу. - `onError`: закрывает флоу, если его отрисовка завершилась с ошибкой. ### Обработчики событий \{#event-handlers\} | Обработчик события | Описание | |:-----------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onCustomAction** | Вызывается, когда пользователь выполняет произвольное действие, например нажимает [кастомную кнопку](paywall-buttons). | | **onUrlPress** | Вызывается, когда пользователь нажимает на URL в вашем флоу. | | **onAndroidSystemBack** | Только при модальном отображении: вызывается, когда пользователь нажимает системную кнопку **Back** на Android. | | **onCloseButtonPress** | Вызывается, когда кнопка закрытия видна и пользователь нажимает её. Рекомендуется закрывать экран флоу в этом обработчике. | | **onPurchaseCompleted** | Вызывается при завершении покупки — независимо от того, была ли она успешной, отменена пользователем или ожидает подтверждения. В случае успешной покупки возвращает обновлённый `AdaptyProfile`. Отмены пользователем и ожидающие платежи (например, требующие родительского подтверждения) вызывают это событие, а не `onPurchaseFailed`. | | **onPurchaseStarted** | Вызывается, когда пользователь нажимает кнопку действия «Купить» для начала процесса покупки. | | **onPurchaseFailed** | Вызывается, когда покупка завершается с ошибкой (например, из-за ограничений оплаты, недействительных продуктов, сбоев сети или ошибок верификации транзакций). Не вызывается при отмене пользователем или ожидающих платежах — в этих случаях срабатывает `onPurchaseCompleted`. | | **onRestoreStarted** | Вызывается, когда пользователь начинает процесс восстановления покупок. | | **onRestoreCompleted** | Вызывается при успешном восстановлении покупок и возвращает обновлённый `AdaptyProfile`. Рекомендуется закрывать экран, если у пользователя есть необходимый `accessLevel`. Подробнее см. в разделе [Статус подписки](react-native-listen-subscription-changes). | | **onRestoreFailed** | Вызывается при ошибке восстановления и возвращает `AdaptyError`. | | **onProductSelected** | Вызывается, когда в представлении флоу выбран любой продукт, — позволяет отслеживать выбор пользователя до совершения покупки. | | **onError** | Вызывается при ошибке во время рендеринга представления и возвращает `AdaptyError`. Такие ошибки не должны возникать — если вы с ними столкнулись, пожалуйста, сообщите нам. | | **onLoadingProductsFailed** | Вызывается при ошибке загрузки продуктов и возвращает `AdaptyError`. Если вы не указали `prefetchProducts: true` при создании представления, AdaptyUI самостоятельно запросит необходимые объекты с сервера. | | **onAppeared** | Вызывается, когда флоу отображается пользователю. На iOS также вызывается, когда пользователь нажимает [кнопку веб-пейвола](web-paywall#step-2a-add-a-web-purchase-button) внутри флоу и веб-пейвол открывается во встроенном браузере. | | **onDisappeared** | Только при модальном отображении: вызывается, когда пользователь закрывает флоу. На iOS также вызывается, когда [веб-пейвол](web-paywall#step-2a-add-a-web-purchase-button), открытый из флоу во встроенном браузере, исчезает с экрана. | | **onWebPaymentNavigationFinished** | Вызывается после попытки открыть [веб-пейвол](web-paywall) для покупки — как при успехе, так и при ошибке. | | **onAnalytics** | Зарезервировано для пользовательских аналитических событий из флоу. Флоу пока не отправляют их в ваш код, поэтому реализовывать его не нужно. | | **onRequestAppReview** | Зарезервировано для запросов отзывов о приложении из флоу. Флоу пока не инициируют такие запросы, поэтому реализовывать его не нужно. | | **onRequestPermission** | Зарезервировано для запросов системных разрешений (например, на push-уведомления или доступ к камере) из флоу. Флоу пока не инициируют запросы разрешений, поэтому реализовывать его не нужно. | | **onObserverPurchaseInitiated** | Только в режиме наблюдателя: вызывается, когда пользователь нажимает кнопку покупки в флоу. Adapty не выполняет покупку — выполните её с помощью собственного кода, а затем передайте транзакцию в Adapty. См. раздел [Обработка покупок в режиме наблюдателя](#handle-purchases-in-observer-mode) ниже. | | **onObserverRestoreInitiated** | Только в режиме наблюдателя: вызывается, когда пользователь нажимает кнопку восстановления в флоу. Adapty не выполняет восстановление — выполните его самостоятельно, а затем передайте восстановленные транзакции. См. раздел [Обработка покупок в режиме наблюдателя](#handle-purchases-in-observer-mode) ниже. | ### Обработка покупок в режиме наблюдателя \{#handle-purchases-in-observer-mode\} Если вы активировали SDK в [режиме наблюдателя](implement-observer-mode-react-native) (`observerMode: true`) и показываете флоу, отрендеренный Adapty, SDK не выполняет покупки самостоятельно. Когда пользователь нажимает кнопку покупки или восстановления, SDK вызывает `onObserverPurchaseInitiated` или `onObserverRestoreInitiated`. Выполните покупку или восстановление с помощью собственного кода, управляйте индикатором загрузки флоу через предоставленные колбэки и после этого [сообщите о транзакции](report-transactions-observer-mode-react-native) в Adapty. ```typescript showLineNumbers const unsubscribe = view.setEventHandlers({ onObserverPurchaseInitiated(product, onStartPurchase, onFinishPurchase) { onStartPurchase(); // show the flow's loading indicator myPurchaseApi(product.vendorProductId) .then((transactionId) => adapty.reportTransaction(transactionId)) .finally(() => onFinishPurchase()); // hide the loading indicator return false; // keep the flow open; dismiss it yourself after success }, onObserverRestoreInitiated(onStartRestore, onFinishRestore) { onStartRestore(); myRestoreApi() .finally(() => onFinishRestore()); return false; }, }); ``` </SDKv4> <SDKv3> :::important Это руководство охватывает обработку событий для покупок, восстановлений, выбора продукта и отображения пейвола. Вам также необходимо реализовать обработку кнопок (закрытие пейвола, открытие ссылок и т. д.). Подробнее читайте в [руководстве по обработке действий кнопок](react-native-handle-paywall-actions). ::: Пейволы, настроенные с помощью [Paywall Builder](adapty-paywall-builder), не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют ряд событий, на которые ваше приложение может реагировать. К таким событиям относятся нажатия кнопок (кнопки закрытия, URL, выбор продуктов и т. д.), а также уведомления о действиях, связанных с покупками на пейволе. Ниже описано, как обрабатывать эти события. :::warning Этот гайд предназначен **исключительно для пейволов нового Paywall Builder**, которые требуют Adapty SDK v3.0 или новее. ::: Для управления процессами на экране пейвола и мониторинга событий реализуйте обработчики событий: <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> В React-компоненте события обрабатываются через отдельные пропсы-обработчики событий в компоненте `AdaptyPaywallView`: ```typescript showLineNumbers title="React Native (TSX)" function MyPaywall({ paywall }) { const onCloseButtonPress = useCallback<EventHandlers['onCloseButtonPress']>(() => {}, []); const onProductSelected = useCallback<EventHandlers['onProductSelected']>((productId) => {}, []); const onPurchaseStarted = useCallback<EventHandlers['onPurchaseStarted']>((product) => {}, []); const onPurchaseCompleted = useCallback<EventHandlers['onPurchaseCompleted']>((purchaseResult, product) => {}, []); const onPurchaseFailed = useCallback<EventHandlers['onPurchaseFailed']>((error, product) => {}, []); const onRestoreStarted = useCallback<EventHandlers['onRestoreStarted']>(() => {}, []); const onRestoreCompleted = useCallback<EventHandlers['onRestoreCompleted']>((profile) => {}, []); const onRestoreFailed = useCallback<EventHandlers['onRestoreFailed']>((error) => {}, []); const onPaywallShown = useCallback<EventHandlers['onPaywallShown']>(() => {}, []); const onRenderingFailed = useCallback<EventHandlers['onRenderingFailed']>((error) => {}, []); const onLoadingProductsFailed = useCallback<EventHandlers['onLoadingProductsFailed']>((error) => {}, []); const onUrlPress = useCallback<EventHandlers['onUrlPress']>((url) => { Linking.openURL(url); }, []); const onCustomAction = useCallback<EventHandlers['onCustomAction']>((actionId) => {}, []); const onWebPaymentNavigationFinished = useCallback<EventHandlers['onWebPaymentNavigationFinished']>(() => {}, []); return ( <AdaptyPaywallView paywall={paywall} style={styles.container} onCloseButtonPress={onCloseButtonPress} onProductSelected={onProductSelected} onPurchaseStarted={onPurchaseStarted} onPurchaseCompleted={onPurchaseCompleted} onPurchaseFailed={onPurchaseFailed} onRestoreStarted={onRestoreStarted} onRestoreCompleted={onRestoreCompleted} onRestoreFailed={onRestoreFailed} onPaywallShown={onPaywallShown} onRenderingFailed={onRenderingFailed} onLoadingProductsFailed={onLoadingProductsFailed} onUrlPress={onUrlPress} onCustomAction={onCustomAction} onWebPaymentNavigationFinished={onWebPaymentNavigationFinished} /> ); } ``` </TabItem> <TabItem value="standalone" label="Модальное представление"> Для модального представления реализуйте метод обработчиков событий. :::important Многократный вызов `setEventHandlers` перезапишет предоставленные обработчики, заменив как стандартные, так и ранее заданные обработчики для указанных событий. ::: ```javascript showLineNumbers title="React Native (TSX)" const view = await createPaywallView(paywall); const unsubscribe = view.setEventHandlers({ onCloseButtonPress() { return true; }, onAndroidSystemBack() { return true; }, onPurchaseCompleted(purchaseResult, product) { return purchaseResult.type !== 'user_cancelled'; }, onPurchaseStarted(product) { /***/}, onPurchaseFailed(error) { /***/ }, onRestoreCompleted(profile) { /***/ }, onRestoreFailed(error) { /***/ }, onProductSelected(productId) { /***/}, onRenderingFailed(error) { /***/ }, onLoadingProductsFailed(error) { /***/ }, onUrlPress(url) { Linking.openURL(url); return false; // Keep paywall open }, onPaywallShown() { /***/ }, onPaywallClosed() { /***/ }, onWebPaymentNavigationFinished() { /***/ }, }); ``` </TabItem> </Tabs> <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```javascript // onCloseButtonPress { //Record the event } // onAndroidSystemBack { //Record the event } // onUrlPress { "url": "https://example.com/terms" } // onCustomAction { "actionId": "login" } // onProductSelected { "productId": "premium_monthly" } // onPurchaseStarted { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onPurchaseCompleted - Success { "purchaseResult": { "type": "success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onPurchaseCompleted - Cancelled { "purchaseResult": { "type": "user_cancelled" }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onPurchaseFailed { "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } }, "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "price": { "amount": 9.99, "currencyCode": "USD", "currencySymbol": "$", "localizedString": "$9.99" } } } // onRestoreCompleted { "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } // onRestoreFailed { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } // onRenderingFailed { "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } // onLoadingProductsFailed { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } // onPaywallShown { //Record the event } // onPaywallClosed { //Record the event } // onWebPaymentNavigationFinished { //Record the event } ``` </Details> Вы можете регистрировать только те обработчики событий, которые вам нужны, — остальные можно пропустить. Неиспользуемые обработчики событий при этом не создаются. Обязательных обработчиков нет. Обработчики событий возвращают булево значение. Если возвращается `true`, процесс отображения считается завершённым: экран пейвола закрывается, а все слушатели событий для этого представления удаляются. У некоторых обработчиков событий есть поведение по умолчанию, которое можно переопределить при необходимости: - `onCloseButtonPress`: закрывает пейвол при нажатии кнопки закрытия. - `onUrlPress`: открывает нажатый URL и оставляет пейвол открытым. - `onAndroidSystemBack` (только для модального представления): закрывает пейвол при нажатии кнопки **Back**. - `onRestoreCompleted`: закрывает пейвол после успешного восстановления. - `onPurchaseCompleted`: закрывает пейвол, если пользователь не отменил покупку. - `onRenderingFailed`: закрывает пейвол, если его рендеринг завершился с ошибкой. ### Обработчики событий \{#event-handlers\} | Обработчик событий | Описание | |:-----------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **onCustomAction** | Вызывается, когда пользователь выполняет пользовательское действие, например нажимает [кастомную кнопку](paywall-buttons). | | **onUrlPress** | Вызывается, когда пользователь нажимает на URL в вашем пейволе. | | **onAndroidSystemBack** | Только для модального представления: вызывается, когда пользователь нажимает системную кнопку Android **Back**. | | **onCloseButtonPress** | Вызывается, когда кнопка закрытия видима и пользователь нажимает её. Рекомендуется закрывать экран пейвола в этом обработчике. | | **onPurchaseCompleted** | Вызывается по завершении покупки — успешной, отменённой пользователем или ожидающей подтверждения. В случае успешной покупки возвращает обновлённый `AdaptyProfile`. Отмены пользователем и отложенные платежи (например, требующие родительского разрешения) вызывают это событие, а не `onPurchaseFailed`. | | **onPurchaseStarted** | Вызывается, когда пользователь нажимает кнопку действия «Купить», чтобы начать процесс покупки. | | **onPurchaseFailed** | Вызывается, когда покупка завершается с ошибкой (например, ограничения платежей, недействительные продукты, сетевые сбои, ошибки верификации транзакций). Не вызывается при отмене пользователем или отложенных платежах — в этих случаях вызывается `onPurchaseCompleted`. | | **onRestoreStarted** | Вызывается, когда пользователь запускает процесс восстановления покупок. | | **onRestoreCompleted** | Вызывается при успешном восстановлении покупок и возвращает обновлённый `AdaptyProfile`. Рекомендуется закрывать экран, если у пользователя есть требуемый `accessLevel`. О том, как это проверить, читайте в разделе [Статус подписки](react-native-listen-subscription-changes). | | **onRestoreFailed** | Вызывается, когда процесс восстановления завершается с ошибкой, и возвращает `AdaptyError`. | | **onProductSelected** | Вызывается при выборе любого продукта в пейволе — позволяет отслеживать, что пользователь выбирает перед покупкой. | | **onRenderingFailed** | Вызывается при возникновении ошибки во время рендеринга и возвращает `AdaptyError`. Такие ошибки не должны возникать, поэтому если вы столкнулись с ней — сообщите нам. | | **onLoadingProductsFailed** | Вызывается при сбое загрузки продуктов и возвращает `AdaptyError`. Если при создании представления вы не задали `prefetchProducts: true`, AdaptyUI самостоятельно получит необходимые объекты с сервера. | | **onPaywallShown** | Вызывается, когда пейвол отображается пользователю. На iOS также вызывается, когда пользователь нажимает [кнопку веб-пейвола](web-paywall#step-2a-add-a-web-purchase-button) внутри пейвола и веб-пейвол открывается во встроенном браузере. | | **onPaywallClosed** | Только для модального представления: вызывается, когда пользователь закрывает пейвол. На iOS также вызывается, когда [веб-пейвол](web-paywall#step-2a-add-a-web-purchase-button), открытый из пейвола во встроенном браузере, исчезает с экрана. | | **onWebPaymentNavigationFinished** | Вызывается после попытки открыть [веб-пейвол](web-paywall) для совершения покупки — независимо от того, успешной она была или нет. | </SDKv3> --- # File: react-native-use-fallback-paywalls-expo --- --- title: "Использование резервных пейволов в проекте Expo" description: "Настройте резервные пейволы в проекте Expo React Native с помощью config plugin react-native-adapty." --- :::important Это руководство применимо к проектам на **Expo**. Если вы используете **чистый React Native (без Expo)**, следуйте [гайду по резервным пейволам для чистого React Native](react-native-use-fallback-paywalls-pure). ::: Чтобы поддерживать бесперебойный пользовательский опыт, важно настроить [резервные пейволы](/fallback-paywalls) для флоу, [пейволов](paywalls) и [онбордингов](onboardings). Это позволит приложению продолжить работу при частичной или полной потере интернет-соединения. * **Если приложение не может обратиться к серверам Adapty:** Оно сможет отобразить резервный флоу или пейвол, а также использовать локальную конфигурацию онбординга. * **Если приложение не может подключиться к интернету:** Оно сможет отобразить резервный флоу или пейвол. Онбординги содержат удалённый контент и требуют интернет-соединения для работы. :::important Прежде чем следовать шагам этого гайда, [скачайте](/local-fallback-paywalls) файлы резервной конфигурации из Adapty. ::: SDK Adapty считывает резервный файл из **нативного** бандла — ресурса iOS внутри пакета `.app` или записи в `android/app/src/main/assets/`. В Expo-проекте команда `npx expo prebuild --clean` каждый раз пересоздаёт эти директории, поэтому добавлять файлы вручную не получится. Конфиг-плагин `react-native-adapty` автоматически подключает файл к нативному бандлу. :::tip Полностью рабочий пример доступен в [приложении `FocusJournalExpo`](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/FocusJournalExpo). ::: ## Настройка \{#configuration\} 1. Разместите резервные JSON-файлы в любом месте проекта — как правило, рядом с другими ассетами: ``` <your-project>/ └── assets/ ├── ios_fallback.json └── android_fallback.json ``` 2. Добавьте опцию `fallbackFile` в запись `react-native-adapty` в файле `app.json` (или `app.config.js`). Каждый ключ платформы необязателен — подключайте только нужные платформы: ```json title="app.json" { "expo": { "plugins": [ [ "react-native-adapty", { "fallbackFile": { "ios": "./assets/ios_fallback.json", "android": "./assets/android_fallback.json" } } ] ] } } ``` :::note Adapty экспортирует отдельный JSON резервного пейвола для каждой платформы — с идентификаторами продуктов Apple на iOS и Google Play на Android. Укажите для каждой платформы свой файл. ::: 3. Пересоздайте нативные проекты: ```sh title="Shell" npx expo prebuild ``` Плагин добавляет iOS-файл в ресурсы бандла Xcode и копирует Android-файл в `android/app/src/main/assets/`. В выводе prebuild появятся строки вида: ``` [react-native-adapty] Registered ios_fallback.json as iOS bundle resource [react-native-adapty] Copied android_fallback.json to android assets/ ``` 4. Зарегистрируйте файл в SDK во время выполнения: ```typescript showLineNumbers title="App.tsx" import { adapty } from 'react-native-adapty'; await adapty.activate('PUBLIC_SDK_KEY'); await adapty.setFallback({ ios: { fileName: 'ios_fallback.json' }, android: { relativeAssetPath: 'android_fallback.json' }, }); ``` Имена файлов, передаваемых в `setFallback`, должны совпадать с базовыми именами файлов, настроенных в `fallbackFile`. :::important `setFallback` должен выполняться до того, как SDK запросит целевой флоу, пейвол или онбординг. ::: ## Проверка \{#verification\} После выполнения `npx expo prebuild` проверьте обе платформы: - **Android**: просмотрите содержимое директории `android/app/src/main/assets/`. Файл, указанный в `fallbackFile.android`, должен присутствовать, а имя файла только для iOS — отсутствовать. - **iOS**: найдите в файле `ios/<ProjectName>.xcodeproj/project.pbxproj` имя файла только для iOS. Оно должно встречаться в `PBXFileReference`, группе `Resources` и `PBXResourcesBuildPhase`. Имя файла только для Android не должно появляться в `project.pbxproj`. --- # File: react-native-use-fallback-paywalls-pure --- --- title: "Использование резервных пейволов в чистом проекте React Native" description: "Настройте резервные пейволы в проекте React Native (без Expo)." --- :::important Этот гайд применяется к **чистым React Native (non-Expo) проектам**. Если вы используете **Expo**, следуйте [гайду по резервным пейволам для Expo](react-native-use-fallback-paywalls-expo). ::: Чтобы поддерживать бесперебойный пользовательский опыт, важно настроить [резервные пейволы](/fallback-paywalls) для флоу, [пейволов](paywalls) и [онбордингов](onboardings). Это позволит приложению продолжить работу при частичной или полной потере интернет-соединения. * **Если приложение не может обратиться к серверам Adapty:** Оно сможет отобразить резервный флоу или пейвол, а также использовать локальную конфигурацию онбординга. * **Если приложение не может подключиться к интернету:** Оно сможет отобразить резервный флоу или пейвол. Онбординги содержат удалённый контент и требуют интернет-соединения для работы. :::important Прежде чем следовать шагам этого гайда, [скачайте](/local-fallback-paywalls) файлы резервной конфигурации из Adapty. ::: ## Конфигурация \{#configuration\} ### Android 1. Добавьте файл резервной конфигурации в ваше приложение. Выберите одну из следующих директорий: * **android/app/src/main/assets/** * **android/app/src/main/res/raw/** Примечание: В папке `res/raw` действуют особые правила именования файлов (название должно начинаться с буквы, без заглавных букв, без специальных символов кроме подчёркивания, без пробелов). 2. Обновите свойство `android` константы `FileLocation`: * Если файл находится в директории `assets`, укажите путь к файлу относительно этой директории. * Если файл находится в директории `res/raw`, укажите имя файла без расширения. ### iOS 1. Добавьте резервный JSON-файл в бандл проекта: откройте меню **File** в XCode и выберите **Add Files to "YourProjectName"**. 2. Передайте имя файла конфигурации в свойство `ios` константы `FileLocation`. ## Пример \{#example\} <Tabs groupId="current-os" queryString> <TabItem value="current" label="Current (v3.8+)" default> ```typescript showLineNumbers //after v3.8 const fileLocation = { ios: { fileName: 'ios_fallback.json' }, android: { //if the file is located in 'android/app/src/main/assets/' relativeAssetPath: 'android_fallback.json' } } await adapty.setFallback(fileLocation); ``` </TabItem> <TabItem value="old" label="Legacy (before v3.8)"> ```typescript showLineNumbers //Legacy (before v3.8) const paywallsLocation = { ios: { fileName: 'ios_fallback.json' }, android: { //if the file is located in 'android/app/src/main/assets/' relativeAssetPath: 'android_fallback.json' } } await adapty.setFallbackPaywalls(paywallsLocation); ``` </TabItem> </Tabs> | Параметр | Описание | | :------------------- | :------------------------------------------------------- | | **fileLocation** | Объект, представляющий расположение файла резервной конфигурации. | --- # File: react-native-localizations-and-locale-codes --- --- title: "Использование локализаций и кодов локали в React Native SDK" description: "Узнайте, как локализовать пейволы в приложении на React Native с помощью Adapty SDK." --- <SDKv4> ## Почему это важно \{#why-this-is-important\} Коды локали используются, когда Adapty выбирает локализацию для флоу и когда вы читаете Remote Config для кастомного пейвола. Коды локали сложны и могут различаться от платформы к платформе, поэтому Adapty опирается на единый внутренний стандарт для всех поддерживаемых платформ. Понимание этого стандарта поможет вам предсказать, какую локализацию получит пользователь. ## Стандарт кодов локали в Adapty \{#locale-code-standard-at-adapty\} В Adapty для кодов локали используется слегка изменённый [стандарт BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): каждый код состоит из подтегов в нижнем регистре, разделённых дефисами. Примеры: `en` (английский), `pt-br` (португальский (Бразилия)), `zh` (китайский упрощённый), `zh-hant` (китайский традиционный). ## Сопоставление кода локали \{#locale-code-matching\} Когда Adapty ищет локализацию, соответствующую локали пользователя, происходит следующее: 1. Строка локали приводится к нижнему регистру, а все символы подчёркивания (`_`) заменяются дефисами (`-`) 2. Adapty ищет локализацию с полностью совпадающим кодом локали 3. Если совпадение не найдено, Adapty берёт подстроку до первого дефиса (`pt` для `pt-br`) и ищет соответствующую локализацию 4. Если совпадение снова не найдено, Adapty возвращает локализацию по умолчанию — `en` Таким образом, `'pt_BR'`, `pt-BR` и `pt-br` ведут к одной и той же локализации. ## Реализация локализаций \{#implementing-localizations\} В SDK v4 при получении флоу код локали не передаётся. - **Пейволы во Flow Builder и Paywall Builder**: Adapty автоматически определяет локализацию на основе настроек устройства и тех локализаций, которые вы настроили в билдере. Отображайте флоу через `createFlowView` — код локали не нужен. - **Кастомные пейволы (Remote Config)**: `getFlow` возвращает все настроенные локализации в `flow.remoteConfigs`. Каждая запись содержит код `lang` и объект `data`. Выберите запись, подходящую пользователю, реализовав собственный фолбэк: ```typescript showLineNumbers const flow = await adapty.getFlow('placement_id'); const config = flow.remoteConfigs?.find((c) => c.lang === 'en') ?? flow.remoteConfigs?.[0]; // read your values from config?.data ``` Правила сопоставления кода локали, описанные выше, показывают, как Adapty нормализует коды `lang`, хранящиеся в каждом Remote Config. </SDKv4> <SDKv3> ## Почему это важно \{#why-this-is-important\} Есть несколько сценариев, в которых важны коды локали — например, когда нужно получить правильный пейвол для текущей локализации вашего приложения. Поскольку коды локали устроены непросто и могут различаться от платформы к платформе, мы опираемся на внутренний стандарт для всех поддерживаемых нами платформ. Но именно из-за этой сложности вам важно понимать, что именно вы отправляете на наш сервер для получения нужной локализации и что происходит дальше — чтобы всегда получать именно то, что ожидаете. ## Стандарт кодов локалей в Adapty \{#locale-code-standard-at-adapty\} Для кодов локалей Adapty использует немного изменённый стандарт [BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): каждый код состоит из подтегов в нижнем регистре, разделённых дефисами. Примеры: `en` (английский), `pt-br` (португальский (Бразилия)), `zh` (упрощённый китайский), `zh-hant` (традиционный китайский). ## Сопоставление кода локали \{#locale-code-matching\} Когда Adapty получает вызов от SDK с кодом локали и начинает искать соответствующую локализацию пейвола, происходит следующее: 1. Входящая строка локали приводится к нижнему регистру, а все символы подчёркивания (`_`) заменяются дефисами (`-`) 2. Затем выполняется поиск локализации с полностью совпадающим кодом локали 3. Если совпадение не найдено, берётся подстрока до первого дефиса (`pt` для `pt-br`) и выполняется поиск по ней 4. Если совпадение снова не найдено, возвращается локализация `en` по умолчанию Таким образом, устройство iOS, отправившее `'pt_BR'`, устройство Android, отправившее `pt-BR`, и другое устройство, отправившее `pt-br`, получат одинаковый результат. ## Реализация локализаций: рекомендуемый подход \{#implementing-localizations-recommended-way\} Если вы занимаетесь локализациями, скорее всего, вы уже работаете с файлами локализованных строк в своём проекте. В таком случае мы рекомендуем добавить пару ключ-значение с нужным кодом локали Adapty в каждый из ваших файлов для соответствующих локализаций. А затем извлекать значение по этому ключу при вызове нашего SDK: ```javascript showLineNumbers // 1. Modify your localization files (e.g., using react-i18next) /* en.json */ { "adapty_paywalls_locale": "en" } /* es.json */ { "adapty_paywalls_locale": "es" } /* pt-BR.json */ { "adapty_paywalls_locale": "pt-br" } // 2. Extract and use the locale code const MyComponent = () => { const { t } = useTranslation(); const fetchPaywall = async () => { const locale = t('adapty_paywalls_locale'); // pass locale code to adapty.getPaywall or adapty.getPaywallForDefaultAudience method const paywall = await adapty.getPaywallForDefaultAudience('placement_id', locale); }; }; ``` Таким образом вы получаете полный контроль над тем, какая локализация будет использована для каждого пользователя вашего приложения. ## Альтернативный способ реализации локализаций \{#implementing-localizations-the-other-way\} Можно получить похожий (но не идентичный) результат, не прописывая явно коды локалей для каждой локализации. Для этого нужно извлечь код локали из устройства — например, с помощью [`react-native-localize`](https://github.com/zoontek/react-native-localize): ```javascript showLineNumbers const fetchPaywall = async () => { // getLocales() returns the user's preferred locales in BCP-47 format (e.g., 'en-US', 'pt-BR') const locale = RNLocalize.getLocales()[0].languageTag; // pass locale code to adapty.getPaywall or adapty.getPaywallForDefaultAudience method const paywall = await adapty.getPaywallForDefaultAudience('placement_id', locale); }; ``` Обратите внимание, что мы не рекомендуем этот подход по ряду причин: 1. На iOS предпочитаемые языки и текущая региональная локаль — это не одно и то же. Чтобы локализация определялась корректно, нужно либо положиться на логику Apple — она работает из коробки при рекомендуемом подходе с локализованными строковыми файлами — либо реализовать аналогичную логику самостоятельно. 2. Локаль устройства может не совпадать ни с одной из локализаций, настроенных в Adapty. В таком случае SDK откатывается к совпадению по первому субтегу или, в крайнем случае, к `en` — что может оказаться не тем языком, который вы хотели бы показать этому пользователю по умолчанию. Should you decide to use this approach anyway — make sure you've covered all the relevant use cases. </SDKv3> --- # File: react-native-web-paywall --- --- title: "Реализация веб-пейволов" description: "Узнайте, как реализовать веб-пейволы в приложении React Native с помощью Adapty SDK." --- :::important Прежде чем начать, убедитесь, что вы [настроили веб-пейвол в дашборде](web-paywall) и установили Adapty SDK версии 3.6.1 или выше. ::: ## Открытие веб-пейволов \{#open-web-paywalls\} Если вы работаете с пейволом, разработанным самостоятельно, вам нужно обрабатывать веб-пейволы с помощью метода SDK. Метод `.openWebPaywall`: 1. Генерирует уникальный URL, позволяющий Adapty связать конкретный показанный пользователю пейвол с веб-страницей, на которую он перенаправляется. 2. Отслеживает возвращение пользователей в приложение и затем с короткими интервалами вызывает `.getProfile`, чтобы определить, обновились ли права доступа профиля. Таким образом, если платёж прошёл успешно и права доступа обновились, подписка активируется в приложении практически мгновенно. ```typescript showLineNumbers title="React Native (TSX)" try { await adapty.openWebPaywall(product); } catch (error) { console.warn('Failed to open web paywall:', error); } ``` :::note Существует две версии метода `openWebPaywall`: 1. `openWebPaywall(product)` — генерирует URL по пейволу и добавляет данные продукта в URL. 2. `openWebPaywall(paywall)` — генерирует URL по пейволу без добавления данных продукта в URL. Используйте её, когда продукты в пейволе Adapty отличаются от продуктов в веб-пейволе. ::: #### Обработка ошибок \{#handle-errors\} | Ошибка | Описание | Рекомендуемое действие | |-----------------------------------------|-------------------------------------------------------------------|-----------------------------------------------------------------------------------------| | AdaptyError.paywallWithoutPurchaseUrl | У пейвола не настроен URL для веб-покупки | Проверьте, правильно ли настроен пейвол в дашборде Adapty | | AdaptyError.productWithoutPurchaseUrl | У продукта отсутствует URL для веб-покупки | Проверьте настройки продукта в дашборде Adapty | | AdaptyError.failedOpeningWebPaywallUrl | Не удалось открыть URL в браузере | Проверьте настройки устройства или предоставьте альтернативный способ покупки | | AdaptyError.failedDecodingWebPaywallUrl | Не удалось корректно закодировать параметры в URL | Убедитесь, что параметры URL валидны и правильно отформатированы | ## Открытие веб-пейволов во встроенном браузере \{#open-web-paywalls-in-an-in-app-browser\} :::important Открытие веб-пейволов во встроенном браузере поддерживается начиная с Adapty SDK v3.15. ::: По умолчанию веб-пейволы открываются во внешнем браузере. Чтобы обеспечить бесшовный пользовательский опыт, можно открывать веб-пейволы во встроенном браузере. Это отображает страницу покупки прямо внутри вашего приложения, позволяя пользователям завершать транзакции без переключения между приложениями. Для этого передайте `WebPresentation.BrowserInApp` вторым аргументом в `openWebPaywall`: ```typescript showLineNumbers title="React Native (TSX)" try { await adapty.openWebPaywall( product, WebPresentation.BrowserInApp, // default – WebPresentation.BrowserOutApp ); } catch (error) { console.warn('Failed to open web paywall:', error); } ``` --- # File: react-native-troubleshoot-paywall-builder --- --- title: "Устранение неполадок Paywall Builder в React Native SDK" description: "Устранение неполадок Paywall Builder в React Native SDK" --- Этот гайд поможет устранить распространённые проблемы при использовании пейволов, созданных в Adapty Paywall Builder, в React Native SDK. ## Получение конфигурации пейвола завершается ошибкой \{#getting-a-paywall-configuration-fails\} **Проблема**: Запрос конфигурации отображения для флоу или пейвола завершается ошибкой. **Причина**: Пейвол не включён для отображения на устройстве в Paywall Builder. **Решение**: Включите переключатель **Show on device** в Paywall Builder. <img src="/assets/shared/img/show-on-device.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Счётчик просмотров пейвола слишком большой \{#the-paywall-view-number-is-too-big\} **Проблема**: Количество просмотров пейвола отображается в два раза больше ожидаемого. **Причина**: Возможно, в коде вызывается `logShowFlow` (React Native SDK v4+) / `logShowPaywall`, что дублирует счётчик просмотров, если вы используете Paywall Builder или Flow Builder. Для флоу и пейволов, созданных с помощью этих инструментов, аналитика отслеживается автоматически, поэтому использовать этот метод не нужно. **Решение**: Убедитесь, что в коде не вызывается `logShowFlow` (React Native SDK v4+) / `logShowPaywall`, если вы используете Paywall Builder или Flow Builder. ## Другие проблемы \{#other-issues\} **Проблема**: Вы столкнулись с другими проблемами, связанными с Paywall Builder, которые не описаны выше. **Решение**: При необходимости обновите SDK до последней версии, воспользовавшись [гайдами по миграции](react-native-sdk-migration-guides). Многие проблемы уже исправлены в новых версиях SDK. --- # File: react-native-quickstart-manual --- --- title: "Включить покупки в пользовательском пейволе в React Native SDK" description: "Интегрируйте Adapty SDK в свои пользовательские пейволы на React Native для поддержки встроенных покупок." --- В этом гайде описано, как интегрировать Adapty в пользовательские пейволы. Сохраняйте полный контроль над реализацией пейвола, пока Adapty SDK получает продукты, обрабатывает новые покупки и восстанавливает предыдущие. :::important **Это руководство предназначено для разработчиков, реализующих кастомные пейволы.** Если вы хотите самый простой способ подключить покупки, используйте [Adapty Flow Builder](react-native-quickstart-paywalls). С Flow Builder вы создаёте флоу в визуальном редакторе без кода, Adapty автоматически обрабатывает всю логику покупок, а тестировать разные дизайны можно без перепубликации приложения. ::: ## Прежде чем начать \{#before-you-start\} ### Настройка продуктов \{#set-up-products\} Чтобы подключить встроенные покупки, нужно разобраться с тремя ключевыми понятиями: - [**Products**](product) – всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ) - [**Paywalls**](paywalls) – конфигурации, определяющие, какие продукты предлагать. В Adapty пейволы — единственный способ получить продукты, но такой подход позволяет менять продукты, цены и офферы без изменений в коде приложения. - [**Placements**](placements) – где и когда показывать пейволы в приложении (например, `main`, `onboarding`, `settings`). Вы настраиваете пейволы для плейсментов в дашборде, а затем запрашиваете их по ID плейсмента в коде. Это упрощает запуск A/B-тестов и показ разных пейволов разным пользователям. Убедитесь, что вы понимаете эти концепции, даже если используете собственный пейвол. По сути, это просто способ управлять продуктами, которые вы продаёте в приложении. Чтобы реализовать собственный пейвол, нужно создать **пейвол** и добавить его в **плейсмент**. Это позволит получать продукты. Чтобы разобраться, что нужно сделать в дашборде, следуйте quickstart-гайду [здесь](quickstart). ### Управление пользователями \{#manage-users\} Вы можете работать как с backend-аутентификацией, так и без неё. Тем не менее Adapty SDK по-разному обрабатывает анонимных и идентифицированных пользователей. Прочитайте [гайд по быстрой идентификации](react-native-quickstart-identify), чтобы разобраться в деталях и убедиться, что вы правильно работаете с пользователями. ## Шаг 1. Получите продукты \{#step-1-get-products\} Чтобы получить продукты для вашего кастомного пейвола, нужно: 1. Получить объект `flow`, передав ID [плейсмента](placements) в метод `getFlow`. 2. Получить массив продуктов для этого флоу с помощью метода `getPaywallProducts`. ```typescript showLineNumbers async function loadPaywall() { try { const flow: AdaptyFlow = await adapty.getFlow('YOUR_PLACEMENT_ID'); const products: AdaptyPaywallProduct[] = await adapty.getPaywallProducts(flow); // Use products to build your custom paywall UI } catch (error) { // Handle the error } } ``` ## Шаг 2. Обработка покупок \{#step-2-accept-purchases\} Когда пользователь нажимает на продукт в вашем кастомном пейволе, вызовите метод `makePurchase` с выбранным продуктом. Он обработает флоу покупки и вернёт обновлённый профиль. ```typescript showLineNumbers async function purchaseProduct(product: AdaptyPaywallProduct) { try { const purchaseResult: AdaptyPurchaseResult = await adapty.makePurchase(product); switch (purchaseResult.type) { case 'success': // Purchase successful, profile updated break; case 'user_cancelled': // User canceled the purchase break; case 'pending': // Purchase is pending (e.g., user will pay offline with cash) break; } } catch (error) { // Handle the error } } ``` ## Шаг 3. Восстановление покупок \{#step-3-restore-purchases\} Сторы требуют, чтобы во всех приложениях с подписками была возможность восстановить покупки. Вызывайте метод `restorePurchases`, когда пользователь нажимает кнопку восстановления. Это синхронизирует историю покупок с Adapty и вернёт обновлённый профиль. ```typescript showLineNumbers async function restorePurchases() { try { const profile: AdaptyProfile = await adapty.restorePurchases(); // Restore successful, profile updated } catch (error) { // Handle the error } } ``` ## Следующие шаги \{#next-steps\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Ваш пейвол готов к отображению в приложении. Протестируйте покупки в [песочнице App Store](test-purchases-in-sandbox) или в [Google Play Store](testing-on-android), чтобы убедиться, что тестовая покупка через пейвол проходит успешно. Чтобы увидеть, как это работает в готовой к продакшену реализации, изучите [CustomPurchaseScreen.tsx](https://github.com/adaptyteam/AdaptySDK-React-Native/blob/master/examples/ExpoGoWebMock/src/CustomPurchaseScreen.tsx) в нашем примере приложения — там показана обработка покупок с правильной обработкой ошибок, состояниями загрузки и управлением состоянием UI. Затем [проверьте, совершил ли пользователь покупку](react-native-check-subscription-status), чтобы решить, показывать ли пейвол или открыть доступ к платным функциям. --- # File: fetch-paywalls-and-products-react-native --- --- title: "Получение пейволов и продуктов для пейволов с Remote Config в React Native SDK" description: "Получайте пейволы и продукты в Adapty React Native SDK для улучшения монетизации пользователей." --- <SDKv4> Прежде чем отображать Remote Config и кастомные пейволы, необходимо получить данные о них. Обратите внимание, что этот раздел посвящён Remote Config и кастомным пейволам. Для получения информации о флоу или пейволах, настроенных в **Flow Builder** или **Paywall Builder**, обратитесь к статье [Получение флоу из Flow Builder, пейволов из Paywall Builder и их конфигурации](react-native-get-pb-paywalls). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Прежде чем начать получать флоу и продукты в мобильном приложении (нажмите, чтобы развернуть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте флоу или пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них флоу или пейвол](create-placement) в дашборде Adapty. 4. [Установите Adapty SDK](sdk-installation-reactnative) в своё мобильное приложение. </details> ## Получение информации о флоу \{#fetch-flow-information\} В Adapty [продукт](product) представляет собой комбинацию продуктов из App Store и Google Play. Эти кросс-платформенные продукты интегрируются во флоу и пейволы, позволяя показывать их в определённых плейсментах мобильного приложения. Чтобы отобразить продукты, нужно получить `AdaptyFlow` из одного из ваших [плейсментов](placements) с помощью метода `getFlow`. :::important **Не задавайте ID продуктов в коде.** Единственный ID, который можно захардкодить — это ID плейсмента. Флоу настраиваются удалённо, поэтому количество продуктов и доступных офферов может меняться в любое время. Приложение должно обрабатывать эти изменения динамически — если сегодня флоу возвращает два продукта, а завтра три, отображайте все без изменений в коде. ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const flow = await adapty.getFlow(id); // the requested flow } catch (error) { // handle the error } ``` | Параметр | Наличие | Описание | |-------------------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указывали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если вы считаете, что у ваших пользователей нестабильный интернет, рассмотрите использование `.returnCacheDataElseLoad`: оно возвращает кешированные данные, если они есть. В этом случае пользователи могут не получать самые свежие данные, зато загрузка будет быстрее независимо от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или принудительной очистке.</p><p></p><p>Adapty SDK хранит флоу и пейволы в двух слоях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](react-native-use-fallback-paywalls). Также используется CDN для более быстрой загрузки флоу и пейволов, а также отдельный резервный сервер на случай недоступности CDN. Эта система обеспечивает получение последней версии флоу и надёжность работы даже при плохом интернет-соединении.</p> | | **loadTimeoutMs** | по умолчанию: 5 сек | <p>Это значение ограничивает таймаут для данного метода. По истечении таймаута будут возвращены кешированные данные или локальный резервный вариант.</p><p></p><p>Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения, указанного в `loadTimeout`, так как операция может включать несколько запросов под капотом.</p> | :::note В v4 метод `getFlow` больше не принимает параметр `locale`. Для кастомных пейволов все доступные локали возвращаются в Remote Config флоу (`flow.remoteConfigs`) — выберите ту, которая соответствует настройкам устройства или приложения пользователя. ::: Не указывайте ID продуктов жёстко в коде! Поскольку флоу настраиваются удалённо, доступные продукты, их количество и специальные предложения (например, бесплатные пробные периоды) могут меняться со временем. Убедитесь, что ваш код обрабатывает подобные сценарии. Например, если изначально вы получаете 2 продукта, приложение должно отображать именно 2 продукта. Но если позднее вы получите 3 продукта, приложение должно отобразить все 3 без каких-либо изменений в коде. Единственное, что нужно указать жёстко, — это ID плейсмента. Параметры ответа: | Параметр | Описание | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Flow | Объект `AdaptyFlow`, содержащий плейсмент, идентификаторы (`id`, `variationId`), название, варианты пейвола (`paywalls`) и массив `remoteConfigs` (по одной записи на каждую настроенную локаль). Чтобы получить продукты для флоу, вызовите `getPaywallProducts(flow)`. | ## Получение продуктов \{#fetch-products\} Получив флоу, вы можете запросить массив продуктов, соответствующих ему: ```typescript showLineNumbers try { // ...flow const products = await adapty.getPaywallProducts(flow); // the requested products list } catch (error) { // handle the error } ``` Параметры ответа: | Parameter | Description | | :-------- |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | Список объектов [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct) с: идентификатором продукта, названием продукта, ценой, валютой, длительностью подписки и рядом других свойств. | При реализации собственного дизайна пейвола вам, скорее всего, потребуется доступ к свойствам объекта [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct). Ниже показаны наиболее часто используемые свойства, но полный список всех доступных свойств см. в документации по ссылке. | Свойство | Описание | |-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Чтобы отобразить название продукта, используйте `product.localizedTitle`. Обратите внимание, что локализация основана на стране стора, выбранной пользователем, а не на языке устройства. | | **Price** | Чтобы отобразить локализованную цену, используйте `product.price?.localizedString`. Локализация основана на настройках языка устройства. Цену в виде числа можно получить через `product.price?.amount` — значение будет указано в местной валюте. Символ валюты доступен через `product.price?.currencySymbol`. | | **Subscription Period** | Чтобы отобразить период подписки (например, неделя, месяц, год и т. д.), используйте `product.subscription?.localizedSubscriptionPeriod`. Локализация основана на настройках языка устройства. Для программного получения периода подписки используйте `product.subscription?.subscriptionPeriod`. Через свойство `unit` можно узнать единицу периода: `'day'`, `'week'`, `'month'`, `'year'` или `'unknown'`. Свойство `numberOfUnits` возвращает количество единиц периода. Например, для квартальной подписки в `unit` будет `'month'`, а в `numberOfUnits` — `3`. | | **Introductory Offer** | Чтобы показать бейдж или другой индикатор наличия introductory offer, проверьте свойство `product.subscription?.offer?.phases`. Это список, который может содержать до двух фаз скидки: фазу бесплатного пробного периода и фазу вводной цены. В каждом объекте фазы доступны следующие полезные свойства:<br/>• `paymentMode` — строка со значениями `'free_trial'`, `'pay_as_you_go'`, `'pay_up_front'` и `'unknown'`. Бесплатные пробные периоды имеют тип `'free_trial'`.<br/>• `price` — сниженная цена в виде числа. Для бесплатных пробных периодов ожидайте значение `0`.<br/>• `localizedNumberOfPeriods` — локализованная строка, описывающая продолжительность предложения. Например, для трёхдневного пробного периода здесь будет `'3 days'`.<br/>• `subscriptionPeriod` — позволяет получить отдельные параметры периода предложения. Работает так же, как описано в предыдущем разделе.<br/>• `localizedSubscriptionPeriod` — форматированный период подписки для скидки в соответствии с языковыми настройками устройства. | ## Ускорьте загрузку флоу с помощью флоу аудитории по умолчанию \{#speed-up-flow-fetching-with-default-audience-flow\} Как правило, флоу загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и плейсментов, а у пользователей слабое интернет-соединение, загрузка флоу может занять больше времени, чем хотелось бы. В таких ситуациях имеет смысл показывать флоу по умолчанию — это обеспечит плавный пользовательский опыт вместо пустого экрана. Чтобы решить эту проблему, используйте метод `getFlowForDefaultAudience`, который получает флоу указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать флоу через метод `getFlow`, как описано в разделе [Получение информации о флоу](fetch-paywalls-and-products-react-native#fetch-flow-information) выше. :::warning Почему мы рекомендуем использовать `getFlow` Метод `getFlowForDefaultAudience` имеет ряд существенных недостатков: - **Возможные проблемы с обратной совместимостью**: если вам нужно показывать разные флоу для разных версий приложения (текущей и будущих), вы можете столкнуться с трудностями. Придётся либо проектировать флоу, совместимые с текущей (устаревшей) версией, либо смириться с тем, что пользователи на текущей (устаревшей) версии могут видеть флоу некорректно. - **Потеря таргетинга**: все пользователи будут видеть один и тот же флоу, созданный для аудитории **All Users**, — это означает отказ от персонализированного таргетинга (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вы готовы принять эти ограничения ради ускоренного получения флоу, используйте метод `getFlowForDefaultAudience` следующим образом. В противном случае используйте `getFlow`, описанный [выше](fetch-paywalls-and-products-react-native#fetch-flow-information). ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const flow = await adapty.getFlowForDefaultAudience(id); // the requested flow } catch (error) { // handle the error } ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кэшированные данные в случае сбоя. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают самые актуальные данные.</p><p></p><p>Однако если вы считаете, что у ваших пользователей нестабильный интернет, рассмотрите использование `.returnCacheDataElseLoad` для возврата кэшированных данных, если они существуют. В этом случае пользователи могут не получить самые последние данные, но загрузка будет быстрее, независимо от качества соединения. Кэш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание, что кэш сохраняется при перезапуске приложения и очищается только при переустановке приложения или вручную.</p> | </SDKv4> <SDKv3> Прежде чем использовать Remote Config и кастомные пейволы, нужно получить информацию о них. Обратите внимание, что этот раздел посвящён Remote Config и кастомным пейволам. Инструкции по получению пейволов, настроенных в Paywall Builder, см. в разделе [Получение пейволов Paywall Builder и их конфигурации](react-native-get-pb-paywalls). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Перед тем как начать получать пейволы и продукты в мобильном приложении (нажмите, чтобы развернуть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте пейвол и добавьте продукты в него](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте пейвол в плейсмент](create-placement) в дашборде Adapty. 4. [Установите SDK Adapty](sdk-installation-reactnative) в своё мобильное приложение. </details> ## Получение информации о пейволе \{#fetch-paywall-information\} В Adapty [продукт](product) объединяет товары из App Store и Google Play. Эти кроссплатформенные продукты интегрируются в пейволы, позволяя показывать их в нужных плейсментах мобильного приложения. Чтобы отобразить продукты, нужно получить [пейвол](paywalls) из одного из ваших [плейсментов](placements) с помощью метода `getPaywall`. :::important **Не хардкодьте идентификаторы продуктов.** Единственный ID, который нужно хардкодить — это ID плейсмента. Пейволы настраиваются удалённо, поэтому количество продуктов и доступных офферов может меняться в любой момент. Ваше приложение должно обрабатывать эти изменения динамически: если сегодня пейвол возвращает два продукта, а завтра три — отображайте все без изменений в коде. ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const paywall = await adapty.getPaywall(id, locale); // the requested paywall } catch (error) { // handle the error } ``` | Параметр | Наличие | Описание | |-------------------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>необязательный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается языковой код, состоящий из одного или нескольких подтегов, разделённых символом минус (**-**). Первый подтег обозначает язык, второй — регион.</p><p></p><p>Например: `en` — английский, `pt-br` — бразильский португальский.</p><p></p><p>Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](react-native-localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Если вы считаете, что у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `.returnCacheDataElseLoad` — оно возвращает кешированные данные, если они есть. В этом случае пользователи могут не получить самые свежие данные, зато загрузка будет быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется после перезапуска приложения и очищается только при его переустановке или вручную.</p><p></p><p>Adapty SDK хранит пейволы на двух уровнях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](react-native-use-fallback-paywalls). Для более быстрой загрузки пейволов используется CDN, а на случай его недоступности — отдельный резервный сервер. Эта система обеспечивает получение актуальных версий пейволов и надёжную работу даже при нестабильном интернет-соединении.</p> | | **loadTimeoutMs** | по умолчанию: 5 сек | <p>Это значение ограничивает тайм-аут метода. По истечении тайм-аута возвращаются кешированные данные или локальный резервный вариант.</p><p></p><p>Обратите внимание: в редких случаях тайм-аут метода может наступить чуть позже указанного в `loadTimeout`, поскольку операция может включать несколько запросов под капотом.</p> | Не задавайте идентификаторы продуктов жёстко в коде! Поскольку пейволы настраиваются удалённо, доступные продукты, их количество и специальные предложения (например, бесплатные пробные периоды) могут меняться со временем. Убедитесь, что ваш код учитывает эти сценарии. Например, если изначально вы получаете 2 продукта, приложение должно отображать эти 2 продукта. Но если позже вы получите 3 продукта, приложение должно показать все 3 без каких-либо изменений в коде. Единственное, что нужно задать жёстко, — это идентификатор плейсмента. Параметры ответа: | Параметр | Описание | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Объект [`AdaptyPaywall`](https://react-native.adapty.io/interfaces/adaptypaywall) с: списком идентификаторов продуктов, идентификатором пейвола, Remote Config и рядом других свойств. | ## Получение продуктов \{#fetch-products\} Получив пейвол, вы можете запросить массив продуктов, соответствующих ему: ```typescript showLineNumbers try { // ...paywall const products = await adapty.getPaywallProducts(paywall); // the requested products list } catch (error) { // handle the error } ``` Параметры ответа: | Параметр | Описание | | :-------- |:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Products | Список объектов [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct) с идентификатором продукта, названием продукта, ценой, валютой, длительностью подписки и рядом других свойств. | При реализации собственного дизайна пейвола вам, скорее всего, понадобятся свойства объекта [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct). Ниже приведены наиболее часто используемые свойства; полный список доступных свойств смотрите в документации по ссылке. | Свойство | Описание | |-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Чтобы отобразить название продукта, используйте `product.localizedTitle`. Локализация основана на стране стора, выбранной пользователем, а не на локали устройства. | | **Price** | Чтобы отобразить цену в локализованном формате, используйте `product.price?.localizedString`. Локализация основана на локали устройства. Также можно получить цену как число через `product.price?.amount` — значение будет в местной валюте. Чтобы получить символ валюты, используйте `product.price?.currencySymbol`. | | **Subscription Period** | Чтобы отобразить период (например, неделя, месяц, год и т. д.), используйте `product.subscription?.localizedSubscriptionPeriod`. Локализация основана на локали устройства. Для программного получения периода подписки используйте `product.subscription?.subscriptionPeriod`. Через свойство `unit` можно узнать единицу периода: `'day'`, `'week'`, `'month'`, `'year'` или `'unknown'`. Свойство `numberOfUnits` возвращает количество единиц периода. Например, для квартальной подписки в `unit` будет `'month'`, а в `numberOfUnits` — `3`. | | **Introductory Offer** | Чтобы показать бейдж или другой индикатор наличия introductory offer, проверьте свойство `product.subscription?.offer?.phases`. Это список, который может содержать до двух фаз скидки: фаза бесплатного пробного периода и фаза вводной цены. Каждый объект фазы содержит следующие полезные свойства:<br/>• `paymentMode`: строка со значениями `'free_trial'`, `'pay_as_you_go'`, `'pay_up_front'` и `'unknown'`. Бесплатные пробные периоды имеют тип `'free_trial'`.<br/>• `price`: сниженная цена в виде числа. Для бесплатных пробных периодов здесь будет `0`.<br/>• `localizedNumberOfPeriods`: строка, локализованная в соответствии с локалью устройства, описывающая длительность предложения. Например, для трёхдневного пробного периода в этом поле будет `'3 days'`.<br/>• `subscriptionPeriod`: альтернативный способ получить детали периода предложения — работает так же, как описано в предыдущем разделе.<br/>• `localizedSubscriptionPeriod`: отформатированный период подписки скидки в соответствии с локалью пользователя. | ## Ускорьте загрузку пейвола с помощью пейвола аудитории по умолчанию \{#speed-up-paywall-fetching-with-default-audience-paywall\} Как правило, пейволы загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и пейволов, а интернет-соединение у пользователей нестабильное, загрузка пейвола может занять больше времени, чем хотелось бы. В таких случаях имеет смысл показывать пейвол аудитории по умолчанию — это обеспечит плавный пользовательский опыт, а не пустой экран. Чтобы решить эту проблему, воспользуйтесь методом `getPaywallForDefaultAudience`, который загружает пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — загружать пейвол через метод `getPaywall`, как описано в разделе [Получение информации о пейволе](fetch-paywalls-and-products-react-native#fetch-paywall-information) выше. :::warning Почему мы рекомендуем использовать `getPaywall` Метод `getPaywallForDefaultAudience` имеет ряд существенных недостатков: - **Потенциальные проблемы с обратной совместимостью**: если вам нужно показывать разные пейволы для разных версий приложения (текущей и будущих), могут возникнуть сложности. Придётся либо проектировать пейволы с поддержкой текущей (устаревшей) версии, либо мириться с тем, что пользователи на текущей (устаревшей) версии могут столкнуться с проблемами при отображении пейволов. - **Потеря таргетинга**: все пользователи будут видеть один и тот же пейвол, настроенный для аудитории **All Users**, — это означает отказ от персонализированного таргетинга (в том числе по странам, маркетинговой атрибуции или собственным пользовательским атрибутам). Если вы готовы принять эти ограничения ради более быстрого получения пейвола, используйте метод `getPaywallForDefaultAudience` следующим образом. В противном случае используйте `getPaywall`, описанный [выше](fetch-paywalls-and-products-react-native#fetch-paywall-information). ::: ```typescript showLineNumbers try { const id = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const paywall = await adapty.getPaywallForDefaultAudience(id, locale); // the requested paywall } catch (error) { // handle the error } ``` :::note Метод `getPaywallForDefaultAudience` доступен начиная с React Native SDK версии 2.11.2. ::: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается, что этот параметр будет языковым кодом, состоящим из одного или нескольких подтегов, разделённых символом «минус» (**-**). Первый подтег обозначает язык, второй — регион.</p><p></p><p>Пример: `en` означает английский, `pt-br` — бразильский португальский.</p><p></p><p>Подробнее о кодах локалей и рекомендуемом подходе к их использованию см. в разделе [Локализации и коды локалей](react-native-localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если вы считаете, что ваши пользователи работают в условиях нестабильного интернета, рассмотрите использование `.returnCacheDataElseLoad` — оно возвращает кешированные данные, если они есть. В этом случае пользователи могут получать не самые последние данные, зато загрузка будет быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы сократить количество сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.</p> | </SDKv3> --- # File: present-remote-config-paywalls-react-native --- --- title: "Отображение пейвола, созданного через Remote Config, в React Native SDK" description: "Узнайте, как показывать пейволы Remote Config в Adapty React Native SDK для персонализации пользовательского опыта." --- <SDKv4> Если вы настроили флоу с помощью Remote Config, вам нужно реализовать рендеринг в коде мобильного приложения, чтобы показывать его пользователям. Поскольку Remote Config предоставляет гибкость, адаптированную под ваши нужды, вы сами определяете, что включено и как выглядит ваш флоу. Мы предоставляем метод для получения Remote Config, давая вам возможность самостоятельно отображать кастомный флоу, настроенный через Remote Config. ## Получение Remote Config флоу и его отображение \{#get-flow-remote-config-and-present-it\} В v4 флоу содержит один объект `AdaptyRemoteConfig` на каждый настроенный язык в массиве `remoteConfigs`. Выберите язык, соответствующий предпочтениям пользователя, и считайте нужные значения из его поля `data`. ```typescript showLineNumbers try { const flow = await adapty.getFlow("YOUR_PLACEMENT_ID"); const config = flow.remoteConfigs?.find((c) => c.lang === "en") ?? flow.remoteConfigs?.[0]; const headerText = config?.data?.["header_text"]; } catch (error) { // handle the error } ``` На этом этапе, получив все необходимые значения, можно приступать к отрисовке и сборке визуально привлекательной страницы. Убедитесь, что дизайн адаптирован под различные экраны мобильных устройств и ориентации — это обеспечит комфортный пользовательский опыт на всех девайсах. :::warning Обязательно [зафиксируйте событие просмотра пейвола](present-remote-config-paywalls-react-native#track-paywall-view-events), как описано ниже, чтобы аналитика Adapty могла собирать данные для воронок и A/B-тестов. ::: После того как флоу отображён, переходите к настройке процесса покупки. Когда пользователь совершает покупку, просто вызовите `.makePurchase()` с продуктом из вашего флоу. Подробнее о методе `.makePurchase()` читайте в разделе [Совершение покупок](react-native-making-purchases). Мы рекомендуем [создать резервный пейвол](react-native-use-fallback-paywalls). Он будет отображаться пользователю при отсутствии интернет-соединения или кэша, обеспечивая бесперебойную работу приложения в таких ситуациях. ## Отслеживание событий просмотра пейвола \{#track-paywall-view-events\} Adapty помогает измерять эффективность ваших флоу. Данные о покупках мы собираем автоматически, однако события просмотра флоу нужно логировать вручную — только вы знаете, когда пользователь видит флоу. Чтобы залогировать событие просмотра флоу, вызовите `.logShowFlow(flow)` — это отразится в метриках вашего пейвола в воронках и A/B-тестах. :::important Вызывать `.logShowFlow(flow)` не нужно, если вы отображаете флоу или пейволы, созданные с помощью [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder). В этих случаях Adapty отслеживает просмотры автоматически. ::: ```typescript showLineNumbers await adapty.logShowFlow(flow); ``` Параметры запроса: | Параметр | Наличие | Описание | | :-------- | :------- |:-------------------------------------------------------------------------------------| | **flow** | обязательный | Объект `AdaptyFlow`, полученный через `adapty.getFlow(placementId)`. | </SDKv4> <SDKv3> Если вы настроили пейвол с помощью Remote Config, вам нужно реализовать его отображение в коде мобильного приложения. Поскольку Remote Config гибко адаптируется под ваши задачи, вы сами решаете, что включить и как будет выглядеть пейвол. Мы предоставляем метод для получения Remote Config — всё остальное остаётся на ваше усмотрение. ## Получение Remote Config пейвола и его отображение \{#get-paywall-remote-config-and-present-it\} Чтобы получить Remote Config пейвола, обратитесь к свойству `remoteConfig` и извлеките нужные значения. ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: "YOUR_PLACEMENT_ID" }); const headerText = paywall.remoteConfig?.data?.["header_text"]; } catch (error) { // handle the error } ``` На этом этапе, получив все необходимые значения, можно приступать к отрисовке и сборке визуально привлекательной страницы. Убедитесь, что дизайн адаптирован под разные экраны мобильных телефонов и ориентации, обеспечивая удобный и бесперебойный пользовательский опыт на всех устройствах. :::warning Обязательно [записывайте событие просмотра пейвола](present-remote-config-paywalls-react-native#track-paywall-view-events), как описано ниже — это позволит аналитике Adapty собирать данные для воронок и A/B-тестов. ::: После того как вы настроили отображение пейвола, переходите к настройке флоу покупки. Когда пользователь совершает покупку, просто вызовите `.makePurchase()` с продуктом из вашего пейвола. Подробнее о методе `.makePurchase()` читайте в разделе [Совершение покупок](react-native-making-purchases). Рекомендуем [создать резервный пейвол](react-native-use-fallback-paywalls). Он будет отображаться пользователю при отсутствии интернета или кэша, обеспечивая бесперебойную работу в таких ситуациях. ## Отслеживание событий просмотра пейвола \{#track-paywall-view-events\} Adapty помогает измерять эффективность ваших пейволов. Данные о покупках собираются автоматически, однако события просмотра пейвола нужно логировать вручную — только вы знаете, когда пользователь видит пейвол. Чтобы зафиксировать событие просмотра пейвола, вызовите `.logShowPaywall(paywall)` — это отразится в метриках вашего пейвола в воронках и A/B-тестах. :::important Вызов `.logShowPaywall(paywall)` не нужен, если вы показываете пейволы, созданные в [Paywall Builder](adapty-paywall-builder). ::: ```typescript showLineNumbers await adapty.logShowPaywall(paywall); ``` Параметры запроса: | Параметр | Наличие | Описание | | :---------- | :------- |:--------------------------------------------------------------------------------------------| | **paywall** | required | Объект [`AdaptyPaywall`](https://react-native.adapty.io/interfaces/adaptypaywall). | </SDKv3> --- # File: react-native-making-purchases --- --- title: "Совершение покупок в мобильном приложении в React Native SDK" description: "Гайд по обработке встроенных покупок и подписок с помощью Adapty." --- Отображение пейволов в мобильном приложении — обязательный шаг для предоставления пользователям доступа к премиум-контенту или сервисам. Однако простого показа пейволов достаточно для поддержки покупок только в том случае, если вы используете [Paywall Builder](adapty-paywall-builder) для их настройки. Если вы не используете Paywall Builder, для совершения покупки и открытия нужного контента необходимо вызвать отдельный метод `.makePurchase()`. Он служит точкой входа для пользователей, которые взаимодействуют с пейволами и инициируют нужные транзакции. Если для продукта, который пользователь хочет купить, настроен активный promotional offer, Adapty автоматически применит его в момент покупки. :::warning Обратите внимание: introductory offer применяется автоматически только при использовании пейволов, созданных в Paywall Builder. В остальных случаях вам потребуется [проверить право пользователя на получение introductory offer на iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios). Пропуск этого шага может привести к отклонению приложения при публикации. Кроме того, пользователи, имеющие право на introductory offer, могут быть списана полная стоимость. ::: Убедитесь, что вы выполнили [начальную настройку](quickstart), не пропустив ни одного шага. Без неё мы не сможем валидировать покупки. ## Совершение покупки \{#make-purchase\} :::note **Используете [Paywall Builder](adapty-paywall-builder)?** Покупки обрабатываются автоматически — этот шаг можно пропустить. **Нужны пошаговые инструкции?** Ознакомьтесь с [гайдом по быстрому старту](react-native-implement-paywalls-manually) — там есть полное руководство по реализации с подробным объяснением каждого шага. ::: ```typescript showLineNumbers try { const purchaseResult = await adapty.makePurchase(product); switch (purchaseResult.type) { case 'success': const isSubscribed = purchaseResult.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features } break; case 'user_cancelled': // Handle the case where the user canceled the purchase break; case 'pending': // Handle deferred purchases (e.g., the user will pay offline with cash) break; } } catch (error) { // Handle the error } ``` | Параметр | Наличие | Описание | | :---------- | :------- |:--------------------------------------------------------------------------------------------------------------------------------------| | **Product** | required | Объект [`AdaptyPaywallProduct`](https://react-native.adapty.io/interfaces/adaptypaywallproduct), полученный из пейвола. | Параметры ответа: | Параметр | Описание | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>Если запрос выполнен успешно, ответ содержит этот объект. Объект [AdaptyProfile](https://react-native.adapty.io/interfaces/adaptyprofile) предоставляет исчерпывающую информацию об уровнях доступа пользователя, подписках и разовых покупках в приложении.</p><p>Проверьте статус уровня доступа, чтобы убедиться, что у пользователя есть необходимый доступ к приложению.</p> | :::warning **Примечание:** если вы используете Apple StoreKit версии ниже 2.0 и Adapty SDK версии ниже 2.9.0, вам нужно указать [общий секрет Apple App Store](app-store-connection-configuration#step-5-enter-app-store-shared-secret) вместо этого. Данный метод в настоящее время устарел согласно Apple. ::: ## Смена подписки при совершении покупки \{#change-subscription-when-making-a-purchase\} Когда пользователь выбирает новую подписку вместо продления текущей, поведение зависит от стора: - В App Store подписка обновляется автоматически в рамках группы подписок. Если пользователь покупает подписку из одной группы, уже имея активную подписку из другой, обе подписки будут активны одновременно. - В Google Play подписка не обновляется автоматически. Переключение нужно реализовать в коде приложения, как описано ниже. Чтобы заменить подписку другой в Android, вызовите метод `.makePurchase()` с дополнительным параметром: ```typescript showLineNumbers try { const purchaseResult = await adapty.makePurchase(product, params); switch (purchaseResult.type) { case 'success': const isSubscribed = purchaseResult.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features } break; case 'user_cancelled': // Handle the case where the user canceled the purchase break; case 'pending': // Handle deferred purchases (e.g., the user will pay offline with cash) break; } } catch (error) { // Handle the error } ``` Дополнительный параметр запроса: | Параметр | Наличие | Описание | | :--------- | :------- | :----------------------------------------------------------- | | **params** | обязательный | объект типа [`MakePurchaseParamsInput`](https://react-native.adapty.io/types/makepurchaseparamsinput). | :::info **Версия 3.8.2+**: Структура `MakePurchaseParamsInput` была обновлена. `oldSubVendorProductId` и `prorationMode` теперь вложены в `subscriptionUpdateParams`, а `isOfferPersonalized` перемещён на верхний уровень. ```javascript makePurchase(product, { android: { subscriptionUpdateParams: { oldSubVendorProductId: 'old_product_id', prorationMode: 'charge_prorated_price' }, isOfferPersonalized: true } }); ``` ::: Подробнее о подписках и режимах замены можно прочитать в документации для разработчиков Google: - [О режимах замены](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Рекомендации Google по режимам замены](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - Режим замены [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE()). Примечание: этот метод доступен только для апгрейда подписки. Даунгрейд не поддерживается. - Режим замены [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED()). Примечание: фактическая смена подписки произойдёт только по окончании текущего расчётного периода. ## Активация промокодов в iOS \{#redeem-offer-codes-in-ios\} <Details> <summary>Об офферных кодах</summary> Офферные коды позволяют предоставлять скидки или бесплатные пробные периоды конкретным пользователям. В отличие от обычных офферов, которые применяются автоматически, офферные коды распространяются за пределами приложения — через email-рассылки, социальные сети или печатные материалы. Пользователи активируют их, вводя код в App Store, переходя по ссылке для активации или через диалог внутри приложения. Чтобы настроить офферные коды, откройте подписку в App Store Connect и перейдите в раздел **Offer Codes**. Вы можете создать [три вида](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes) офферных кодов: - **Free** — подписка бесплатна на заданный период, следующее продление — по полной цене. - **Pay as you go** — пользователь платит сниженную цену в каждом расчётном периоде на протяжении заданного срока, после чего подписка продлевается по полной цене. - **Pay up front** — пользователь единовременно платит сниженную цену за весь срок оффера, после чего подписка продлевается по полной цене. Добавлять офферные коды в Adapty не нужно. Apple помечает каждую транзакцию в период действия оффера категорией офферного кода. Это касается как первоначальной активации, так и всех последующих продлений со скидкой. Adapty обнаруживает метку и записывает каждую транзакцию с категорией оффера `offer_code`. Как только период оффера заканчивается и подписка продлевается по полной цене, метка исчезает. Вы можете фильтровать аналитику по типу оффера **Offer Code** в [дашборде Adapty](controls-filters-grouping-compare-proceeds). #### Устранение расхождений в выручке \{#revenue-discrepancy-troubleshooting\} Если транзакция по офферному коду отображается в Adapty по полной цене продукта вместо сниженной цены оффера, проверьте следующее в App Store Connect: - Для офферного кода настроены корректные цены для всех регионов, где пользователи могут его активировать. - Цена оффера задана для конкретной страны или региона пользователя. Apple передаёт региональную цену в транзакции. Если для оффера не настроена региональная цена, Apple может передать полную цену продукта. Вы можете фильтровать и проверять транзакции по офферным кодам в [дашборде Adapty](controls-filters-grouping-compare-proceeds) по фильтрам типа оффера **Offer Code** и **Offer Discount Type**. #### Устаревшие промокоды (deprecated) \{#legacy-promo-codes-deprecated\} :::warning Apple прекратила поддержку промокодов для встроенных покупок в марте 2026 года. Офферные коды заменяют их с расширенными возможностями: настраиваемые условия применения, сроки действия и до 1 миллиона кодов в квартал. Если вы ранее использовали промокоды для встроенных покупок, перейдите на офферные коды в App Store Connect. ::: Устаревшие промокоды (не более 100 на приложение на версию) предоставляли бесплатный доступ к подписке. В отличие от офферных кодов, Apple не включала информацию о скидке в транзакции по промокодам — в чеке указывалась полная цена продукта. В результате Adapty записывал эти транзакции по полной цене, что приводило к расхождениям в выручке между аналитикой Adapty и App Store Connect. Если вы видите исторические транзакции по полной цене, которые должны были быть бесплатными, скорее всего, они связаны с устаревшими промокодами. Поскольку эти коды больше не поддерживаются, перейдите на офферные коды для точного учёта выручки. </Details> Чтобы отобразить в приложении форму активации промокода: ```typescript showLineNumbers adapty.presentCodeRedemptionSheet(); ``` :::danger По нашим наблюдениям, форма активации промокода в некоторых приложениях работает нестабильно. Мы рекомендуем перенаправлять пользователя напрямую в App Store. Для этого откройте URL следующего формата: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: ## Управление предоплаченными планами (Android) \{#manage-prepaid-plans-android\} Если пользователи вашего приложения могут приобретать [предоплаченные планы](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (например, купить невозобновляемую подписку на несколько месяцев), вы можете включить [отложенные транзакции](https://developer.android.com/google/play/billing/subscriptions#pending) для таких планов. ```typescript showLineNumbers adapty.activate("PUBLIC_SDK_KEY", { android: { pendingPrepaidPlansEnabled: true } }); ``` --- # File: react-native-restore-purchase --- --- title: "Восстановление покупок в мобильном приложении с React Native SDK" description: "Узнайте, как восстанавливать покупки в Adapty для обеспечения бесперебойного пользовательского опыта." --- Восстановление покупок на iOS и Android позволяет пользователям снова получить доступ к ранее приобретённому контенту — подпискам или встроенным покупкам — без повторного списания средств. Это особенно удобно, если пользователь удалил и переустановил приложение или перешёл на новое устройство и хочет вернуть доступ к купленному контенту без дополнительной оплаты. :::note В пейволах, созданных с помощью [Paywall Builder](adapty-paywall-builder), покупки восстанавливаются автоматически — дополнительный код не нужен. Если это ваш случай, этот шаг можно пропустить. ::: Чтобы восстановить покупку, если вы не используете [Paywall Builder](adapty-paywall-builder) для настройки пейвола, вызовите метод `.restorePurchases()`: ```typescript showLineNumbers try { const profile = await adapty.restorePurchases(); const isSubscribed = profile.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // restore access } } catch (error) { // handle the error } ``` Параметры ответа: | Параметр | Описание | |---------|-----------| | **Profile** | <p>Объект [`AdaptyProfile`](https://react-native.adapty.io/interfaces/adaptyprofile). Эта модель содержит информацию об уровнях доступа, подписках и разовых покупках.</p><p>Проверьте **статус уровня доступа**, чтобы определить, есть ли у пользователя доступ к приложению.</p> | :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: --- # File: implement-observer-mode-react-native --- --- title: "Implement Observer mode in React Native SDK" description: "Реализация режима наблюдателя в Adapty для отслеживания событий подписки пользователей в React Native SDK." --- Если у вас уже есть собственная инфраструктура для покупок и вы не готовы полностью переходить на Adapty, вы можете попробовать [Observer mode](observer-vs-full-mode). В базовом виде Observer Mode предлагает расширенную аналитику и бесшовную интеграцию с системами атрибуции и аналитики. Если это вам подходит, нужно сделать только следующее: 1. Включить его при настройке Adapty SDK, установив параметр `observerMode` в `true`. Следуйте инструкциям по настройке для [React Native](sdk-installation-reactnative). 2. [Передавать транзакции](report-transactions-observer-mode-react-native) из вашей существующей инфраструктуры покупок в Adapty. ### Настройка Observer mode \{#observer-mode-setup\} Включите Observer mode, если вы самостоятельно обрабатываете покупки и статус подписки, а Adapty используете только для отправки событий подписки и аналитики. :::important В режиме Observer mode Adapty SDK не закрывает транзакции — убедитесь, что вы обрабатываете их самостоятельно. ::: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { observerMode: true, // Enable observer mode }); ``` Параметры: | Параметр | Описание | | --------------------------- | ------------------------------------------------------------ | | observerMode | Булево значение, которое управляет [Observer mode](observer-vs-full-mode). Значение по умолчанию: `false`. | ## Использование пейволов Adapty в Observer Mode \{#using-adapty-paywalls-in-observer-mode\} Если вы также хотите использовать пейволы Adapty и возможности A/B-тестирования — это возможно, но в Observer mode потребует дополнительной настройки. Вот что нужно сделать помимо шагов выше: 1. Отображайте пейволы как обычно для [пейволов на базе Remote Config](present-remote-config-paywalls-react-native). 3. [Привяжите пейволы](report-transactions-observer-mode-react-native) к транзакциям покупок. --- # File: report-transactions-observer-mode-react-native --- --- title: "Отчёт о транзакциях в Observer Mode в React Native SDK" description: "Отчитывайтесь о транзакциях покупок в Adapty Observer Mode для отслеживания данных о пользователях и доходах в React Native SDK." --- <Tabs groupId="sdk-version" queryString> <TabItem value="current" label="Adapty SDK v3.4+ (current)" default> В Observer Mode SDK Adapty не может самостоятельно отслеживать покупки, сделанные через вашу существующую систему. Вам нужно передавать транзакции из вашего стора. Это важно настроить **до** релиза приложения, чтобы избежать ошибок в аналитике. Используйте `reportTransaction`, чтобы явно сообщить Adapty о каждой транзакции. :::warning **Не пропускайте передачу транзакций!** Если вы не вызовете `reportTransaction`, Adapty не распознает транзакцию — она не появится в аналитике и не будет передана в интеграции. ::: Если вы используете пейволы Adapty, передавайте `variationId` при отчёте о транзакции. Это свяжет покупку с пейволом, который её инициировал, и обеспечит точную аналитику пейволов. ```typescript showLineNumbers const variationId = paywall.variationId; try { await adapty.reportTransaction(transactionId, variationId); } catch (error) { // handle the `AdaptyError` } ``` Параметры: | Параметр | Обязательность | Описание | | ------------- | -------------- | ---------------------------------------------------------- | | transactionId | обязательный | <ul><li> Для iOS: идентификатор транзакции.</li><li> Для Android: строковый идентификатор (`purchase.getOrderId`) покупки, где покупка является экземпляром класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) из библиотеки биллинга.</li></ul> | | variationId | необязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://react-native.adapty.io/interfaces/adaptypaywall). | </TabItem> <TabItem value="old" label="Adapty SDK 3.3.x (legacy)" default> В Observer Mode SDK Adapty не может самостоятельно отслеживать покупки, сделанные через вашу существующую систему. Вам нужно передавать транзакции из вашего стора или восстанавливать их. Это важно настроить **до** релиза приложения, чтобы избежать ошибок в аналитике. Используйте `reportTransaction` на обеих платформах, чтобы явно сообщить о каждой транзакции, а на Android дополнительно вызывайте `restorePurchases`, чтобы Adapty гарантированно её распознал. :::warning **Не пропускайте передачу транзакций!** Если вы не вызовете эти методы, Adapty не распознает транзакцию — она не появится в аналитике и не будет передана в интеграции. ::: Если вы используете пейволы Adapty, передавайте `variationId` при отчёте о транзакции. Это свяжет покупку с пейволом, который её инициировал, и обеспечит точную аналитику пейволов. ```typescript showLineNumbers if (Platform.OS === 'android') { try { await adapty.restorePurchases(); } catch (error) { // handle the error } } ... const variationId = paywall.variationId; try { await adapty.reportTransaction(transactionId, variationId); } catch (error) { // handle the `AdaptyError` } ``` Параметры: | Параметр | Обязательность | Описание | | ------------- | -------------- | ---------------------------------------------------------- | | transactionId | обязательный | <ul><li> Для iOS, StoreKit 1: объект [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction).</li><li> Для iOS, StoreKit 2: объект [Transaction](https://developer.apple.com/documentation/storekit/transaction).</li><li> Для Android: строковый идентификатор (`purchase.getOrderId`) покупки, где покупка является экземпляром класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) из библиотеки биллинга.</li></ul> | | variationId | необязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://react-native.adapty.io/interfaces/adaptypaywall). | </TabItem> <TabItem value="old2" label="Adapty SDK up to 3.2.x (legacy)" default> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> **Передача транзакций** - Версии до 3.1.x автоматически отслеживают транзакции в App Store, поэтому ручная передача не требуется. - Версия 3.2 не поддерживает Observer Mode. </TabItem> <TabItem value="kotlin" label="Android and Android-based cross-platforms" default> **Передача транзакций** Используйте `restorePurchases` для передачи транзакции в Adapty в Observer Mode, как описано на странице [Восстановление покупок в коде приложения](react-native-restore-purchase). :::warning **Не пропускайте передачу транзакций!** Если вы не вызовете `restorePurchases`, Adapty не распознает транзакцию — она не появится в аналитике и не будет передана в интеграции. ::: </TabItem> </Tabs> **Привязка пейволов к транзакциям** SDK Adapty не может определить источник покупок, так как их обрабатываете вы. Поэтому, если вы планируете использовать пейволы и/или A/B-тесты в Observer Mode, вам нужно связать транзакцию из вашего стора с соответствующим пейволом в коде мобильного приложения. Это важно сделать правильно до релиза приложения — иначе возникнут ошибки в аналитике. ```typescript const variationId = paywall.variationId; try { await adapty.setVariationId('transactionId', variationId); } catch (error) { // handle the `AdaptyError` } ``` Параметры запроса: | Параметр | Обязательность | Описание | | ------------- | -------------- | ---------------------------------------------------------- | | transactionId | обязательный | <p>Для iOS, StoreKit 1: объект [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction).</p><p>Для iOS, StoreKit 2: объект [Transaction](https://developer.apple.com/documentation/storekit/transaction).</p><p>Для Android: строковый идентификатор (purchase.getOrderId) покупки, где покупка является экземпляром класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) из библиотеки биллинга.</p> | | variationId | обязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://react-native.adapty.io/interfaces/adaptypaywall). | </TabItem> </Tabs> --- # File: react-native-troubleshoot-purchases --- --- title: "Устранение проблем с покупками в React Native SDK" description: "Устранение проблем с покупками в React Native SDK" --- Этот гайд поможет вам решить распространённые проблемы при реализации покупок вручную в React Native SDK. ## makePurchase вызывается успешно, но профиль не обновляется \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\} **Проблема**: метод `makePurchase` завершается успешно, но профиль пользователя и статус подписки в Adapty не обновляются. **Причина**: как правило, это указывает на неполную настройку Google Play Store или проблемы с конфигурацией. **Решение**: убедитесь, что вы выполнили все [шаги по настройке Google Play](initial-android). ## makePurchase вызывается дважды \{#makepurchase-is-invoked-twice\} **Проблема**: метод `makePurchase` вызывается несколько раз для одной и той же покупки. **Причина**: обычно это происходит, когда процесс покупки запускается несколько раз из-за проблем с управлением состоянием UI или быстрых повторных действий пользователя. **Решение**: убедитесь, что вы выполнили все [шаги по настройке Google Play](initial-android). ## AdaptyError.cantMakePayments в режиме наблюдателя \{#adaptyerrorcantmakepayments-in-observer-mode\} **Проблема**: при использовании `makePurchase` в режиме наблюдателя возникает ошибка `AdaptyError.cantMakePayments`. **Причина**: в режиме наблюдателя вы должны обрабатывать покупки самостоятельно, а не использовать метод `makePurchase` из Adapty. **Решение**: если вы используете `makePurchase` для покупок, отключите режим наблюдателя. Нужно либо использовать `makePurchase`, либо обрабатывать покупки самостоятельно в режиме наблюдателя. Подробнее см. в разделе [Реализация режима наблюдателя](implement-observer-mode-react-native). ## Ошибка Adapty: (code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\} **Проблема**: вы получаете ошибку о недоступности биллинга из Google Play Store. **Причина**: эта ошибка не связана с Adapty. Это ошибка Google Play Billing Library, означающая, что биллинг недоступен на устройстве. **Решение**: данная ошибка не связана с Adapty. Подробнее о ней можно узнать в документации Play Store: [Handle BillingResult response codes](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers. ## Not found makePurchasesCompletionHandlers \{#not-found-makepurchasescompletionhandlers\} **Проблема**: возникают ошибки, связанные с тем, что `makePurchasesCompletionHandlers` не найден. **Причина**: как правило, это связано с проблемами при тестировании в песочнице. **Решение**: создайте нового пользователя песочницы и повторите попытку. Обычно это решает проблемы с обработчиками завершения покупки в песочнице. ## Другие проблемы \{#other-issues\} **Проблема**: у вас возникают другие проблемы с покупками, не описанные выше. **Решение**: при необходимости обновите SDK до последней версии с помощью [гайдов по миграции](react-native-sdk-migration-guides). Многие проблемы устранены в новых версиях SDK. --- # File: react-native-identifying-users --- --- title: "Идентификация пользователей в React Native SDK" description: "Узнайте, как идентифицировать пользователей в приложении React Native с помощью Adapty SDK." --- Adapty создаёт внутренний profile ID для каждого пользователя. Однако если у вас есть собственная система аутентификации, вы можете задать свой Customer User ID. Пользователей можно находить по Customer User ID в разделе [Профили](profiles-crm), а также использовать его в [серверном API](getting-started-with-server-side-api) — он будет передаваться во все интеграции. ### Установка customer user ID при инициализации \{#setting-customer-user-id-on-configuration\} Если ID пользователя известен на момент инициализации, передайте его в параметре `customerUserId` метода `.activate()`: ```typescript showLineNumbers adapty.activate("PUBLIC_SDK_KEY", { customerUserId: "YOUR_USER_ID" }); ``` :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Установка customer user ID после конфигурации \{#setting-customer-user-id-after-configuration\} Если при конфигурации SDK у вас ещё нет user ID, его можно задать позже в любой момент с помощью метода `.identify()`. Чаще всего этот метод используют после регистрации или авторизации, когда анонимный пользователь становится аутентифицированным. ```typescript showLineNumbers try { await adapty.identify("YOUR_USER_ID"); // successfully identified } catch (error) { // handle the error } ``` Параметры запроса: - **Customer User ID** (обязательный): строковый идентификатор пользователя. :::warning Повторная отправка важных данных пользователя В некоторых случаях, например когда пользователь снова входит в свой аккаунт, серверы Adapty уже располагают информацией об этом пользователе. В таких ситуациях SDK автоматически переключится на работу с новым пользователем. Если вы передавали какие-либо данные анонимному пользователю — например, пользовательские атрибуты или атрибуцию из сторонних сетей — вам необходимо повторно отправить эти данные для идентифицированного пользователя. Также важно учитывать, что после идентификации пользователя следует заново запросить все пейволы и продукты, поскольку данные нового пользователя могут отличаться. ::: ### Выход и вход в систему \{#logging-out-and-logging-in\} Вы можете выйти из системы в любой момент, вызвав метод `.logout()`: ```typescript showLineNumbers try { await adapty.logout(); // successful logout } catch (error) { // handle the error } ``` После этого можно авторизовать пользователя с помощью метода `.identify()`. ## Назначение `appAccountToken` (iOS) \{#assign-appaccounttoken-ios\} [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) — это **UUID**, который позволяет связывать транзакции App Store с вашим внутренним идентификатором пользователя. StoreKit прикрепляет этот токен к каждой транзакции, поэтому ваш бэкенд может сопоставлять данные App Store с конкретными пользователями. Используйте стабильный UUID, сгенерированный для каждого пользователя, и применяйте его для одного и того же аккаунта на всех устройствах. Это гарантирует, что покупки и уведомления App Store будут корректно связаны с нужным пользователем. Вы можете задать токен двумя способами — при активации SDK или при идентификации пользователя. :::important Вы должны всегда передавать `appAccountToken` вместе с `customerUserId`. Если передать только токен, он не будет включён в транзакцию. ::: ```typescript showLineNumbers // Во время конфигурации: adapty.activate("PUBLIC_SDK_KEY", { customerUserId: "YOUR_USER_ID", ios: { appAccountToken: "YOUR_APP_ACCOUNT_TOKEN" }, }); // Или при идентификации пользователей try { await adapty.identify("YOUR_USER_ID", { ios: {appAccountToken: 'YOUR_APP_ACCOUNT_TOKEN'} }); // успешно идентифицирован } catch (error) { // обработайте ошибку } ``` ### Задайте обфусцированные идентификаторы аккаунта (Android) \{#set-obfuscated-account-ids-android\} Google Play требует обфусцированные идентификаторы аккаунта в ряде сценариев — для защиты конфиденциальности и безопасности пользователей. Эти идентификаторы помогают Google Play отслеживать покупки, не раскрывая личные данные пользователя, что особенно важно для предотвращения мошенничества и аналитики. Устанавливать эти идентификаторы нужно, если приложение работает с чувствительными данными пользователей или должно соответствовать определённым требованиям по конфиденциальности. Обфусцированные идентификаторы позволяют Google Play отслеживать покупки, не раскрывая реальные идентификаторы пользователей. ```typescript showLineNumbers // Во время настройки: adapty.activate("PUBLIC_SDK_KEY", { customerUserId: "YOUR_USER_ID", android: { obfuscatedAccountId: 'YOUR_OBFUSCATED_ACCOUNT_ID' } }); // Или при идентификации пользователей try { await adapty.identify("YOUR_USER_ID", { android: { obfuscatedAccountId: 'YOUR_OBFUSCATED_ACCOUNT_ID' } }); // успешно идентифицирован } catch (error) { // обработать ошибку } ``` ## Определение пользователей на разных устройствах \{#detect-users-across-devices\} При активации SDK он автоматически считывает существующие права пользователя из StoreKit (iOS) или Google Play Billing (Android) и синхронизирует их с бэкендом Adapty. Активная подписка появляется в профиле Adapty без вызова `restorePurchases` со стороны приложения. Что **не** происходит автоматически — так это распознавание того, что профиль на новом устройстве принадлежит тому же пользователю, что и профиль на исходном устройстве. Adapty сопоставляет профили по Customer User ID, поэтому непрерывность идентификации зависит от того, что вы используете в качестве CUID. **Что Adapty может определить между устройствами** | Ваша настройка | Что Adapty определяет | Что нужно сделать | | --- | --- | --- | | Customer User ID = `device_id` (без входа в аккаунт) | Новое устройство получает другой CUID и, следовательно, другой профиль. Подписка синхронизируется с новым профилем через событие **Access level updated**, но `subscription_started` не срабатывает — новый профиль считается наследником исходной покупки. Аналитика, основанная на `subscription_started`, будет занижать количество возвращающихся пользователей. | Используйте стабильный идентификатор аккаунта в качестве Customer User ID, чтобы вернувшийся пользователь соответствовал существующему профилю на разных устройствах. | | Customer User ID = стабильный идентификатор аккаунта (вход на каждом устройстве) | SDK автоматически синхронизирует подписку при `activate()`, а `identify()` сопоставляет существующий профиль по CUID. | Никаких дополнительных действий не требуется — и идентификация, и подписка разрешаются автоматически. | | Наследник Apple Family Sharing | Член семьи получает подписку только через событие **Access level updated** — `subscription_started` не срабатывает. | Отслеживайте событие **Access level updated**. Полную матрицу событий см. в разделе [Apple Family Sharing](apple-family-sharing). | | Один аккаунт Apple/Google, разные пользователи внутри приложения | Первый профиль, зафиксировавший покупку, становится родительским. Последующие профили видят подписку через цепочку наследования с одним событием **Access level updated**. | Требуйте входа в аккаунт, затем выберите [режим совместного использования](sharing-paid-access-between-user-accounts), подходящий для вашей модели. | **Восстановление покупок на новом устройстве** Добавьте кнопку «Восстановить покупки», инициируемую пользователем, на свой пейвол. Apple App Review (руководство 3.1.1) её требует, и она служит запасным вариантом на случай, если автоматическая синхронизация пропустит граничный случай. Кнопка должна вызывать `restorePurchases` в вашем SDK. Программный вызов `restorePurchases` при первом запуске не нужен для обычного использования — SDK уже выполняет аналогичную операцию при `activate()`. Программные вызовы стоит использовать только для принудительной проверки чека, например при отладке отсутствующего доступа после завершения `activate()`. --- # File: react-native-setting-user-attributes --- --- title: "Установка атрибутов пользователя в React Native SDK" description: "Узнайте, как обновлять атрибуты пользователя и данные профиля в приложении React Native с помощью Adapty SDK." --- Вы можете задавать пользователям приложения дополнительные атрибуты: email, номер телефона и другие. Атрибуты можно использовать для создания пользовательских [сегментов](segments) или просматривать их в CRM. ### Установка атрибутов пользователя \{#setting-user-attributes\} Чтобы задать атрибуты пользователя, вызовите метод `.updateProfile()`: ```typescript showLineNumbers // Only for TypeScript validation const params: AdaptyProfileParameters = { email: 'email@email.com', phoneNumber: '+18888888888', firstName: 'John', lastName: 'Appleseed', gender: 'other', birthday: new Date().toISOString(), }; try { await adapty.updateProfile(params); } catch (error) { // handle `AdaptyError` } ``` Обратите внимание: атрибуты, ранее установленные с помощью метода `updateProfile`, сбрасываться не будут. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Список допустимых ключей \{#the-allowed-keys-list\} Допустимые ключи `<Key>` для `AdaptyProfileParameters.Builder` и соответствующие значения `<Value>`: | Ключ | Значение | |---|-----| | <p>email</p><p>phoneNumber</p><p>firstName</p><p>lastName</p> | String | | gender | Enum, допустимые значения: `female`, `male`, `other` | | birthday | Date | ### Пользовательские атрибуты \{#custom-user-attributes\} Вы можете задавать собственные атрибуты — как правило, они связаны с тем, как пользователь работает с приложением. Например, в фитнес-приложении это может быть количество тренировок в неделю, в приложении для изучения языков — уровень знаний. Атрибуты можно использовать в сегментах для создания целевых пейволов и предложений, а также в аналитике, чтобы выяснить, какие продуктовые метрики сильнее всего влияют на выручку. ```typescript showLineNumbers try { await adapty.updateProfile({ codableCustomAttributes: { key_1: 'value_1', key_2: 2, }, }); } catch (error) { // handle `AdaptyError` } ``` Чтобы удалить существующий ключ, используйте метод `.withRemoved(customAttributeForKey:)`: ```typescript showLineNumbers try { // to remove a key, pass null as its value await adapty.updateProfile({ codableCustomAttributes: { key_1: null, key_2: null, }, }); } catch (error) { // handle `AdaptyError` } ``` Иногда нужно узнать, какие пользовательские атрибуты уже были установлены. Для этого используйте поле `customAttributes` объекта `AdaptyProfile`. :::warning Имейте в виду, что значение `customAttributes` может быть устаревшим: атрибуты могут отправляться с разных устройств в любое время, поэтому данные на сервере могут измениться после последней синхронизации. ::: ### Ограничения \{#limits\} - Не более 30 пользовательских атрибутов на пользователя - Длина имени ключа — не более 30 символов. Допустимы буквенно-цифровые символы и следующие знаки: `_` `-` `.` - Значение может быть строкой или числом с плавающей точкой длиной не более 50 символов. --- # File: react-native-listen-subscription-changes --- --- title: "Проверка статуса подписки в React Native SDK" description: "Отслеживайте и управляйте статусом подписки пользователей в Adapty для повышения удержания клиентов в вашем React Native приложении." --- С Adapty отслеживать статус подписки очень просто. Вам не нужно вручную прописывать идентификаторы продуктов в коде — достаточно проверить наличие активного [уровня доступа](access-level). <details> <summary>Перед проверкой статуса подписки (нажмите, чтобы развернуть)</summary> - Для iOS настройте [App Store Server Notifications](enable-app-store-server-notifications) - Для Android настройте [Real-time Developer Notifications (RTDN)](enable-real-time-developer-notifications-rtdn) </details> ## Уровень доступа и объект AdaptyProfile \{#access-level-and-the-adaptyprofile-object\} Уровни доступа — это свойства объекта [AdaptyProfile](https://react-native.adapty.io/interfaces/adaptyprofile). Рекомендуем получать профиль при запуске приложения, например когда вы [идентифицируете пользователя](react-native-identifying-users#setting-customer-user-id-on-configuration), и обновлять его при любых изменениях. Так вы сможете использовать объект профиля без повторных запросов к серверу. Чтобы получать уведомления об обновлениях профиля, подпишитесь на изменения, как описано в разделе [Прослушивание обновлений профиля, включая уровни доступа](react-native-listen-subscription-changes) ниже. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ## Получение уровня доступа с сервера \{#retrieving-the-access-level-from-the-server\} Чтобы получить уровень доступа с сервера, используйте метод `.getProfile()`: ```typescript showLineNumbers try { const profile = await adapty.getProfile(); } catch (error) { // handle the error } ``` Параметры ответа: | Параметр | Описание | | --------- | ------------------------------------------------------------ | | Profile | <p>Объект [AdaptyProfile](https://react-native.adapty.io/interfaces/adaptyprofile). Как правило, для определения наличия у пользователя премиум-доступа к приложению достаточно проверить статус уровня доступа в профиле.</p><p></p><p>Метод `.getProfile` возвращает наиболее актуальный результат, поскольку всегда пытается обратиться к API. Если по какой-то причине (например, при отсутствии интернет-соединения) Adapty SDK не может получить данные с сервера, возвращаются данные из кэша. Важно учитывать, что Adapty SDK регулярно обновляет кэш `AdaptyProfile`, чтобы поддерживать информацию в актуальном состоянии.</p> | Метод `.getProfile()` возвращает профиль пользователя, из которого можно получить статус уровня доступа. В приложении может быть несколько уровней доступа. Например, в новостном приложении с независимыми подписками на разные разделы можно создать уровни доступа «sports» и «science». Однако в большинстве случаев достаточно одного уровня доступа — стандартного «premium». Пример проверки стандартного уровня доступа «premium»: ```typescript showLineNumbers try { const profile = await adapty.getProfile(); const isActive = profile.accessLevels?.["premium"]?.isActive; if (isActive) { // grant access to premium features } } catch (error) { // handle the error } ``` ### Прослушивание обновлений статуса подписки \{#listening-for-subscription-status-updates\} При каждом изменении подписки пользователя Adapty генерирует событие. Чтобы получать сообщения от Adapty, выполните дополнительную настройку: ```typescript showLineNumbers // Create an "onLatestProfileLoad" event listener adapty.addEventListener('onLatestProfileLoad', profile => { // handle any changes to subscription state }); ``` Adapty также генерирует событие при запуске приложения — в этом случае передаётся кэшированный статус подписки. ### Кэш статуса подписки \{#subscription-status-cache\} Кэш, реализованный в Adapty SDK, хранит статус подписки профиля. Это означает, что даже при недоступности сервера кэшированные данные позволяют получить информацию о статусе подписки профиля. Вместе с тем прямые запросы данных из кэша невозможны. SDK периодически опрашивает сервер каждую минуту для проверки обновлений профиля. При наличии изменений — новых транзакций или других обновлений — они передаются в кэшированные данные для синхронизации с сервером. --- # File: react-native-deal-with-att --- --- title: "Работа с ATT в React Native SDK" description: "Начните работу с Adapty на React Native для упрощения настройки подписок и управления ими." --- Если ваше приложение использует фреймворк AppTrackingTransparency и отображает пользователю запрос на авторизацию отслеживания, необходимо передать [статус авторизации](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/) в Adapty. ```typescript showLineNumbers try { await adapty.updateProfile({ // you can also pass a string value (validated via tsc) if you prefer appTrackingTransparencyStatus: AppTrackingTransparencyStatus.Authorized, }); } catch (error) { // handle `AdaptyError` } ``` :::warning Настоятельно рекомендуем передавать это значение как можно раньше при его изменении — только в этом случае данные будут своевременно отправлены в настроенные вами интеграции. ::: --- # File: kids-mode-react-native --- --- title: "Режим для детей в React Native SDK" description: "Легко включите режим для детей, чтобы соответствовать политикам Apple и Google. IDFA, GAID и рекламные данные не собираются в React Native SDK." --- Если ваше приложение на React Native предназначено для детей, вы должны соблюдать политики [Apple](https://developer.apple.com/kids/) и [Google](https://support.google.com/googleplay/android-developer/answer/9893335). Если вы используете Adapty SDK, несколько простых шагов помогут настроить его в соответствии с этими политиками и пройти проверку в сторах. :::important На iOS Kids Mode включается через трейт Swift Package `KidsMode`, который исключает из сборки весь код, связанный с IDFA, AdSupport и AppTrackingTransparency. Для этого требуется SDK v4 (который устанавливает нативный iOS SDK через Swift Package Manager) и **Xcode 26** или новее. См. раздел [Изменения в iOS Podfile](#updates-in-your-ios-podfile) ниже. ::: ## Что нужно настроить? \{#whats-required\} Вам нужно настроить SDK, чтобы отключить сбор следующих данных: - [IDFA (Identifier for Advertisers)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) (iOS) - [Android Advertising ID (AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248) (Android) - [IP-адрес](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Кроме того, рекомендуем осторожно обращаться с customer user ID. Идентификатор в формате `<FirstName.LastName>` однозначно будет расценён как сбор персональных данных, так же как и использование email. Для режима «Дети» лучшая практика — использовать случайные или анонимизированные идентификаторы (например, хэшированные ID или UUID, сгенерированные устройством), чтобы обеспечить соответствие требованиям. ## Включение режима Kids Mode \{#enabling-kids-mode\} ### Обновления в дашборде Adapty В дашборде Adapty нужно отключить сбор IP-адресов. Перейдите в [App settings](https://app.adapty.io/settings/general) и нажмите **Disable IP address collection** в разделе **Collect users' IP address**. ### Обновления в коде вашего мобильного приложения \{#updates-in-your-mobile-app-code\} Чтобы соответствовать требованиям политик, отключите сбор IDFA (iOS), GAID/AAID (Android) и IP-адреса пользователя при активации SDK: ```typescript showLineNumbers title="App.tsx" adapty.activate('YOUR_PUBLIC_SDK_KEY', { // Disable IP address collection ipAddressCollectionDisabled: true, // Disable IDFA collection on iOS ios: { idfaCollectionDisabled: true, }, // Disable Google Advertising ID collection on Android android: { adIdCollectionDisabled: true, }, }); ``` ### Обновления в iOS Podfile \{#updates-in-your-ios-podfile\} Для категории App Store Kids (или соответствия требованиям COPPA) нативный iOS SDK должен быть собран с трейтом Swift-пакета `KidsMode`, который исключает из компиляции весь код, связанный с IDFA, AdSupport и AppTrackingTransparency. React Native устанавливает нативный SDK через Swift Package Manager, который не может передавать трейты пакетов, поэтому SDK предоставляет вспомогательный инструмент для Podfile, применяющий трейт автоматически. Для этого шага требуется **Xcode 26** или новее. В файле `ios/Podfile` подключите вспомогательный инструмент и вызовите его **после** `react_native_post_install`: ```ruby showLineNumbers title="ios/Podfile" require Pod::Executable.execute_command('node', ['-p', 'require.resolve( "react-native-adapty/ios/adapty_kids_mode.rb", {paths: [process.argv[1]]}, )', __dir__]).strip # ... post_install do |installer| react_native_post_install( installer, config[:reactNativePath], :mac_catalyst_enabled => false ) adapty_enable_kids_mode(installer) end ``` Затем выполните `pod install`: ```sh showLineNumbers title="Shell" cd ios && pod install ``` Чтобы убедиться, что режим Kids Mode активен, проверьте, что в логе `adapty.activate(...)` указано `kids_mode_enabled: true`. Оставьте вызов хелпера в `post_install` на постоянной основе — React Native пересоздаёт ссылки на Swift-пакеты при каждом запуске `pod install`, а хелпер заново применяет нужный атрибут каждый раз. ### Обновления в вашем Android-манифесте \{#updates-in-your-android-manifest\} :::note Если ваше приложение ориентировано **исключительно** на детскую аудиторию и компилируется под Android 13 (API 33) или выше, Google Play требует не запрашивать разрешение `AD_ID`. Другой SDK в вашем приложении (аналитика, атрибуция или реклама) может добавить это разрешение через слияние манифестов. Установка `adIdCollectionDisabled` останавливает сбор идентификатора в Adapty, но не удаляет разрешение, объявленное другим SDK. ::: Чтобы удалить разрешение, добавьте следующее внутри элемента `<manifest>` в файле `android/app/src/main/AndroidManifest.xml`. Элемент `<manifest>` должен объявлять `xmlns:tools="http://schemas.android.com/tools"`. ```xml showLineNumbers title="AndroidManifest.xml" <uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove" /> ``` --- # File: react-native-get-onboardings --- --- title: "Получение онбордингов в React Native SDK" description: "Узнайте, как получать онбординги в Adapty для React Native." --- :::warning **Онбординги устарели в SDK v4 и будут удалены в одном из следующих релизов.** Они больше не получают исправлений и улучшений. Используйте [флоу](react-native-get-pb-paywalls): в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает плавные анимации, единый нативный внешний вид, быструю загрузку и отсутствие зависимости от WebView. Подробнее в разделах [Получение флоу и пейволов](react-native-get-pb-paywalls) и [Отображение флоу и пейволов](react-native-present-paywalls). ::: После того как вы [оформили визуальную часть онбординга](design-onboarding) в Paywall Builder на дашборде Adapty, его можно отобразить в приложении на React Native. Первый шаг — получить онбординг, связанный с плейсментом, и конфигурацию его отображения, как описано ниже. Прежде чем начать, убедитесь, что: 1. Установлен [Adapty React Native SDK](sdk-installation-reactnative) версии 3.8.0 или выше. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). ## Получение онбординга \{#fetch-onboarding\} Когда вы создаёте [онбординг](onboardings) в нашем no-code конструкторе, он сохраняется как контейнер с конфигурацией, которую приложение должно получить и отобразить. Этот контейнер управляет всем процессом: какой контент показывать, как его представлять и как обрабатывать действия пользователя (ответы на вопросы викторины, заполнение форм и т. д.). Контейнер также автоматически отслеживает аналитические события, поэтому реализовывать отдельное отслеживание просмотров не нужно. Для лучшей производительности запрашивайте конфигурацию онбординга заранее — это даёт изображениям достаточно времени для загрузки до того, как они будут показаны пользователям. Чтобы получить онбординг, используйте метод `getOnboarding`: ```typescript showLineNumbers try { const placementId = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const onboarding = await adapty.getOnboarding(placementId, locale); // the requested onboarding } catch (error) { // handle the error } ``` Затем вызовите метод `createOnboardingView`, чтобы создать экземпляр представления. :::warning Результат метода `createOnboardingView` можно использовать только один раз. Если вам нужно использовать его повторно, снова вызовите метод `createOnboardingView`. Повторный вызов без пересоздания может привести к ошибке `AdaptyUIError.viewAlreadyPresented`. ::: ```typescript showLineNumbers // for the Adapty SDK < 3.14 – import {createOnboardingView} from 'react-native-adapty/dist/ui'; if (onboarding.hasViewConfiguration) { try { const view = await createOnboardingView(onboarding); } catch (error) { // handle the error } } else { //use your custom logic } ``` Параметры: | Параметр | Наличие | Описание | |-------------------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | <p>Идентификатор локализации онбординга. Ожидается языковой код, состоящий из одного или двух подтегов, разделённых символом минус (**-**). Первый подтег обозначает язык, второй — регион.</p><p></p><p>Пример: `en` — английский, `pt-br` — бразильский португальский.</p><p>Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильный интернет, рассмотрите вариант `.returnCacheDataElseLoad` — он возвращает кешированные данные, если они есть. В этом случае пользователи могут получить не самые свежие данные, зато загрузка будет быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется после перезапуска приложения и очищается только при его переустановке или принудительной очистке вручную.</p><p></p><p>Adapty SDK хранит онбординги локально в двух слоях: регулярно обновляемый кеш, описанный выше, и резервные онбординги. Для более быстрой загрузки онбордингов используется CDN, а на случай его недоступности — отдельный резервный сервер. Такая система гарантирует, что вы всегда получаете актуальную версию онбордингов, сохраняя надёжность даже при слабом интернет-соединении.</p> | | **loadTimeoutMs** | по умолчанию: 5 сек | <p>Это значение ограничивает таймаут метода. Если таймаут истекает, возвращаются кешированные данные или локальный резервный вариант.</p><p>Обратите внимание: в редких случаях метод может завершиться чуть позже указанного в `loadTimeout` значения, поскольку операция может включать несколько внутренних запросов.</p> | Параметры ответа: | Параметр | Описание | |:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Объект [`AdaptyOnboarding`](https://react-native.adapty.io/interfaces/adaptyonboarding) с: идентификатором и конфигурацией онбординга, Remote Config и рядом других свойств. | ## Ускорьте получение онбординга с помощью онбординга для аудитории по умолчанию \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} Как правило, онбординги загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и онбордингов, а у пользователей слабое интернет-соединение, загрузка онбординга может занять больше времени, чем хотелось бы. В таких случаях имеет смысл показывать онбординг по умолчанию — чтобы пользователь видел хоть что-то, а не пустой экран. Чтобы решить эту проблему, воспользуйтесь методом `getOnboardingForDefaultAudience`, который получает онбординг указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать онбординг с помощью метода `getOnboarding`, как описано в разделе [Получение онбординга](#fetch-onboarding) выше. :::warning Используйте `getOnboarding` вместо `getOnboardingForDefaultAudience`, поскольку последний имеет существенные ограничения: - **Проблемы совместимости**: Может вызывать сложности при поддержке нескольких версий приложения — придётся либо проектировать с учётом обратной совместимости, либо мириться с тем, что старые версии могут отображать контент некорректно. - **Без персонализации**: Показывает контент только для аудитории «All Users», без таргетинга по стране, атрибуции или пользовательским атрибутам. Если для вашего сценария скорость загрузки важнее этих недостатков, используйте `getOnboardingForDefaultAudience`, как показано ниже. В противном случае используйте `getOnboarding`, как описано [выше](#fetch-onboarding). ::: ```typescript showLineNumbers try { const placementId = 'YOUR_PLACEMENT_ID'; const locale = 'en'; const onboarding = await adapty.getOnboardingForDefaultAudience(placementId, locale); // запрошенный онбординг } catch (error) { // обработка ошибки } ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | <p>Идентификатор локализации онбординга. Ожидается языковой код, состоящий из одного или двух подтегов, разделённых символом минус (**-**). Первый подтег обозначает язык, второй — регион.</p><p></p><p>Например: `en` — английский язык, `pt-br` — бразильский португальский.</p><p>Подробнее о кодах локалей и рекомендациях по их использованию — в разделе [Локализации и коды локалей](localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и в случае сбоя возвращает кэшированные данные. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `.returnCacheDataElseLoad` — этот режим возвращает кэшированные данные, если они есть. В таком сценарии пользователи могут получить не самые последние данные, но загрузка будет быстрее независимо от качества соединения. Кэш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы сократить количество сетевых запросов.</p><p></p><p>Обратите внимание: кэш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.</p><p></p><p>Adapty SDK хранит онбординги локально в двух слоях: регулярно обновляемый кэш, описанный выше, и резервные онбординги. Для более быстрой загрузки мы также используем CDN и отдельный резервный сервер на случай недоступности CDN. Эта система гарантирует, что вы всегда получаете актуальную версию онбордингов, сохраняя надёжность даже при нестабильном интернете.</p> | --- # File: react-native-present-onboardings --- --- title: "Отображение онбордингов в React Native SDK" description: "Узнайте, как отображать онбординги на React Native для повышения конверсии и дохода." --- :::warning **Онбординги устарели в SDK v4 и будут удалены в одном из следующих релизов.** Они больше не получают исправлений и улучшений. Используйте [флоу](react-native-get-pb-paywalls) вместо них: в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это даёт более плавные анимации, единый нативный стиль, быструю загрузку и отсутствие зависимости от WebView. Смотрите [Получение флоу и пейволов](react-native-get-pb-paywalls) и [Отображение флоу и пейволов](react-native-present-paywalls), чтобы начать работу. ::: Если вы настроили онбординг с помощью билдера, вам не нужно беспокоиться о его отрисовке в коде мобильного приложения для показа пользователю. Такой онбординг содержит и то, что должно отображаться, и то, как это должно выглядеть. Перед началом убедитесь, что: 1. Вы установили [Adapty React Native SDK](sdk-installation-reactnative) версии 3.8.0 или новее. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). Adapty React Native SDK предоставляет два способа показа онбордингов: - **React-компонент**: встроенный компонент позволяет интегрировать его в архитектуру приложения и систему навигации. - **Модальное представление** ## React-компонент \{#react-component\} Чтобы встроить онбординг в существующее дерево компонентов, используйте компонент `AdaptyOnboardingView` напрямую в иерархии React Native-компонентов. Встроенный компонент позволяет интегрировать его в архитектуру и навигационную систему приложения. :::note На Android рекомендуем выполнить дополнительную настройку `AdaptyOnboardingView`, чтобы избежать визуального артефакта при рендеринге. См. [Системный UI перекрывает контент онбординга на Android](#system-ui-overlaps-onboarding-content-on-android). ::: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK версии 3.14 и выше" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onAnalytics = useCallback<OnboardingEventHandlers['onAnalytics']>((event, meta) => {}, []); const onClose = useCallback<OnboardingEventHandlers['onClose']>((actionId, meta) => {}, []); const onCustom = useCallback<OnboardingEventHandlers['onCustom']>((actionId, meta) => {}, []); const onPaywall = useCallback<OnboardingEventHandlers['onPaywall']>((actionId, meta) => {}, []); const onStateUpdated = useCallback<OnboardingEventHandlers['onStateUpdated']>((action, meta) => {}, []); const onFinishedLoading = useCallback<OnboardingEventHandlers['onFinishedLoading']>((meta) => {}, []); const onError = useCallback<OnboardingEventHandlers['onError']>((error) => {}, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onAnalytics={onAnalytics} onClose={onClose} onCustom={onCustom} onPaywall={onPaywall} onStateUpdated={onStateUpdated} onFinishedLoading={onFinishedLoading} onError={onError} /> ); } ``` </TabItem> <TabItem value="old" label="Версия SDK < 3.14" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { return ( <AdaptyOnboardingView onboarding={onboarding} style={{ flex: 1 }} eventHandlers={{ onAnalytics(event, meta) { // Handle analytics events }, onClose(actionId, meta) { // Handle close actions }, onCustom(actionId, meta) { // Handle custom actions }, onPaywall(actionId, meta) { // Handle paywall actions }, onStateUpdated(action, meta) { // Handle state updates }, onFinishedLoading(meta) { // Handle when onboarding finishes loading }, onError(error) { // Handle errors }, }} /> ); } ``` </TabItem> </Tabs> ## Модальное отображение \{#modal-presentation\} Чтобы отобразить онбординг как отдельный экран, который пользователь может закрыть, вызовите метод `view.present()` на `view`, созданном методом `createOnboardingView`. Каждый `view` можно использовать только один раз. Если нужно показать онбординг снова, вызовите `createOnboardingView` ещё раз, чтобы создать новый экземпляр `view`. :::warning Повторное использование одного и того же `view` без его пересоздания запрещено. Это приведёт к ошибке `AdaptyUIError.viewAlreadyPresented`. ::: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK версии 3.14 или новее" default> ```typescript showLineNumbers title="React Native (TSX)" const view = await createOnboardingView(onboarding); // Optional: handle onboarding events (close, custom actions, etc) // view.setEventHandlers({ ... }); try { await view.present(); } catch (error) { // handle the error } ``` </TabItem> <TabItem value="old" label="SDK версия < 3.14" default> ```typescript showLineNumbers title="React Native (TSX)" const view = await createOnboardingView(onboarding); view.setEventHandlers(); // handle close press, etc try { await view.present(); } catch (error) { // handle the error } ``` </TabItem> </Tabs> ### Настройка стиля презентации для iOS \{#configure-ios-presentation-style\} Настройте способ отображения онбординга на iOS, передав параметр `iosPresentationStyle` в метод `present()`. Параметр принимает значения `'full_screen'` (по умолчанию) или `'page_sheet'`. ```typescript showLineNumbers try { await view.present({ iosPresentationStyle: 'page_sheet' }); } catch (error) { // handle the error } ``` ## Загрузчик во время онбординга \{#loader-during-onboarding\} При отображении онбординга в React Native вы можете заметить короткую белую вспышку или экран загрузки до того, как онбординг появится. Это происходит, пока нативный вид инициализируется. Справиться с этим можно по-разному — в зависимости от ваших потребностей и рабочего процесса. #### Управление сплэш-экраном с помощью onFinishedLoading \{#control-splash-screen-using-onfinishedloading\} :::note Этот подход доступен только при использовании React-компонента. Для модальной презентации он недоступен. ::: Рекомендуемый подход для React Native — удерживать сплэш-экран или пользовательский оверлей видимым до полной загрузки онбординга, а затем скрывать его вручную. При использовании React-компонента (`AdaptyOnboardingView`) дождитесь события `onFinishedLoading`, прежде чем скрывать сплэш-экран или оверлей: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK версии 3.14 и выше" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const [isLoading, setIsLoading] = useState(true); const onFinishedLoading = useCallback<OnboardingEventHandlers['onFinishedLoading']>((meta) => { // Hide your splash screen or custom overlay here setIsLoading(false); }, []); return ( <> <AdaptyOnboardingView onboarding={onboarding} onFinishedLoading={onFinishedLoading} // ... other callbacks /> {isLoading && <YourCustomLoadingOverlay />} </> ); } ``` </TabItem> <TabItem value="old" label="SDK версии < 3.14"> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const [isLoading, setIsLoading] = useState(true); return ( <> <AdaptyOnboardingView onboarding={onboarding} eventHandlers={{ onFinishedLoading(meta) { // Hide your splash screen or custom overlay here setIsLoading(false); }, // ... other handlers }} /> {isLoading && <YourCustomLoadingOverlay />} </> ); } ``` </TabItem> </Tabs> #### Настройка нативного загрузчика \{#customize-native-loader\} :::important Expo-managed workflow не поддерживает размещение пользовательских нативных макетов (например, `res/layout` на Android). Для приложений на Expo единственным подходящим решением является управление экраном загрузки или использование оверлея React Native. ::: Вы можете заменить нативный загрузчик, используя платформенные макеты на Android и iOS. Если вы используете модальное представление, это ваш единственный вариант. Однако такой подход обычно менее удобен для приложений React Native: - Требует отдельных реализаций для Android и iOS - Несовместим с Expo-managed workflow Определите плейсхолдер для каждой платформы: - **iOS**: Добавьте `AdaptyOnboardingPlaceholderView.xib` в ваш Xcode-проект. [Подробнее](ios-present-onboardings#add-smooth-transitions-between-the-splash-screen-and-onboarding). - **Android**: Создайте `adapty_onboarding_placeholder_view.xml` в `res/layout` и определите там плейсхолдер. [Подробнее](android-present-onboardings#add-smooth-transitions-between-the-splash-screen-and-onboarding). ## Настройка способа открытия ссылок в онбординге \{#customize-how-links-open-in-onboardings\} :::important Настройка способа открытия ссылок в онбординге поддерживается начиная с Adapty SDK v3.15.1. ::: По умолчанию ссылки в онбординге открываются во встроенном браузере. Это обеспечивает бесшовный пользовательский опыт: веб-страницы отображаются прямо внутри приложения, и пользователям не нужно переключаться между приложениями. Если вы хотите открывать ссылки во внешнем браузере, это поведение можно изменить, установив параметр `externalUrlsPresentation` в значение `WebPresentation.BrowserOutApp`: <Tabs groupId="rn-onboarding-views" queryString> <TabItem value="component" label="React component" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onAnalytics = useCallback<OnboardingEventHandlers['onAnalytics']>((event, meta) => {}, []); const onClose = useCallback<OnboardingEventHandlers['onClose']>((actionId, meta) => {}, []); const onCustom = useCallback<OnboardingEventHandlers['onCustom']>((actionId, meta) => {}, []); const onPaywall = useCallback<OnboardingEventHandlers['onPaywall']>((actionId, meta) => {}, []); const onStateUpdated = useCallback<OnboardingEventHandlers['onStateUpdated']>((action, meta) => {}, []); const onFinishedLoading = useCallback<OnboardingEventHandlers['onFinishedLoading']>((meta) => {}, []); const onError = useCallback<OnboardingEventHandlers['onError']>((error) => {}, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} externalUrlsPresentation={WebPresentation.BrowserOutApp} // default – BrowserInApp onAnalytics={onAnalytics} onClose={onClose} onCustom={onCustom} onPaywall={onPaywall} onStateUpdated={onStateUpdated} onFinishedLoading={onFinishedLoading} onError={onError} /> ); } ``` </TabItem> <TabItem value="modal" label="Модальная презентация"> ```typescript showLineNumbers title="React Native (TSX)" const view = await createOnboardingView( onboarding, { externalUrlsPresentation: WebPresentation.BrowserOutApp } // default – BrowserInApp ); try { await view.present(); } catch (error) { // handle the error } ``` </TabItem> </Tabs> ## Устранение неполадок \{#troubleshooting\} ### Системный интерфейс перекрывает контент онбординга на Android \{#system-ui-overlaps-onboarding-content-on-android\} :::note Эта настройка поддерживается только в bare-проектах React Native. Если вы используете Expo managed workflow, добавить этот Android-ресурс напрямую не получится. Чтобы применить эту настройку, нужно создать пользовательский Expo config plugin, который добавит соответствующий Android-ресурс, и зарегистрировать его в `app.config.js`. Это необходимо, потому что Expo управляет нативным Android-проектом за вас. ::: При использовании `AdaptyOnboardingView` на Android системные элементы интерфейса — строка состояния и панель навигации — могут отображаться поверх контента онбординга. Чтобы этого избежать, добавьте следующий булев ресурс в ваше приложение: 1. Перейдите в `android/app/src/main/res/values`. Если файла `bools.xml` нет — создайте его. 2. Добавьте следующий ресурс: ```xml <resources> <bool name="adapty_onboarding_enable_safe_area_paddings">false</bool> </resources> ``` Обратите внимание, что это изменение применяется глобально ко всем онбордингам в вашем приложении. ## Дальнейшие шаги \{#next-steps\} После отображения онбординга вам нужно будет [обработать действия пользователя и события](react-native-handling-onboarding-events). Узнайте, как обрабатывать события онбординга, чтобы реагировать на действия пользователей и отслеживать аналитику. --- # File: react-native-handling-onboarding-events --- --- title: "Обработка событий онбординга в React Native SDK" description: "Обработка событий, связанных с онбордингом, в React Native с использованием Adapty." --- :::warning **Онбординги объявлены устаревшими в SDK v4 и будут удалены в одном из будущих релизов.** Они больше не получают исправлений и улучшений. Используйте [флоу](react-native-get-pb-paywalls): в отличие от онбордингов, которые работают внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает более плавные анимации, единый нативный внешний вид, более быструю загрузку и отсутствие зависимости от WebView. Подробнее в разделах [Получение флоу и пейволов](react-native-get-pb-paywalls) и [Отображение флоу и пейволов](react-native-present-paywalls). ::: Перед началом убедитесь, что: Онбординги, настроенные с помощью билдера, генерируют события, на которые может реагировать ваше приложение. Способ обработки этих событий зависит от выбранного подхода к отображению: - **Модальное представление**: требует настройки обработчиков событий, которые обрабатывают события для всех представлений онбординга - **React-компонент**: обрабатывает события через встроенные параметры колбэков непосредственно в виджете 1. Вы установили [Adapty React Native SDK](sdk-installation-reactnative) версии 3.8.0 или выше. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). Чтобы управлять процессами на экране онбординга в вашем мобильном приложении и отслеживать их, реализуйте обработчики событий: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK версии 3.14 или выше" default> <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> Для React-компонента события обрабатываются через отдельные пропы-обработчики событий в компоненте `AdaptyOnboardingView`: ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onAnalytics = useCallback<OnboardingEventHandlers['onAnalytics']>((event, meta) => {}, []); const onClose = useCallback<OnboardingEventHandlers['onClose']>((actionId, meta) => {}, []); const onCustom = useCallback<OnboardingEventHandlers['onCustom']>((actionId, meta) => {}, []); const onPaywall = useCallback<OnboardingEventHandlers['onPaywall']>((actionId, meta) => {}, []); const onStateUpdated = useCallback<OnboardingEventHandlers['onStateUpdated']>((action, meta) => {}, []); const onFinishedLoading = useCallback<OnboardingEventHandlers['onFinishedLoading']>((meta) => {}, []); const onError = useCallback<OnboardingEventHandlers['onError']>((error) => {}, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onAnalytics={onAnalytics} onClose={onClose} onCustom={onCustom} onPaywall={onPaywall} onStateUpdated={onStateUpdated} onFinishedLoading={onFinishedLoading} onError={onError} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> Для модального представления реализуйте метод обработчиков событий. :::important Повторный вызов `setEventHandlers` переопределит ранее установленные обработчики, заменив как стандартные, так и пользовательские обработчики для указанных событий. ::: ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.setEventHandlers({ onAnalytics(event, meta) { // Отслеживание аналитических событий }, onClose(actionId, meta) { // Обработка действия закрытия view.dismiss(); return true; }, onCustom(actionId, meta) { // Обработка пользовательских действий }, onPaywall(actionId, meta) { // Обработка действий пейвола }, onStateUpdated(action, meta) { // Обработка обновлений пользовательского ввода }, onFinishedLoading(meta) { // Онбординг завершил загрузку }, onError(error) { // Обработка ошибок загрузки }, }); try { await view.present(); } catch (error) { // обработка ошибки } ``` </TabItem> </Tabs> </TabItem> <TabItem value="old" label="SDK версия < 3.14"> Для SDK версии < 3.14 поддерживается только модальное представление: ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.registerEventHandlers({ onAnalytics(event, meta) { // Отслеживание аналитических событий }, onClose(actionId, meta) { // Обработка действия закрытия view.dismiss(); return true; }, onCustom(actionId, meta) { // Обработка пользовательских действий }, onPaywall(actionId, meta) { // Обработка действий с пейволом }, onStateUpdated(action, meta) { // Обработка обновлений пользовательского ввода }, onFinishedLoading(meta) { // Онбординг завершил загрузку }, onError(error) { // Обработка ошибок загрузки }, }); try { await view.present(); } catch (error) { // обработка ошибки } ``` </TabItem> </Tabs> ## Типы событий \{#event-types\} В следующих разделах описаны различные типы событий, которые можно обрабатывать вне зависимости от используемого подхода к отображению. ### Обработка пользовательских действий \{#handle-custom-actions\} В конструкторе можно добавить действие **custom** к кнопке и задать ему идентификатор. <img src="/assets/shared/img/ios-events-1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Затем этот ID можно использовать в коде и обрабатывать его как пользовательское действие. Например, если пользователь нажимает кастомную кнопку — **Login** или **Allow notifications** — обработчик события сработает с параметром `actionId`, соответствующим **Action ID** из билдера. Вы можете задавать собственные ID, например `"allowNotifications"`. <Tabs groupId="version" queryString> <TabItem value="new" label="SDK версии 3.14 и выше" default> <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onCustom = useCallback<OnboardingEventHandlers['onCustom']>((actionId, meta) => { switch (actionId) { case 'login': login(); break; case 'allow_notifications': allowNotifications(); break; } }, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onCustom={onCustom} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.setEventHandlers({ onCustom(actionId, meta) { switch (actionId) { case 'login': login(); break; case 'allow_notifications': allowNotifications(); break; } }, }); ``` </TabItem> </Tabs> </TabItem> <TabItem value="old" label="Версия SDK < 3.14"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.registerEventHandlers({ onCustom(actionId, meta) { switch (actionId) { case 'login': login(); break; case 'allow_notifications': allowNotifications(); break; } }, }); ``` </TabItem> </Tabs> <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "actionId": "allow_notifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ``` </Details> ### Завершение загрузки онбординга \{#finishing-loading-onboarding\} Когда онбординг завершает загрузку, срабатывает следующее событие: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK версии 3.14 или выше" default> <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onFinishedLoading = useCallback<OnboardingEventHandlers['onFinishedLoading']>((meta) => { console.log('Onboarding loaded:', meta.onboardingId); }, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onFinishedLoading={onFinishedLoading} /> ); } ``` </TabItem> <TabItem value="standalone" label="Модальное представление"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.setEventHandlers({ onFinishedLoading(meta) { console.log('Onboarding loaded:', meta.onboardingId); }, }); ``` </TabItem> </Tabs> </TabItem> <TabItem value="old" label="SDK версия < 3.14"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.registerEventHandlers({ onFinishedLoading(meta) { console.log('Onboarding loaded:', meta.onboardingId); }, }); ``` </TabItem> </Tabs> <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ``` </Details> ### Закрытие онбординга \{#closing-onboarding\} Онбординг считается закрытым, когда пользователь нажимает кнопку с назначенным действием **Close**. <img src="/assets/shared/img/ios-events-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important Обратите внимание, что вам нужно самостоятельно обработать ситуацию, когда пользователь закрывает онбординг. Например, необходимо прекратить его отображение. ::: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK версии 3.14 и выше" default> <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React-компонент" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding, navigation }) { const onClose = useCallback<OnboardingEventHandlers['onClose']>((actionId, meta) => { navigation.goBack(); }, [navigation]); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onClose={onClose} /> ); } ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.setEventHandlers({ onClose(actionId, meta) { await view.dismiss(); return true; }, }); ``` </TabItem> </Tabs> </TabItem> <TabItem value="old" label="SDK version < 3.14"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.registerEventHandlers({ onClose(actionId, meta) { await view.dismiss(); return true; }, }); ``` </TabItem> </Tabs> <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> ### Открытие пейвола \{#opening-a-paywall\} :::tip Обрабатывайте это событие, чтобы открыть пейвол внутри онбординга. Если вы хотите открыть пейвол после его закрытия, есть более простой способ — обработайте действие закрытия и откройте пейвол без привязки к данным события. ::: Наиболее удобный подход при работе с пейволами в онбординге — сделать action ID равным placement ID пейвола. <Tabs groupId="version" queryString> <TabItem value="new" label="SDK версия 3.14 или выше" default> <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React component" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onPaywall = useCallback<OnboardingEventHandlers['onPaywall']>((actionId, meta) => { openPaywall(actionId); }, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onPaywall={onPaywall} /> ); } const openPaywall = async (placementId) => { // Implement your paywall opening logic here }; ``` </TabItem> <TabItem value="standalone" label="Modal presentation"> Обратите внимание, что в iOS одновременно на экране может отображаться только одно представление (пейвол или онбординг). Если вы показываете пейвол поверх онбординга, вы не можете программно управлять онбордингом в фоне. Попытка закрыть онбординг закроет пейвол вместо него, оставив онбординг видимым. Чтобы избежать этого, всегда закрывайте представление онбординга перед показом пейвола. ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.setEventHandlers({ onPaywall(actionId, meta) { view.dismiss().then(() => { openPaywall(actionId); }); }, }); const openPaywall = async (placementId) => { // Implement your paywall opening logic here }; ``` </TabItem> </Tabs> </TabItem> <TabItem value="old" label="SDK версия < 3.14"> Обратите внимание: в iOS одновременно на экране может отображаться только одно представление — пейвол или онбординг. Если вы показываете пейвол поверх онбординга, программно управлять онбордингом в фоне не получится. Попытка закрыть онбординг закроет пейвол, и онбординг останется видимым. Чтобы избежать этого, всегда закрывайте онбординг перед показом пейвола. ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.registerEventHandlers({ onPaywall(actionId, meta) { view.dismiss().then(() => { openPaywall(actionId); }); }, }); const openPaywall = async (placementId) => { // Реализуйте здесь логику открытия пейвола }; ``` </TabItem> </Tabs> <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ``` </Details> ### Отслеживание навигации \{#tracking-navigation\} Вы получаете аналитическое событие при различных событиях, связанных с навигацией во время онбординга: <Tabs groupId="version" queryString> <TabItem value="new" label="SDK версии 3.14 или новее" default> <Tabs groupId="presentation-method" queryString> <TabItem value="platform" label="React компонент" default> ```typescript showLineNumbers title="React Native (TSX)" function MyOnboarding({ onboarding }) { const onAnalytics = useCallback<OnboardingEventHandlers['onAnalytics']>((event, meta) => { trackEvent(event.name, meta.onboardingId); }, []); return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} onAnalytics={onAnalytics} /> ); } ``` </TabItem> <TabItem value="standalone" label="Модальное представление"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.setEventHandlers({ onAnalytics(event, meta) { trackEvent(event.name, meta.onboardingId); }, }); ``` </TabItem> </Tabs> </TabItem> <TabItem value="old" label="SDK версия < 3.14"> ```javascript showLineNumbers title="React Native" const view = await createOnboardingView(onboarding); const unsubscribe = view.registerEventHandlers({ onAnalytics(event, meta) { trackEvent(event.name, meta.onboardingId); }, }); ``` </TabItem> </Tabs> Объект `event` может быть одного из следующих типов: | Тип | Описание | |------------|-------------| | `onboardingStarted` | Когда онбординг загружен | | `screenPresented` | Когда отображается любой экран | | `screenCompleted` | Когда экран завершён. Включает необязательный `elementId` (идентификатор завершённого элемента) и необязательный `reply` (ответ пользователя). Срабатывает, когда пользователь выполняет любое действие для выхода с экрана. | | `secondScreenPresented` | Когда отображается второй экран | | `userEmailCollected` | Срабатывает, когда email пользователя собирается через поле ввода | | `onboardingCompleted` | Срабатывает, когда пользователь достигает экрана с идентификатором `final`. Если вам нужно это событие, [присвойте идентификатор `final` последнему экрану](design-onboarding). | | `unknown` | Для любого нераспознанного типа события. Включает `name` (название неизвестного события) и `meta` (дополнительные метаданные) | Каждое событие содержит информацию `meta` со следующими полями: | Поле | Описание | |------------|-------------| | `onboardingId` | Уникальный идентификатор онбординга | | `screenClientId` | Идентификатор текущего экрана | | `screenIndex` | Позиция текущего экрана в потоке | | `screensTotal` | Общее количество экранов в потоке | <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```javascript // onboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // screenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // screenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // secondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // userEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // onboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> --- # File: react-native-onboarding-input --- --- title: "Обработка данных онбординга в React Native SDK" description: "Сохраняйте и используйте данные онбординга в вашем React Native приложении с Adapty SDK." --- :::warning **Онбординги устарели в SDK v4 и будут удалены в одном из следующих релизов.** Они больше не получают исправлений и улучшений. Используйте [флоу](react-native-get-pb-paywalls): в отличие от онбордингов, работающих внутри WebView, флоу рендерятся нативно на устройстве — это обеспечивает более плавные анимации, единый нативный внешний вид, быструю загрузку и отсутствие зависимости от WebView. Читайте [Получение флоу и пейволов](react-native-get-pb-paywalls) и [Отображение флоу и пейволов](react-native-present-paywalls), чтобы начать работу. ::: Когда пользователи отвечают на вопрос викторины или вводят данные в поле ввода, вызывается метод `onStateUpdatedAction`. Вы можете сохранять или обрабатывать тип поля в своём коде. Например: ```javascript // Full-screen presentation const unsubscribe = view.setEventHandlers({ onStateUpdated(action, meta) { // Process data }, }); // Embedded widget <AdaptyOnboardingView onboarding={onboarding} eventHandlers={{ onStateUpdated(action, meta) { // Process data }, }} /> ``` Смотрите формат действия [здесь](https://react-native.adapty.io/types/onboardingstateupdatedaction). <Details> <summary>Примеры сохранённых данных (формат может отличаться в зависимости от вашей реализации)</summary> ```javascript // Example of a saved select action { "elementId": "preference_selector", "elementType": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" }, "meta": { "onboardingId": "onboarding_123", "screenClientId": "preferences_screen", "screenIndex": 1, "totalScreens": 3 } } // Example of a saved multi-select action { "elementId": "interests_selector", "elementType": "multi_select", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ], "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "totalScreens": 3 } } // Example of a saved input action { "elementId": "name_input", "elementType": "input", "value": { "type": "text", "value": "John Doe" }, "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "totalScreens": 3 } } // Example of a saved date picker action { "elementId": "birthday_picker", "elementType": "date_picker", "value": { "day": 15, "month": 6, "year": 1990 }, "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "totalScreens": 3 } } ``` </Details> ## Сценарии использования \{#use-cases\} ### Обогащение профилей пользователей данными \{#enrich-user-profiles-with-data\} Если вы хотите сразу связать введённые данные с профилем пользователя и не спрашивать одно и то же дважды, нужно [обновить профиль пользователя](react-native-setting-user-attributes) с этими данными при обработке действия. Например, вы просите пользователей ввести имя в текстовое поле с ID `name` и хотите установить значение этого поля как имя пользователя. Также вы просите ввести email в поле `email`. В коде приложения это может выглядеть так: ```javascript showLineNumbers // Full-screen presentation const unsubscribe = view.setEventHandlers({ onStateUpdated(action, meta) { // Store user preferences or responses if (action.elementType === 'input') { const profileParams = {}; // Map elementId to appropriate profile field switch (action.elementId) { case 'name': if (action.value.type === 'text') { profileParams.firstName = action.value.value; } break; case 'email': if (action.value.type === 'email') { profileParams.email = action.value.value; } break; } // Update profile if we have data to update if (Object.keys(profileParams).length > 0) { adapty.updateProfile(profileParams).catch(error => { // handle the error }); } } }, }); // Embedded widget <AdaptyOnboardingView onboarding={onboarding} eventHandlers={{ onStateUpdated(action, meta) { // Store user preferences or responses if (action.elementType === 'input') { const profileParams = {}; // Map elementId to appropriate profile field switch (action.elementId) { case 'name': if (action.value.type === 'text') { profileParams.firstName = action.value.value; } break; case 'email': if (action.value.type === 'email') { profileParams.email = action.value.value; } break; } // Update profile if we have data to update if (Object.keys(profileParams).length > 0) { adapty.updateProfile(profileParams).catch(error => { // handle the error }); } } }, }} /> ``` ### Настройка пейволов на основе ответов \{#customize-paywalls-based-on-answers\} Используя квизы в онбординге, вы можете настраивать пейволы, которые показываются пользователям после его прохождения. Например, можно спросить пользователей об их опыте в спорте и показывать разные CTA и продукты различным группам пользователей. 1. [Добавьте квиз](onboarding-quizzes) в конструкторе онбординга и назначьте значимые ID его вариантам ответов. 2. Обрабатывайте ответы квиза по их ID и [задавайте пользовательские атрибуты](react-native-setting-user-attributes) для пользователей. ```javascript showLineNumbers // Full-screen presentation const unsubscribe = view.setEventHandlers({ onStateUpdated(action, meta) { // Handle quiz responses and set custom attributes if (action.elementType === 'select') { const profileParams = {}; // Map quiz responses to custom attributes switch (action.elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) profileParams.codableCustomAttributes = { experience: action.value.value }; break; } // Update profile if we have data to update if (Object.keys(profileParams).length > 0) { adapty.updateProfile(profileParams).catch(error => { // handle the error }); } } }, }); // Embedded widget <AdaptyOnboardingView onboarding={onboarding} eventHandlers={{ onStateUpdated(action, meta) { // Handle quiz responses and set custom attributes if (action.elementType === 'select') { const profileParams = {}; // Map quiz responses to custom attributes switch (action.elementId) { case 'experience': // Set custom attribute 'experience' with the selected value (beginner, amateur, pro) profileParams.codableCustomAttributes = { experience: action.value.value }; break; } // Update profile if we have data to update if (Object.keys(profileParams).length > 0) { adapty.updateProfile(profileParams).catch(error => { // handle the error }); } } }, }} /> ``` 3. [Создайте сегменты](segments) для каждого значения кастомного атрибута. 4. Создайте [плейсмент](placements) и добавьте [аудитории](audience) для каждого созданного сегмента. 5. [Отобразите пейвол](react-native-paywalls) для плейсмента в коде вашего приложения. Если в вашем онбординге есть кнопка, открывающая пейвол, реализуйте код пейвола как [реакцию на действие этой кнопки](react-native-handling-onboarding-events#opening-a-paywall). --- # File: react-native-sdk-call-order --- --- title: "Порядок вызовов в React Native SDK" description: "Избегайте потери доступа к премиуму, проблем с атрибуцией и периодических ошибок #2002, вызывая методы Adapty SDK в правильном порядке." --- `adapty.activate()` должен завершиться до вызова любого другого метода Adapty SDK. Пока он не выполнен, у SDK нет состояния. Любой вызов, сделанный до или параллельно с `activate()`, завершится ошибкой [`#2002 notActivated`](react-native-handle-errors#custom-network-codes). Если ваше приложение аутентифицирует пользователей и вы получаете customer user ID после запуска, вызовите `adapty.identify()` в этот момент. Не вызывайте методы, требующие действий пользователя, пока `identify` не завершится. Вызовы, выполняемые параллельно с ним, либо завершаются ошибкой [`#3006 profileWasChanged`](react-native-handle-errors#custom-network-codes), либо применяются к анонимному профилю, созданному при активации. В этом случае атрибуция, MMP ID вроде `appsflyer_id` и привязка установки не всегда переносятся на идентифицированный профиль. Если ваше приложение не аутентифицирует пользователей, пропустите `identify` и продолжайте работать с анонимным профилем. MMP и аналитические SDK (AppsFlyer, Adjust, Branch, PostHog) подчиняются тому же правилу. Сначала инициализируйте их и дождитесь колбэков с UID, и только потом вызывайте `adapty.activate`. Иначе MMP ID попадёт в краткий анонимный профиль и не всегда переносится в идентифицированный. Подробнее об особенностях AppsFlyer — в разделе [AppsFlyer](appsflyer). ## Правильный порядок \{#the-correct-order\} Ваш путь зависит от двух вещей: когда вы узнаёте customer user ID и используете ли вы MMP или аналитический SDK. - **Шаги 2 и 5**: Обязательны для каждого приложения. Активируйте SDK, затем вызывайте его методы. - **Шаги 1 и 3**: Нужны только при интеграции MMP или аналитического SDK (AppsFlyer, Adjust, Branch, PostHog). - **Шаг 4**: Нужен только если ваше приложение аутентифицирует пользователей и получает customer user ID после запуска. Если идентификатор пользователя известен при запуске приложения, передайте его напрямую в `activate()` (шаг 2a). В этом случае анонимный профиль не создаётся, поэтому шаг 4 не нужен. | Шаг | Вызов | Когда | Примечания | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | Инициализируйте MMP или аналитический SDK (AppsFlyer, Adjust, PostHog, Branch) | При запуске приложения, первым делом | Дождитесь колбэка с UID от MMP, например `getAppsFlyerUID`. | | 2a | `adapty.activate('YOUR_PUBLIC_SDK_KEY', { customerUserId: 'YOUR_USER_ID' })` | При запуске приложения, после шага 1, если у вас есть customer user ID | Рекомендуется. Анонимный профиль не создаётся никогда. | | 2b | `adapty.activate('YOUR_PUBLIC_SDK_KEY')` без `customerUserId` | При запуске приложения, после шага 1, если у вас нет customer user ID (или вы его не собираете) | Adapty создаёт анонимный профиль. | | 3 | `adapty.updateAttribution(data, source, networkUserId)` для каждого MMP | После шага 2, до любых вызовов, связанных с действиями пользователя | Обязательно, чтобы идентификаторы MMP попали в нужный профиль. | | 4 | `await adapty.identify('YOUR_USER_ID')` | После шага 3 (или шага 2, если MMP нет), до шага 5 — только на пути 2b с аутентификацией | Всегда используйте `await`. Параллельные вызовы во время `identify` приводят к ошибке `#3006 profileWasChanged`. | | 5 | `getPaywall`, `getPaywallProducts`, `restorePurchases`, `makePurchase`, `updateAttribution`, `updateProfile` | После шага 4, если вы вызываете `identify`; иначе — после шага 3 (или шага 2, если MMP нет) | Этим вызовам необходим стабильный профиль. | :::important Пропуск этих шагов приводит к потере премиум-доступа у вернувшихся пользователей, отсутствию `appsflyer_id` в профилях и показу пейволов для неправильной аудитории. ::: ## Установки через web2app и веб-воронки \{#web2app-and-web-funnel-installs\} Если пользователь совершает покупку через веб-чекаут (Stripe, Paddle) и после этого устанавливает нативное приложение, первый вызов `activate()` на устройстве создаёт новый анонимный профиль. Этот профиль не связан с веб-профилем. Если вы можете определить customer user ID до запуска приложения (из вашего флоу авторизации или install referrer), передайте его напрямую в `activate()`. В противном случае веб-покупка останется невидимой на устройстве, пока вы не вызовете `identify('YOUR_USER_ID')`, а затем `restorePurchases`. О том, какие метаданные нужно передавать при каждом веб-чекауте, читайте в статьях: - [Stripe](stripe) - [Paddle](paddle) --- # File: react-native-optimize-paywall-fetching --- --- title: "Оптимизация загрузки пейволов в React Native SDK" description: "Надёжная загрузка пейволов Adapty: время, кэширование и резервные паттерны для React Native." --- Надёжная загрузка пейвола в React Native решает три задачи: быстрый рендеринг, возврат пейвола с учётом аудитории и корректный фолбэк при медленном интернете. Правила ниже охватывают подходы к тайммингу, кэшированию и резервным сценариям. :::tip Предполагается, что `adapty.activate()` и `adapty.identify()` уже выполнились. См. [Порядок вызовов в React Native SDK](react-native-sdk-call-order). ::: ## Правила и подводные камни \{#rules-and-pitfalls\} | Делайте так | Не делайте так | Почему | |---|---|---| | Загружайте только тот плейсмент, который собираетесь показать. | Предзагружайте все плейсменты одновременно при запуске. | Массовая предзагрузка блокирует JS-поток и приводит к чёрному экрану во время всплеска. | | Вызывайте `getPaywall` после того, как атрибуция успела разрешиться — например, через 1–2 секунды после `activate` или после срабатывания `onProfileUpdate`. | Вызывайте `getPaywall` при монтировании корневого компонента. | Атрибуция ещё не применилась. Пейвол разрешается для аудитории по умолчанию и незаметно обходит сегменты и персонализацию ASA. | | Задайте `loadTimeoutMs` и настройте [резервный пейвол](fallback-paywalls) для каждого плейсмента. | Ждите ответа `getPaywall` бесконечно. | Без таймаута пользователи с плохим соединением видят пустой экран до тех пор, пока сеть не ответит — или закрывают приложение. | Подробнее о параметрах `fetchPolicy` и `loadTimeoutMs` — в разделе [Получение пейволов и продуктов](fetch-paywalls-and-products-react-native), а о выборе подходящего плейсмента — в разделе [Плейсменты](placements). ## Настройка для нестабильного соединения \{#tune-for-poor-connectivity\} Для рынков с постоянно плохим качеством связи (сельская местность, транспорт, регионы с проблемами маршрутизации): - Устанавливайте `fetchPolicy: .returnCacheDataElseLoad` для каждого запроса, кроме самого первого. - Настройте [резервный пейвол](fallback-paywalls) для каждого плейсмента в дашборде Adapty. - Задайте `loadTimeoutMs` равным 3–5 секундам и используйте резервный пейвол при срабатывании таймаута. - Не блокируйте отображение пейвола на `getProfile()`. Вызывайте `getPaywall` независимо, чтобы медленный запрос профиля не задерживал интерфейс. --- # File: react-native-show-aa-targeted-paywall --- --- title: "Показ пейвола с таргетингом Apple Ads при первом запуске в React Native SDK" description: "Показывайте пейвол сразу и обновляйте его для пользователей Apple Ads после применения атрибуции в React Native, используя AdaptyProfile.appliedAttributionSources." --- Атрибуция Apple Ads (AA) приходит асинхронно после вызова `adapty.activate()`. При первом запуске она обычно ещё не поступила, поэтому `getPaywall` разрешается по аудитории по умолчанию, и пользователи Apple Ads не видят пейвол, настроенный под AA-сегмент. Вместо того чтобы откладывать показ пейвола до получения атрибуции, покажите его сразу, а затем обновите, как только атрибуция AA будет применена — тогда пользователи Apple Ads увидят целевой вариант, а все остальные не будут ждать. `AdaptyProfile.appliedAttributionSources` сообщает, когда атрибуция AA применена. ## Перед началом работы \{#before-you-start\} Вам понадобится: - Adapty React Native SDK **3.17.1** или новее. - Apple Ads, настроенные для приложения в Adapty. См. [Apple Ads](apple-search-ads). ## Как это работает \{#how-it-works\} После вызова `adapty.activate()` SDK в фоновом режиме запрашивает атрибуцию Apple Ads у Apple и передаёт результат в бэкенд Adapty. Когда AA становится активным источником атрибуции для профиля, SDK доставляет обновлённый `AdaptyProfile` в ваш обработчик `onLatestProfileLoad`, где в массиве `appliedAttributionSources` появляется `'apple_search_ads'`. Это позволяет загружать пейвол в два этапа: 1. Вызовите `getPaywall` сразу. Поскольку атрибуция ещё не применена, Adapty обрабатывает запрос по аудитории по умолчанию, и пользователь сразу видит пейвол. 2. Когда появится `'apple_search_ads'`, вызовите `getPaywall` ещё раз. Теперь Adapty обработает запрос по аудитории Apple Ads и вернёт целевой пейвол, который заменит первый. `appliedAttributionSources` может быть пустым или отсутствовать. Это означает одно из двух: - атрибуция Apple Ads для этого профиля ещё не обработана, или - атрибуция вообще не поступала. При любом сценарии шаг 1 безопасен — Adapty обрабатывает запрос по той аудитории, которая соответствует текущему состоянию профиля, как правило, это аудитория по умолчанию. Шаг 2 выполняется только после того, как в данных появляется `'apple_search_ads'`. :::important При каждом последующем запуске кешированный профиль уже содержит `'apple_search_ads'` в `appliedAttributionSources`, поэтому первый же вызов `getPaywall` возвращает пейвол, сегментированный по Apple Ads, — никакого повторного запроса или видимых изменений не происходит. Двухшаговый флоу актуален только при первом запуске, пока атрибуция ещё не получена. ::: ## Реализация \{#implementation\} Покажите пейвол сразу, затем ждите события `'apple_search_ads'` и обновляйте пейвол при его получении. 1. **Активируйте SDK.** См. [Установка и настройка React Native SDK](sdk-installation-reactnative). 2. **Загрузите и покажите пейвол** с помощью `getPaywall` как обычно — не блокируйте выполнение в ожидании атрибуции. 3. **Подпишитесь на обновления профиля** через `adapty.addEventListener('onLatestProfileLoad', …)` и отслеживайте появление `'apple_search_ads'`. Когда оно появится, снова запросите пейвол и покажите обновлённый. Если вы ещё не настроили слушатель, см. [Отслеживание обновлений подписки](react-native-check-subscription-status#listen-to-subscription-updates): ```typescript const subscription = adapty.addEventListener('onLatestProfileLoad', async profile => { if (!profile.appliedAttributionSources?.includes('apple_search_ads')) return; const targeted = await adapty.getPaywall(placementId); // present the targeted paywall in place of the first one }); // Call subscription.remove() after the upgrade, or after a timeout (see below). ``` 4. **Остановите прослушивание по таймауту.** Большинство пользователей никогда не получают атрибуцию Apple Ads, поэтому вместо того чтобы держать слушателя открытым на протяжении всей сессии, удалите его через некоторое время. Настройте [резервный пейвол](react-native-use-fallback-paywalls) для плейсмента, чтобы пользователь всегда что-то видел в случае неудачного запроса. ## Полный пример \{#complete-example\} `onAppleAdsAttribution` завершается успешно, когда атрибуция Apple Ads применена, или отклоняется по истечении `timeoutMs`. В примере ниже пейвол загружается сразу, а затем перезапрашивается, когда приходит атрибуция — пользователи Apple Ads получают целевой пейвол, а если атрибуция так и не придёт, остаётся первоначальный пейвол: ```typescript const APPLE_ADS_SOURCE = 'apple_search_ads'; const placementId = 'YOUR_PLACEMENT_ID'; function hasAppleAdsAttribution(profile: AdaptyProfile): boolean { return profile.appliedAttributionSources?.includes(APPLE_ADS_SOURCE) ?? false; } /** * Resolves once Apple Ads attribution is applied to the profile. * Rejects with a timeout error if attribution never arrives within `timeoutMs`. * Call after `adapty.activate()`. */ export function onAppleAdsAttribution(timeoutMs: number): Promise<void> { return new Promise((resolve, reject) => { let timer: ReturnType<typeof setTimeout> | undefined; let subscription: { remove: () => void } | undefined; const stop = () => { clearTimeout(timer); subscription?.remove(); }; subscription = adapty.addEventListener('onLatestProfileLoad', profile => { if (!hasAppleAdsAttribution(profile)) return; stop(); resolve(); }); timer = setTimeout(() => { stop(); reject(new Error(`Apple Ads attribution timed out after ${timeoutMs}ms`)); }, timeoutMs); }); } let paywall = await adapty.getPaywall(placementId); onAppleAdsAttribution(30_000) .then(() => adapty.getPaywall(placementId)) .then(updated => { paywall = updated; }) .catch(() => { console.log('Apple Ads attribution or loading failed'); }); ``` При первом запуске пользователи Apple Ads на мгновение видят пейвол по умолчанию, прежде чем он заменяется. Если вы показываете пейволы с помощью Paywall Builder, решите, допустимо ли повторное отображение, или применяйте обновление только до того, как пейвол был показан. Настройте `timeoutMs` в соответствии с тем, как долго вы готовы ждать атрибуцию — она, как правило, приходит в течение нескольких секунд после запуска. Если ваше приложение уже слушает `onLatestProfileLoad` для других целей (например, [проверки статуса подписки](react-native-check-subscription-status#listen-to-subscription-updates)), менять ничего не нужно. `adapty.addEventListener` поддерживает несколько независимых слушателей, так что этот добавляется сам по себе, не затрагивая остальные. --- # File: react-native-test --- --- title: "Тестирование и релиз в React Native SDK" description: "Узнайте, как тестировать и выпускать приложение на React Native с помощью Adapty SDK." --- Если вы уже интегрировали Adapty SDK в своё приложение на React Native, следующий шаг — убедиться, что всё настроено правильно и покупки работают корректно на платформах iOS и Android. Для этого нужно протестировать как интеграцию SDK, так и сам процесс покупок в песочнице Apple и тестовой среде Google Play. ## Тестирование приложения \{#test-your-app\} Подробные инструкции по тестированию встроенных покупок описаны в платформенных гайдах: [гайд по тестированию на iOS](test-purchases-in-sandbox) и [гайд по тестированию на Android](testing-on-android). ## Подготовка к релизу \{#prepare-for-release\} Перед отправкой приложения в стор ознакомьтесь с [чеклистом для релиза](release-checklist) и убедитесь, что: - Подключение к стору и серверные уведомления настроены - Покупки проходят и передаются в Adapty - Уровни доступа корректно открываются и восстанавливаются - Выполнены требования к конфиденциальности и прохождению ревью --- # File: InvalidProductIdentifiers-react-native --- --- title: "Исправление ошибки Code-1000 noProductIDsFound в React Native SDK" description: "Устраните ошибки недопустимых идентификаторов продуктов при управлении подписками в Adapty." --- Ошибка с кодом 1000, `noProductIDsFound`, означает, что ни один из продуктов, запрошенных на пейволе, не доступен для покупки в App Store, хотя они там и перечислены. Иногда вместе с этой ошибкой может появляться предупреждение `InvalidProductIdentifiers`. Если предупреждение появляется без ошибки — можно смело его игнорировать. Если вы столкнулись с ошибкой `noProductIDsFound`, выполните следующие шаги для её устранения: ## Шаг 1. Проверьте Bundle ID \{#step-2-check-bundle-id\} 1. Откройте [App Store Connect](https://appstoreconnect.apple.com/apps). Выберите своё приложение и перейдите в раздел **General** → **App Information**. 2. Скопируйте **Bundle ID** в подразделе **General Information**. <Zoom> <img src="/docs/img/afd5012-bundle_id_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. Откройте вкладку [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) в верхнем меню Adapty и вставьте скопированное значение в поле **Bundle ID**. <Zoom> <img src="/docs/img/2d64163-bundle_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 4. Вернитесь на страницу **App information** в App Store Connect и скопируйте **Apple ID**. 5. На странице [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) в дашборде Adapty вставьте этот ID в поле **Apple app ID**. ## Шаг 2. Проверьте продукты \{#step-3-check-products\} 1. Перейдите в **App Store Connect** и откройте раздел [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions) в меню слева. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите на название группы подписок. Вы увидите свои продукты в разделе **Subscriptions**. 3. Убедитесь, что тестируемый продукт отмечен как **Ready to Submit**. <img src="/assets/shared/img/ready-to-submit.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Сравните ID продукта из таблицы с тем, что указан во вкладке [**Products**](https://app.adapty.io/products) дашборда Adapty. Если ID не совпадают, скопируйте ID продукта из таблицы и [создайте продукт](create-product) с этим ID в дашборде Adapty. <img src="/assets/shared/img/product-id-copy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 3. Проверьте доступность продукта \{#step-4-check-product-availability\} 1. Вернитесь в **App Store Connect** и откройте раздел **Subscriptions**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите на название группы подписок, чтобы просмотреть ваши продукты. 3. Выберите продукт, который хотите протестировать. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Прокрутите до раздела **Availability** и убедитесь, что в нём перечислены все необходимые страны и регионы. <img src="/assets/shared/img/product-availability.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 4. Проверьте цены продуктов \{#step-5-check-product-prices\} 1. Снова откройте раздел **Monetization** → **Subscriptions** в **App Store Connect**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите на название группы подписок. 3. Выберите продукт, который хотите протестировать. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Прокрутите вниз до раздела **Subscription Pricing** и раскройте секцию **Current Pricing for New Subscribers**. <img src="/assets/shared/img/check-prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Убедитесь, что все необходимые цены указаны. <img src="/assets/shared/img/product-pricing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 5. Убедитесь, что статус приложения, банковский счёт и налоговые формы активны \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\} 1. На главной странице [**App Store Connect**](https://appstoreconnect.apple.com/) нажмите **Business**. <img src="/assets/shared/img/business.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Выберите название своей компании. 3. Прокрутите вниз и убедитесь, что ваши **Paid Apps Agreement**, **Bank Account** и **Tax forms** отображаются как **Active**. Выполнив эти шаги, вы сможете устранить предупреждение `InvalidProductIdentifiers` и запустить продукты в сторе. ## Шаг 6. Пересоздайте продукт, если он завис \{#step-6-recreate-the-product-if-its-stuck\} Шаги 1–5 могут пройти успешно — статус `Approved`, совпадающий Bundle ID, рабочий API-ключ — но SDK всё равно возвращает `1000 noProductIDsFound`. В таком случае продукт, возможно, завис в реестре Apple. Реестр продуктов Apple иногда переходит в состояние, при котором продукт существует в интерфейсе App Store Connect, но недоступен через путь поиска StoreKit. Удалите продукт в App Store Connect и пересоздайте его с тем же идентификатором. После пересоздания подождите до 24 часов, пока изменения распространятся. --- # File: cantMakePayments-react-native --- --- title: "Исправление ошибки Code-1003 cantMakePayment в React Native SDK" description: "Решение ошибки при совершении покупок при управлении подписками в Adapty." --- Ошибка 1003, `cantMakePayments`, означает, что на этом устройстве нельзя совершать встроенные покупки. Если вы столкнулись с ошибкой `cantMakePayments`, обычно это происходит по одной из следующих причин: - Ограничения устройства: ошибка не связана с Adapty. Способы решения описаны ниже. - Настройка Observer mode: метод `makePurchase` и Observer mode нельзя использовать одновременно. Подробнее — в соответствующем разделе ниже. ## Проблема: ограничения устройства \{#issue-device-restrictions\} | Проблема | Решение | |---------------------------------|-------------------------------------------------------------------------------------------------------------------| | Ограничения Screen Time | Отключите ограничения встроенных покупок в [Screen Time](https://support.apple.com/en-us/102470) | | Аккаунт заблокирован | Обратитесь в службу поддержки Apple для решения проблем с аккаунтом | | Региональные ограничения | Используйте аккаунт App Store из поддерживаемого региона | ## Проблема: одновременное использование Observer mode и makePurchase \{#issue-using-both-observer-mode-and-makepurchase\} Если вы используете `makePurchase` для обработки покупок, Observer mode не нужен. [Observer mode](observer-vs-full-mode) требуется только в том случае, если логику покупок вы реализуете самостоятельно. Таким образом, если вы используете `makePurchase`, можно смело убрать активацию Observer mode из кода инициализации SDK. --- # File: migration-to-react-native-sdk-v4 --- --- title: "Миграция Adapty React Native SDK на v. 4.0" description: "Перейдите на Adapty React Native SDK v4.0 (beta): замените paywall API на flow API, совместимые как с Flow Builder, так и с Paywall Builder." --- Adapty React Native SDK 4.0 (beta) вводит флоу и переименовывает paywall API соответствующим образом. Новые API работают как с новым Flow Builder, так и с существующим Paywall Builder — никаких изменений в настройках дашборда Adapty не требуется. ## Краткий справочник \{#quick-reference\} | v3 | v4 | |---|---| | `adapty.getPaywall(placementId, locale?, params?)` | `adapty.getFlow(placementId, params?)` | | `adapty.getPaywallForDefaultAudience(placementId, locale?, params?)` | `adapty.getFlowForDefaultAudience(placementId, params?)` | | `adapty.getPaywallProducts(paywall)` | `adapty.getPaywallProducts(flow)` | | `adapty.logShowPaywall(paywall)` | `adapty.logShowFlow(flow)` | | `AdaptyPaywall` (тип) | `AdaptyFlow` | | `createPaywallView(paywall)` | `createFlowView(flow)` | | `AdaptyPaywallView` (компонент) | `AdaptyFlowView` | | `EventHandlers` (тип) | `FlowEventHandlers` | | `onPaywallShown` | `onAppeared` | | `onPaywallClosed` | `onDisappeared` | | `onRenderingFailed` | `onError` | `AdaptyPaywallProduct` сохраняет своё название — продукты по-прежнему принадлежат флоу, а `getPaywallProducts` теперь принимает `AdaptyFlow`. Методы `getFlow` и `getFlowForDefaultAudience` больше не принимают параметр `locale`. Методы представления `present`, `dismiss`, `setEventHandlers` и `showDialog`, а также обработчики событий `onCloseButtonPress`, `onUrlPress`, `onCustomAction`, `onProductSelected`, `onPurchaseStarted`, `onPurchaseCompleted`, `onPurchaseFailed`, `onRestoreStarted`, `onRestoreCompleted`, `onRestoreFailed`, `onLoadingProductsFailed`, `onWebPaymentNavigationFinished` и `onAndroidSystemBack` сохраняют те же названия, что и в v3. Некоторые стандартные поведения изменились — см. [Изменения стандартного поведения](#default-behavior-changes). ## Минимальная версия iOS \{#minimum-ios-version\} Adapty React Native SDK 4.0 повышает минимальную версию iOS с 13.0 до **iOS 15.0**. Перед обновлением установите значение deployment target не ниже 15.0. ## Установка \{#installation\} ### Обновите пакет \{#update-the-package\} v4.0 — это предварительный релиз, поэтому укажите точную версию — npm не выбирает предварительные версии через диапазоны с `^` или `~`: ```bash showLineNumbers npm install react-native-adapty@4.0.0 # or yarn add react-native-adapty@4.0.0 ``` ### iOS: нативные SDK теперь подключаются через Swift Package Manager \{#ios-native-sdks-now-come-through-swift-package-manager\} [Репозиторий спецификаций CocoaPods переходит в режим только для чтения в декабре 2026 года](https://blog.cocoapods.org/CocoaPods-Specs-Repo/), поэтому начиная с v4 нативные SDK `Adapty`, `AdaptyUI` и `AdaptyPlugin` **больше не подключаются как под-зависимости CocoaPods** — podspec подтягивает их через **Swift Package Manager** (с помощью хелпера `spm_dependency`). Это требует двух вещей: - **React Native 0.75 или новее** — нужна для вспомогательного подспека `spm_dependency`. На более старой версии `pod install` завершится с явной ошибкой; сначала обновите React Native или оставайтесь на `react-native-adapty` 3.x. - **Динамические фреймворки** — зависимости SPM требуют динамической компоновки. Способ её включения отличается для Expo и обычного React Native. #### Expo Добавьте конфиг-плагин [`expo-build-properties`](https://docs.expo.dev/versions/latest/sdk/build-properties/) и задайте динамическую компоновку iOS-фреймворков в `app.json` (или `app.config.js`): ```json showLineNumbers title="app.json" { "expo": { "plugins": [ [ "expo-build-properties", { "ios": { "useFrameworks": "dynamic" } } ] ] } } ``` Затем установите плагин и пересоздайте нативный проект: ```bash showLineNumbers npx expo install expo-build-properties npx expo prebuild --clean ``` #### Bare React Native Добавьте динамические фреймворки в iOS-таргет, затем переустановите поды: ```ruby showLineNumbers title="ios/Podfile" use_frameworks! :linkage => :dynamic ``` ```bash showLineNumbers cd ios && pod install --repo-update ``` Если ранее вы подключали `Adapty`, `AdaptyUI` или `AdaptyPlugin` как зависимости CocoaPods, сначала удалите все явные строки `pod 'Adapty'`, `pod 'AdaptyUI'` или `pod 'AdaptyPlugin'` из вашего `Podfile`. :::warning Переход со статической компоновки по умолчанию на динамические фреймворки может конфликтовать с библиотеками, которые ещё не поддерживают модульные заголовки, и несовместим с Flipper. Если возникнут ошибки сборки, ознакомьтесь с этим [материалом об интеграции Swift Package Manager с библиотеками React Native](https://www.callstack.com/blog/integrating-swift-package-manager-with-react-native-libraries). ::: Полная инструкция по установке — в разделе [Установка Adapty SDK](sdk-installation-reactnative). ## Получение флоу \{#fetching-flows\} ### getPaywall → getFlow Тип возвращаемого значения изменился с `AdaptyPaywall` на `AdaptyFlow`, а параметр `locale` убран — при рендеринге флоу локаль определяется автоматически; для кастомных пейволов все локали возвращаются в `flow.remoteConfigs`: ```diff showLineNumbers - const paywall = await adapty.getPaywall('YOUR_PLACEMENT_ID', 'en'); + const flow = await adapty.getFlow('YOUR_PLACEMENT_ID'); ``` `getPaywallForDefaultAudience` переименован аналогично: ```diff showLineNumbers - const paywall = await adapty.getPaywallForDefaultAudience('YOUR_PLACEMENT_ID', 'en'); + const flow = await adapty.getFlowForDefaultAudience('YOUR_PLACEMENT_ID'); ``` ### getPaywallProducts(paywall) → getPaywallProducts(flow) `getPaywallProducts` сохраняет своё название, но теперь принимает `AdaptyFlow`: ```diff showLineNumbers - const products = await adapty.getPaywallProducts(paywall); + const products = await adapty.getPaywallProducts(flow); ``` ## Модель данных \{#data-model\} `getFlow` возвращает `AdaptyFlow` вместо `AdaptyPaywall`, и структура объекта изменилась: | поле v3 `AdaptyPaywall` | поле v4 `AdaptyFlow` | Действие | |---|---|---| | `remoteConfig?` (одно) | `remoteConfigs?: AdaptyRemoteConfig[]` (массив) | Флоу содержит один Remote Config на каждый настроенный язык. Читайте тот, который соответствует пользователю: `flow.remoteConfigs?.find((c) => c.lang === 'en')`. | | `products` | `flow.paywalls[i].productIdentifiers` | Идентификаторы продуктов теперь хранятся в каждом варианте флоу, а не в самом флоу. | | `webPurchaseUrl?` | `flow.paywalls[i].webPurchaseUrl` | Перенесено из флоу в каждый вариант пейвола. | | `version?: number` | `flowVersionId?: string` | Переименовано, тип изменён с `number` на `string`. | | `hasViewConfiguration` | удалено | Удалите все проверки `hasViewConfiguration` из кода. | | `requestLocale` | удалено | Локаль больше не является частью модели. | | _(новое)_ | `paywalls: AdaptyFlowPaywall[]` | Каждый элемент — один вариант пейвола во флоу. | | _(новое)_ | `responseCreatedAt: number` | Временная метка ответа сервера в миллисекундах. | Product identifiers moved from the flow to each variation: ```diff showLineNumbers - const ids = paywall.products; + const ids = flow.paywalls[0].productIdentifiers; ``` ## Методы веб-пейвола \{#web-paywall-methods\} `openWebPaywall` и `createWebPaywallUrl` сохраняют свои названия, но первым аргументом теперь передаётся `AdaptyFlowPaywall` (вариант флоу) вместо `AdaptyPaywall`. По-прежнему можно передать `AdaptyPaywallProduct`. ```diff showLineNumbers const flow = await adapty.getFlow('YOUR_PLACEMENT_ID'); - await adapty.openWebPaywall(paywall); + await adapty.openWebPaywall(flow.paywalls[0]); ``` ## Отслеживание просмотров флоу \{#tracking-flow-views\} ### logShowPaywall → logShowFlow `logShowPaywall` переименован в `logShowFlow` и теперь принимает `AdaptyFlow`. Событие по-прежнему логируется для той же вариации, так что существующие метрики воронки и A/B-тестов продолжат работать без изменений в дашборде. ```diff showLineNumbers - await adapty.logShowPaywall(paywall); + await adapty.logShowFlow(flow); ``` Как и в v3, вызывать этот метод при отображении флоу или пейволов, отрисованных с помощью [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder), не нужно — Adapty отслеживает такие просмотры автоматически. ## Отображение флоу \{#displaying-flows\} ### createPaywallView → createFlowView Переименуйте фабричную функцию и передайте `AdaptyFlow`. Методы возвращаемого контроллера (`present`, `dismiss`, `setEventHandlers`, `showDialog`) остаются без изменений: ```diff showLineNumbers - import { createPaywallView } from 'react-native-adapty'; + import { createFlowView } from 'react-native-adapty'; - const view = await createPaywallView(paywall); + const view = await createFlowView(flow); await view.present(); ``` ### AdaptyPaywallView → AdaptyFlowView Если вы используете React-компонент, переименуйте его и передайте проп `flow`: ```diff showLineNumbers - import { AdaptyPaywallView } from 'react-native-adapty'; + import { AdaptyFlowView } from 'react-native-adapty'; - <AdaptyPaywallView paywall={paywall} /* … */ /> + <AdaptyFlowView flow={flow} /* … */ /> ``` :::note Флоу-вью, созданное с помощью `createFlowView`, является одноразовым: после вызова `dismiss()` вью уничтожается, поэтому для повторного отображения флоу вызовите `createFlowView` снова. Встроенное `AdaptyFlowView` закрывается путём размонтирования — возврат `true` из обработчика не закрывает встроенное вью, поэтому вместо этого изменяйте собственное состояние, например в `onCloseButtonPress`. ::: ## Обработка событий \{#handling-events\} Интерфейс обработчика событий переименован с `EventHandlers` на `FlowEventHandlers`, а три колбэка переименованы. Тела существующих обработчиков менять не нужно — просто переименуйте: ```diff showLineNumbers - onPaywallShown: () => { /* … */ }, + onAppeared: () => { /* … */ }, - onPaywallClosed: () => { /* … */ }, + onDisappeared: () => { /* … */ }, - onRenderingFailed: (error) => { /* … */ }, + onError: (error) => { /* … */ }, ``` Все остальные обработчики событий сохраняют свои названия. Два из них также получают второй аргумент: `onPurchaseCompleted` теперь принимает `(purchaseResult, product)`, а `onPurchaseFailed` — `(error, product)`, где `product` — это задействованный `AdaptyPaywallProduct`. Полный список см. в разделе [Обработка событий флоу и пейвола](react-native-handling-events-1). :::note `onDisappeared` срабатывает только для флоу, открытого модально через `createFlowView().present()`. Компонент `AdaptyFlowView` не предоставляет его как проп — чтобы скрыть встроенное представление, размонтируйте его. ::: В v4 также добавлен ряд возможностей, которые можно включить по желанию: - Методы `adapty.openWebUrl(url, openIn?)` и `adapty.requestAppReview()` — обеспечивают работу стандартных обработчиков `onUrlPress` и `onRequestAppReview`, поэтому URL-адреса и запросы на отзыв об приложении обрабатываются нативно «из коробки». Вызывайте их напрямую только если переопределяете эти обработчики. - Обработка покупок в режиме Observer внутри флоу с помощью новых обработчиков `onObserverPurchaseInitiated` / `onObserverRestoreInitiated`. См. [Обработка покупок в режиме Observer](react-native-handling-events-1#handle-purchases-in-observer-mode). ## Удалённые и устаревшие API \{#removed-and-deprecated-apis\} ### setFallbackPaywalls → setFallback `setFallbackPaywalls` удалён. Используйте `setFallback` с тем же аргументом: ```diff showLineNumbers - await adapty.setFallbackPaywalls(fileLocation); + await adapty.setFallback(fileLocation); ``` ### Удалённые экспорты \{#removed-exports\} Эти символы больше не экспортируются из `react-native-adapty`. Удалите их импорты: - **`AdaptyPaywall`**: Используйте `AdaptyFlow` вместо него. - **`ProductReference`**: Используйте `AdaptyProductIdentifier`, считываемый из `flow.paywalls[i].productIdentifiers`. - **`AdaptyPaywallBuilder`**: Удалён. Флоу и пейволы рендерятся нативно. - **`AdaptyAndroidSubscriptionUpdateParameters`**: Используйте вложенную форму `subscriptionUpdateParams` (см. ниже). ### activate: lockMethodsUntilReady `lockMethodsUntilReady` удалён, и теперь это поведение включено постоянно. Удалите его из вызова `activate` — иначе код не скомпилируется: ```diff showLineNumbers - await adapty.activate('PUBLIC_SDK_KEY', { lockMethodsUntilReady: true }); + await adapty.activate('PUBLIC_SDK_KEY'); ``` ### Обновление подписки на Android с помощью makePurchase \{#makepurchase-android-subscription-update\} Плоская структура параметров обновления подписки на Android удалена. Перенесите `oldSubVendorProductId` и `prorationMode` в вложенный объект `subscriptionUpdateParams`, а `isOfferPersonalized` оставьте на верхнем уровне. Полный пример см. в разделе [Совершение покупок](react-native-making-purchases). ### Android: отступы безопасной зоны \{#android-safe-area-paddings\} Булевый ресурс Android `<bool name="adapty_paywall_enable_safe_area_paddings">…</bool>` удалён. Удалите его из `res/values/bools.xml` и управляйте отступами безопасной зоны во время выполнения через параметр `enableSafeArea` при создании представления флоу. По умолчанию он равен `true` для модального отображения и `false` для встроенного компонента. ### Режим мок-данных \{#mock-mode\} Если вы запускаете SDK в режиме мок-данных (Expo Go или веб-превью), переименуйте ключ мок-конфигурации `paywalls` в `flows`. ## Изменения поведения по умолчанию \{#default-behavior-changes\} Эти изменения не приводят к ошибкам компиляции, поэтому проверяйте их во время выполнения: - **`onAndroidSystemBack`**: Поведение по умолчанию изменилось: раньше представление закрывалось, теперь остаётся открытым. Чтобы вернуть прежнее поведение, возвращайте `true` из обработчика. - **`onPurchaseCompleted`**: Поведение по умолчанию изменилось: раньше представление закрывалось (если только пользователь не отменил покупку), теперь всегда остаётся открытым. Чтобы вернуть прежнее поведение, возвращайте `purchaseResult.type !== 'user_cancelled'` из обработчика. - **`onRestoreCompleted`**: Поведение по умолчанию изменилось: раньше представление закрывалось после успешного восстановления покупок, теперь остаётся открытым. Чтобы вернуть прежнее поведение, возвращайте `true` из обработчика. - **`onUrlPress`**: Теперь по умолчанию URL открывается через нативный слой с учётом настройки браузера (встроенный или внешний) из дашборда. Переопределите обработчик, чтобы открывать URL самостоятельно. ## Устаревший API онбординга \{#onboarding-api-deprecation\} Устаревший API онбординга помечен как deprecated в v4.0 в пользу [Flow Builder](adapty-flow-builder). Он продолжает работать, а IDE отмечает устаревшие символы через аннотации `@deprecated` — никаких предупреждений в рантайме нет. Эти символы будут удалены в будущих версиях, поэтому планируйте перенос ваших онбордингов во Flow Builder. Устаревшие символы: `getOnboarding`, `getOnboardingForDefaultAudience`, `createOnboardingView` и `AdaptyOnboardingView`. --- # File: migration-react-native-314 --- --- title: "Миграция Adapty React Native SDK на v3.14" description: "Мигрируйте на Adapty React Native SDK v3.14 для повышения производительности и новых функций монетизации." --- Adapty React Native SDK 3.14.0 — это мажорный релиз, который содержит улучшения, требующие выполнения шагов миграции с вашей стороны: - Метод `registerEventHandlers` заменён методом `setEventHandlers`. - В `AdaptyOnboardingView` обработчики событий теперь передаются как отдельные пропсы вместо объекта `eventHandlers` - Введён новый упрощённый стиль импорта UI-компонентов - Метод `logShowOnboarding` удалён - Минимальная версия React Native обновлена до 0.73.0 - Стиль презентации iOS по умолчанию для пейволов и онбордингов изменился с page sheet на полноэкранный ## Замените `registerEventHandlers` на `setEventHandlers` \{#replace-registereventhandlers-with-seteventhandlers\} Метод `registerEventHandlers`, используемый для работы с Adapty Paywall Builder и Onboarding Builder, заменён методом `setEventHandlers`. Если вы используете Adapty Paywall Builder и/или Adapty Onboarding Builder, найдите `registerEventHandlers` в коде своего приложения и замените на `setEventHandlers`. Изменение внесено для большей ясности в поведении метода: обработчики теперь работают по одному, поскольку каждый возвращает `true`/`false`, а наличие нескольких обработчиков для одного события делало итоговое поведение непредсказуемым. Обратите внимание: при использовании React-компонентов `AdaptyOnboardingView` или `AdaptyPaywallView` вам не нужно возвращать `true`/`false` из обработчиков событий, так как видимость компонента управляется через собственный state. Возвращаемые значения нужны только при модальном показе экранов, где SDK сам управляет жизненным циклом представления. :::important Каждый новый вызов `setEventHandlers` перезаписывает переданные обработчики, заменяя как стандартные, так и ранее заданные для указанных событий. ::: ```diff showLineNumbers - const unsubscribe = view.registerEventHandlers({ - // your event handlers - }) const unsubscribe = view.setEventHandlers({ // your event handlers }) ``` ## Обновление путей импорта для UI-компонентов \{#update-import-paths-for-ui-components\} В Adapty SDK 3.14.0 появился упрощённый стиль импорта UI-компонентов. Теперь вместо импорта из `react-native-adapty/dist/ui` можно импортировать напрямую из `react-native-adapty`. Новый стиль импорта лучше соответствует стандартным практикам React Native и делает строки импорта чище. Если вы используете UI-компоненты вроде `AdaptyPaywallView` или `AdaptyOnboardingView`, обновите импорты, как показано ниже: ```diff showLineNumbers - import { AdaptyPaywallView } from 'react-native-adapty/dist/ui'; + import { AdaptyPaywallView } from 'react-native-adapty'; - import { AdaptyOnboardingView } from 'react-native-adapty/dist/ui'; + import { AdaptyOnboardingView } from 'react-native-adapty'; - import { createPaywallView } from 'react-native-adapty/dist/ui'; + import { createPaywallView } from 'react-native-adapty'; - import { createOnboardingView } from 'react-native-adapty/dist/ui'; + import { createOnboardingView } from 'react-native-adapty'; ``` :::note Для обратной совместимости старый стиль импорта (`react-native-adapty/dist/ui`) по-прежнему поддерживается. Однако мы рекомендуем использовать новый стиль импорта для единообразия и ясности. ::: ## Обновите обработчики событий онбординга в React-компоненте \{#update-onboarding-event-handlers-in-the-react-component\} Обработчики событий для онбордингов перенесены за пределы объекта `eventHandlers` в `AdaptyOnboardingView`. Если вы отображаете онбординги с помощью `AdaptyOnboardingView`, обновите структуру обработки событий. :::important Обратите внимание на рекомендуемый способ реализации обработчиков событий. Чтобы избежать пересоздания объектов при каждом рендере, используйте `useCallback` для функций, которые обрабатывают события. ::: ```diff showLineNumbers import React, { useCallback } from 'react'; - import { AdaptyOnboardingView } from 'react-native-adapty/dist/ui'; + import { AdaptyOnboardingView } from 'react-native-adapty'; + import type { OnboardingEventHandlers } from 'react-native-adapty'; + + function MyOnboarding({ onboarding }) { + const onAnalytics = useCallback<OnboardingEventHandlers['onAnalytics']>((event, meta) => {}, []); + const onClose = useCallback<OnboardingEventHandlers['onClose']>((actionId, meta) => {}, []); + const onCustom = useCallback<OnboardingEventHandlers['onCustom']>((actionId, meta) => {}, []); + const onPaywall = useCallback<OnboardingEventHandlers['onPaywall']>((actionId, meta) => {}, []); + const onStateUpdated = useCallback<OnboardingEventHandlers['onStateUpdated']>((action, meta) => {}, []); + const onFinishedLoading = useCallback<OnboardingEventHandlers['onFinishedLoading']>((meta) => {}, []); + const onError = useCallback<OnboardingEventHandlers['onError']>((error) => {}, []); + return ( <AdaptyOnboardingView onboarding={onboarding} style={styles.container} - eventHandlers={{ - onAnalytics(event, meta) { /* ... */ }, - onClose(actionId, meta) { /* ... */ }, - onCustom(actionId, meta) { /* ... */ }, - onPaywall(actionId, meta) { /* ... */ }, - onStateUpdated(action, meta) { /* ... */ }, - onFinishedLoading(meta) { /* ... */ }, - onError(error) { /* ... */ }, - }} + onAnalytics={onAnalytics} + onClose={onClose} + onCustom={onCustom} + onPaywall={onPaywall} + onStateUpdated={onStateUpdated} + onFinishedLoading={onFinishedLoading} + onError={onError} /> ); + } ``` :::note Для обратной совместимости проп `eventHandlers` по-прежнему поддерживается, но считается устаревшим. Рекомендуем перейти на отдельные пропы обработчиков событий, как показано выше. ::: ## Удалите `logShowOnboarding` \{#delete-logshowonboarding\} В Adapty SDK 3.14.0 метод `logShowOnboarding` удалён из SDK. Если вы использовали этот метод, после обновления SDK до версии 3.14 и выше он будет недоступен. Вместо него вы можете [создавать онбординги в no-code конструкторе онбордингов Adapty](onboardings). Аналитика для таких онбордингов отслеживается автоматически, а возможностей для кастомизации достаточно много. ## Обновите React Native \{#update-react-native\} Начиная с Adapty SDK 3.14.0, минимальная поддерживаемая версия React Native — 0.73.0. Если вы используете более раннюю версию, обновите React Native до 0.73.0 или выше, чтобы работа с Adapty SDK оставалась стабильной и предсказуемой. ## Обновите стиль представления iOS для модальных пейволов и онбордингов \{#update-ios-presentation-style-for-modal-paywalls-and-onboardings\} В Adapty SDK 3.14.0 стиль представления по умолчанию для пейволов и онбордингов, отображаемых с помощью метода `view.present()`, изменился с page sheet на полный экран на iOS. Если вы хотите сохранить прежний стиль представления page sheet, передайте параметр `iosPresentationStyle` в метод `present()`: ```typescript showLineNumbers title="React Native (TSX)" try { await view.present({ iosPresentationStyle: 'page_sheet' }); } catch (error) { // handle the error } ``` --- # File: react-native-migration-guide-380 --- --- title: "Миграция Adapty React Native SDK на v3.8" description: "Переходите на Adapty React Native SDK v3.8 для повышения производительности и новых возможностей монетизации." --- Adapty SDK 3.8.0 — это мажорный релиз, в котором появились улучшения, требующие выполнения ряда шагов миграции. ## Обновление типа входных данных для получения параметров плейсмента \{#update-input-type-for-getting-placement-params\} `GetPaywallParamsInput` переименован в `GetPlacementParamsInput`: ```diff showLineNumbers - type GetPaywallParamsInput = { + type GetPlacementParamsInput = { placementId: string; locale?: string; fetchPolicy?: AdaptyPlacementFetchPolicy; loadTimeoutMs?: number; } ``` ## Обновление метода для установки резервного пейвола \{#update-fallback-method\} Метод для установки резервных пейволов обновлён, а тип для указания расположения резервного файла переименован: ```diff showLineNumbers - adapty.setFallbackPaywalls(paywallsLocation: Input.FallbackPaywallsLocation); + adapty.setFallback(fileLocation: Input.FileLocation); ``` ## Обновление доступа к свойствам пейвола \{#update-paywall-property-access\} Следующие свойства перенесены из `AdaptyPaywall` в `AdaptyPlacement`: ```diff showLineNumbers - paywall.abTestName - paywall.audienceName - paywall.revision - paywall.placementId + paywall.placement.abTestName + paywall.placement.audienceName + paywall.placement.revision + paywall.placement.id ``` --- # File: migration-to-react-native-sdk-34 --- --- title: "Миграция Adapty React Native SDK на v. 3.4" description: "Переходите на Adapty React Native SDK v3.4 для улучшенной производительности и новых функций монетизации." --- Adapty SDK 3.4.0 — это мажорный релиз, который вносит изменения, требующие миграции с вашей стороны. ## Обновите файлы резервных пейволов \{#update-fallback-paywall-files\} Обновите файлы резервных пейволов, чтобы обеспечить совместимость с новой версией SDK: 1. [Скачайте обновлённые файлы резервных пейволов](fallback-paywalls) из дашборда Adapty. 2. [Замените существующие резервные пейволы в своём мобильном приложении](react-native-use-fallback-paywalls) новыми файлами. ## Обновите реализацию Observer Mode \{#update-implementation-of-observer-mode\} Если вы используете Observer Mode, обязательно обновите его реализацию. Раньше для передачи транзакций в Adapty использовались разные методы. В новой версии метод `reportTransaction` должен применяться единообразно — как на Android, так и на iOS. Этот метод явно сообщает Adapty о каждой транзакции, гарантируя её распознавание. Если при покупке использовался пейвол, передайте variation ID, чтобы связать транзакцию с ним. :::warning **Не пропускайте отчёт о транзакции!** Если вы не вызовете `reportTransaction`, Adapty не распознает транзакцию, она не появится в аналитике и не будет отправлена в интеграции. ::: ```diff showLineNumbers - if (Platform.OS === 'android') { - try { - await adapty.restorePurchases(); - } catch (error) { - // handle the error - } - } const variationId = paywall.variationId; try { await adapty.reportTransaction(transactionId, variationId); } catch (error) { // handle the `AdaptyError` } ``` --- # File: migration-to-react-native330 --- --- title: "Миграция Adapty React Native SDK на v3.3" description: "Перейдите на Adapty React Native SDK v3.3 для повышения производительности и новых функций монетизации." --- Adapty SDK 3.3.1 — это крупный релиз, который принёс ряд улучшений, требующих от вас выполнения нескольких шагов миграции. 1. Обновите Adapty SDK до версии 3.3.x. 2. Обновите модели. 3. Удалите метод `getProductsIntroductoryOfferEligibility`. 4. Обновите совершение покупки. 5. Обновите отображение пейвола, созданного в Paywall Builder. 6. Пересмотрите реализацию таймера, заданного разработчиком. 7. Обновите обработку событий покупки в Paywall Builder. 8. Обновите обработку событий пользовательских действий в Paywall Builder. 9. Измените колбэк `onProductSelected`. 10. Удалите параметры сторонних интеграций из метода `updateProfile`. 11. Обновите настройки интеграций для Adjust, AirBridge, Amplitude, AppMetrica, Appsflyer, Branch, Facebook Ads, Firebase и Google Analytics, Mixpanel, OneSignal и Pushwoosh. 12. Обновите реализацию режима Observer mode. ## Обновление Adapty React Native SDK до версии 3.3.x \{#upgrade-adapty-react-native-sdk-to-33x\} До версии 3.3.1 SDK `react-native-adapty` являлся основным и обязательным SDK для работы Adapty в вашем приложении. SDK `@adapty/react-native-ui` был необязательным и требовался только при использовании Adapty Paywall Builder. Начиная с версии 3.3.1, SDK `@adapty/react-native-ui` считается устаревшим, а его функциональность перенесена в SDK `react-native-adapty`. Чтобы обновиться до версии 3.3.1, выполните следующие шаги: 1. Обновите пакет `react-native-adapty` до версии 3.3.1. 2. Удалите пакет `@adapty/react-native-ui` из зависимостей проекта. 3. Синхронизируйте зависимости проекта, чтобы применить изменения. ## Изменения в моделях \{#changes-in-models\} ### Новые модели \{#new-models\} 1. [AdaptySubscriptionOffer](https://react-native.adapty.io/interfaces/adaptysubscriptionoffer): ```typescript showLineNumbers export interface AdaptySubscriptionOffer { readonly identifier: AdaptySubscriptionOfferId; phases: AdaptyDiscountPhase[]; android?: { offerTags?: string[]; }; } ``` 2. [AdaptySubscriptionOfferId](https://react-native.adapty.io/types/adaptysubscriptionofferid): ```typescript showLineNumbers export type AdaptySubscriptionOfferId = | { id?: string; type: 'introductory'; } | { id: string; type: 'promotional' | 'win_back'; }; ``` ### Изменённые модели \{#changed-models\} 1. [AdaptyPaywallProduct](https://react-native.adapty.io/interfaces/adaptypaywallproduct): - Переименовано свойство `subscriptionDetails` в `subscription`. <p> </p> ```diff showLineNumbers - subscriptionDetails?: AdaptySubscriptionDetails; + subscription?: AdaptySubscriptionDetails; ``` 2. [AdaptySubscriptionDetails](https://react-native.adapty.io/interfaces/adaptysubscriptiondetails): - `promotionalOffer` удалён. Теперь promotional offer передаётся в свойстве `offer` только если он доступен. В этом случае `offer?.identifier?.type` будет равен `'promotional'`. - `introductoryOfferEligibility` удалён (офферы возвращаются только если пользователь имеет право на них). - `offerId` удалён. ID оффера теперь хранится в `AdaptySubscriptionOffer.identifier`. - `offerTags` перемещён в `AdaptySubscriptionOffer.android`. <p> </p> ```diff showLineNumbers - introductoryOffers?: AdaptyDiscountPhase[]; + offer?: AdaptySubscriptionOffer; ios?: { - promotionalOffer?: AdaptyDiscountPhase; subscriptionGroupIdentifier?: string; }; android?: { - offerId?: string; basePlanId: string; - introductoryOfferEligibility: OfferEligibility; - offerTags?: string[]; renewalType?: 'prepaid' | 'autorenewable'; }; } ``` 3. [AdaptyDiscountPhase](https://react-native.adapty.io/interfaces/adaptydiscountphase): - Поле `identifier` удалено из модели `AdaptyDiscountPhase`. Идентификатор оффера теперь хранится в `AdaptySubscriptionOffer.identifier`. <p> </p> ```diff showLineNumbers - ios?: { - readonly identifier?: string; - }; ``` ### Удалённые модели \{#remove-models\} 1. `AttributionSource`: - Вместо него теперь используется строка там, где ранее применялся `AttributionSource`. 2. `OfferEligibility`: - Эта модель удалена, так как больше не нужна. Теперь оффер возвращается только если пользователь имеет право на его получение. ## Удаление метода `getProductsIntroductoryOfferEligibility` \{#remove-getproductsintroductoryoffereligibility-method\} До Adapty SDK 3.3.1 объекты продуктов всегда включали офферы, даже если пользователь не имел права на их получение. Это требовало ручной проверки права на получение оффера перед его использованием. Начиная с версии 3.3.1, объект продукта включает офферы только если пользователь имеет на них право. Это упрощает процесс: если оффер присутствует, можно считать, что пользователь имеет право на его получение. ## Обновление процесса покупки \{#update-making-purchase\} В более ранних версиях отменённые и ожидающие покупки считались ошибками и возвращали коды `2: 'paymentCancelled'` и `25: 'pendingPurchase'` соответственно. Начиная с версии 3.3.1, отменённые и ожидающие покупки считаются успешными результатами и должны обрабатываться соответствующим образом: ```typescript showLineNumbers try { const purchaseResult = await adapty.makePurchase(product); switch (purchaseResult.type) { case 'success': const isSubscribed = purchaseResult.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features } break; case 'user_cancelled': // Handle the case where the user canceled the purchase break; case 'pending': // Handle deferred purchases (e.g., the user will pay offline with cash) break; } } catch (error) { // Handle the error } ``` ## Обновление отображения пейволов Paywall Builder \{#update-paywall-builder-paywall-presentation\} Актуальные примеры смотрите в документации [Отображение новых пейволов Paywall Builder в React Native](react-native-present-paywalls). ```diff showLineNumbers - import { createPaywallView } from '@adapty/react-native-ui'; + import { createPaywallView } from 'react-native-adapty/dist/ui'; const view = await createPaywallView(paywall); view.registerEventHandlers(); // handle close press, etc try { await view.present(); } catch (error) { // handle the error } ``` ## Обновление реализации таймеров, определяемых разработчиком \{#update-developer-defined-timer-implementation\} Переименуйте параметр `timerInfo` в `customTimers`: ```diff showLineNumbers - let timerInfo = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) } + let customTimers = { 'CUSTOM_TIMER_NY': new Date(2025, 0, 1) } //and then you can pass it to createPaywallView as follows: - view = await createPaywallView(paywall, { timerInfo }) + view = await createPaywallView(paywall, { customTimers }) ``` ## Изменение событий покупки в Paywall Builder \{#modify-paywall-builder-purchase-events\} Раньше: - Отменённые покупки вызывали коллбэк `onPurchaseCancelled`. - Ожидающие покупки возвращали код ошибки `25: 'pendingPurchase'`. Теперь: - Оба случая обрабатываются коллбэком `onPurchaseCompleted`. #### Шаги для миграции: \{#steps-to-migrate\} 1. Удалите коллбэк `onPurchaseCancelled`. 2. Удалите обработку кода ошибки `25: 'pendingPurchase'`. 3. Обновите коллбэк `onPurchaseCompleted`: ```typescript showLineNumbers const view = await createPaywallView(paywall); const unsubscribe = view.registerEventHandlers({ // ... other optional callbacks onPurchaseCompleted(purchaseResult, product) { switch (purchaseResult.type) { case 'success': const isSubscribed = purchaseResult.profile?.accessLevels['YOUR_ACCESS_LEVEL']?.isActive; if (isSubscribed) { // Grant access to the paid features } break; // highlight-start case 'user_cancelled': // Handle the case where the user canceled the purchase break; case 'pending': // Handle deferred purchases (e.g., the user will pay offline with cash) break; // highlight-end } // highlight-start return purchaseResult.type !== 'user_cancelled'; // highlight-end }, }); ``` ## Обновление событий кастомных действий в Paywall Builder \{#modify-paywall-builder-custom-action-events\} Удалённые коллбэки: - `onAction` - `onCustomEvent` Добавленный коллбэк: - Новый коллбэк `onCustomAction(actionId)`. Используйте его для кастомных действий. ## Изменение коллбэка `onProductSelected` \{#modify-onproductselected-callback\} Ранее `onProductSelected` принимал объект `product`. Теперь требуется `productId` в виде строки. ## Удаление параметров сторонних интеграций из метода `updateProfile` \{#remove-third-party-integration-parameters-from-updateprofile-method\} Идентификаторы сторонних интеграций теперь задаются с помощью метода `setIntegrationIdentifier`. Метод `updateProfile` больше не принимает их. ## Обновление конфигурации SDK сторонних интеграций \{#update-third-party-integration-sdk-configuration\} Чтобы интеграции корректно работали с Adapty React Native SDK 3.3.1 и выше, обновите конфигурации SDK для следующих интеграций согласно разделам ниже. Кроме того, если вы использовали `AttributionSource` для получения идентификатора атрибуции, измените код так, чтобы передавать нужный идентификатор в виде строки. ### Adjust Обновите код вашего мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с Adjust](adjust#connect-your-app-to-adjust). ```diff showLineNumbers import { Adjust, AdjustConfig } from "react-native-adjust"; import { adapty } from "react-native-adapty"; var adjustConfig = new AdjustConfig(appToken, environment); // Before submiting Adjust config... adjustConfig.setAttributionCallbackListener(attribution => { // Make sure Adapty SDK is activated at this point // You may want to lock this thread awaiting of `activate` adapty.updateAttribution(attribution, "adjust"); }); // ... Adjust.create(adjustConfig); + Adjust.getAdid((adid) => { + if (adid) + adapty.setIntegrationIdentifier("adjust_device_id", adid); + }); ``` ### AirBridge \{#airbridge\} Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Конфигурация SDK для интеграции AirBridge](airbridge#connect-your-app-to-airbridge). ```diff showLineNumbers import Airbridge from 'airbridge-react-native-sdk'; import { adapty } from 'react-native-adapty'; try { const deviceId = await Airbridge.state.deviceUUID(); - await adapty.updateProfile({ - airbridgeDeviceId: deviceId, - }); + await adapty.setIntegrationIdentifier("airbridge_device_id", deviceId); } catch (error) { // handle `AdaptyError` } ``` ### Amplitude \{#amplitude\} Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Конфигурация SDK для интеграции Amplitude](amplitude#sdk-configuration). ```diff showLineNumbers import { adapty } from 'react-native-adapty'; try { - await adapty.updateProfile({ - amplitudeDeviceId: deviceId, - amplitudeUserId: userId, - }); + await adapty.setIntegrationIdentifier("amplitude_device_id", deviceId); + await adapty.setIntegrationIdentifier("amplitude_user_id", userId); } catch (error) { // handle `AdaptyError` } ``` ### AppMetrica Обновите код своего мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [Настройка SDK для интеграции с AppMetrica](appmetrica#sdk-configuration). ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import AppMetrica, { DEVICE_ID_KEY, StartupParams, StartupParamsReason } from '@appmetrica/react-native-analytics'; // ... const startupParamsCallback = async ( params?: StartupParams, reason?: StartupParamsReason ) => { const deviceId = params?.deviceId if (deviceId) { try { - await adapty.updateProfile({ - appmetricaProfileId: 'YOUR_ADAPTY_CUSTOMER_USER_ID', - appmetricaDeviceId: deviceId, - }); + await adapty.setIntegrationIdentifier("appmetrica_profile_id", 'YOUR_ADAPTY_CUSTOMER_USER_ID'); + await adapty.setIntegrationIdentifier("appmetrica_device_id", deviceId); } catch (error) { // handle `AdaptyError` } } } AppMetrica.requestStartupParams(startupParamsCallback, [DEVICE_ID_KEY]) ``` ### AppsFlyer Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с AppsFlyer](appsflyer#connect-your-app-to-appsflyer). ```diff showLineNumbers import { adapty, AttributionSource } from 'react-native-adapty'; import appsFlyer from 'react-native-appsflyer'; appsFlyer.onInstallConversionData(installData => { try { - const networkUserId = appsFlyer.getAppsFlyerUID(); - adapty.updateAttribution(installData, AttributionSource.AppsFlyer, networkUserId); + const uid = appsFlyer.getAppsFlyerUID(); + adapty.setIntegrationIdentifier("appsflyer_id", uid); + adapty.updateAttribution(installData, "appsflyer"); } catch (error) { // handle the error } }); // ... appsFlyer.initSdk(/*...*/); ``` ### Branch \{#branch\} Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Конфигурация SDK для интеграции Branch](branch#connect-your-app-to-branch). ```diff showLineNumbers import { adapty, AttributionSource } from 'react-native-adapty'; import branch from 'react-native-branch'; branch.subscribe({ enComplete: ({ params, }) => { - adapty.updateAttribution(params, AttributionSource.Branch); + adapty.updateAttribution(params, "branch"); }, }); ``` ### Facebook Ads Обновите код вашего мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [Настройка SDK для интеграции с Facebook Ads](facebook-ads#connect-your-app-to-facebook-ads). ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import { AppEventsLogger } from 'react-native-fbsdk-next'; try { const anonymousId = await AppEventsLogger.getAnonymousID(); - await adapty.updateProfile({ - facebookAnonymousId: anonymousId, - }); + await adapty.setIntegrationIdentifier("facebook_anonymous_id", anonymousId); } catch (error) { // handle `AdaptyError` } ``` ### Firebase и Google Analytics \{#firebase-and-google-analytics\} Обновите код мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [настройка SDK для интеграции с Firebase и Google Analytics](firebase-and-google-analytics). ```diff showLineNumbers import analytics from '@react-native-firebase/analytics'; import { adapty } from 'react-native-adapty'; try { const appInstanceId = await analytics().getAppInstanceId(); - await adapty.updateProfile({ - firebaseAppInstanceId: appInstanceId, - }); + await adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId); } catch (error) { // handle `AdaptyError` } ``` ### Mixpanel \{#mixpanel\} Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Конфигурация SDK для интеграции Mixpanel](mixpanel#sdk-configuration). ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import { Mixpanel } from 'mixpanel-react-native'; // ... try { - await adapty.updateProfile({ - mixpanelUserId: mixpanelUserId, - }); + await adapty.setIntegrationIdentifier("mixpanel_user_id", mixpanelUserId); } catch (error) { // handle `AdaptyError` } ``` ### OneSignal Обновите код мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [Настройка SDK для интеграции с OneSignal](onesignal#sdk-configuration). <Tabs groupId="current-os" queryString> <TabItem value="v5+" label="OneSignal SDK v5+ (текущий)" default> ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import OneSignal from 'react-native-onesignal'; OneSignal.User.pushSubscription.addEventListener('change', (subscription) => { const subscriptionId = subscription.current.id; if (subscriptionId) { - adapty.updateProfile({ - oneSignalSubscriptionId: subscriptionId, - }); + adapty.setIntegrationIdentifier("one_signal_subscription_id", subscriptionId); } }); ``` </TabItem> <TabItem value="pre-v5" label="OneSignal SDK v. до 4.x (устаревший)" default> ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import OneSignal from 'react-native-onesignal'; OneSignal.addSubscriptionObserver(event => { const playerId = event.to.userId; - adapty.updateProfile({ - oneSignalPlayerId: playerId, - }); + adapty.setIntegrationIdentifier("one_signal_player_id", playerId); }); ``` </TabItem> </Tabs> ### Pushwoosh \{#pushwoosh\} Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Конфигурация SDK для интеграции Pushwoosh](pushwoosh#sdk-configuration). ```diff showLineNumbers import { adapty } from 'react-native-adapty'; import Pushwoosh from 'pushwoosh-react-native-plugin'; // ... try { - await adapty.updateProfile({ - pushwooshHWID: hwid, - }); + await adapty.setIntegrationIdentifier("pushwoosh_hwid", hwid); } catch (error) { // handle `AdaptyError` } ``` ## Обновление реализации режима Observer \{#update-observer-mode-implementation\} Обновите способ привязки пейволов к транзакциям. Раньше для назначения `variationId` использовался метод `setVariationId`. Теперь можно передавать `variationId` напрямую при записи транзакции с помощью нового метода `reportTransaction`. Итоговый пример кода смотрите в разделе [Привязка пейволов к транзакциям покупок в режиме Observer](report-transactions-observer-mode-react-native). :::warning Не забывайте фиксировать транзакцию с помощью метода `reportTransaction`. Если пропустить этот шаг, Adapty не распознает транзакцию, не предоставит уровни доступа, не включит её в аналитику и не отправит в интеграции. Этот шаг обязателен! ::: :::note Обратите внимание, что порядок параметров метода `reportTransaction` отличается от порядка параметров метода `setVariationId`. ::: ```diff showLineNumbers const variationId = paywall.variationId; try { - await adapty.setVariationId(variationId, transactionId); + await adapty.reportTransaction(transactionId, variationId); } catch (error) { // handle the `AdaptyError` } ``` --- # File: migration-to-react-native-sdk-v3 --- --- title: "Миграция Adapty React Native SDK на v3.0" description: "Мигрируйте на Adapty React Native SDK v3.0 для повышения производительности и новых возможностей монетизации." --- Adapty SDK v3.0 добавляет поддержку нового [Adapty Paywall Builder](adapty-paywall-builder) — обновлённой версии no-code инструмента для создания пейволов. Благодаря максимальной гибкости и широким возможностям дизайна ваши пейволы станут ещё эффективнее и прибыльнее. ## Обновление до версии 3.0.1 \{#upgrade-to-version-301\} 1. Обновитесь до версии 3.0.1 обычным способом. 2. Замените файлы резервного пейвола: 1. [Скачайте последнюю версию](fallback-paywalls) из дашборда Adapty. 2. Сохраните их на устройстве пользователя и передайте в метод `.setFallbackPaywalls`, как описано [здесь](react-native-use-fallback-paywalls). --- # End of Documentation _Generated on: 2026-07-24T13:01:12.835Z_ _Successfully processed: 45/45 files_ # TUTORIAL - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: ru Generated on: 2026-07-24T13:01:12.838Z Total files: 277 --- # File: is-adapty-right-for-me --- --- title: "Подходит ли мне Adapty?" description: "Узнайте, подходит ли Adapty для вашего случая. Запускаете новое приложение, оптимизируете доходы или переходите с другого инструмента — здесь вы найдёте, с чего начать." --- Adapty — это платформа для встроенных покупок в мобильных приложениях. Она охватывает подписки, разовые и расходуемые покупки: от обработки платежей и валидации чеков до аналитики, A/B-тестов и интеграций. Вот как Adapty работает в разных ситуациях. ## Я запускаю новое приложение со встроенными покупками \{#im-launching-a-new-app-with-in-app-purchases\} Независимо от того, продаёте ли вы подписки, разовые или расходуемые покупки, Adapty закрывает весь стек: - **SDK для 7 платформ**: iOS, Android, React Native, Flutter, Unity, Kotlin Multiplatform и Capacitor. - **Обработка покупок**: подписки с обновлениями и повторными попытками, разовые покупки, расходуемые покупки и валидация чеков — всё это берёт на себя Adapty. - **Конструктор пейволов без кода**: создавайте и публикуйте пейволы, не написав ни строчки UI-кода. - **Аналитика с первого дня**: отслеживайте выручку, пробные периоды, конверсии и многое другое, как только появятся первые пользователи. Готовы начать? Следуйте [гайду по быстрому старту](quickstart). ## Я хочу A/B-тесты, аналитику и интеграции \{#i-want-ab-tests-analytics-and-integrations\} Adapty помогает оптимизировать то, что уже работает: - **A/B-тестирование**: Тестируйте разные цены, дизайны пейволов, длительность пробных периодов и promotional offer, чтобы найти лучший вариант конверсии. Используйте [AI Growth Advisor](autopilot), чтобы получать рекомендации по A/B-тестам, адаптированные к вашему приложению на основе данных 20 000 приложений с подписками. - **Аналитические графики**: Отслеживайте MRR, LTV, отток, удержание и десятки других метрик. - **Сегментация аудитории**: Показывайте нужным группам пользователей персонализированные пейволы и предложения. - **Remote Config пейволов**: Вносите изменения в пейволы без выпуска новой версии приложения. - **Интеграции со сторонними сервисами**: Отправляйте события о покупках в Amplitude, AppsFlyer, Adjust, Mixpanel и другие инструменты, которые уже использует ваша команда. Изучите [A/B-тесты](ab-tests), [Аналитику](analytics), [Интеграции с сервисами аналитики](analytics-integration) или [Интеграции с сервисами атрибуции](attribution-integration). ## Хочу реализовать встроенные покупки с помощью LLM \{#i-want-to-implement-in-app-purchases-with-an-llm\} Документация Adapty оптимизирована для работы с AI-ассистентами вроде Cursor, Claude, ChatGPT и других. Каждая страница доступна в формате Markdown, а для каждой платформы есть пошаговые гайды по реализации с помощью LLM: - **Готовые к копированию гайды**: отправьте гайд своему LLM и дайте ему провести вас через каждый этап реализации. - **Доступ в формате Markdown**: добавьте `.md` к любому URL документации или нажмите **Copy for LLM**, чтобы получить чистую текстовую версию. - **Поддержка Context7 MCP**: подключите документацию Adapty напрямую к вашей IDE с поддержкой LLM. Выберите платформу и приступайте: [Интегрируйте Adapty с помощью ИИ](adapty-cursor). ## Я хочу запускать и оптимизировать кампании в Apple Ads \{#i-want-to-run-and-optimize-apple-ads-campaigns\} Если вы используете Apple Search Ads, Adapty Ads Manager связывает эффективность кампаний напрямую с метриками выручки — без необходимости в MMP: - **Данные о производительности в реальном времени**: отслеживайте кампании, группы объявлений и ключевые слова. - **Сквозное отслеживание дохода**: следите за цепочкой от поиска до установки, пробного периода, подписки и LTV. - **Прогнозы и рекомендации на основе ИИ**: прогнозируйте окупаемость и получайте советы по масштабированию. - **ИИ-агент**: задавайте вопросы об аккаунте на обычном языке и получайте полные ответы и рекомендации по всей воронке. - **Автоматизации на основе правил**: удерживайте целевые показатели CPA и ROAS на нужном уровне. Начните работу с [Adapty Ads Manager](adapty-ads-manager). ## Я хочу отслеживать источники пользователей \{#i-want-to-track-where-my-users-come-from\} Adapty Attribution — это встроенное решение для атрибуции, которое связывает рекламные расходы с установками приложения и доходами от подписок: - **Единый маркетинговый дашборд**: ROAS, установки и выручка по всем каналам в одном месте. - **Встроенная атрибуция**: связывайте рекламные кампании с установками и выручкой без сторонних MMP. - **Трекинговые ссылки**: создавайте ссылки в Adapty и добавляйте их в кампании для точной атрибуции. - **Отложенные диплинки**: направляйте пользователей к нужному контенту после установки — даже если приложение не было установлено в момент клика. - **Когортный анализ**: оценивайте эффективность привлечения и поведение пользователей в динамике. Узнайте больше об [атрибуции Adapty](adapty-user-acquisition). ## Я хочу конвертировать пробных пользователей и возвращать ушедших подписчиков через email \{#i-want-to-convert-trial-users-and-recover-churned-subscribers-via-email\} Adapty Mail превращает данные ваших пользователей из Adapty в AI-генерируемые email-кампании, которые охватывают пробных пользователей, ушедших подписчиков и другие жизненные события: - **AI-генерация кампаний**: Adapty автоматически создаёт текст и дизайн каждой кампании на основе вашего бренд-профиля. - **Триггеры жизненного цикла**: Запускайте кампании автоматически при наступлении ключевых событий жизненного цикла. - **Веб-пейвол с чекаутом**: Персонализированные ссылки для оплаты, привязанные к каждому получателю, — покупки атрибутируются к письму, которое их инициировало. - **Отправка с вашего домена**: Все письма отправляются с вашего верифицированного домена — отдельная email-платформа не нужна. Подробнее об [Adapty Mail](adapty-mail). ## Я хочу быстро итерироваться без релизов приложения \{#i-want-to-iterate-fast-without-app-releases\} После интеграции Adapty большая часть повседневной работы происходит в дашборде — без новых версий приложения: - **Флоу**: Проектируйте пейволы и онбординги в визуальном редакторе и публикуйте изменения мгновенно. - **A/B-тесты из дашборда**: Запускайте эксперименты, меняйте цены и заменяйте офферы без изменений в коде. - **Аналитика в дашборде**: Отслеживайте выручку, отток, триалы и конверсии в реальном времени. - **Отчёты в Slack и по электронной почте**: Получайте автоматические обновления по метрикам, важным для вашей команды. Изучите [флоу](adapty-flow-builder) или ознакомьтесь с [Аналитикой](charts). ## Я продаю через веб и мне нужно мобильное приложение \{#i-sell-on-the-web-and-need-a-mobile-app\} Если ваши пользователи уже платят через сайт, а вы добавляете мобильное приложение, Adapty синхронизирует покупки между платформами: - **Интеграция со Stripe и Paddle**: автоматическая синхронизация веб-покупок в Adapty. - **Синхронизация веб и мобайл**: пользователи, оплатившие на сайте, получают доступ в приложении, и наоборот. - **Единая кросс-платформенная аналитика**: веб и мобильная выручка в одном дашборде. Настройте [интеграцию со Stripe](stripe), [интеграцию с Paddle](paddle) или узнайте, как [синхронизировать подписчиков из веба и мобайла](sync-subscribers-from-web). ## Я мигрирую с другого инструмента \{#im-migrating-from-another-tool\} Adapty упрощает переход с других платформ управления подписками: - **Гайды по миграции**: пошаговые инструкции по переходу с других платформ подписок. - **Observer Mode**: сохраните существующий код биллинга и внедряйте Adapty постепенно с помощью [Observer mode](observer-vs-full-mode) — начните с аналитики и A/B-тестов, а затем расширяйте по мере готовности. - **Импорт исторических данных**: перенесите историю транзакций в Adapty, чтобы аналитика оставалась полной. Узнайте о [миграции на Adapty](migrate-to-adapty-from-another-solutions) и [импорте исторических данных](importing-historical-data-to-adapty). --- Всё ещё изучаете возможности? [Гайд по быстрому старту](quickstart) — отличное место для начала. --- # File: integrate-payments --- --- title: "Интеграция со сторами и платёжными платформами" description: "Интегрируйте Adapty с App Store, Google Play, кастомными сторами, Stripe и Paddle." --- Чтобы начать работу с Adapty, сначала настройте интеграцию со сторами, в которых ваши пользователи приобретают продукты. Adapty подключается к различным сторам и веб-платёжным провайдерам, собирая все встроенные покупки и аналитику в одном месте. ## Интеграция со сторами и веб-платежами \{#integrate-with-stores-and-web-payments\} Выберите свой стор ниже, чтобы перейти к детальным шагам интеграции: - [App Store](initial_ios) - [Google Play](initial-android) - Веб-платежи: - [Stripe](stripe) - [Paddle](paddle) - [Другие сторы](custom-store) ## Следующие шаги \{#next-steps\} После подключения стора или платёжной платформы можно перейти к [добавлению продуктов](quickstart-products). --- # File: quickstart-products --- --- title: "Добавление продуктов" description: "Добавьте продукты или подписки в Adapty и свяжите их с листингами App Store, Google Play, Stripe, Paddle или собственного стора." --- :::tip Настраиваете Adapty программно? Этот шаг можно выполнить с помощью [Developer CLI](developer-cli-quickstart). ::: Прежде чем использовать основные возможности Adapty, нужно добавить каждый продукт, который вы продаёте, и связать его со всеми сторами или платёжными платформами, которые вы поддерживаете. Это позволит доставлять продукты на устройства пользователей и отслеживать их в аналитике. В Adapty всё, что продаёт ваше приложение, — это **продукт**. Если один и тот же товар есть в App Store, Google Play или Stripe, их можно объединить в один продукт в Adapty. Настройте один раз и управляйте сразу на всех платформах. Давайте добавим ваш первый продукт. <Tabs groupId="products" queryString> <TabItem value="no-products" label="Продуктов в сторах ещё нет" default> <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/qUpC2XG-r5E?si=7Komyv4_PUQ4FaEH" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> </TabItem> <TabItem value="products-in-stores" label="Продукты в сторах уже есть"> <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/nlkdKCF0SwY?si=VVigzHcpv3waKJmI" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> </TabItem> </Tabs> ## Добавьте свой первый продукт \{#add-your-first-product\} :::tip В этом разделе описаны основные шаги для создания продукта. Подробнее — в гайде по [созданию продуктов](create-product). ::: Допустим, вы хотите добавить ежемесячную подписку как продукт. 1. Перейдите в раздел [Products](https://app.adapty.io/products) в главном меню Adapty. 2. Нажмите **Create product** в правом верхнем углу. <img src={require('./img/products-tab.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important **Дальнейшие шаги зависят от того, есть ли у вас уже продукты в App Store и/или Google Play:** ::: <Tabs groupId="products" queryString> <TabItem value="no-products" label="Продуктов в сторах ещё нет" default> :::important Перед началом убедитесь, что вы настроили интеграцию с [App Store](initial_ios) и/или [Google Play](initial-android). Для App Store — [добавьте API-ключ App Store Connect](app-store-connection-configuration#step-6-add-app-store-connect-api-key), чтобы Adapty мог публиковать продукты. ::: 3. Выберите **Create a new product and push to stores**. 4. Заполните данные о продукте: - **Product name**: название продукта, которое видите только вы в дашборде Adapty. - **Access Level**: уникальный идентификатор, определяющий, какие функции открываются после покупки. Если все платные пользователи получают одинаковый доступ, можно использовать уровень доступа по умолчанию: `premium`. Для более сложных сценариев создайте дополнительные [уровни доступа](access-level). - **Subscription duration**: выберите длительность подписки из списка. - **Weekly/Monthly/2 Months/3 Months/6 Months/Annual**: длительность подписки. - **Lifetime**: используйте пожизненный период для продуктов, которые открывают премиум-функции навсегда. - **Non-Subscriptions**: для продуктов, которые не являются подписками и не имеют срока действия. Могут использоваться для дополнительных функций, расходуемых покупок и т. д. - **Consumables**: расходуемые покупки можно приобретать несколько раз и использовать в течение жизненного цикла приложения. Примеры: игровая валюта, дополнения. Учтите, что расходуемые покупки не влияют на уровни доступа. - **Price (USD)**: цена продукта в долларах США. Она будет использована как базовая для автоматического расчёта цен по всем странам. Позже вы сможете [настроить цены для разных стран и регионов](edit-product#set-country-specific-prices). <img src={require('./img/create-product-push.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Нажмите **Save & Continue** и перейдите на вкладку **App Store** или **Google Play**, чтобы заполнить данные о продукте для стора. <Tabs> <TabItem value="App Store" label="App Store" default> - **Product ID**: придумайте постоянный уникальный идентификатор продукта. - **Product group**: выберите существующую группу продуктов, созданную в App Store Connect, или нажмите **Create new Product Group** и укажите её название и ID. После того как Adapty её создаст, вы сможете выбрать её из выпадающего списка. - **Screenshot**: загрузите скриншот покупки в приложении, на котором чётко виден товар или услуга. Этот скриншот используется только для ревью App Store и не отображается в нём публично. Требования к размеру и формату скриншота — [здесь](https://developer.apple.com/help/app-store-connect/reference/app-information/screenshot-specifications/). :::warning Если это ваш первый продукт для данного приложения, его нужно вручную отправить на ревью в App Store Connect. В дальнейшем этого не потребуется. После завершения ревью статус продукта в Adapty обновится автоматически. ::: </TabItem> <TabItem value="Google Play" label="Google Play" default> - **Base Product ID**: придумайте постоянный уникальный идентификатор продукта. - **Subscription**: выберите существующую группу подписок, созданную в Google Play Console, или нажмите **Create new Product Group** и укажите её название и ID. После того как Adapty её создаст, вы сможете выбрать её из выпадающего списка. </TabItem> </Tabs> 6. Для iOS настройте introductory offer — бесплатный пробный период — выбрав его **Free duration** из выпадающего списка. На этом этапе можно добавить бесплатный пробный период. После того как основной продукт пройдёт ревью в сторе, вы сможете [добавить другие офферы](offers) (например, promotional или win-back), привязав их существующие ID из консоли стора. :::important Introductory offer не синхронизируются с Google Play автоматически. В отличие от App Store, в Google Play нет отдельного типа «introductory offer» — пробные периоды и скидочные предложения настраиваются как **офферы** в базовом плане. [Создайте оффер в Google Play Console и привяжите его к продукту Adapty](google-play-offers). ::: </TabItem> <TabItem value="products-in-stores" label="Продукты в сторах уже есть"> 3. Выберите **Connect an existing store product**. 4. Заполните данные о продукте: - **Product name**: название продукта, которое видите только вы в дашборде Adapty. - **Access level ID**: уникальный идентификатор, определяющий, какие функции открываются после покупки. Если все платные пользователи получают одинаковый доступ, можно использовать уровень доступа по умолчанию: `premium`. Для более сложных сценариев создайте дополнительные [уровни доступа](access-level). - **Subscription duration**: выберите длительность подписки из списка. - **Weekly/Monthly/2 Months/3 Months/6 Months/Annual**: длительность подписки. - **Lifetime**: используйте пожизненный период для продуктов, которые открывают премиум-функции навсегда. - **Non-Subscriptions**: для продуктов, которые не являются подписками и не имеют срока действия. Могут использоваться для дополнительных функций, расходуемых покупок и т. д. - **Consumables**: расходуемые покупки можно приобретать несколько раз и использовать в течение жизненного цикла приложения. Примеры: игровая валюта, дополнения. Учтите, что расходуемые покупки не влияют на уровни доступа. - **Price (USD)**: цена продукта в долларах США. Если продукт уже есть в сторе, это значение не влияет на его реальную цену — можно выбрать любое из списка. Позже вы сможете [настроить цены для разных регионов](edit-product#set-country-specific-prices) прямо в дашборде Adapty. <img src={require('./img/product-info.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <br /> 5. Добавьте данные о сторе. Выберите свой стор: <Tabs> <TabItem value="App Store" label="App Store" default> - **App Store Product ID**: уникальный идентификатор для доступа к продукту на устройствах. Если вы не можете его найти, проверьте, что ID верный и принадлежит нужному приложению. </TabItem> <TabItem value="Google Play" label="Google Play" default> - **Google Play Product ID**: идентификатор продукта из Play Store. Выберите его из списка существующих ID. Если вы не можете его найти, проверьте, что ID верный и принадлежит нужному приложению. - **Base plan ID**: ID базового плана для продукта в Play Store. - **Legacy fallback product**: резервный продукт, используемый исключительно для приложений на старых версиях Adapty SDK (2.5 и ниже). Укажите значение в формате `<subscription_id>:<base_plan_id>`. :::important Introductory offer не синхронизируются с Google Play автоматически. В отличие от App Store, в Google Play нет отдельного типа «introductory offer» — пробные периоды и скидочные предложения настраиваются как **офферы** в базовом плане. [Создайте оффер в Google Play Console и привяжите его к продукту Adapty](google-play-offers). ::: <details> <summary>Нажмите, чтобы узнать, где найти Google Play Product ID и Base plan ID.</summary> 1. Перейдите в раздел **Monetize with Play > Products > Subscriptions** в вашем аккаунте [Google Play Console](https://play.google.com/console/developers/android/app). 2. Откройте нужную **подписку**. 3. Product ID будет в разделе **Subscription details**, а Base plan ID — в столбце **ID and duration** раздела **Base plans and offers**. <img src={require('./img/play-store-id.png').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </details> </TabItem> <TabItem value="Stripe" label="Stripe" default> - **Stripe Product ID**: уникальный идентификатор продукта в Stripe. - **Stripe Price ID**: уникальный идентификатор цены, связанной с продуктом, в Stripe. <details> <summary>Нажмите, чтобы узнать, где найти Stripe Product ID и Price ID.</summary> 1. Перейдите в [Product Catalog](https://dashboard.stripe.com/products?active=true) в Stripe. 2. Откройте нужный продукт. 3. Вы увидите: - Stripe Product ID (выглядит как `prod_...`) — в правом верхнем углу. - Stripe Price ID (выглядит как `price_...`) — в столбце **API ID** раздела **Pricing**. <img src={require('./img/product-stripe.png').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </details> </TabItem> <TabItem value="Paddle" label="Paddle" default> - **Paddle Product ID**: уникальный идентификатор продукта в Paddle. - **Paddle Price ID**: уникальный идентификатор цены, связанной с продуктом, в Paddle. <details> <summary>Нажмите, чтобы узнать, где найти Paddle Product ID и Price ID.</summary> 1. Перейдите в [Product Catalog](https://vendors.paddle.com/products-v2) в Paddle. 2. Откройте нужный продукт. 3. Вы увидите: - Paddle Product ID (выглядит как `pro_...`) — в разделе **Additional details**. - Paddle Price ID (выглядит как `pri_...`) — в столбце **ID** раздела **Prices**. <img src={require('./img/paddle-product-price.webp').default} style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </details> </TabItem> <TabItem value="Custom" label="Custom store" default> Вы можете выбрать существующий custom store или добавить новый и привязать к нему продукт. Обратите внимание: Adapty отслеживает транзакции только из App Store, Google Play и Stripe. Для custom store транзакции нужно передавать через серверный API Adapty — метод [Set transaction](api-adapty/operations/setTransaction). </TabItem> </Tabs> 6. При необходимости вы можете [создать офферы](create-offer) для продукта. Нажмите **Yes, add offers**, чтобы добавить их, или **No, thanks**, чтобы пропустить. Продукт появится в списке продуктов. <img src={require('./img/created-product.png').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </TabItem> </Tabs> ## Следующие шаги \{#next-steps\} После того как вы добавили продукты в Adapty, можно переходить к [настройке пейволов](quickstart-paywalls) — это единственный способ начать их продавать. --- # File: quickstart-paywalls --- --- title: "Включение покупок" description: "Добавьте флоу или пейвол в Adapty, чтобы показывать продукты, а затем привяжите его к плейсменту." --- :::info Чтобы следовать этому гайду, убедитесь, что вы завершили [интеграцию со стором](integrate-payments) и создали хотя бы один продукт, как описано в предыдущем [гайде по добавлению продуктов](quickstart-products). ::: Теперь, когда у вас есть продукты, нужно показать их пользователям. Adapty предлагает три варианта: - **Flow Builder (рекомендуется)**: визуальный редактор без кода для всего пути покупки. SDK рендерит результат нативно, и писать UI-код не нужно. - **Пейвол вручную**: вы создаёте пейвол, прикрепляете к нему продукты и отрисовываете UI самостоятельно в коде приложения. - **Adapty Paywall Builder (Legacy)**: редактор пейволов без кода. Оба варианта заканчиваются одинаково: вы привязываете созданное к [плейсменту](placements). Именно плейсмент вызывает приложение в рантайме, чтобы получить нужный контент для нужного пользователя. <Tabs groupId="purchase-setup" queryString> <TabItem value="flow-builder" label="Используйте Flow Builder" default> :::important Flow Builder в настоящее время поддерживает iOS, Android, React Native, Flutter и Capacitor SDK v4 и выше. Поддержка других платформ появится в ближайшее время. ::: Флоу — это один или несколько экранов с встроенными продуктами. Вы создаёте его в [Flow Builder](adapty-flow-builder) — без написания кода. SDK Adapty отображает флоу нативно на каждой платформе. Ваше приложение вызывает `getFlow`, SDK показывает экраны, обрабатывает покупки и отправляет события. Никакого отдельного UI-кода, никакого пейвола, который нужно поддерживать параллельно. ## 1. Создайте флоу \{#1-build-the-flow\} 1. Перейдите в раздел [**Flows**](https://app.adapty.io/flows) главного меню Adapty. 2. Нажмите **Create flow** и создайте свой флоу. Подробнее о [Adapty Flow Builder](adapty-flow-builder). Шаблонные гайды ниже разбирают самые распространённые сценарии шаг за шагом: <CustomDocCardList ids={['basic-paywall-screen', 'show-plans-bottom-sheet', 'paywall-with-tabs', 'paywall-features-per-product', 'onboarding-flow-tutorial']} /> Как только флоу сохранён и опубликован, переходите к его привязке к плейсменту. :::warning Не забудьте опубликовать флоу! Без публикации его нельзя добавить в плейсмент. ::: ### 2. Добавьте флоу в плейсмент \{#2-add-the-flow-to-a-placement\} Создайте <InlineTooltip tooltip="плейсмент">Плейсмент — это конкретная точка в приложении, где вы показываете флоу, пейвол, онбординг или A/B-тест. Плейсменты позволяют показывать контент нужной [аудитории](audience). Подробнее о [плейсментах](placements).</InlineTooltip>, чтобы приложение могло запрашивать флоу во время выполнения. Начнём с самого важного — плейсмента онбординга. Позже вы сможете добавить другие [значимые плейсменты](choose-meaningful-placements) в пользовательском пути. 1. Перейдите в раздел [**Placements**](https://app.adapty.io/placements) в главном меню Adapty и откройте вкладку **Flows**. 2. Нажмите **Create placement**. 3. Введите **Placement name** (например, `main` или `onboarding`). Это внутренний идентификатор в дашборде Adapty. 4. Введите **Placement ID**. Этот ID вы будете использовать в SDK Adapty для загрузки флоу плейсмента. 5. Нажмите **Run flow** и выберите только что созданный флоу. 6. Нажмите **Save & publish**. В коде приложения вы хардкодите только идентификаторы плейсментов. Всё остальное — какой флоу запускается, какие продукты он продаёт, как выглядит — настраивается в дашборде Adapty и может быть изменено в любой момент без обновления приложения. :::tip Adapty позволяет показывать разные флоу разным группам пользователей и анализировать эффективность. Подробнее об [аудиториях](audience) и [A/B-тестах](ab-tests). ::: </TabItem> <TabItem value="manual-paywall" label="Реализовать пейвол вручную"> <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/e4o7Z2tUGL8?si=ipwbW3VVN0fIg0R0" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> Пейвол — это удалённо настраиваемый контейнер для одного или нескольких продуктов. Adapty передаёт список продуктов и опциональный [Remote Config](customize-paywall-with-remote-config) в формате JSON — ваш код в приложении считывает их и отрисовывает интерфейс. :::tip Настраиваете Adapty программно? Этот шаг можно выполнить с помощью [Developer CLI](developer-cli-quickstart). ::: ### 1. Создайте пейвол 1. Перейдите в раздел [**Paywalls**](https://app.adapty.io/paywalls) в главном меню Adapty. 2. Нажмите **Create paywall**. 3. Введите **Paywall name** — это внутренний идентификатор в дашборде Adapty. 4. Нажмите **Add product** и выберите продукты, которые будут отображаться на пейволе. 5. (Опционально) Откройте вкладку **Remote config** и добавьте нужные вашему приложению JSON-данные (заголовки, тексты, флаги функций). Подробнее см. в разделе [Проектирование пейвола с Remote Config](customize-paywall-with-remote-config). 6. Нажмите **Create as a draft**, затем опубликуйте, когда будете готовы. <img src="/assets/shared/img/quickstart-paywall.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Этот пейвол отображается в коде вашего приложения. <InlineTooltip tooltip="реализация пейволов вручную">Воспользуйтесь гайдом для вашей платформы: [iOS](ios-implement-paywalls-manually), [Android](android-implement-paywalls-manually), [React Native](react-native-implement-paywalls-manually), [Flutter](flutter-implement-paywalls-manually), [Unity](unity-implement-paywalls-manually).</InlineTooltip> ### 2. Добавьте пейвол в плейсмент \{#2-add-the-paywall-to-a-placement\} Создайте <InlineTooltip tooltip="плейсмент">Плейсмент — это конкретная точка в вашем приложении, где показывается флоу, пейвол, онбординг или A/B-тест. Плейсменты позволяют показывать контент определённым [аудиториям](audience). Подробнее о [плейсментах](placements).</InlineTooltip>, чтобы приложение могло запрашивать пейвол во время выполнения. Начнём с самого важного — плейсмента для онбординга. Позже вы сможете добавить больше [значимых плейсментов](choose-meaningful-placements) на разных этапах пути пользователя. 1. Перейдите в раздел [**Placements**](https://app.adapty.io/placements) главного меню Adapty и откройте вкладку **Paywalls**. 2. Нажмите **Create placement**. 3. Введите **Placement name** (например, `main` или `onboarding`). Это внутренний идентификатор в дашборде Adapty. 4. Введите **Placement ID**. Этот идентификатор используется в SDK для загрузки пейвола плейсмента. 5. Нажмите **Run paywall** и выберите только что созданный пейвол. 6. Нажмите **Save & publish**. В коде приложения вы жёстко прописываете только идентификаторы плейсментов. Всё остальное — какой пейвол показывается, какие продукты он продаёт, Remote Config — настраивается в дашборде Adapty и может быть изменено в любой момент без обновления приложения. <img src="/assets/shared/img/add-placement.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::tip Adapty позволяет показывать разные пейволы разным группам пользователей и анализировать эффективность. Узнайте больше об [аудиториях](audience) и [A/B-тестах](ab-tests). ::: </TabItem> <TabItem value="paywall-builder" label="Adapty Paywall Builder (Legacy)"> Пейвол, созданный в [Paywall Builder](adapty-paywall-builder), — это no-code экран с продуктами, встроенными напрямую. SDK Adapty отрисовывает его нативно, поэтому писать UI-код не нужно. :::warning Paywall Builder полностью функционален, но Adapty больше не добавляет в него новые функции и не выпускает обновления. Для новых проектов используйте [Flow Builder](adapty-flow-builder). ::: ### 1. Создайте пейвол \{#build-the-paywall\} 1. Перейдите в раздел [**Paywalls**](https://app.adapty.io/paywalls) главного меню Adapty. 2. Нажмите **Create paywall**. 3. Введите **Paywall name** — это внутренний идентификатор в дашборде Adapty. 4. Нажмите **Add product** и выберите продукты, которые будут отображаться на пейволе. 5. Откройте вкладку **Builder & Generator**. Создайте пейвол на основе шаблона или сгенерируйте его с помощью ИИ. 6. Включите переключатель **Show on device**, чтобы SDK мог отображать пейвол. ### 2. Добавьте пейвол в плейсмент \{#2-add-the-paywall-to-a-placement\} Создайте <InlineTooltip tooltip="плейсмент">Плейсмент — это конкретная точка в вашем приложении, где отображается флоу, пейвол, онбординг или A/B-тест. Плейсменты позволяют показывать контент определённым [аудиториям](audience). Подробнее о [плейсментах](placements).</InlineTooltip>, чтобы приложение могло запрашивать пейвол во время выполнения. 1. Перейдите в раздел [**Placements**](https://app.adapty.io/placements) главного меню Adapty и откройте вкладку **Paywalls**. 2. Нажмите **Create placement**. 3. Введите **Placement name** (например, `main` или `onboarding`). Это внутренний идентификатор в дашборде Adapty. 4. Введите **Placement ID**. Этот идентификатор используется в SDK для загрузки пейвола плейсмента. 5. Нажмите **Run paywall** и выберите созданный пейвол. 6. Нажмите **Save & publish**. В коде приложения вы хардкодите только идентификаторы плейсментов. Всё остальное — какой пейвол запущен, какие продукты он предлагает, как он выглядит — настраивается в дашборде Adapty и может быть изменено в любой момент без обновления приложения. </TabItem> </Tabs> ## Следующие шаги \{#next-steps\} Теперь у вас есть что отдавать SDK. Далее [интегрируйте SDK](quickstart-sdk) в своё приложение и начните получать плейсмент. --- # File: quickstart-sdk --- --- title: "Интеграция Adapty SDK в код приложения" description: "Интегрируйте Adapty с App Store, Google Play, пользовательскими сторами, Stripe и Paddle." --- Интегрируйте Adapty SDK в своё приложение, чтобы: - Обрабатывать покупки, валидацию чеков и управление подписками прямо из коробки - Создавать и тестировать пейволы без обновления приложения - Получать подробную аналитику покупок без дополнительной настройки — когорты, LTV, отток и воронки включены - Поддерживать актуальный статус подписки пользователя во всех сессиях и на всех устройствах - Интегрировать приложение с сервисами маркетинговой атрибуции и аналитики буквально в одну строку кода ## Как это работает \{#how-does-it-work\} Для базовой реализации Adapty SDK нужно сделать всего три вещи: 1. Установить и инициализировать SDK. 2. Передать обработку встроенных покупок Adapty. 3. Отслеживать статус подписки в профиле. Adapty определяет статус подписки, её тип и срок действия — SDK просто получает эту информацию. Порядок и детали могут отличаться в зависимости от приложения, но в целом это всё. ## Начало работы \{#get-started\} Выберите платформу и приступайте: **iOS** - **[Быстрый старт с SDK](ios-sdk-overview)** - **[Примеры приложений](https://github.com/adaptyteam/AdaptySDK-iOS/tree/master/Examples)** **Android** - **[Быстрый старт с SDK](android-sdk-overview)** - **[Пример приложения](https://github.com/adaptyteam/AdaptySDK-Android/tree/master/app)** **React Native** - **[Быстрый старт с SDK](react-native-sdk-overview)** - **[Примеры приложений](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/)** **Flutter** - **[Быстрый старт с SDK](flutter-sdk-overview)** - **[Пример приложения](https://github.com/adaptyteam/AdaptySDK-Flutter/tree/master/example)** **Unity** - **[Быстрый старт с SDK](unity-sdk-overview)** - **[Пример приложения](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Assets)** **Capacitor** - **[Быстрый старт с SDK](capacitor-sdk-overview)** - **[Примеры приложений](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples)** **Kotlin Multiplatform**: - **[Быстрый старт с SDK](kmp-sdk-overview)** - **[Пример приложения](https://github.com/adaptyteam/AdaptySDK-KMP/tree/main/example)** ## Следующие шаги \{#next-steps\} После настройки Adapty SDK в коде приложения можно перейти к [тестированию реализации](quickstart-test). --- # File: quickstart-test --- --- title: "Тестирование интеграции с Adapty" description: "Быстро проверьте интеграцию с Adapty: активация SDK, загрузка пейволов и встроенные покупки в App Store, Google Play, Stripe и Paddle." --- Всё готово! Теперь убедитесь, что интеграция работает корректно и покупки отображаются в дашборде Adapty. Лучший способ проверить интеграцию от начала до конца — выполнить тестовую покупку, а затем проверить результаты. ## 1. Тестирование встроенных покупок \{#1-test-in-app-purchases\} Следуйте инструкции для вашего стора или платёжной платформы. ### App store \{#app-store\} Рекомендуем использовать тестовый аккаунт (Sandbox Apple ID) и проводить тестирование на реальном устройстве. Подробнее обо всех шагах читайте в статье о [тестировании в App Store Sandbox](test-purchases-in-sandbox). :::warning Тестируйте на реальном устройстве для получения наиболее надёжных результатов. Тестирование на симуляторе возможно, но мы его не рекомендуем — оно менее надёжно. ::: ### Google Play Store \{#google-play-store\} Создайте тестового пользователя и протестируйте приложение на реальном устройстве. Подробнее обо всех шагах читайте в статье о [тестировании в Google Play Store](testing-on-android). :::note Google [рекомендует](https://support.google.com/googleplay/android-developer/answer/14316361) использовать реальное устройство для тестирования. Если вы всё же решите использовать эмулятор, убедитесь, что на нём установлен Google Play — это необходимо для корректной работы приложения. ::: ### Stripe \{#stripe\} Для тестирования покупок в Stripe необходимо подключить Stripe к Adapty с помощью API-ключа для тестового режима Stripe. Транзакции, совершённые в тестовом режиме Stripe, будут считаться Sandbox-транзакциями в Adapty. Подробнее обо всех шагах подключения читайте в [статье об интеграции со Stripe](stripe#6-test-your-integration). ### Paddle \{#paddle\} Для тестирования покупок в Paddle необходимо подключить Paddle к Adapty с помощью API-ключа для тестовой среды Paddle. Транзакции, совершённые в тестовой среде Paddle, будут считаться тестовыми в Adapty. Подробнее обо всех шагах подключения читайте в [статье об интеграции с Paddle](paddle#4-test-your-integration). ## 2. Проверка тестовых покупок \{#2-validate-test-purchases\} После выполнения тестовой покупки проверьте наличие соответствующей транзакции в [**Event Feed**](https://app.adapty.io/event-feed) дашборда Adapty. Если покупка не отображается в **Event Feed**, Adapty её не отслеживает. Подробнее читайте в гайде по [проверке тестовых покупок](validate-test-purchases). <img src="/assets/shared/img/test-event-feed.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Следующие шаги \{#next-steps\} Поздравляем с успешным онбордингом в Adapty! Теперь вы готовы развивать встроенные покупки. Подготовьтесь к релизу в продакшн: <Button id="release-checklist"> Чеклист для релиза </Button> Или продолжите работу с перечисленными ниже разделами: - **[A/B-тестирование](ab-tests)**: Экспериментируйте с ценами, длительностью подписок, пробными периодами и визуальными элементами, чтобы найти наиболее эффективные комбинации. - **[Аналитика](how-adapty-analytics-works)**: Изучайте детальные метрики монетизации, чтобы понять поведение пользователей и оптимизировать доход. - **Интеграции**: Adapty отправляет [события подписки](events) в сторонние инструменты аналитики и атрибуции, такие как [Amplitude](amplitude), [AppsFlyer](appsflyer), [Adjust](adjust), [Branch](branch), [Mixpanel](mixpanel), [Facebook Ads](facebook-ads), [AppMetrica](appmetrica), а также в пользовательский [Webhook](webhook). :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: --- # File: release-checklist --- --- title: "Чеклист перед релизом" description: "Следуйте чеклисту Adapty, чтобы обеспечить плавный процесс обновления приложения." --- Рады, что вы выбрали Adapty! Надеемся, интеграция прошла гладко. Этот гайд поможет убедиться, что приложение готово к публикации в сторах, а монетизация работает корректно. ## Что нужно подготовить заранее \{#pre-flight-essentials\} Перед началом проверки убедитесь, что у вас есть: - Реальное устройство с sandbox-аккаунтом - Доступ к дашборду Adapty - Доступ к App Store Connect / Google Play Console :::note Хотя sandbox-покупки можно тестировать на симуляторах, реальные устройства необходимы для полноценного тестирования всех сценариев — включая диалоги оплаты и биометрическую аутентификацию. ::: <Button id="test-purchases-in-sandbox"> Гайд по тестированию для App Store </Button> <Button id="testing-on-android"> Гайд по тестированию для Google Play </Button> ## Универсальные проверки \{#universal-validations\} - [ ] **Подключение стора**: Убедитесь, что вы подключили Adapty к App Store и/или Google Play: - [ ] [App Store](initial_ios) - [ ] [Google Play](initial-android) - [ ] **Доставка событий подписки**: Убедитесь, что серверные уведомления настроены: - [ ] [Серверные уведомления App Store](enable-app-store-server-notifications) - [ ] [Уведомления разработчика в реальном времени (RTDN)](enable-real-time-developer-notifications-rtdn) - [ ] **Идентификация профиля**: Проверьте логику идентификации пользователей и убедитесь, что покупки привязываются к нужному профилю: - [ ] [Убедитесь, что логика идентификации в коде приложения соответствует вашему сценарию использования](ios-quickstart-identify) - [ ] [Убедитесь, что вы понимаете логику parent/inheritor при совместном использовании платного доступа между профилями пользователей](sharing-paid-access-between-user-accounts) - [ ] **Офферы**: Если в приложении используются promotional offer из App Store, убедитесь, что вы [добавили ключ встроенных покупок](app-store-connection-configuration#step-4-for-trials-and-special-offers--set-up-promotional-offers) как в основное поле, так и в раздел **App Store promotional offers**. - [ ] **Сбор данных**: Убедитесь в соответствии требованиям конфиденциальности: - [ ] Если вам необходимо соответствовать требованиям законодательства о конфиденциальности (например, GDPR или CCPA) или приложение предназначено для детей, управляйте тем, [включён ли сбор и передача IDFA и IP-адреса](sdk-installation-ios#data-policies). - [ ] Если в приложении используется AppTrackingTransparency, убедитесь, что вы [передаёте статус авторизации в Adapty](ios-deal-with-att). - [ ] **Метки конфиденциальности**: [Узнайте подробнее](apple-app-privacy) о том, какие данные собирает Adapty и какие флаги нужно выставить при проверке. ## Проверка покупок \{#purchase-validations\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Перед запуском убедитесь, что покупки в вашем приложении работают корректно и пейвол готов к ревью в сторе. Способ проверки встроенных покупок зависит от того, как вы их реализовали: - Вы отображаете пейвол, созданный в Adapty Paywall Builder - Вы реализовали собственный пейвол и используете метод `makePurchase` внутри него для обработки покупок - Вы используете Adapty в режиме наблюдателя (как с Adapty Paywall Builder, так и с собственным пейволом) <Tabs groupId="paywall" queryString> <TabItem value="builder" label="Adapty Paywall Builder" default> **Цель**: Adapty отображает пейвол, пользователи могут покупать продукты, доступ открывается, а флоу восстановления покупок работает. - [ ] Ваше приложение [отображает пейвол](ios-present-paywalls) из того же плейсмента, который вы будете выпускать. - [ ] Пейвол отображается на экране. Если загрузка занимает слишком много времени (например, при нестабильном интернете у вас или ваших пользователей), рассмотрите возможность [настройки политики загрузки](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder). - [ ] Пейвол соответствует ожидаемому варианту (аудитория/локаль, если применимо). При необходимости вы можете [изменить приоритет аудитории](change-audience-priority). - [ ] Продукты и цены отображаются на пейволе. Обратите внимание, что API Apple иногда может предоставлять некорректные цены во время тестирования (особенно при различных региональных настройках), поэтому уделяйте приоритет тестированию функциональности процесса покупки, а не точности цен, — Adapty не влияет на цены в сторе. - [ ] Покупка в песочнице завершается успешно. Получен коллбэк об успешной покупке. - [ ] Доступ открывается и сохраняется. Убедитесь, что [платный доступ предоставляется на основе текущего профиля Adapty](ios-check-subscription-status#connect-profile-with-paywall-logic). - [ ] После покупки профиль Adapty содержит активный уровень доступа. - [ ] Платные функции открываются, когда профиль содержит этот уровень доступа (а не только по коллбэку покупки). - [ ] Восстановление покупок работает. При переустановке приложения или установке на новое устройство автоматическое восстановление покупок работает согласно настройке [Sharing paid access](sharing-paid-access-between-user-accounts). Если у вас нет бэкенд-аутентификации, покупки восстанавливаются автоматически независимо от настройки. В остальных случаях убедитесь, что пользователи могут восстановить покупки после переустановки приложения. - [ ] Требования для ревью стора: - [ ] Кнопка **Restore purchases** присутствует на пейволе. Вы можете добавить её в Paywall Builder, и при нажатии она будет автоматически обрабатывать восстановление покупок. - [ ] Условия использования и политика конфиденциальности доступны с экрана пейвола, а нажатие на эти ссылки открывает их в браузере. </TabItem> <TabItem value="makepurchase" label="Пользовательский пейвол (makePurchase)" default> **Цель**: вы отрисовываете UI; Adapty обрабатывает покупки, обновления профиля и восстановления. - [ ] Идентификаторы продуктов не зашиты в коде приложения. В коде хардкодятся только идентификаторы [плейсментов](placements). - [ ] Приложение [загружает продукты](fetch-paywalls-and-products) из того же плейсмента, который будет в проде. - [ ] Список продуктов загружается успешно. Если загрузка занимает слишком много времени (например, при нестабильном интернете у вас или пользователей), рассмотрите возможность [изменить политику загрузки](fetch-paywalls-and-products#fetch-paywall-information). - [ ] Загруженные продукты соответствуют ожидаемому варианту (аудитории/локали, если применимо). При необходимости можно [изменить приоритет аудитории](change-audience-priority). - [ ] Продукты и цены отображаются на пейволе. Обратите внимание: Apple API иногда возвращает неточные цены в процессе тестирования (особенно при разных региональных настройках), поэтому при тестировании важнее проверить корректность самого процесса покупки, а не точность цен — на цены в сторе Adapty не влияет. - [ ] Покупка в [песочнице](making-purchases) через `makePurchase` завершается успешно: - [ ] Успешный результат покупки обрабатывается. - [ ] Статусы «ожидание», «ошибка» и «отмена» обрабатываются корректно. - [ ] Если вы [используете Remote Config](present-remote-config-paywalls), его значения корректно передаются на пейвол. - [ ] При показе пейвола вызывается метод [`logShowFlow` (iOS SDK v4+) / `logShowPaywall`](present-remote-config-paywalls#track-paywall-view-events). - [ ] Покупка в песочнице завершается успешно. Получен коллбэк об успешной покупке. - [ ] Доступ разблокируется и сохраняется. Убедитесь, что [платный доступ предоставляется на основе текущего профиля Adapty](ios-check-subscription-status#connect-profile-with-paywall-logic). - [ ] После покупки в профиле Adapty активен соответствующий уровень доступа. - [ ] Платные функции разблокируются при наличии этого уровня доступа в профиле, а не только по коллбэку покупки. - [ ] Восстановление покупок работает. При переустановке приложения или установке на новом устройстве автоматическое восстановление покупок работает в соответствии с настройкой [общего доступа к покупкам](sharing-paid-access-between-user-accounts). Если у вас нет серверной аутентификации, покупки восстанавливаются автоматически независимо от этой настройки. В остальных случаях убедитесь, что пользователи могут восстановить покупки после переустановки приложения. - [ ] Требования для ревью в сторе: - [ ] Кнопка **Restore purchases** доступна и [корректно обрабатывает восстановление покупок](restore-purchase). - [ ] Пользовательское соглашение и политика конфиденциальности доступны с экрана пейвола, а нажатие на соответствующие ссылки открывает их в браузере. </TabItem> <TabItem value="observer" label="Режим наблюдателя"> **Цель**: Вы самостоятельно обрабатываете покупки, обновления профиля и восстановление; Adapty получает отчёты о транзакциях. - [ ] **Ваше приложение завершает покупки через собственный флоу покупки** (StoreKit / BillingClient / бэкенд): - [ ] Покупка в песочнице успешно проходит в интерфейсе стора. - [ ] Незавершённые/неудачные/отменённые сценарии корректно обрабатываются в приложении. - [ ] **Транзакции передаются в Adapty**. - [ ] Observer mode [включён в коде приложения](implement-observer-mode). - [ ] Покупка отображается в ленте событий Adapty. - [ ] Обновления, отмены и возвраты отражаются со временем (при наличии). - [ ] **Отслеживаются просмотры пейвола**. Метод [`logShowFlow` (iOS SDK v4+) / `logShowPaywall`](present-remote-config-paywalls#track-paywall-view-events) вызывается при показе пейвола. - [ ] **Восстановление покупок работает в вашей реализации**. Переустановка приложения или смена устройства корректно восстанавливает доступ. - [ ] **Требования к проверке стором**: - [ ] Действие **Restore purchases** доступно и запускает флоу восстановления. - [ ] Условия использования и политика конфиденциальности доступны с пейвола или экрана покупки и открываются в браузере. </TabItem> </Tabs> Если у вас возникнут вопросы по интеграции SDK, воспользуйтесь AI-чатботом в правом нижнем углу или напишите нам на [support@adapty.io](mailto:support@adapty.io). --- # File: observer-vs-full-mode --- --- title: "Observer mode" description: "Сравнение Observer Mode и Full Mode в Adapty для подписок." --- Adapty — мощная и гибкая платформа для встроенных покупок, созданная для увеличения выручки и базы подписчиков. Adapty предоставляет настраиваемые пейволы для конкретных сегментов пользователей, A/B-тесты цен, длительностей, пробных периодов и визуальных элементов, а также комплексные аналитические инструменты для монетизации приложения и интеграции со сторонними сервисами. Если у вас уже есть собственная инфраструктура покупок и вы не готовы переходить на систему Adapty, можно использовать Observer mode. Этот ограниченный режим не задействует пейволы Adapty, таргетирование аудитории, управление подписками (включая обработку продлений и повторных попыток оплаты) и ориентирован исключительно на аналитику. Несмотря на ограничения, Observer mode предлагает широкие аналитические возможности: интеграцию с системами атрибуции, расширенную аналитику, инструменты для коммуникаций и CRM-профили. Оба режима стоят одинаково и требуют обновления мобильного приложения, поэтому выбор сводится к следующему: перейти на инфраструктуру Adapty ради полной функциональности или сохранить текущую инфраструктуру, получив только сторонние интеграции и аналитику. | Функциональность | Observer mode | Full mode | |-------------|-------------|---------| | **Комплексная аналитика** | ✅ | ✅ | | **Сторонние интеграции** | ✅ | ✅ | | **Реагирование на события покупок для предоставления/ограничения платного доступа пользователям** | ❌ | ✅ | | **Управление инфраструктурой покупок** | Вы | Adapty | | **A/B-тестирование** | <p>Возможно, но требует значительно большего объёма дополнительного кода и настройки, чем в Full Mode.</p> | ✅ | | **Время внедрения** | <p>Для аналитики и интеграций: менее часа</p><p>С A/B-тестами: до недели с учётом тщательного тестирования</p> | Несколько часов | ## Как работает Observer mode \{#how-observer-mode-works\} В Observer mode вы передаёте новые транзакции от Apple/Google в Adapty SDK, а SDK перенаправляет их на бэкенд Adapty. При этом вы самостоятельно управляете доступом к платному контенту в приложении, завершаете транзакции, обрабатываете продления, решаете проблемы с оплатой и так далее. ## Как настроить Observer mode \{#how-to-set-up-observer-mode\} 1. Выполните начальную интеграцию Adapty [с Google Play](initial-android) и [с App Store](initial_ios). 2. Включите Observer mode при настройке Adapty SDK, установив параметр `observerMode` в значение `true`. Следуйте инструкциям по настройке для [iOS](sdk-installation-ios#activate-adapty-module-of-adapty-sdk), [Android](sdk-installation-android#activate-adapty-module-of-adapty-sdk), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter#activate-adapty-module-of-adapty-sdk), [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform#activate-adapty-sdk) и [Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk). 3. [Передайте транзакции](report-transactions-observer-mode) из вашей существующей инфраструктуры покупок в Adapty для iOS и кросс-платформенных фреймворков на базе iOS. 4. (Опционально) Если вы хотите использовать сторонние интеграции, настройте их, как описано в разделе [Настройка сторонних интеграций](configuration). :::warning В Observer mode Adapty SDK не завершает транзакции — убедитесь, что вы обрабатываете этот аспект самостоятельно. ::: ## Как использовать пейволы и A/B-тесты в Observer mode \{#how-to-use-paywalls-and-ab-tests-in-observer-mode\} В Observer mode Adapty SDK не может определить источник покупок, поскольку они совершаются в вашей собственной инфраструктуре. Поэтому, если вы планируете использовать пейволы и/или A/B-тесты в Observer mode, необходимо связывать транзакцию из стора с соответствующим пейволом в коде мобильного приложения при передаче транзакции. Кроме того, пейволы, созданные с помощью Paywall Builder, должны отображаться особым образом при использовании Observer mode: - Отображайте пейволы в Observer mode для [iOS](implement-observer-mode) или [Android](android-present-paywall-builder-paywalls-in-observer-mode). - [Связывайте пейволы с транзакциями покупок](report-transactions-observer-mode) при передаче транзакций в Observer mode. --- # File: migration-from-revenuecat --- --- title: "Миграция с RevenueCat" description: "Мигрируйте с RevenueCat на Adapty по нашему пошаговому гайду." --- Миграция состоит из 5 логических шагов и занимает в среднем 2 часа. 90% всех миграций укладываются менее чем в один рабочий день. 1. Изучите ключевые отличия; создайте и настройте аккаунт Adapty _(5 минут)_; 2. Установите Adapty SDK для вашей платформы ([iOS](sdk-installation-ios), [Android](sdk-installation-android), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter), [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform), [Unity](sdk-installation-unity)) вместо RevenueCat SDK _(1 час)_; 3. Настройте [уведомления сервера Apple App Store](enable-app-store-server-notifications) для Adapty и (опционально) [пересылку raw-событий](enable-app-store-server-notifications#raw-events-forwarding) _(5 минут)_; 4. Протестируйте и выпустите обновление приложения _(30 минут);_ 5. (Опционально) Запросите у поддержки RevenueCat исторические данные в формате CSV _(5 минут);_ 6. (Опционально) Импортируйте исторические данные через поддержку Adapty _(30 минут)_. :::info Подписчики мигрируют автоматически Все пользователи, когда-либо активировавшие подписку, автоматически появятся в Adapty, как только откроют новую версию приложения с Adapty SDK. Валидация статуса подписки и доступ к премиум-контенту будут восстановлены автоматически. ::: Перед выпуском новой версии приложения с Adapty SDK обязательно ознакомьтесь с нашим [чеклистом перед релизом](release-checklist). ## Изучите ключевые отличия; создайте и настройте аккаунт Adapty \{#learn-the-core-differences-create-and-prepare-an-adapty-account\} SDK Adapty и RevenueCat устроены схожим образом. Главное отличие — в сетевом взаимодействии и скорости: Adapty SDK разработан так, чтобы как можно быстрее предоставлять информацию по запросу. Например, при запросе пейвола вы сначала получаете [Remote Config](customize-paywall-with-remote-config) для предварительной сборки онбординга или пейвола, а затем запрашиваете продукты отдельным запросом. Терминология немного отличается: | RevenueCat | Adapty | | :---------- | :-------------- | | Package | Product | | Offering | Paywall | | Paywall | Paywall Builder | | Entitlement | Access level | В Adapty есть концепция [плейсмента](placements). Это логическое место внутри приложения, где пользователь может совершить покупку. В большинстве случаев плейсментов один или два: - Онбординг (80% всех покупок происходит именно там); - Общий (показывается в настройках или внутри приложения после онбординга). <img src="/assets/shared/img/2406d97-image.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '300px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Установите Adapty SDK и замените RevenueCat SDK \{#install-adapty-sdk-and-replace-revenuecat-sdk\} Установите Adapty SDK для вашей платформы ([iOS](sdk-installation-ios), [Android](sdk-installation-android), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter), [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform), [Unity](sdk-installation-unity)) в своём приложении. Нужно заменить несколько методов SDK на стороне приложения. Разберём наиболее распространённые функции и то, как их заменить на методы Adapty SDK. ### Активация SDK \{#sdk-activation\} Замените `Purchases.configure` на `Adapty.activate`. ### Получение пейволов (офферингов) \{#getting-paywalls-offerings\} Замените `Purchases.shared.getOfferings` на [`Adapty.getPaywall`](fetch-paywalls-and-products#fetch-paywall-information). В Adapty пейвол всегда запрашивается через [placement id](placements). На практике вы запрашиваете не более 1–2 пейволов, поэтому мы намеренно сделали именно так — чтобы ускорить SDK и снизить сетевую нагрузку. ### Получение профиля пользователя \{#getting-a-user-customer-profile\} Замените `Purchases.shared.getCustomerInfo` на `Adapty.getProfile`. ### Получение продуктов \{#getting-products\} В RevenueCat используется следующая структура: `Purchases.shared.getOfferings`, затем `self.offering?.availablePackages`. В Adapty вы сначала запрашиваете пейвол (см. выше), чтобы получить мгновенный доступ к [Remote Config](customize-paywall-with-remote-config) Adapty, а затем запрашиваете продукты через [`Adapty.getPaywallProducts`](fetch-paywalls-and-products#fetch-products). ### Совершение покупки \{#making-a-purchase\} Замените `Purchases.shared.purchase` на [`Adapty.makePurchase`](making-purchases#make-purchase). ### Проверка уровня доступа (entitlement) \{#checking-access-level-entitlement\} Получите профиль пользователя (см. выше) и замените `customerInfo?.entitlements["premium"]?.isActive == true` на [`profile.accessLevels["premium"]?.isActive == true`](subscription-status#retrieving-the-access-level-from-the-server). ### Восстановление покупки \{#restore-purchase\} Замените `Purchases.shared.restorePurchases` на [`Adapty.restorePurchases`](restore-purchase). ### Проверка авторизации пользователя \{#check-if-the-user-is-logged-in\} Замените `Purchases.shared.isAnonymous` на `if profile.customerUserId == nil`. ### Вход пользователя \{#log-in-user\} Замените `Purchases.shared.logIn` на [`Adapty.identify`](identifying-users#set-customer-user-id-after-configuration). ### Выход пользователя \{#log-out-user\} Замените `Purchases.shared.logOut` на [`Adapty.logout`](identifying-users#logging-out-and-logging-in). ## Переключите серверные уведомления App Store на Adapty \{#switch-app-store-server-side-notifications-to-adapty\} Как это сделать — читайте [здесь](migrate-to-adapty-from-another-solutions#changing-apple-server-notifications). ## Протестируйте и выпустите новую версию приложения \{#test-and-release-a-new-version-of-your-app\} Если вы дошли до этого пункта, значит вы уже: - [x] Настроили дашборд Adapty - [x] Установили Adapty SDK - [x] Заменили логику SDK на функции Adapty - [x] Переключили серверные уведомления App Store на Adapty и при необходимости включили пересылку raw-событий в RevenueCat - [ ] Совершили покупку в песочнице - [ ] Выпустили новую версию приложения Если все пункты выше отмечены, просто совершите тестовую покупку в песочнице, а затем выпустите приложение. :::info Пройдите [чеклист перед релизом](release-checklist). Выполните финальную проверку по нашему списку, чтобы убедиться в корректности интеграции или добавить дополнительные функции, например [атрибуцию](attribution-integration) или интеграцию с [аналитикой](analytics-integration). ::: ## (Опционально) Экспортируйте исторические данные RevenueCat в формате CSV \{#optional-export-your-revenuecat-historical-data-in-csv-format\} :::warning Не торопитесь с импортом исторических данных Подождите не менее недели после релиза с SDK, прежде чем импортировать исторические данные. За это время мы получим из SDK информацию о ценах покупок, и импортируемые данные станут более актуальными. ::: Экспортируйте исторические данные из RevenueCat в формате CSV, следуя инструкциям в [официальной документации RevenueCat](https://www.revenuecat.com/docs/integrations/scheduled-data-exports). ## (Опционально) Запросите у поддержки RevenueCat токены Google Purchase Tokens \{#optional-ask-revenuecat-support-for-google-purchase-tokens\} Если вам нужно импортировать транзакции Google Play, обратитесь в поддержку RevenueCat через их [страницу поддержки](https://app.revenuecat.com/settings/support) и запросите CSV-файл с Google Purchase Tokens. Google Purchase Token — это уникальный идентификатор, предоставляемый Google Play для каждой транзакции; он необходим для точного отслеживания и верификации покупок в Adapty. Эта информация не включается в стандартный экспортный файл. Файл содержит три столбца: - `user_id` - `google_purchase_token` - `google_product_id` ## Напишите нам для импорта исторических данных \{#write-us-to-import-your-historical-data\} Свяжитесь с нами через мессенджер на сайте или по электронной почте [support@adapty.io](mailto:support@adapty.io), прикрепив ваши CSV-файлы. 1. Отправьте CSV-файл, экспортированный из RevenueCat, напрямую в нашу службу поддержки. 2. Если вы импортируете транзакции Google Play, приложите CSV-файл с Google Purchase Tokens, полученный от поддержки RevenueCat. 3. Укажите, какой идентификатор пользователя следует использовать в качестве Customer User ID (основного идентификатора пользователя в Adapty): `rc_original_app_user_id` или `rc_last_seen_app_user_id_alias`. Наша команда поддержки импортирует ваши транзакции в Adapty. Для каждой транзакции будут импортированы следующие данные: | Параметр | Описание | | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | user_id | Customer User ID — основной идентификатор пользователя в Adapty и вашей системе. | | apple_original_transaction_id | Для цепочек подписок — это дата покупки исходной транзакции, связанная через `store_original_transaction_id`. | | google_product_id | Идентификатор продукта в Google Play Store. | | google_purchase_token | Уникальный идентификатор, предоставляемый Google Play для каждой транзакции; необходим для валидации. | | country | Страна пользователя. | | created_at | Дата и время создания пользователя. | | subscription_expiration_date | Дата и время окончания подписки. | | email | Email конечного пользователя. | | phone_number | Номер телефона конечного пользователя. | | idfa | Identifier for Advertisers (IDFA) — идентификатор устройства пользователя, присваиваемый Apple. | | idfv | Identifier for Vendors (IDFV) — код, присваиваемый всем приложениям одного разработчика и используемый совместно на одном устройстве. | | advertising_id | Уникальный идентификатор, предоставляемый ОС Android и используемый рекламодателями для отслеживания. | | attribution_channel | Название маркетингового канала. | | attribution_campaign | Название маркетинговой кампании. | | attribution_ad_group | Группа объявлений атрибуции. | | attribution_ad_set | Набор объявлений атрибуции. | | attribution_creative | Ключевое слово креатива атрибуции. | Кроме того, будут импортированы идентификаторы интеграций для следующих сервисов: Amplitude, Mixpanel, AppsFlyer, Adjust и FacebookAds. ## FAQ \{#faq\} ### Я успешно установил Adapty SDK и выпустил новую версию приложения. Что произойдёт с моими действующими подписчиками, которые не обновили приложение до версии с Adapty SDK? \{#i-successfully-installed-adapty-sdk-and-released-a-new-app-version-with-it-what-will-happen-to-my-legacy-subscribers-who-did-not-update-to-a-version-with-adapty-sdk\} Большинство пользователей заряжают телефоны на ночь — именно тогда App Store обычно автоматически обновляет все приложения, так что это не должно стать проблемой. Небольшое количество платных подписчиков всё же может не обновиться, но они по-прежнему сохранят доступ к премиум-контенту. Беспокоиться об этом не нужно, и принудительно заставлять их обновляться тоже. ### Нужно ли мне как можно скорее экспортировать исторические данные из RevenueCat, или я рискую их потерять? \{#do-i-need-to-export-my-historical-data-from-revenuecat-as-quickly-as-possible-or-will-i-lose-it\} Торопиться не нужно: сначала выпустите приложение с Adapty SDK, а потом передайте нам исторические данные. Мы восстановим историю платежей ваших пользователей и заполним [профили](profiles-crm) и [графики](charts). ### Я использую MMP (AppsFlyer, Adjust и др.) и аналитику (Mixpanel, Amplitude и др.). Как убедиться, что всё будет работать? \{#i-use-mmp-appsflyer-adjust-etc-and-analytics-mixpanel-amplitude-etc-how-do-i-make-sure-that-everything-will-work\} Сначала нужно передать нам идентификаторы сторонних сервисов через наш SDK — тех, которым вы хотите отправлять данные. Ознакомьтесь с гайдом по [интеграции атрибуции](attribution-integration) и [интеграции аналитики](analytics-integration). Для исторических данных и существующих пользователей **обязательно передайте нам эти идентификаторы из данных, экспортированных из RevenueCat.** --- # File: migration-from-superwall --- --- title: "Миграция с Superwall" description: "Мигрируйте с Superwall на Adapty с помощью пошагового руководства, которое сопоставляет каждый SDK-вызов и концепцию." --- Большинство миграций с Superwall на Adapty занимают около двух часов. Вы заменяете SDK, направляете серверные уведомления стора на Adapty и выпускаете новую версию приложения. Платные подписчики сохраняют доступ — Adapty восстанавливает его из чеков App Store и Google Play при первом запуске. :::info Ваши подписчики мигрируют автоматически Все пользователи, когда-либо активировавшие подписку, переходят на Adapty сразу после открытия новой версии приложения с SDK Adapty. Валидация статуса подписки и доступ к премиум-функциям восстанавливаются автоматически. ::: ## Структура руководства \{#how-this-guide-is-organized\} Миграция состоит из шести шагов: 1. [Сопоставьте концепции Superwall с Adapty](#map-your-superwall-concepts-to-adapty) 2. [Установите SDK Adapty](#install-the-adapty-sdk) 3. [Замените SDK-вызовы](#replace-sdk-calls) 4. [Переключите серверные уведомления App Store и Google Play](#switch-app-store-and-google-play-server-notifications) 5. [Протестируйте и выпустите релиз](#test-and-release) 6. [(Опционально) Импортируйте исторические данные](#optional-import-historical-data) ## Сопоставьте концепции Superwall с Adapty \{#map-your-superwall-concepts-to-adapty\} Большинство концепций Superwall имеют прямой аналог в Adapty: | Superwall | Adapty | Что меняется | | :------------------- | :------------------------------------------------ | :--------------------------------------------------------------------------- | | Campaign | [Плейсмент](placements) + [Аудитория](audience) | Логика кампании разделяется на плейсмент (место показа) и аудиторию (правило). | | Placement | [Плейсмент](placements) | Та же концепция, то же название. | | Audience filter | [Аудитория](audience) | Наборы правил живут внутри плейсмента. | | Entitlement | [Уровень доступа](access-level) | Именованный идентификатор (например, `premium`). | | WebView paywall | [Пейвол Paywall Builder](adapty-paywall-builder) | Отрисовывается SDK Adapty нативно вместо `WKWebView`. | | `PurchaseController` | Встроенный | Протокол реализовывать не нужно — Adapty обрабатывает покупки самостоятельно. | | Feature gating | Проверка [уровня доступа](access-level) | Проверяйте `profile.accessLevels["premium"]?.isActive`. | Перед тем как касаться кода, стоит осмыслить два ключевых отличия: - **Получение и показ — это отдельные шаги**: `register` в Superwall за один вызов получает пейвол, оценивает кампанию и показывает UI. Adapty разделяет эти шаги — вы получаете пейвол, загружаете его конфигурацию, а затем показываете. Это добавляет несколько строк кода, но позволяет предзагружать конфигурации, показывать собственное состояние загрузки или отменять показ по вашей логике. - **Статус подписки привязан к уровню доступа**: Superwall предоставляет единственное публикуемое свойство `subscriptionStatus`. Adapty возвращает [`AdaptyProfile`](https://swift.adapty.io/documentation/adapty/adaptyprofile) с именованными уровнями доступа, так что один пользователь может одновременно иметь уровни доступа `sports` и `science`. Для синхронного чтения кешируйте профиль из `AdaptyDelegate`, а не вызывайте `getProfile()` при каждой загрузке экрана. ## Установите SDK Adapty \{#install-the-adapty-sdk\} Установите SDK Adapty для вашей платформы — [iOS](sdk-installation-ios), [Android](sdk-installation-android), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter), [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform), [Unity](sdk-installation-unity) или [Capacitor](sdk-installation-capacitor) — и одновременно удалите SuperwallKit из проекта. ## Замените SDK-вызовы \{#replace-sdk-calls\} Пройдитесь по каждой части интеграции и замените вызовы Superwall на эквиваленты Adapty. Ссылки в конце каждого подраздела охватывают все семь платформенных SDK — перейдите по той, которая соответствует вашему приложению. ### Инициализация SDK \{#initialize-the-sdk\} Замените `Superwall.configure` на `Adapty.activate`. Смотрите руководство по установке для вашей платформы — [iOS](sdk-installation-ios), [Android](sdk-installation-android), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter), [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform), [Unity](sdk-installation-unity) или [Capacitor](sdk-installation-capacitor). ### Идентификация и выход пользователей \{#identify-and-log-out-users\} Замените `Superwall.shared.identify` на `Adapty.identify`, а `Superwall.shared.reset` — на `Adapty.logout`. Оба SDK генерируют анонимный профиль при первом запуске, поэтому эти вызовы нужны только при входе или выходе пользователя. После идентификации заново получайте пейволы — новый пользователь может попасть в другую аудиторию. Смотрите руководство по идентификации для вашей платформы — [iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), [Kotlin Multiplatform](kmp-identifying-users), [Unity](unity-identifying-users) или [Capacitor](capacitor-identifying-users). ### Получение и показ пейвола \{#fetch-and-present-a-paywall\} Замените `Superwall.shared.register` на двухшаговый флоу: получите пейвол через `Adapty.getPaywall`, загрузите конфигурацию его вида через `AdaptyUI.getPaywallConfiguration`, а затем покажите его. Два важных отличия: - **Feature gating заменяет замыкание `feature:`**: после закрытия пейвола проверьте активный уровень доступа в возвращённом профиле (или через `Adapty.getProfile`) и разветвляйте логику оттуда. - **Пейволы отрисовываются SDK**: Superwall рендерит пейволы внутри `WKWebView`. Adapty рендерит пейволы Paywall Builder нативно — шрифты, информация о продуктах и кнопки отрисовываются SDK. Смотрите быстрый старт по пейволам для вашей платформы — [iOS](ios-quickstart-paywalls), [Android](android-quickstart-paywalls), [React Native](react-native-quickstart-paywalls), [Flutter](flutter-quickstart-paywalls), [Kotlin Multiplatform](kmp-quickstart-paywalls), [Unity](unity-quickstart-paywalls) или [Capacitor](capacitor-quickstart-paywalls). ### Проверка статуса подписки \{#check-subscription-status\} Замените `Superwall.shared.subscriptionStatus` на проверку именованного уровня доступа в профиле: `profile.accessLevels["premium"]?.isActive`. Отслеживайте изменения через `AdaptyDelegate.didLoadLatestProfile(_:)` вместо паттерна с `@Published`-свойством, и кешируйте профиль на своей стороне для синхронного чтения. Смотрите руководство по статусу подписки для вашей платформы — [iOS](ios-check-subscription-status), [Android](android-check-subscription-status), [React Native](react-native-check-subscription-status), [Flutter](flutter-check-subscription-status), [Kotlin Multiplatform](kmp-check-subscription-status), [Unity](unity-check-subscription-status) или [Capacitor](capacitor-check-subscription-status). ### Обработка покупок и восстановлений \{#handle-purchases-and-restores\} При использовании Paywall Builder оба SDK обрабатывают покупки автоматически внутри UI пейвола — **этот шаг можно пропустить**. Для кастомных пейволов Superwall требует реализации `PurchaseController`. В Adapty этого не нужно: замените `PurchaseController.purchase` на `Adapty.makePurchase`, а `PurchaseController.restorePurchases` — на `Adapty.restorePurchases`. SDK самостоятельно выполняет валидацию. Смотрите быстрый старт по кастомным пейволам для вашей платформы — [iOS](ios-quickstart-manual), [Android](android-quickstart-manual), [React Native](react-native-quickstart-manual), [Flutter](flutter-quickstart-manual), [Kotlin Multiplatform](kmp-quickstart-manual), [Unity](unity-quickstart-manual) или [Capacitor](capacitor-quickstart-manual). ### Установка атрибутов пользователя \{#set-user-attributes\} Замените `Superwall.shared.setUserAttributes` на `Adapty.updateProfile`. Смотрите руководство по атрибутам пользователя для вашей платформы — [iOS](setting-user-attributes), [Android](android-setting-user-attributes), [React Native](react-native-setting-user-attributes), [Flutter](flutter-setting-user-attributes), [Kotlin Multiplatform](kmp-setting-user-attributes), [Unity](unity-setting-user-attributes) или [Capacitor](capacitor-setting-user-attributes). ## Переключите серверные уведомления App Store и Google Play \{#switch-app-store-and-google-play-server-notifications\} Направьте серверные уведомления стора на Adapty. Adapty работает и без них, но аналитика, сторонние интеграции и метрики A/B-тестов зависят от этих уведомлений: - **App Store**: следуйте инструкции [Включить серверные уведомления App Store](enable-app-store-server-notifications). - **Google Play**: следуйте инструкции [Включить уведомления для разработчиков в реальном времени](enable-real-time-developer-notifications-rtdn). Если вы хотите запускать Superwall и Adapty параллельно в процессе выкатки, используйте [переадресацию необработанных событий](enable-app-store-server-notifications#raw-events-forwarding) — Adapty проксирует события стора обратно в Superwall, пока вы проверяете новую интеграцию. ## Протестируйте и выпустите релиз \{#test-and-release\} Перед релизом убедитесь, что выполнены все пункты: - [x] Настроен дашборд Adapty (продукты, пейволы, плейсменты, уровни доступа) - [x] Установлен SDK Adapty - [x] Вызовы Superwall SDK заменены на эквиваленты Adapty - [x] Серверные уведомления App Store и Google Play направлены на Adapty - [ ] Совершена покупка в песочнице - [ ] Отправлен новый релиз приложения Пройдите [чеклист перед релизом](release-checklist) для финальной проверки. ## (Опционально) Импортируйте исторические данные \{#optional-import-historical-data\} Superwall не хранит ваш статус подписки — это делают App Store и Google Play. Adapty валидирует чеки при первом запуске, поэтому платные пользователи сохраняют доступ без какого-либо импорта. Если вы хотите, чтобы исторические транзакции были добавлены в аналитику Adapty, следуйте инструкции [Импорт исторических данных в Adapty](importing-historical-data-to-adapty). Подождите не менее недели после выпуска SDK, чтобы он успел собрать актуальные цены покупок. ## FAQ \{#faq\} ### Что произойдёт с подписчиками, которые не обновят приложение? \{#what-happens-to-subscribers-who-dont-update-the-app\} Большинство пользователей обновляют приложения автоматически в ночное время, поэтому доля пользователей на старой версии быстро сокращается. Подписчики на старой версии сохраняют доступ напрямую через App Store или Google Play — принудительное обновление не требуется. ### Перенесутся ли аудитории моих кампаний Superwall? \{#do-my-superwall-campaign-audiences-carry-over\} Нет. Фильтры аудиторий Superwall и аудитории Adapty настраиваются в разных дашбордах и используют разные идентификаторы. Воссоздайте свой таргетинг в виде [аудиторий](audience) внутри [плейсментов](placements) Adapty. В большинстве приложений используется один-два плейсмента (онбординг и общий внутриприложенческий триггер), поэтому пересборка обычно занимает немного времени. ### Есть ли в Adapty аналог `getPresentationResult`? \{#does-adapty-have-an-equivalent-to-getpresentationresult\} Не в виде единственного вызова. Чтобы проверить, будет ли показан пейвол для плейсмента, вызовите `Adapty.getPaywall(placementId:)` и разветвляйте логику по результату. Если вызов успешен, значит для аудитории этого пользователя назначен пейвол. Если вызов завершается с ошибкой из-за отсутствия настроенного пейвола, пропустите показ и выполните резервную логику. --- # File: importing-historical-data-to-adapty --- --- title: "Импорт исторических данных в Adapty" description: "Импортируйте исторические данные в Adapty для детальной аналитики." --- После установки SDK Adapty и публикации приложения вы можете просматривать своих пользователей и подписчиков в разделе [Profiles](profiles-crm). Но что, если у вас есть устаревшая инфраструктура и нужно мигрировать на Adapty, или вы просто хотите видеть существующие данные в Adapty? :::note Импорт данных не обязателен Adapty автоматически предоставит уровни доступа историческим пользователям и восстановит их события покупок, как только они откроют приложение с интегрированным SDK Adapty. Для этого сценария импорт исторических данных не нужен. Тем не менее импорт данных обеспечивает точную аналитику, если у вас большой объём исторических транзакций, хотя в целом он не является обязательным условием миграции. ::: Чтобы импортировать данные в Adapty: 1. Экспортируйте транзакции в CSV-файл (для iOS, Android и Stripe нужны отдельные файлы). Подробные требования к формату файла описаны в разделе [Формат файла импорта](importing-historical-data-to-adapty#import-file-format) ниже. 2. Если какой-либо файл превышает 1 ГБ, подготовьте выборку данных примерно из 100 строк. 3. Загрузите все файлы на Google Drive (можно сжать их, но держите отдельно). 4. Для транзакций iOS убедитесь, что раздел **In-app purchase API** в [**App settings**](https://app.adapty.io/settings/ios-sdk) заполнен **Issuer ID**, **Key ID** и **Private key** (файл .P8) — даже если вы используете StoreKit 1. Подробные инструкции см. в разделах [Укажите Issuer ID и Key ID](app-store-connection-configuration#step-2-provide-issuer-id-and-key-id) и [Загрузите файл In-App Purchase Key](app-store-connection-configuration#step-3-upload-in-app-purchase-key-file). 5. Поделитесь ссылками с нашей командой по [email](mailto:support@adapty.io) или через онлайн-чат в дашборде Adapty. Не беспокойтесь: импорт исторических данных не создаст дубликатов, даже если данные пересекаются с уже существующими записями в Adapty. ## Известные ограничения для Android \{#known-limitations-for-android\} 1. Восстанавливаются только активные подписки; истёкшие транзакции восстановлены не будут. 2. Восстанавливаются только последние продления подписки; вся цепочка покупок восстановлена не будет. 3. Если цена продукта изменилась с момента покупки, будет использоваться текущая цена, что может привести к некорректным данным о стоимости. :::note Если у вас большой объём Android-транзакций, перед началом импорта может потребоваться [запросить увеличение квоты Google Play Developer API](google-play-quota-increase), чтобы не превысить лимит по умолчанию. ::: ## Формат файла импорта \{#import-file-format\} :::tip Если вы мигрируете с RevenueCat, можно отправить файл экспорта RevenueCat напрямую — конвертация не нужна. Инструкции по экспорту см. в [документации RevenueCat](https://www.revenuecat.com/docs/integrations/scheduled-data-exports). ::: Подготовьте данные в файле или нескольких файлах, соответствующих следующим требованиям: - [ ] Формат файла — .CSV. - [ ] Отдельные файлы для Android, iOS и Stripe. - [ ] Каждый файл импорта содержит все [обязательные столбцы](importing-historical-data-to-adapty#required-fields). - [ ] Столбцы в файлах импорта имеют заголовки. - [ ] Заголовки столбцов точно соответствуют значениям в колонке **Column name** в таблице ниже. Проверьте наличие опечаток. - [ ] Необязательные столбцы могут отсутствовать в файле. Не добавляйте пустые столбцы для данных, которых у вас нет. - [ ] Файлы импорта не должны содержать дополнительных столбцов, не указанных в таблице. Если они есть, удалите их. - [ ] Значения разделены запятыми. - [ ] Значения не заключены в кавычки. - [ ] Если у одного пользователя несколько **apple_original_transaction_id**, добавьте их отдельными строками для каждого **apple_original_transaction_id**. В противном случае мы можем не восстановить расходуемые покупки. В качестве примеров используйте следующие файлы для [iOS](https://raw.githubusercontent.com/adaptyteam/adapty-docs/refs/heads/main/Downloads/adapty_import_ios_sample.csv) и [Android](https://raw.githubusercontent.com/adaptyteam/adapty-docs/refs/heads/main/Downloads/adapty_import_android_sample.csv). ### Доступные столбцы файла импорта \{#available-import-file-columns\} | Название столбца | Наличие | Описание | |-----------|--------|-----------| | **user_id** | обязательный | ID вашего пользователя | | **apple_original_transaction_id** | обязательный для iOS | <p>Оригинальный идентификатор транзакции или OTID ([подробнее](https://developer.apple.com/documentation/appstoreserverapi/originaltransactionid)), используется в механизме импорта StoreKit 2. Поскольку у одного пользователя может быть несколько OTID, для успешного импорта достаточно указать хотя бы один.</p><p></p><p>**Примечание:** Для этого импорта необходимо настроить учётные данные In-app purchase API в дашборде Adapty. Как это сделать — [здесь](app-store-connection-configuration#step-3-upload-in-app-purchase-key-file).</p> | | **google_product_id** | обязательный для Google | ID продукта в Google Play Store. | | **google_purchase_token** | обязательный для Google | Уникальный идентификатор, представляющий пользователя и ID продукта для приобретённой встроенной покупки | | **google_is_subscription** | обязательный для Google | Возможные значения: `1` \| `0` | | **stripe_token** | обязательный для Stripe | Токен объекта Stripe, представляющий уникальную покупку. Может быть токеном подписки Stripe (`sub_...`) или Payment Intent (`pi_...`). | | **subscription_expiration_date** | необязательный | Дата истечения подписки, т.е. следующая дата списания, дата и время с часовым поясом (2020-12-31T23:59:59-06:00) | | **created_at** | необязательный | Дата и время создания профиля (2019-12-31 23:59:59-06:00) | | **birthday** | необязательный | Дата рождения пользователя в формате 2000-12-31 | | **email** | необязательный | Электронная почта вашего пользователя | | **gender** | необязательный | Пол пользователя | | **phone_number** | необязательный | Номер телефона вашего пользователя | | **country** | необязательный | формат [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) | | **first_name** | необязательный | Имя вашего пользователя | | **last_name** | необязательный | Фамилия вашего пользователя | | **last_seen** | необязательный | Дата и время с часовым поясом (2020-12-31T23:59:59-06:00) | | **idfa** | необязательный | Идентификатор для рекламодателей (IDFA) — случайный идентификатор устройства, назначаемый Apple. Применяется только для iOS-приложений | | **idfv** | необязательный | Идентификатор для поставщиков (IDFV) — уникальный код, назначаемый всем приложениям одного разработчика. Применяется только для iOS-приложений | | **advertising_id** | необязательный | Advertising ID — уникальный код, назначаемый операционной системой Android, который рекламодатели могут использовать для уникальной идентификации устройства пользователя | | **amplitude_user_id** | необязательный | ID пользователя из Amplitude | | **amplitude_device_id** | необязательный | ID устройства из Amplitude | | **mixpanel_user_id** | необязательный | ID пользователя из Mixpanel | | **appmetrica_profile_id** | необязательный | ID профиля пользователя из AppMetrica | | **appmetrica_device_id** | необязательный | ID устройства из AppMetrica | | **appsflyer_id** | необязательный | Уникальный идентификатор из AppsFlyer | | **adjust_device_id** | необязательный | ID устройства из Adjust | | **facebook_anonymous_id** | необязательный | Уникальный идентификатор, генерируемый Facebook для пользователей, анонимно взаимодействующих с вашим приложением или сайтом, то есть не вошедших в Facebook | | **branch_id** | необязательный | Уникальный идентификатор из Branch | | **attribution_source** | необязательный | Источник интеграции атрибуции, например appsflyer | | **attribution_status** | необязательный | organic | | **attribution_channel** | необязательный | Канал атрибуции, привлёкший транзакцию | | **attribution_campaign** | необязательный | Кампания атрибуции, привлёкшая транзакцию | | **attribution_ad_group** | необязательный | Группа объявлений атрибуции, привлёкшая транзакцию | | **attribution_ad_set** | необязательный | Набор объявлений атрибуции, привлёкший транзакцию | | **attribution_creative** | необязательный | Конкретные визуальные или текстовые элементы рекламного объявления или маркетинговой кампании, которые отслеживаются для оценки эффективности достижения целевых действий: кликов, конверсий или установок | | **custom_attributes** | необязательный | Определите до 30 пользовательских атрибутов в виде JSON-словаря в формате ключ-значение: <ul><li>**key**: (string) название пользовательского атрибута</li><li> **value**: (string, integer, float или boolean) значение пользовательского атрибута.</li></ul><p> Формат: `"{'string_value': 'some_value', 'float_value': 123.0, 'int_value': 456}"`.</p><p>Обратите внимание на использование двойных и одинарных кавычек в формате. Имейте в виду, что булевы значения и целые числа будут преобразованы в числа с плавающей точкой.</p> | ### Обязательные поля \{#required-fields\} Для каждой платформы существует 2 группы обязательных полей: **user_id** и данные, идентифицирующие покупки для соответствующей платформы. Обязательные поля по платформам указаны в таблице ниже. | Платформа | Обязательные поля | |--------|---------------| | iOS | <p>user_id</p><p>apple_original_transaction_id</p> | | Android | <p>user_id</p><p>google_product_id</p><p>google_purchase_token</p><p>google_is_subscription</p> | | Stripe | <p>user_id</p><p>stripe_token</p> | Без этих полей Adapty не сможет получить транзакции. Для точной аналитики по когортам укажите `created_at`. Если это поле не заполнено, датой установки будет считаться дата первой покупки. ### Импорт данных в Adapty \{#import-data-to-adapty\} Свяжитесь с нами и поделитесь файлами импорта через [support@adapty.io](mailto:support@adapty.io) или через онлайн-чат в [дашборде Adapty](https://app.adapty.io/overview). --- # File: migrate-integrations-to-adapty --- --- title: "Миграция интеграций на Adapty" description: "Переключите интеграции с аналитикой и атрибуцией с устаревшего решения на Adapty без дублирования событий и нарушения работы кампаний." --- Миграция на Adapty — это не только смена SDK. Сторонние инструменты аналитики и атрибуции, такие как Amplitude и Adjust, тоже требуют скоординированного переключения. При правильном подходе количество дублирующихся или пропущенных событий будет минимальным, а кампании не пострадают. ## Сопоставьте события \{#map-your-events\} В большинстве интеграций Adapty названия событий можно настраивать. Вы можете задать те же имена, которые уже используются в ваших дашбордах и кампаниях — аналитика и отчёты по кампаниям продолжат работать с теми же названиями событий после переключения. Полный список доступных событий в Adapty см. в разделе [События](events). Для Adjust интеграция использует идентификаторы событий вместо пользовательских названий. Перенесите существующие идентификаторы событий из дашборда Adjust в настройки интеграции Adapty. Подробнее см. в [руководстве по интеграции с Adjust](adjust). ## Как Adapty создаёт события интеграции \{#how-adapty-creates-integration-events\} Чтобы отправить событие в интеграцию, Adapty должен иметь профиль пользователя. Профиль создаётся одним из двух способов: - **Исторический импорт**: профиль создаётся при [импорте исторических данных о транзакциях](importing-historical-data-to-adapty) до запуска SDK. - **Взаимодействие через SDK**: профиль создаётся автоматически, когда пользователь впервые открывает приложение с SDK Adapty. Adapty узнаёт о покупках, совершённых в устаревшей системе, в режиме реального времени. Однако отправить событие в интеграцию он может только после того, как профиль покупателя будет создан. Профиль создаётся при первом запуске приложения с SDK Adapty. Пользователи, не обновившие приложение, не будут генерировать события интеграции. ## Подготовка перед днём миграции \{#prepare-before-migration-day\} ### Исключите исторические события \{#exclude-historical-events\} Включите **Exclude Historical Events** в [настройках интеграции](configuration). Это предотвратит отправку в интеграцию событий, относящихся к периоду до первой сессии пользователя с SDK Adapty. Этот параметр особенно важен при [историческом импорте](importing-historical-data-to-adapty), когда Adapty единовременно обрабатывает большой объём прошлых транзакций. Без него эти транзакции сгенерируют огромное количество событий в вашем инструменте аналитики. ### Настройте интеграцию заранее \{#set-up-the-integration-in-advance\} Adapty позволяет настраивать и тестировать интеграцию, не включая её. Вы можете задать учётные данные, маппинг событий и фильтры, не активируя интеграцию до нужного момента. Настройки сохраняются при включении, поэтому ничего не теряется, пока интеграция отключена. Чтобы найти нужную интеграцию, см. разделы [Интеграции атрибуции](attribution-integration), [Интеграции аналитики](analytics-integration), [Интеграции сервисов рассылок](messaging) или [Webhook и ETL-интеграции](webhook-and-etl). ## Переключение в день миграции \{#switch-on-migration-day\} Отключите интеграцию в устаревшем решении и одновременно включите её в Adapty. Работа обоих решений одновременно приведёт к дублированию событий. В день миграции приостановите крупные кампании по привлечению пользователей. Это снизит риск ошибок оптимизации кампаний из-за событий в окне перехода. ## Чего ожидать \{#what-to-expect\} Некоторое количество пропущенных или дублирующихся событий интеграции при миграции неизбежно. При правильном переключении число затронутых событий будет незначительным. Основная причина пробелов — описанная выше особенность: Adapty может отправлять события интеграции по покупке только после создания профиля пользователя. Покупки, совершённые в устаревшей системе, не генерируют события интеграции Adapty до тех пор, пока покупатель не откроет приложение с SDK Adapty. ## Интеграции и серверные уведомления \{#integrations-vs-server-to-server-notifications\} Adapty рекомендует использовать интеграции, а не пересылать сырые серверные уведомления от сторов напрямую в инструменты аналитики или атрибуции. Преимущества интеграций: - **Единый формат**: события из всех сторов — App Store, Google Play, Stripe — используют одинаковый формат. - **Обогащённые данные**: события включают данные, которые собирает Adapty, — например, состояние подписки и атрибуты пользователя. Сырые уведомления этого не содержат. --- # File: whats-new --- --- title: "Что нового" description: "Будьте в курсе последних возможностей и улучшений в Adapty" --- Узнавайте о последних функциях, улучшениях, обновлениях SDK и изменениях в документации, которые помогут оптимизировать монетизацию вашего приложения. На этой странице собраны самые важные релизы каждого месяца. :::note Есть отзывы о новых функциях? Будем рады услышать вас! Свяжитесь с нами через [доску обратной связи](https://adapty.featurebase.app/en?b=69831ba5e82e7a3391632ec2). ::: ## Июль 2026 \{#july-2026\} - **Виртуальные валюты**: определяйте внутриигровые валюты — токены, монеты, кристаллы, — начисляйте и отслеживайте баланс каждого пользователя и читайте эти балансы с сервера через server-side API. [Подробнее](virtual-currencies) - **AI-агент в Apple Ads Manager**: задавайте вопросы советнику-чату о производительности ваших Apple Ads и получайте ответы на основе данных ваших кампаний — без ручного составления отчётов. [Подробнее](ads-manager-ai-agent) - **Новые автоматизации в Apple Ads Manager**: Автоматизируйте изменения на уровне кампаний и групп объявлений с двумя новыми типами правил — в дополнение к существующим автоматизациям по ключевым словам и поисковым запросам. [Правила кампаний](ads-manager-automations-campaign-rules) | [Правила групп объявлений](ads-manager-automations-ad-group-rules) - **Профили в Adapty Mail**: Представление каждого подписчика, где в одном месте отображается его путь, текущий статус подписки и статус отписки. [Подробнее](mail-profiles) - **SDK v4 для React Native, Flutter, Capacitor и Kotlin Multiplatform**: SDK v4 с поддержкой флоу уже доступны. React Native, Flutter и Capacitor вышли в общий доступ, а Kotlin Multiplatform v4 запущен — каждый со своим гайдом по миграции. [React Native](migration-to-react-native-sdk-v4) | [Flutter](migration-to-flutter-sdk-v4) | [Capacitor](migration-to-capacitor-sdk-v4) | [Kotlin Multiplatform](migration-to-kmp-sdk-v4) - **Новые поля вебхуков**: Вебхук-пейлоады теперь содержат исходную цену и скидку по каждой транзакции — так удобнее отслеживать promotional offer и introductory offer в downstream-системах. Эти поля доступны только в вебхуках. [Подробнее](webhook-event-types-and-fields) - **Зачёркнутые цены во флоу**: Показывайте перечёркнутую исходную цену рядом со скидочной, с бейджем скидки, прямо в Flow Builder. [Подробнее](strikethrough-price) - **Галерея шаблонов флоу**: Начните новый флоу с профессионально оформленного шаблона вместо пустого холста, а затем настройте его под своё приложение. [Подробнее](paywall-builder-templates) - **Кнопка Install tools**: В каждой статье документации теперь есть кнопка **Install tools** в шапке. Она открывает модальное окно с готовыми командами для установки навыка интеграции Adapty SDK в Claude Code, Copilot CLI, Gemini CLI, Codex и другие AI-ассистенты для разработки. [Подробнее](adapty-sdk-integration-skill) - **Новый способ установки Unity SDK**: Теперь Unity SDK можно установить через Swift Package Manager — добавлено руководство по устранению типичных проблем при настройке. [Подробнее](sdk-installation-unity) - **Нижний контейнер во Flow Builder**: Прикреплённая панель внизу экрана, которая остаётся на месте, пока остальной контент прокручивается — удобно для кнопок CTA, юридических текстов и ссылок. [Подробнее](builder-containers#footer) - **Видеоуроки по Flow Builder**: Растущий плейлист на YouTube с пошаговыми руководствами по созданию флоу, теперь встроенный в гайды по Flow Builder. [Подробнее](adapty-flow-builder) ## Июнь 2026 \{#june-2026\} - **Flows теперь работают на Android**: Визуальный no-code редактор для пейволов и онбордингов теперь поддерживает Android SDK v4 и выше, наряду с iOS. Экраны отображаются нативно, без веб-вью. [Подробнее](adapty-flow-builder) - **CPP A/B-тесты в Apple Ads Manager**: Сравнивайте custom product pages друг с другом прямо в Apple Ads. Выберите от 2 до 4 страниц — включая текущую дефолтную — и Apple Ads будет распределять трафик между ними и показывать, какая из них лучше конвертирует. [Подробнее](ads-manager-cpp-ab-tests) - **Adapty Mail API**: Отправляйте профили пользователей и транзакции напрямую в Adapty Mail с вашего сервера, без прохождения данных через SDK. Используйте это для наполнения базы подписчиков, переноса подписчиков из других ваших приложений или для использования вашего бэкенда как единого источника данных. [Подробнее](mail-send-data-via-api) - **Показывать пейвол с таргетингом Apple Ads при первом запуске**: атрибуция Apple Ads поступает после активации SDK, поэтому пейвол, запрошенный слишком рано, не попадёт в аудиторию Apple Ads. Используйте `AdaptyProfile.appliedAttributionSources`, чтобы показать пейвол с таргетингом Apple Ads сразу после получения данных атрибуции. [iOS](ios-show-aa-targeted-paywall) | [React Native](react-native-show-aa-targeted-paywall) | [Capacitor](capacitor-show-aa-targeted-paywall) - **Автосохранение во Flow Builder**: Flow Builder теперь автоматически сохраняет прогресс раз в минуту — несохранённые изменения больше не теряются при уходе со страницы. Вы по-прежнему можете сохранить черновик вручную сочетанием **Cmd/Ctrl + S**. [Подробнее](builder-save-publish) - **Новые видеоуроки по Flow Builder**: Два новых руководства охватывают построение навигации между экранами флоу и настройку состояний элементов — выбранного, активного и отключённого. [Навигация во флоу](onboarding-navigation-branching) | [Состояния элементов](builder-element-states) - **Документация на японском и вьетнамском языках**: Документация Adapty теперь доступна на японском (日本語) и вьетнамском (Tiếng Việt) языках. Переключайте языки с помощью селектора в верхней навигации. ## Май 2026 \{#may-2026\} - **Флоу (бета)**: Создавайте целые последовательности экранов в визуальном no-code конструкторе — одноэкранные пейволы, многошаговые онбординги и всё, что между ними, — всё в одном флоу. Экраны рендерятся нативно без веб-вью, а обновлять тексты, дизайн и логику можно без выпуска нового релиза приложения. Поддерживаются iOS, Android, React Native, Flutter и Capacitor SDK версии 4 и выше. [Подробнее](adapty-flow-builder) - **Autopilot теперь адаптируется к результатам тестов**: Выступая в роли AI-менеджера по росту, он обновляет план роста после каждого завершённого раунда. Следующая гипотеза строится на основе того, какие эксперименты уже проведены, какие из них выиграли и какие направления ещё стоит исследовать — вместо следования фиксированной последовательности. [Узнать больше](autopilot-how-it-works#how-ai-growth-advisor-decides-what-to-recommend) - **Activation ARPU в Autopilot Market Insights**: Новый график сравнивает средний доход с одной новой установки вашего приложения со средним значением по категории. Используйте его вместе с воронкой конверсии — высокая конверсия при низком Activation ARPU может указывать на заниженную цену офферов. [Подробнее](autopilot-analysis#activation-arpu) - **Аналитика в Adapty Mail**: Сравнивайте метрики доставки и выручку, атрибутированную письмам, для каждой кампании в одном представлении. Группируйте, детализируйте и фильтруйте по кампании, сегменту, варианту A/B-теста, сообщению или триггеру, а затем переходите к любой строке для детального анализа. [Подробнее](mail-analytics) - **Профиль бренда в Adapty Mail**: Единый профиль, который определяет текст писем, тон, визуальное оформление и контент веб-пейвола. Adapty формирует его на основе страницы вашего приложения в сторе, лендинга, юридических страниц и профилей в соцсетях — вы можете просматривать и редактировать каждый раздел прямо на месте. [Узнать больше](mail-brand) - **Прогнозы в Adapty UA**: прогнозируемый доход, ROAS, прибыль от рекламы, ARPU и ARPPU для каждой когорты — чтобы сравнивать кампании до того, как они успеют созреть. Прогнозы строятся на основе исторических данных когорт вашего приложения, обновляются ежедневно и доступны для периодов когорт от D0 до D360 или любого произвольного дня. [Подробнее](ua-predicted-metrics) - **Новые поля в кастомном S3-экспорте Adapty UA**: Теперь кастомный S3-экспорт включает поля `bundle_id`, `device_brand`, `device_model`, `os_version`, `app_version` и `sdk_version`. Сегментируйте и объединяйте данные атрибуции по устройству и версии приложения на стороне получателя. [Подробнее](ua-custom-s3) - **Аудитории плейсментов в CLI**: Команды `adapty placements create` и `adapty placements update` теперь поддерживают флаг `--audiences` — JSON-массив из записей `{segment_ids, paywall_id, priority}` — с его помощью можно нацеливать разные пейволы на разные сегменты прямо из терминала. Новая команда `adapty paywalls placements` выводит список всех плейсментов, в которых используется заданный пейвол, чтобы вы могли оценить последствия замены ещё до её применения. [Подробнее](developer-cli-reference#placements) - **Документация на испанском языке**: Документация Adapty теперь доступна на испанском языке (Español). Переключить язык можно с помощью селектора языка в верхней навигации. ## Апрель 2026 \{#april-2026\} - **Adapty Mail**: AI-генерируемые email-кампании, которые превращают пользователей триала в платных подписчиков. Создавайте, отправляйте и отслеживайте атрибуцию кампаний прямо из вашего проекта в Adapty — без отдельной email-платформы. [Подробнее](adapty-mail) - **Диагностика пейвола в Autopilot**: узнайте, что нужно улучшить на пейволе, ещё до того, как запускать тест. Загрузите скриншот — Autopilot вернёт рекомендации на основе бенчмарков лучших приложений в вашей категории, а также AI-предложения по макету и тексту. Рекомендации на основе бенчмарков становятся раундами A/B-тестов в вашем плане роста. [Подробнее](autopilot-analysis#paywall-analysis) - **Более понятные рекомендации для каждого предложения Autopilot**: Теперь каждая гипотеза содержит объяснение того, почему это важно (анализ на основе данных о том, как ваш пейвол отклоняется от устоявшихся паттернов), что нужно изменить и как настроить A/B-тест, а также какие метрики отслеживать — в новом разделе «Как интерпретировать результаты». [Подробнее](autopilot-execute-plan#step-1-view-the-hypothesis) - **Поддерживайте актуальность плана роста Autopilot**: Обновляйте анализ, чтобы получать свежие рыночные данные и новые предложения, и при необходимости обращайтесь к предыдущим версиям в истории версий. Гипотезы сгруппированы по вкладкам: Top priority, All, Pricing, Visual, Geo-pricing и Archived. [Подробнее](autopilot-growth-plan) - **Распределение выручки по длительности в Autopilot**: Посмотрите, не сконцентрирована ли ваша выручка в одной длительности подписки. На новом графике Market Insights отображается ваш доход по длительностям рядом со среднеотраслевым показателем для вашей категории и страны. [Подробнее](autopilot-analysis#revenue-distribution-by-duration) - **Обновлённые прогнозы LTV и дохода**: прогнозируемые LTV и доход теперь используют данные когортного удержания вашего приложения при наличии достаточной истории, а в остальных случаях — средние значения по другим приложениям. Так даже новые приложения получают полезные прогнозы в аналитике и A/B-тестах. [Подробнее](predicted-ltv-and-revenue) - **Отправка всех событий в Adapty UA**: Дайте Meta и TikTok полную картину конверсий для более точного моделирования аудиторий. Adapty теперь поддерживает пересылку установок и транзакций от органических и неатрибутированных пользователей в ваш пиксель, а не только от пользователей, привязанных к кампании. [Meta](ua-facebook#send-all-events) | [TikTok](ua-tiktok#send-all-events) - **Документация на русском и турецком**: Документация Adapty теперь доступна на русском (Русский) и турецком (Türkçe) языках. Переключайте язык с помощью селектора языка в верхней навигации. ## Март 2026 \{#march-2026\} - **Developer CLI**: Управляйте аккаунтом Adapty прямо из терминала, не открывая дашборд. CLI позволяет создавать приложения, задавать уровни доступа, настраивать продукты, создавать пейволы и конфигурировать плейсменты — всё это поддаётся автоматизации в скриптовых окружениях. Также доступен [Adapty CLI skill](https://github.com/adaptyteam/adapty-cli/tree/main/skills/adapty-cli) для AI-ассистентов, помогающий им работать с CLI. [Подробнее](developer-cli) - **Обзорная страница в Apple Ads Manager**: все ключевые метрики Apple Ads в одном месте, каждая с графиком тренда. Фильтруйте по приложению через выпадающий список в шапке, настраивайте набор метрик, тип графика и отображение выручки. [Подробнее](ads-manager-overview) - **Market Intelligence в Apple Ads Manager**: смотрите, по каким ключевым словам конкуренты запускают рекламу в 50+ странах, и добавляйте лучшие ключевые слова прямо в свои кампании. [Подробнее](ads-manager-market-intelligence) - **Полная автоматизация управления ключевыми словами в Apple Ads Manager**: автоматически корректируйте ставки, приостанавливайте или активируйте ключевые слова и перемещайте их между группами объявлений на основе заданных вами правил эффективности. [Подробнее](ads-manager-automations-keyword-rules) - **История ставок в Apple Ads Manager**: просматривайте полный журнал изменений ставки CPT для любого ключевого слова — когда произошло каждое изменение, предыдущее и новое значения, а также какое правило автоматизации его вызвало. [Подробнее](ads-manager-manage-keywords#bid-history) - **Визуальные раунды в Autopilot**: предложения по дизайну пейвола теперь являются полноценными раундами в вашем плане роста — они отображаются в боковой панели рядом с монетизационными раундами. Каждый визуальный раунд включает макет дизайна, описание ситуаций, в которых этот паттерн работает лучше всего, и ключевые метрики, на которые он влияет. [Подробнее](autopilot-growth-plan#view-the-growth-plan) - **Добавляйте собственные гипотезы в Autopilot**: расширяйте план роста кастомными раундами. Укажите название, описание, тип раунда (монетизация или визуальный), целевые метрики, а для монетизационных раундов — задействованные продукты. [Подробнее](autopilot-growth-plan#add-your-own-hypothesis) - **Меняйте порядок раундов Autopilot**: перетаскивайте этапы плана роста и выстраивайте их в том порядке, который лучше всего соответствует вашей стратегии. [Подробнее](autopilot) - **Гео-прайсинг раунды в Autopilot**: Тестируйте изменения цен для отдельных стран как новый тип раунда в плане роста. На основе данных Market Insights Autopilot рекомендует, повысить, снизить или оставить цены без изменений в каждой стране. Добавьте рекомендацию как раунд гео-прайсинга, чтобы запустить её как A/B-тест — одновременно можно запускать до 5 раундов. [Подробнее](autopilot-growth-plan#geo-pricing-hypotheses) - **Автоматизация поисковых запросов в Apple Ads Manager**: Автоматически продвигайте выигрышные поисковые запросы в ключевые слова с точным совпадением и исключайте их из источника — без ручной загрузки отчётов. Правила можно создавать из шаблонов или строить с нуля с произвольными условиями и расписанием. [Подробнее](ads-manager-automations-search-terms) - **Maximize Conversions bidding в Apple Ads Manager**: При создании кампаний теперь можно выбрать стратегию ставок Maximize Conversions. Алгоритм Apple максимизирует количество загрузок в рамках вашего бюджета с учётом опционального целевого CPA. [Подробнее](ads-manager-create-campaign) - **Интеграция FunnelFox в Adapty UA**: В Adapty UA доступна новая интеграция с FunnelFox. [FunnelFox](ua-funnelfox) - **Документация на китайском**: Документация Adapty теперь доступна на китайском языке (中文). Переключайте языки с помощью селектора в верхней навигации. ## Февраль 2026 \{#february-2026\} - **Цены на продукты по странам**: устанавливайте разные цены для каждой страны прямо в дашборде Adapty — Adapty автоматически синхронизирует изменения с App Store Connect и Google Play. Каждое обновление цены фиксируется в журнале аудита, так что ни одно изменение не останется незамеченным. [Подробнее](edit-product) - **Цены конкурентов по странам в Autopilot**: сравнивайте цены своих подписок с ценами конкурентов на ключевых рынках. [Подробнее](autopilot-analysis#market-and-competitor-analysis) - **Контроль версий онбординга**: Отслеживайте версии онбордингов и управляйте ими с полной историей изменений. Просматривайте правки и откатывайтесь при необходимости. - **Графики конверсии пейволов в аналитике**: Два новых графика конверсии — Paywall view → Trial и Paywall view → Paid — показывают, как ваши пейволы конвертируют посетителей в подписчиков. [Подробнее](analytics-conversion) - **Дублирование сегментов**: Копируйте существующий сегмент со всеми его фильтрами, не создавая похожий с нуля. Удобно при запуске нескольких кампаний или A/B-тестов с пересекающимися аудиториями. [Подробнее](segments#duplicate-segments) - **Push-уведомления в мобильном приложении Adapty**: Настраивайте push-уведомления для 14 типов событий прямо в iOS-приложении Adapty, чтобы следить за активностью подписок, не открывая дашборд. [Подробнее](push-notifications) - **Kotlin Multiplatform SDK 3.15**: Добавлена поддержка онбординга, веб-пейволов и улучшения API. [Подробнее](migration-to-kmp-315) - **Capacitor SDK 3.16**: Добавлена поддержка Capacitor 8. Проекты на Capacitor 7 следует оставить на SDK v3.15. [Подробнее](migration-to-capacitor-316) - **Гайды по интеграции SDK с помощью LLM**: Пошаговые гайды по интеграции Adapty с помощью AI-ассистентов. Каждый гайд проведёт вашу LLM через полную реализацию — от настройки дашборда до покупок. [iOS](adapty-cursor) | [Android](adapty-cursor-android) | [React Native](adapty-cursor-react-native) | [Flutter](adapty-cursor-flutter) | [Unity](adapty-cursor-unity) | [Kotlin Multiplatform](adapty-cursor-kmp) | [Capacitor](adapty-cursor-capacitor). Для автоматического флоу в одну команду попробуйте новый **adapty-sdk-integration skill** (бета): [iOS](adapty-sdk-integration-skill) | [Android](adapty-sdk-integration-skill-android) | [React Native](adapty-sdk-integration-skill-react-native) | [Flutter](adapty-sdk-integration-skill-flutter) | [Unity](adapty-sdk-integration-skill-unity) | [Kotlin Multiplatform](adapty-sdk-integration-skill-kmp) | [Capacitor](adapty-sdk-integration-skill-capacitor) ## Январь 2026 \{#january-2026\} - **Capacitor SDK официально выпущен**: Capacitor SDK теперь готов к использованию в продакшне после масштабного тестирования. Создавайте приложения с подписками для iOS и Android на базе Capacitor с полной поддержкой интеграции Adapty. [Подробнее](capacitor-sdk-overview) - **Autopilot для новых приложений**: Анализ Autopilot теперь доступен даже если у вашего приложения ещё нет обширной истории транзакций. Получайте рекомендации по оптимизации цен на основе данных и стройте план роста с первого дня. [Подробнее](autopilot) - **Глобальные возможности ценообразования в Autopilot**: выявляйте потенциал роста выручки на ваших ключевых рынках с помощью рекомендаций по ценам для отдельных стран. Autopilot анализирует конверсию и покупательную способность для пяти наиболее перспективных стран и на основе индекса Adapty Pricing Index даёт рекомендации: повысить, снизить или оставить цены без изменений. [Подробнее](autopilot) - **Метрики конверсии при восстановлении оплаты**: Новые графики аналитики отслеживают доход, восстановленный после проблем с оплатой и льготных периодов. Отслеживайте «Billing issue converted», «Billing issue converted revenue», «Grace period converted» и «Grace period converted revenue», чтобы оценивать эффективность удержания пользователей. - **Прямое управление рекламой в Apple Ads Manager**: Создавайте и управляйте кампаниями Apple Ads прямо внутри Adapty, не переключаясь между платформами. [Подробнее](ads-manager-manage-ads) - **Аналитика Apple Ads Manager**: Получите доступ к подробным метрикам эффективности на уровне объявлений и данным атрибуции в Adapty. Просматривайте эффективность кампаний, аналитику групп объявлений и данные атрибуции в едином дашборде. [Подробнее](adapty-ads-manager-analytics) - **Графики атрибуции Apple Ads**: Объединяйте несколько метрик атрибуции в настраиваемых графиках для анализа эффективности Apple Ads вместе с данными по подпискам. [Подробнее](adapty-ads-manager-analytics#charts) - **Сегменты на основе данных атрибуции Apple Ads**: Создавайте сегменты пользователей на основе данных атрибуции Apple Ads — всего два клика. Таргетируйте пользователей по кампании, группе объявлений или ключевому слову для более точного анализа и экспериментов. [Подробнее](ads-manager-create-segments) - **Новая платформа документации**: Сайт документации переехал на новую платформу — теперь функции обновляются быстрее, а работать с документацией стало удобнее: улучшен поиск, навигация и структура контента. ## Декабрь 2025 \{#december-2025\} - **Документация Apple Ads Manager**: Совместите данные рекламных кампаний Apple Search Ads с метриками выручки в едином дашборде аналитики. Новая документация охватывает создание кампаний, управление группами объявлений и способы отслеживания ROI рекламных расходов вместе с показателями подписок. [Подробнее](ads-manager) - **Встроенные веб-пейволы**: Показывайте веб-пейволы прямо в приложении через встроенный браузер — без переходов во внешние ссылки. [iOS](ios-web-paywall#open-web-paywalls-in-an-in-app-browser) | [Android](android-web-paywall#open-web-paywalls-in-an-in-app-browser) | [React Native](react-native-web-paywall#open-web-paywalls-in-an-in-app-browser) | [Flutter](flutter-web-paywall#open-web-paywalls-in-an-in-app-browser) - **Скользящие сегменты**: Создавайте динамические сегменты аудитории, которые автоматически обновляются на основе скользящих временных окон. Например, можно создать сегмент «пользователи, установившие приложение за последние 7 дней» — он будет постоянно обновляться и всегда отражать самых новых клиентов. [Подробнее](segments#available-attributes) - **Гайды по настройке кампаний в Meta и TikTok**: Пошаговые инструкции по созданию и отслеживанию кампаний в Meta (Facebook и Instagram) и TikTok с настройкой отслеживания конверсий и интеграцией аналитики. [Meta](meta-create-campaign) | [TikTok](tiktok-create-campaign) - **Гайды по быстрому старту для ручной реализации пейволов**: Интегрируйте встроенные покупки быстрее с помощью пошаговых гайдов по интеграции SDK Adapty в ваш собственный UI пейвола. [iOS](ios-implement-paywalls-manually) | [Android](android-implement-paywalls-manually) | [React Native](react-native-implement-paywalls-manually) | [Flutter](flutter-implement-paywalls-manually) | [Unity](unity-implement-paywalls-manually) | [Kotlin Multiplatform](kmp-quickstart-manual) | [Capacitor](capacitor-quickstart-manual) - **Встроенный браузер для ссылок в онбордингах**: Внешние ссылки в онбордингах теперь по умолчанию открываются во встроенном браузере, не уводя пользователей из приложения. При необходимости можно настроить открытие в стороннем браузере. [iOS](ios-present-onboardings#customize-how-links-open-in-onboardings) | [Android](android-present-onboardings#customize-how-links-open-in-onboardings) | [React Native](react-native-present-onboardings#customize-how-links-open-in-onboardings) - **Улучшенные подсказки Autopilot**: Autopilot теперь даёт более точные рекомендации по оптимизации цен на основе углублённого анализа данных о подписках. [Попробуйте Autopilot](autopilot) - **Тёмная тема документации**: В документации теперь поддерживается тёмная тема — она переключается автоматически по системным настройкам или вручную в правом верхнем углу. --- # File: adapty-ecosystem --- --- title: "Экосистема Adapty" description: "Adapty — платформа для встроенных покупок в мобильных приложениях. Узнайте, что делает каждый продукт и как они связаны между собой." --- Adapty — платформа для встроенных покупок в мобильных приложениях, созданная с единой целью: сделать приложения прибыльными. Здесь есть всё для роста дохода: привлечение пользователей, их конвертация, удержание подписчиков и возврат тех, кто ушёл. Одна регистрация открывает доступ ко всей экосистеме Adapty с первого дня. Просто нажмите на логотип Adapty, чтобы переключаться между продуктами: - **Core** — обрабатывайте покупки без StoreKit и Google Play Billing, создавайте пейволы без кода и отслеживайте выручку в реальном времени. Остальные продукты строятся на этой основе. - **Adapty Ads Manager** — запускайте и оптимизируйте Apple Ads, ориентируясь на реальную выручку от подписок. - **Adapty Attribution** — узнайте, какие рекламные каналы действительно приносят доход, без MMP. - **Adapty Mail** — конвертируйте триалы и возвращайте ушедших пользователей с помощью автоматических email-рассылок. Ещё два продукта дополняют основную четвёрку: **FunnelFox** (воронки web-to-app и размещённый чекаут) и **Adapty Finance** (авансы в счёт будущей выручки от подписок). ## Как продукты связаны друг с другом \{#how-the-products-fit-together\} Каждый продукт взаимодействует с пользователем на определённом этапе жизненного цикла. Наведите курсор на любой выделенный элемент, чтобы увидеть краткое определение, или перейдите по ссылке к документации. <ProductMap /> :::link См. также: [Подходит ли мне Adapty?](is-adapty-right-for-me) ::: ## Создан для AI-воркфлоу \{#built-for-ai-workflows\} Запускайте Adapty прямо из AI-агента в вашем редакторе — интегрируйте, управляйте и обращайтесь к нему, не выходя из среды разработки. Направьте агента на [навык интеграции SDK](adapty-sdk-integration-skill) для вашей платформы, и он выполнит всю настройку одной командой, или следуйте [пошаговому LLM-гайду](adapty-cursor), чтобы проверять каждый шаг самостоятельно. Ваш AI-инструмент может подключиться к документации любым удобным способом. Скопируйте любую страницу в формате Markdown с помощью кнопки **Copy for LLM** или укажите ссылку на [`llms.txt`](https://adapty.io/docs/ru/llms.txt) — карту всей документации. Для живого доступа MCP-сервер [Context7](https://context7.com/adaptyteam/adapty-docs) подтягивает наиболее релевантные фрагменты кода прямо в Cursor, Claude Code и другие IDE. Все точки входа описаны в разделе [Управление Adapty с помощью AI](manage-adapty-with-ai). ## Core \{#core\} Core — это базовая платформа Adapty. Она показывает ваши пейволы, управляет покупками и отслеживает результаты. ### SDK и сторы \{#sdks-and-stores\} Забудьте о платёжной инфраструктуре. Adapty берёт на себя покупки, валидацию чеков и продления — и поддерживает статус каждого подписчика актуальным в реальном времени, так что вы всегда знаете, у кого есть доступ и на каком основании. Внутри вашего приложения [SDK](installation-of-adapty-sdks) берут на себя весь процесс покупки или [работают поверх вашего существующего биллинга](observer-vs-full-mode), если он уже есть. Поддерживаются 7 платформ: [iOS](ios-sdk-overview), [Android](android-sdk-overview), [React Native](react-native-sdk-overview), [Flutter](flutter-sdk-overview), [Unity](unity-sdk-overview), [Kotlin Multiplatform](kmp-sdk-overview) и [Capacitor](capacitor-sdk-overview). На стороне сервера Adapty напрямую подключается к сторам, поэтому каждое продление, возврат и проблема с оплатой доходят до вас в реальном времени — даже когда приложение закрыто. Поддерживаются: [App Store](initial_ios), [Google Play](initial-android), [Stripe](stripe) и [Paddle](paddle), а также [кастомная интеграция](custom-store) для любого другого провайдера. ### Продукты, офферы и уровни доступа \{#products-offers-and-access-levels\} Adapty разделяет то, что вы продаёте, и то, к чему получает доступ пользователь. Благодаря этому вы можете менять цены, заменять продукты или запускать офферы без обновления приложения. Всё держится на трёх составляющих: - **[Продукты](product)** — один продукт объединяет SKU из App Store, Play Store и веба — подписки, разовые покупки или расходуемые — так вы управляете каталогом в одном месте. - **[Офферы](offers)** — инструменты повышения конверсии: introductory offer, promotional offer и win-back offer. - **[Уровни доступа](access-level)** — позволяют разделить права доступа и конкретные продукты. ### Флоу и плейсменты \{#flows-and-placements\} Создавайте экраны, которые приносят доход — пейволы, онбординги, квизы — без написания кода. [Флоу](adapty-flow-builder) рендерятся прямо на устройстве через SDK, так что вы можете менять тексты, дизайн и цены в любое время без обновления приложения. Начните с [шаблона](paywall-builder-templates) или с чистого холста. Привяжите каждый флоу к [плейсменту](placements), а затем настройте таргетинг на разные [аудитории](audience), составленные из [сегментов](segments). ### Профили и сегменты \{#profiles-and-segments\} Найдите любого подписчика и просмотрите его полную историю — [профили](profiles-crm) содержат хронологию событий, статус подписки, доход и пользовательские атрибуты. Делите аудиторию на [сегменты](segments) по любому критерию, чтобы персонализировать отображение, фильтровать аналитику и настраивать A/B-тесты. [Лента событий](event-feed) отображает каждое событие подписки в режиме реального времени. ### A/B-тесты и AI Growth Advisor \{#ab-tests-and-ai-growth-advisor\} Увеличивайте доход, находя то, что конвертирует лучше всего: - **[A/B-тесты](ab-tests)** — тестируйте разные цены, длительность триалов и дизайны флоу. - **[AI Growth Advisor](autopilot)** — подскажет, что именно стоит A/B-тестировать следующим. Сравнивает ваш пейвол с 20 000+ приложениями на подписке и ранжирует эксперименты по ожидаемому росту дохода. Подробнее — [как это работает](autopilot-how-it-works). ### Аналитика и прогнозы \{#analytics-and-predictions\} [Аналитика](analytics) превращает данные из сторов, SDK и атрибуции в дашборд доходов в реальном времени с [десятками метрик](metric-comparison-table) — значительно больше, чем показывают App Store и Google Play сами по себе. Углубляйтесь с помощью [когортного](analytics-cohorts), [воронкового](analytics-funnels), [retention-](analytics-retention) и [конверсионного](analytics-conversion) анализов. [Прогнозы](predicted-ltv-and-revenue) предсказывают LTV каждой когорты на месяцы вперёд и определяют [победителей A/B-тестов](predictions-in-ab-tests) до достижения статистической значимости. Запланированные [отчёты](reports) приходят прямо на почту. ### Developer CLI \{#developer-cli\} [Adapty Developer CLI](developer-cli-quickstart) позволяет настраивать продукты, плейсменты и уровни доступа из командной строки — альтернатива дашборду для тех, кто предпочитает терминал. ## Adapty Ads Manager \{#adapty-ads-manager\} [Adapty Ads Manager](adapty-ads-manager) — это платформа для работы с Apple Ads. Она заменяет стандартную консоль Apple Ads: предлагает оптимизацию на основе ИИ, атрибуцию доходов в реальном времени и конкурентную аналитику. Поскольку Core уже отслеживает каждую установку, пробный период, подписку и продление, Ads Manager напрямую связывает рекламные расходы с LTV. MMP не нужен. Ключевые возможности: - **[Кампании и ключевые слова](ads-manager)** — создавайте и управляйте ими вместе с группами объявлений и ставками прямо из дашборда Adapty. - **[AI Agent](ads-manager-ai-agent)** — запросы по всей воронке на естественном языке и рекомендации. - **[Market Intelligence](ads-manager-market-intelligence)** — стратегии конкурентов по ключевым словам в 50+ странах. - **[CPP A/B Tests](ads-manager-cpp-ab-tests)** — сравнение кастомных страниц продукта друг с другом. - **[Automations](ads-manager-automations)** — кампании оптимизируются сами. Ставки, ключевые слова и поисковые запросы корректируются автоматически, когда ваши метрики пересекают заданные пороги. ## Атрибуция Adapty \{#adapty-attribution\} [Атрибуция Adapty](adapty-user-acquisition) связывает установки приложения и доход от подписок с рекламными кампаниями, которые их привлекли. Она объединяет расходы рекламных платформ, клики по трекинговым ссылкам и события установок из SDK в единые отчёты по ROAS, LTV и когортам по всем платным каналам. Никакого внешнего MMP не требуется. Ключевые возможности: - **[Интеграции с рекламными платформами](ua-integrations)** — Meta Ads, TikTok for Business, FunnelFox и S3/GCS-пайпы. - **[Трекинговые ссылки](ua-tracking-links)** — создаются в Adapty, добавляются в кампании и сопоставляются с установками при первом запуске. - **[Отложенные диплинки](ua-deferred-data)** — направляют новых пользователей к нужному контенту в приложении при первом запуске, даже если они перешли по ссылке до установки. - **[Данные атрибуции](ua-attribution-data)** — получайте данные атрибуции в приложении для реализации собственной логики. ## Adapty Mail \{#adapty-mail\} [Adapty Mail](adapty-mail) превращает пользовательские данные в email-кампании, созданные с помощью ИИ. Сервис формирует [профиль бренда](mail-brand) на основе страницы вашего приложения в сторе, лендинга и социальных профилей, а затем за считанные минуты генерирует полную цепочку писем. Письма отправляются с вашего верифицированного домена, а каждая покупка атрибутируется к письму, которое её принесло. Никаких сторонних email-платформ не требуется. Ключевые возможности: - **[Кампании](mail-email-campaigns)** — полная многописьмовая последовательность, генерируемая за один раз. Начинает отправку, как только вы привязываете её к флоу. - **[Флоу](mail-flows)** — привязывает кампанию к сегменту и триггеру по событию подписки, например *никогда не покупал* или *проблема с оплатой*, чтобы письма отправлялись автоматически. - **[Веб-пейвол](mail-checkout)** — персонализированные страницы оформления заказа, отдельные для каждого получателя, чтобы покупки атрибутировались обратно к письму. - **[Сегменты](mail-segments)** и **[профили](mail-profiles)** — охватывайте пользователей с наибольшей вероятностью конверсии. Создайте сегмент на основе статуса покупки, страны или дохода, затем запустите по нему кампанию или флоу. Данные поступают из Adapty Core и ограничены идентифицированными профилями с указанным email. ## Подключите Adapty к вашему стеку \{#connect-adapty-to-your-existing-stack\} [Сторонние интеграции](configuration) передают события подписок в аналитические, атрибуционные и мессенджинг-платформы, которые уже использует ваша команда: - **Аналитика**: [Amplitude](amplitude), [Mixpanel](mixpanel), [PostHog](posthog), [Firebase / Google Analytics](firebase-and-google-analytics), [AppMetrica](appmetrica), [SplitMetrics Acquire](splitmetrics). - **Атрибуция**: [AppsFlyer](appsflyer), [Adjust](adjust), [Branch](branch), [Airbridge](airbridge), [Apple Ads](apple-search-ads), [Singular](singular), [Tenjin](tenjin), [Asapty](asapty), [Facebook Ads](facebook-ads). - **Сообщения**: [Braze](braze), [OneSignal](onesignal), [Pushwoosh](pushwoosh), [Slack](slack). - **Вебхуки и ETL**: пользовательские [вебхуки](webhook), [Amazon S3](s3-exports), [Google Cloud Storage](google-cloud-storage). ## Более широкая экосистема \{#the-wider-ecosystem\} Ещё два продукта подключаются к данным Adapty, но решают задачи за пределами основного жизненного цикла. ### FunnelFox [FunnelFox](https://funnelfox.com/docs/integrations/subscription-management/adapty) — это конструктор воронок web-to-app. Он создаёт лендинги и квизы, которые направляют пользователей в ваше приложение. Его [платёжный движок](https://funnelfox.com/docs/billing/integration-billing-funnelfox) принимает оплату на веб-сайте. Подключите FunnelFox к Adapty для отслеживания подписок и атрибуции дохода. ### Adapty Finance [Adapty Finance](https://adapty.io/blog/introducing-adapty-finance/) позволяет получить будущую выручку от подписок авансом — без ожидания выплат от сторов. ## Следующие шаги \{#next-steps\} - **[Подходит ли мне Adapty?](is-adapty-right-for-me)** — обзор платформы с упором на сценарии использования. - **[Quickstart guide](quickstart)** — подключите стор, добавьте продукты и интегрируйте SDK. - **[Управление Adapty с помощью ИИ](manage-adapty-with-ai)** — все точки входа для работы с Adapty через инструменты ИИ-кодинга. --- # File: generate-in-app-purchase-key --- --- title: "Генерация ключа In-App Purchase в App Store Connect" description: "Создайте ключ встроенной покупки для безопасных транзакций." --- **In-App Purchase Key** — это специализированный API-ключ, создаваемый в App Store Connect для валидации покупок путём подтверждения их подлинности. :::note Для генерации API-ключей для App Store Server API необходимо иметь роль Admin или Account Holder в App Store Connect. Подробнее о создании API-ключей можно прочитать в [документации Apple Developer](https://developer.apple.com/documentation/appstoreserverapi/creating-api-keys-to-authorize-api-requests). ::: 1. Откройте **App Store Connect**. Перейдите в раздел [**Users and Access** → **Integrations** → **In-App Purchase**](https://appstoreconnect.apple.com/access/integrations/api/subs). 2. Нажмите кнопку добавления **(+)** рядом с заголовком **Active**. <img src="/assets/shared/img/6d737db-generate_in-app_key.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. В открывшемся окне **Generate In-App Purchase Key** введите название ключа для вашего удобства. В Adapty оно использоваться не будет. 4. Нажмите кнопку **Generate**. После закрытия окна **Generate in-App Purchase Key** созданный ключ появится в списке **Active**. <img src="/assets/shared/img/fac066b-download_inapp_file.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. После генерации API-ключа нажмите кнопку **Download In-App Purchase Key**, чтобы скачать ключ в виде файла. <img src="/assets/shared/img/d59faff-download_in-app_purchase_key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. В окне **Download in-App Purchase Key** нажмите кнопку **Download**. Файл будет сохранён на вашем компьютере. Храните этот файл в надёжном месте — он понадобится для последующей загрузки в дашборд Adapty. Обратите внимание: сгенерированный файл можно скачать только один раз, поэтому убедитесь в его сохранности до момента загрузки. Сгенерированный .p8-ключ из раздела **In-App Purchase** будет использоваться при [настройке начальной интеграции Adapty с App Store](app-store-connection-configuration#step-3-upload-in-app-purchase-key-file). **Что дальше:** - [Настройка интеграции с App Store](app-store-connection-configuration) --- # File: app-store-connection-configuration --- --- title: "Настройка интеграции с App Store" description: "Настройте подключение к App Store для удобного отслеживания подписок." --- <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/VJQbzoTCkqs?si=l7BPX9mIu6GVGZ0Z" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> В этом разделе описывается, как настроить соединение между App Store и Adapty для вашего iOS-приложения. Это необходимо для отображения аналитики подписок и валидации покупок. Настройку можно выполнить во время первоначального онбординга или позже в разделе **App Settings** дашборда Adapty. Хотя вы могли настроить интеграцию мобильного приложения с Adapty во время онбординга, эти настройки можно изменить позже в разделе **App settings**. :::danger Изменения конфигурации можно безопасно вносить во время фазы песочницы, пока ваше мобильное приложение не вышло в релиз с установленным Adapty SDK. Изменения после релиза могут нарушить флоу покупок в приложении. ::: ## Шаг 1. Укажите Bundle ID и Apple app ID \{#step-1-provide-bundle-id-and-apple-app-id\} Bundle ID — уникальный идентификатор вашего приложения в App Store. Он необходим для базовой функциональности Adapty, например для обработки подписок. 1. Откройте [App Store Connect](https://appstoreconnect.apple.com/apps). Выберите своё приложение и перейдите в раздел **General** → **App Information**. 2. Скопируйте **Bundle ID** в подразделе **General Information**. <Zoom> <img src="/docs/img/afd5012-bundle_id_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. Откройте вкладку [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) в верхнем меню Adapty и вставьте скопированное значение в поле **Bundle ID**. <Zoom> <img src="/docs/img/2d64163-bundle_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 4. Вернитесь на страницу **App information** в App Store Connect и скопируйте **Apple ID**. 5. На странице [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) в дашборде Adapty вставьте этот ID в поле **Apple app ID**. ## Шаг 2. Укажите Issuer ID и Key ID \{#step-2-provide-issuer-id-and-key-id\} **In-app purchase Issuer ID**, называемый **Issuer ID** в App Store Connect, — это специальный идентификатор, определяющий эмитента, который создал токен аутентификации. **In-App Purchase Key ID**, называемый **Key ID** в App Store Connect, — это уникальный идентификатор, связанный с криптографическим ключом, сгенерированным в разделе [Generate In-App Purchase Key in App Store Connect](generate-in-app-purchase-key). 1. Откройте **App Store Connect**. Перейдите в раздел [**Users and Access** → **Integrations** → **In-App Purchase**](https://appstoreconnect.apple.com/access/integrations/api/subs). 2. В списке **Active** найдите ключ, созданный в разделе [Создание ключа In-App Purchase в App Store Connect](generate-in-app-purchase-key). <img src="/assets/shared/img/19a2868-issuer_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Скопируйте **Issuer ID** и вставьте его в поле **In-app purchase Issuer ID** в дашборде Adapty. <img src="/assets/shared/img/c2b42e7-issuer_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Скопируйте **Key ID** и вставьте его в поле **In-app purchase Key ID** в дашборде Adapty. ## Шаг 3. Загрузите файл In-App Purchase Key \{#step-3-upload-in-app-purchase-key-file\} Загрузите файл **In-App Purchase Key**, скачанный в разделе [Создание In-App Purchase Key в App Store Connect](generate-in-app-purchase-key), <img src="/assets/shared/img/88cdfff-download_inapp_file.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> в поле **Private key (.p8 file)** в дашборде Adapty. <img src="/assets/shared/img/253b840-in-app_file_upload.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 4. Для пробных периодов и специальных предложений — настройте promotional offers \{#step-4-for-trials-and-special-offers--set-up-promotional-offers\} :::important Этот шаг обязателен, если в вашем приложении есть [пробные периоды или другие promotional offers](offers). ::: 1. Скопируйте тот же Key ID, который вы использовали на [Шаге 2](#step-2-provide-issuer-id-and-key-id), в поле **Subscription key ID** в разделе **App Store promotional offers**. 2. Загрузите тот же файл **In-App Purchase Key**, который вы использовали на [Шаге 3](#step-3-upload-in-app-purchase-key-file), в область **Subscription key (.p8 file)** в разделе **App Store promotional offers**. <img src="/assets/shared/img/promo-key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 5. Введите общий секрет App Store \{#step-5-enter-app-store-shared-secret\} **App Store shared secret**, также известный как App Store Connect Shared Secret, — это 32-символьная шестнадцатеричная строка, используемая для встроенных покупок и валидации чеков подписки. 1. Откройте [App Store Connect](https://appstoreconnect.apple.com/apps). Выберите своё приложение и перейдите в раздел **General** → **App Information**. 2. Прокрутите вниз до подраздела **App-Specific Shared Secret**. <img src="/assets/shared/img/2bd112a-shared_secret_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::info Если подраздел **App-Specific Shared Secret** отсутствует, убедитесь, что у вас есть роль Account Holder или Admin. Если у вас роль Admin, но подраздел всё равно не отображается, попросите Account Holder приложения (того, кто создал приложение в App Store Connect) сгенерировать App Store Shared Secret для приложения. После этого подраздел станет видим и для Admins. ::: 3. Нажмите кнопку **Manage**. <img src="/assets/shared/img/2d8b4c0-shared_secret_apple_copy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. В открывшемся окне **App-Specific Shared Secret** скопируйте **Shared Secret**. Если общий секрет не отображается, сначала нажмите кнопку **Manage** или **Generate** (в зависимости от того, какая доступна), а затем скопируйте **Shared Secret**. 5. Вставьте скопированный **Shared Secret** в поле **App Store shared secret** в дашборде Adapty. <img src="/assets/shared/img/4f9624d-shared_secret.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. Нажмите кнопку **Save** в дашборде Adapty, чтобы сохранить изменения. ## Шаг 6. Добавьте ключ App Store Connect API \{#step-6-add-app-store-connect-api-key\} Создайте ключ App Store Connect API и добавьте его в Adapty, чтобы [управлять продуктами в App Store прямо из дашборда Adapty](create-product#create-product-and-push-to-store): 1. В App Store Connect перейдите в [**Users and Access > Integrations > Team keys**](https://appstoreconnect.apple.com/access/integrations/api) и нажмите **+**. <img src="/assets/shared/img/app-store-connect-api.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. В окне **Generate API key window** введите имя ключа и предоставьте ему доступ уровня **Admin**. <img src="/assets/shared/img/generate-api-key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Нажмите **Download** рядом с ключом. Обратите внимание, что скачать его можно только один раз. <img src="/assets/shared/img/download-api-key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. В дашборде Adapty перейдите в [**App settings > iOS SDK**](https://app.adapty.io/settings/ios-sdk) и нажмите **Connect API key**. <img src="/assets/shared/img/connect-api-key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Заполните поля в открывшемся окне: - **Issuer ID**: скопируйте из [**Users and Access > Integrations > Team keys**](https://appstoreconnect.apple.com/access/integrations/api). Он находится над таблицей **API keys**. <img src="/assets/shared/img/issuer-id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> - **Key ID**: Скопируйте из [**Users and Access > Integrations > Team keys**](https://appstoreconnect.apple.com/access/integrations/api). Он находится в таблице **API keys** рядом с вашим ключом. <img src="/assets/shared/img/key-id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> - **API key**: Загрузите файл API-ключа, скачанный из App Store Connect. <img src="/assets/shared/img/app-store-connect-key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. Нажмите **Connect**. **Что дальше** - [Включить серверные уведомления App Store](enable-app-store-server-notifications) --- # File: enable-app-store-server-notifications --- --- title: "Включение уведомлений сервера App Store" description: "Включите уведомления сервера App Store для отслеживания событий подписки в режиме реального времени." --- Настройка уведомлений сервера App Store необходима для обеспечения точности данных: они позволяют получать обновления из App Store в режиме реального времени, включая информацию о возвратах средств и других событиях. :::important Для полной поддержки App Store Server Notifications V2 требуется Adapty iOS SDK версии 2.10.0 или выше. ::: 1. Скопируйте **URL for App Store server notification** в дашборде Adapty. <img src="/assets/shared/img/2901185-app_server_notifications.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Откройте [App Store Connect](https://appstoreconnect.apple.com/apps). Выберите приложение и перейдите в раздел **General** → **App Information**, подраздел **App Store Server Notifications**. 3. Вставьте скопированный **URL for App Store server notification** в поля **Production Server URL** и **Sandbox Server URL**. <img src="/assets/shared/img/86fb3d2-app_server_notifications_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Пересылка необработанных событий \{#raw-events-forwarding\} Иногда вам всё равно может понадобиться получать необработанные S2S-события от Apple. Чтобы продолжать их получать при использовании Adapty, просто добавьте свой endpoint в поле **URL for forwarding raw Apple events** — мы будем пересылать события в том виде, в котором получаем их от Apple. <img src="/assets/shared/img/e9f4bba-CleanShot_2021-03-16_at_19.30.272x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> **Что дальше** Настройте SDK Adapty для: - [iOS](sdk-installation-ios) - [React Native](sdk-installation-reactnative) - [Flutter](sdk-installation-flutter) - [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) - [Unity](sdk-installation-unity) --- # File: troubleshoot-app-store-integration --- --- title: "Устранение неполадок интеграции App Store" description: "Решение распространённых проблем с настройкой Apple App Store — незакрытые соглашения, задержки серверных уведомлений и расхождения цен." --- В этой статье описаны распространённые проблемы с интеграцией App Store. В каждом разделе указаны симптомы, причина и способ решения. ## Продукты не отображаются \{#products-dont-appear\} Оба симптома указывают на одну и ту же причину: - API-ключ App Store Connect настроен правильно, но Adapty не может получить продукты. - Продукты есть в App Store Connect, но в Adapty они не отображаются — или отображаются не все. При попытке покупки SDK сообщает «Product Id not found». Наиболее частая причина — **неподписанные соглашения Apple**: платное соглашение, налоговые формы или банковские реквизиты в статусе ожидания или не подписаны. Когда соглашения не подписаны, App Store Connect API молча возвращает 403 на запросы к продуктовым эндпоинтам. Никакой явной ошибки Adapty не видит — продукты просто отфильтровываются без предупреждения. Перейдите в **App Store Connect → Agreements, Tax, and Banking** и подпишите все ожидающие соглашения. Затем выполните повторную синхронизацию в разделе **App settings → iOS SDK** дашборда Adapty. ## Уведомления App Store Server Notifications отображаются как «Delayed» \{#app-store-server-notifications-show-delayed\} В App Store Connect статус уведомлений App Store Server Notifications может отображаться как **Delayed**. Это означает, что Apple задерживает отправку уведомлений о событиях подписки — продления, отмены и проблемы с оплатой ставятся в очередь и поступают с опозданием. На статистику установок это не влияет. Adapty считает установки с первого запуска приложения, а не на основе серверных уведомлений. Если данные о продлениях или отменах запаздывают, статус Delayed — наиболее вероятная причина. Обычно он устраняется автоматически по мере того, как Apple обрабатывает накопившуюся очередь. ## Цены в Adapty не совпадают с App Store \{#prices-in-adapty-dont-match-app-store\} Поле **price** на странице редактирования продукта в Adapty ведёт себя по-разному в зависимости от того, как продукт был добавлен. Если вы создаёте продукт в Adapty и публикуете его в сторе через дашборд, эта цена используется как начальная цена в сторе. Если вы добавляете продукт, который уже существует в сторе, эта цена является заглушкой. Аналитика, интеграции и SDK Adapty используют реальные цены, полученные из App Store. Изменения цен в App Store не синхронизируются и не обновляют заглушку, и на данный момент отредактировать её через дашборд нельзя. ## CSV-экспорт цен пуст \{#csv-price-export-is-empty\} Если CSV-экспорт цен вернул только заголовки столбцов, значит ключ API App Store Connect настроен не полностью. См. [Шаг 6 — Добавьте ключ API App Store Connect](app-store-connection-configuration#step-6-add-app-store-connect-api-key). ## Не получается отправить новые продукты в App Store \{#cant-push-new-products-to-app-store\} Adapty может автоматически отправлять новые продукты в App Store Connect при их создании в дашборде. Эта возможность недоступна, если интеграция с App Store настроена не полностью. Необходимы два параметра: - **Apple app ID**: настройте в [Шаг 1 — Укажите Bundle ID и Apple app ID](app-store-connection-configuration#step-1-provide-bundle-id-and-apple-app-id). - **App Store Connect API key**: настройте в [Шаг 6 — Добавьте ключ App Store Connect API](app-store-connection-configuration#step-6-add-app-store-connect-api-key). --- # File: enabling-of-devepoler-api --- --- title: "Включение Developer APIs в Google Play Console" description: "Включите Developer API от Adapty для автоматизации управления подписками в вашем приложении." --- <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/7dN50n5bcLc?si=c2znttIb--4VcrRO" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> Если ваше мобильное приложение доступно в Play Store, активация Developer APIs необходима для его интеграции с Adapty. Этот шаг обеспечивает бесперебойный обмен данными между вашим приложением и нашей платформой, поддерживает автоматизированные процессы и анализ данных в реальном времени для оптимизации модели подписки. Необходимо включить следующие API: - [Google Play Android Developer API](https://console.cloud.google.com/apis/library/androidpublisher.googleapis.com) - [Google Play Developer Reporting API](https://console.cloud.google.com/apis/library/playdeveloperreporting.googleapis.com) - [Cloud Pub/Sub API](https://console.cloud.google.com/marketplace/product/google/pubsub.googleapis.com) Если ваше приложение не распространяется через Play Store, этот шаг можно пропустить. Однако если вы продаёте через Play Store, этот шаг можно отложить на потом, хотя он необходим для базовой работы Adapty. После завершения онбординга вы сможете настроить параметры стора в разделе **App settings**. Вот как включить Developer APIs в Google Play Console: 1. Откройте [Google Cloud Console](https://console.cloud.google.com/). 2. В левом верхнем углу окна Google Cloud выберите проект, который хотите использовать, или создайте новый. Убедитесь, что вы используете один и тот же проект Google Cloud вплоть до загрузки файла ключа сервисного аккаунта в Adapty. <img src="/assets/shared/img/fd66a11-google_cloud_project.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Откройте страницу [**Google Play Android Developer API**](https://console.cloud.google.com/apis/library/androidpublisher.googleapis.com). <img src="/assets/shared/img/f754f72-google_play_api.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Нажмите кнопку **Enable** и дождитесь появления статуса **Enabled**. Это означает, что Google Android Developer API включён. <img src="/assets/shared/img/d47ed14-google_play_api_create_credentials.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Откройте страницу [**Google Play Developer Reporting API**](https://console.cloud.google.com/apis/library/playdeveloperreporting.googleapis.com). <img src="/assets/shared/img/966cf73-Google_play_developer_reporting_api.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. Нажмите кнопку **Enable** и дождитесь появления статуса **Enabled**. <img src="/assets/shared/img/e776d77-Google_play_developer_reporting_api_enabled.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Откройте страницу [**Cloud Pub/Sub API**](https://console.cloud.google.com/marketplace/product/google/pubsub.googleapis.com). <img src="/assets/shared/img/b13f609-enable_Cloud_Pub_Sub_API.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 8. Нажмите кнопку **Enable** и дождитесь появления статуса **Enabled**. <img src="/assets/shared/img/3f45602-Cloud_Pub_Sub_API_enabled.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Developer APIs включены. Проверить это можно на странице [**APIs & Services**](https://console.cloud.google.com/apis/dashboard) в Google Cloud Console. Прокрутите страницу вниз и убедитесь, что в таблице в нижней части страницы присутствуют все 3 API: - Google Play Android Developer API - Google Play Developer Reporting API - Cloud Pub/Sub API <img src="/assets/shared/img/b81d174-google_enabled_api.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> **Что дальше** - [Создание сервисного аккаунта в Google Cloud Console](create-service-account) --- # File: create-service-account --- --- title: "Создание сервисного аккаунта в Google Cloud Console" description: "Узнайте, как создать сервисный аккаунт для безопасного доступа к API в Adapty." --- Чтобы Adapty мог автоматически получать данные, в Google Play Console необходим сервисный аккаунт. 1. Откройте раздел [**IAM & Admin** -> **Service accounts**](https://console.cloud.google.com/iam-admin/serviceaccounts) в Google Cloud Console. Убедитесь, что выбран нужный проект. <img src="/assets/shared/img/17bbf45-google_cloud_create_service_account.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. В окне **Service accounts** нажмите кнопку **Create service account**. <img src="/assets/shared/img/b93eec1-service_account_details.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. В подразделе **Service account details** окна **Create service account** введите желаемое имя в поле **Service Account Name**. Рекомендуем включить в него слово «Adapty», чтобы было понятно назначение аккаунта. Поле **Service account ID** заполнится автоматически. 4. Скопируйте адрес электронной почты сервисного аккаунта и сохраните его для дальнейшего использования. 5. Нажмите кнопку **Create and continue**. <img src="/assets/shared/img/e69d713-grant_access_to_project.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. В выпадающем списке **Select a role** подраздела **Grant this service account access to project** выберите **Pub/Sub -> Pub/Sub Admin**. Эта роль необходима для включения уведомлений разработчика в реальном времени. <img src="/assets/shared/img/976299c-service_account_role.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Нажмите кнопку **Add another role**. 8. В появившемся выпадающем списке **Role** выберите **Monitoring -> Monitoring Viewer**. Эта роль необходима для мониторинга очереди уведомлений. 9. Нажмите кнопку **Continue**. <img src="/assets/shared/img/ffe8d82-grant_user_access.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 10. Нажмите кнопку **Done**, не внося никаких изменений. Откроется окно **Service accounts**. **Что дальше** - [Предоставьте разрешения сервисному аккаунту в Google Play Console](grant-permissions-to-service-account) --- # File: grant-permissions-to-service-account --- --- title: "Предоставление прав сервисному аккаунту в Google Play Console" description: "Предоставьте права сервисным аккаунтам для безопасного и эффективного доступа через API." --- Предоставьте необходимые права сервисному аккаунту, который Adapty будет использовать для управления подписками и валидации покупок. 1. Откройте страницу [**Users and permissions**](https://play.google.com/console/u/0/developers/8970033217728091060/users-and-permissions) в Google Play Console и нажмите кнопку **Invite new users**. <img src="/assets/shared/img/7b0e614-users_and_permissions.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. На странице **Invite user** введите email сервисного пользователя, которого вы создали. <img src="/assets/shared/img/3afd002-invite_user.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Перейдите на вкладку **Account permissions**. <img src="/assets/shared/img/4e2717b-account_permissions.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Выберите следующие права: - View app information and download bulk reports (read-only) - View financial data, orders, and cancellation survey responses - Manage orders and subscriptions - Manage store presence 5. Нажмите кнопку **Invite user**. 6. В окне **Send invite?** нажмите кнопку **Send invite**. Сервисный аккаунт появится в списке пользователей. **Что дальше** - [Создайте файл ключа сервисного аккаунта в Google Play Console](create-service-account-key-file) --- # File: create-service-account-key-file --- --- title: "Создание файла ключа сервисного аккаунта в Google Play Console" description: "Узнайте, как создать файл ключа сервисного аккаунта для интеграции с Adapty." --- Чтобы связать мобильное приложение в Play Store с Adapty, нужно создать специальные файлы ключей сервисного аккаунта в Google Play Console и загрузить их в Adapty. Эти файлы обеспечивают безопасность приложения и защищают его от несанкционированного доступа. :::warning Обычно новый сервисный аккаунт становится активным не раньше чем через 24 часа. Однако есть [лайфхак](https://stackoverflow.com/a/60691844). После создания сервисного аккаунта в [Google Play Console](https://play.google.com/apps/publish/) откройте любое приложение и перейдите в **Monetize** -> **Products** -> **Subscriptions/In-app products**. Отредактируйте описание любого продукта и сохраните изменения. Это должно активировать сервисный аккаунт сразу, а изменения после этого можно откатить. ::: 1. Откройте раздел [**Service accounts**](https://console.cloud.google.com/iam-admin/serviceaccounts) в Google Play Console. Убедитесь, что выбран нужный проект. <img src="/assets/shared/img/c3156cb-action_manage_keys.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. В открывшемся окне нажмите **Add key** и выберите **Create new key** в выпадающем меню. <img src="/assets/shared/img/44b30ee-create_new_key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. В окне **Create private key for [Your_project_name]** нажмите **Create**. Приватный ключ будет сохранён на ваш компьютер в виде JSON-файла. Найти его можно по имени, указанному в окне **Private key saved to your computer**. <img src="/assets/shared/img/e7b8101-cretae_private_key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. В окне **Create private key for Your_project_name** нажмите кнопку **Create**. Приватный ключ будет сохранён на ваш компьютер в виде JSON-файла. При необходимости найти его можно по имени, указанному в открывшемся окне **Private key saved to your computer**. <img src="/assets/shared/img/187ddc6-Private_key_saved.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Этот файл понадобится вам при [настройке интеграции с Google Play Store](google-play-store-connection-configuration). :::warning Обычно новый сервисный аккаунт становится активным не раньше чем через 24 часа. Однако есть [лайфхак](https://stackoverflow.com/a/60691844). После создания сервисного аккаунта в [Google Play Console](https://play.google.com/apps/publish/) откройте любое приложение и перейдите в **Monetize** -> **Products** -> **Subscriptions/In-app products**. Отредактируйте описание любого продукта и сохраните изменения. Это должно активировать сервисный аккаунт сразу, а изменения после этого можно откатить. ::: **Что дальше** - [Настройка интеграции с Google Play Store](google-play-store-connection-configuration) --- # File: google-play-store-connection-configuration --- --- title: "Настройка интеграции с Google Play Store" description: "Настройте подключение Google Play Store в Adapty для корректной обработки встроенных покупок." --- В этом разделе описан процесс интеграции вашего мобильного приложения, распространяемого через Google Play, с Adapty. Вам нужно ввести данные конфигурации приложения из Play Store в дашборд Adapty. Этот шаг необходим для валидации покупок и получения обновлений подписок из Play Store в Adapty. Вы можете выполнить этот процесс во время первоначального онбординга или внести изменения позже в разделе **App Settings** дашборда Adapty. :::danger Изменение конфигурации допустимо только до выпуска мобильного приложения с интегрированными пейволами Adapty. Изменения после релиза нарушат интеграцию, и пейволы перестанут отображаться в вашем приложении. ::: ## Шаг 1. Укажите Package name \{#step-1-provide-package-name\} Package name — это уникальный идентификатор вашего приложения в Google Play Store. Он необходим для базовой функциональности Adapty, например для обработки подписок. 1. Откройте [Google Play Developer Console](https://play.google.com/console/u/0/developers). 2. Выберите приложение, ID которого вам нужен. Откроется окно **Dashboard**. <img src="/assets/shared/img/7889edb-package_name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Найдите идентификатор продукта под названием приложения и скопируйте его. 4. Откройте [**App settings**](https://app.adapty.io/settings/android-sdk) в верхнем меню Adapty. <img src="/assets/shared/img/b00066c-package_name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. На вкладке **Android SDK** окна **App settings** вставьте скопированный **Package name**. ## Шаг 2. Загрузите файл ключа аккаунта \{#step-2-upload-the-account-key-file\} 1. Загрузите файл закрытого ключа сервисного аккаунта в формате JSON, созданный на шаге [Создание файла ключа сервисного аккаунта](create-service-account), в поле **Service account key file**. <img src="/assets/shared/img/20fdba1-service_key_file.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Не забудьте нажать кнопку **Save**, чтобы сохранить изменения. **Что дальше** - [Включите уведомления разработчика в реальном времени (RTDN) в Google Play Console](enable-real-time-developer-notifications-rtdn) --- # File: enable-real-time-developer-notifications-rtdn --- --- title: "Включение уведомлений в реальном времени (RTDN) в Google Play Console" description: "Будьте в курсе важных событий и обеспечьте точность данных, включив уведомления в реальном времени (RTDN) в Google Play Console для Adapty. Узнайте, как настроить RTDN для получения мгновенных обновлений о возвратах и других событиях из Play Store" --- Настройка уведомлений в реальном времени (RTDN) необходима для обеспечения точности данных: она позволяет мгновенно получать обновления из Play Store, включая информацию о возвратах и других событиях. ## Включение уведомлений \{#enable-notifications\} 1. Убедитесь, что **Google Cloud Pub/Sub** включён. Перейдите по [этой ссылке](https://console.cloud.google.com/flows/enableapi?apiid=pubsub) и выберите проект вашего приложения. Если вы ещё не включили **Google Cloud Pub/Sub**, сделайте это здесь. <img src="/assets/shared/img/pubsub.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Перейдите в [**App settings > Android SDK**](https://app.adapty.io/settings/android-sdk) из верхнего меню Adapty и скопируйте содержимое поля **Enable Pub/Sub API** рядом с заголовком **Google Play RTDN topic name**. <img src="/assets/shared/img/a72ff2d-copy_topic.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> :::note Если содержимое поля **Enable Pub/Sub API** имеет неправильный формат (правильный формат начинается с `projects/...`), обратитесь к разделу [Исправление неправильного формата в поле Enable Pub/Sub API](enable-real-time-developer-notifications-rtdn#fixing-incorrect-format-in-enable-pubsub-api-field) за помощью. ::: 3. Откройте [Google Play Console](https://play.google.com/console/), выберите своё приложение и перейдите в **Monetize with Play** -> **Monetization setup**. В разделе **Google Play Billing** установите флажок **Enable real-time notifications**. 4. Вставьте содержимое поля **Enable Pub/Sub API**, скопированное в **App Settings** Adapty, в поле **Topic name**. 5. Нажмите **Save changes** в Google Play Console. <img src="/assets/shared/img/e55ba0e-paste_topic_name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Тестирование уведомлений \{#test-notifications\} Чтобы проверить, успешно ли вы подписались на уведомления в реальном времени: 1. Сохраните изменения в настройках Google Play Console. 2. В Google Play Console под полем **Topic name** нажмите **Send test notification**. <img src="/assets/shared/img/rtdn-test.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Перейдите в [**App settings > Android SDK**](https://app.adapty.io/settings/android-sdk) в Adapty. Если тестовое уведомление было отправлено, вы увидите его статус над названием топика. <img src="/assets/shared/img/rtdn-adapty-test.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Исправление неправильного формата в поле Enable Pub/Sub API \{#fixing-incorrect-format-in-enable-pubsub-api-field\} Если содержимое поля **Enable Pub/Sub API** имеет неправильный формат (правильный формат начинается с `projects/...`), выполните следующие шаги для устранения проблемы: ### 1. Проверка активации API и прав доступа \{#1-verify-api-enablement-and-permissions\} Убедитесь, что все необходимые API включены и права доступа к сервисному аккаунту настроены правильно. Даже если вы уже выполняли эти шаги, пройдите их повторно, чтобы не пропустить ни одного. Повторите шаги из следующих разделов: 1. [Включение API разработчика в Google Play Console](enabling-of-devepoler-api) 2. [Создание сервисного аккаунта в Google Cloud Console](create-service-account) 3. [Выдача прав сервисному аккаунту в Google Play Console](grant-permissions-to-service-account) 4. [Создание файла ключа сервисного аккаунта в Google Play Console](create-service-account-key-file) 5. [Настройка интеграции с Google Play Store](google-play-store-connection-configuration) ### 2. Изменение политик домена \{#2-adjust-domain-policies\} Измените политики **Domain restricted contacts** и **Domain restricted sharing**: 1. Откройте [Google Cloud Console](https://console.cloud.google.com/) и выберите проект, в котором создан сервисный аккаунт для управления вашим приложением. 2. В разделе **Quick Access** выберите **IAM & Admin**. <img src="/assets/shared/img/google-cloud-IAM-and-Admin.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. На левой панели выберите **Organization Policies**. 4. Найдите политику **Domain restricted contacts**. <img src="/assets/shared/img/google-cloud-policy-action.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Нажмите кнопку с многоточием в столбце **Actions** и выберите **Edit policy**. 6. В окне редактирования политики: 1. В разделе **Policy source** выберите переключатель **Override parent's policy**. 2. В разделе **Policy enforcement** выберите переключатель **Replace**. 3. В разделе **Rules** нажмите кнопку **ADD A RULE**. <img src="/assets/shared/img/google-cloud-edit-policy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. В разделе **New rule** -> **Policy values** выберите **Allow All**. <img src="/assets/shared/img/google-cloud-allow-all-policy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Нажмите **SET POLICY**. 7. Повторите шаги 4–6 для политики **Domain restricted sharing**. После этого пересоздайте содержимое поля **Enable Pub/Sub API** рядом с заголовком **Google Play RTDN topic name**. Теперь поле будет в правильном формате. Не забудьте вернуть **Policy source** в значение **Inherit parent's policy** для обновлённых политик после успешного включения уведомлений в реальном времени (RTDN). ## Переадресация необработанных событий \{#raw-events-forwarding\} В некоторых случаях вам может потребоваться получать необработанные S2S-события от Google. Чтобы продолжать их получать при использовании Adapty, просто добавьте свой эндпоинт в поле **URL for forwarding raw Google events** — мы будем передавать события в том виде, в котором они приходят от Google. <img src="/assets/shared/img/e388892-001774-September-22-GhkjOFbT.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- **Что дальше** Настройте Adapty SDK для: - [Android](sdk-installation-android) - [React Native](sdk-installation-reactnative) - [Flutter](sdk-installation-flutter) - [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) - [Unity](sdk-installation-unity) --- # File: stripe --- --- title: "Начальная интеграция со Stripe" description: "Интегрируйте Stripe с Adapty для бесперебойной обработки платежей по подпискам." --- Adapty поддерживает web2app-флоу подписок, отслеживая веб-платежи и подписки, оформленные через [Stripe](https://stripe.com/). Интеграция охватывает покупки, инициированные через веб (Stripe Checkout, hosted payment pages или кастомные веб-флоу), и синхронизирует их с доступом в мобильном приложении и аналитикой. Она полезна в следующих сценариях: - Автоматическое предоставление доступа к платным функциям пользователям, которые оплатили на сайте, а затем установили приложение и вошли в аккаунт - Хранение всей аналитики подписок в едином дашборде Adapty (включая когорты, прогнозы и весь остальной аналитический инструментарий) Несмотря на то что веб-покупки становятся всё популярнее для приложений, Apple App Store разрешает использование альтернативных платёжных систем вместо встроенных покупок только для цифровых товаров и только в США. Убедитесь, что вы не продвигаете веб-подписки внутри приложения для других стран — иначе приложение может быть отклонено или заблокировано. Ниже описаны шаги по настройке интеграции со Stripe. :::important Эта интеграция ориентирована на отслеживание и синхронизацию веб-покупок через Stripe. Если вам нужно направить пользователей из приложения на веб-страницу оплаты, см. [Веб-пейволы](web-paywall). ::: ## 1\. Подключите Stripe к Adapty \{#1-connect-stripe-to-adapty\} Интеграция в основном базируется на том, что Adapty получает данные о подписках от Stripe через вебхук. Поэтому нужно связать аккаунт Adapty с аккаунтом Stripe: предоставить API-ключи и настроить URL вебхука Adapty в Stripe. Чтобы автоматизировать настройку вебхука, установите приложение Adapty в Stripe: :::note Шаги ниже одинаковы как для Production-, так и для Test-режима Stripe, однако для каждого из них потребуются разные API-ключи. ::: 0. Определите, в каком режиме вы подключаете Stripe — тестовом или боевом. Если сначала вы настраиваете в тестовом режиме, шаги ниже нужно будет повторить и для боевого. 1. Перейдите в [Stripe App Marketplace](https://marketplace.stripe.com/apps/adapty) и установите приложение Adapty. Обратите внимание, что режим песочницы не поддерживает установку приложений — это можно сделать только в Production- или Test-режиме. <img src="/assets/shared/img/stripe1.png"/> 2. Выдайте приложению необходимые разрешения — это позволит Adapty получать доступ к данным и истории подписок. Затем нажмите **Continue to app settings**, чтобы продолжить. В нижней части всплывающего окна с разрешениями можно выбрать, устанавливать приложение в Live- или Test-режиме. <img src="/assets/shared/img/stripe2.png"/> 3. Во всплывающем окне сгенерируйте новый ограниченный ключ. Для этого потребуется подтвердить личность через email, Touch ID или ключ безопасности. После генерации ключ больше не будет доступен для просмотра, поэтому сразу сохраните его в менеджере паролей или защищённом хранилище. <img src="/assets/shared/img/stripe4.png"/> 4. Скопируйте сгенерированный ключ из всплывающего окна и перейдите в [App Settings → Stripe](https://app.adapty.io/settings/stripe) в Adapty. Вставьте ключ в поле **Stripe App Restricted API Key** соответствующего режима. Обратите внимание, что для Test- и Live-режима нужны разные ключи. <img src="/assets/shared/img/Stripe3.png"/> Готово! Теперь создайте продукты в Stripe и добавьте их в Adapty. <Details> <summary>Устаревший способ установки</summary> 1. Перейдите в [Developers → API Keys](https://dashboard.stripe.com/apikeys) в Stripe: <img src="/assets/shared/img/6549602-CleanShot_2023-12-06_at_17.29.122x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите кнопку **Reveal live (test) key button** рядом с заголовком **Secret key**, скопируйте ключ и перейдите в [App Settings → Stripe](https://app.adapty.io/settings/stripe) в Adapty. Вставьте ключ туда: <img src="/assets/shared/img/2989508-CleanShot_2023-12-07_at_14.59.122x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Затем скопируйте URL вебхука из нижней части той же страницы в Adapty. Перейдите в [**Developers** → **Webhooks**](https://dashboard.stripe.com/webhooks) в Stripe и нажмите кнопку **Add endpoint**: <img src="/assets/shared/img/e7149f5-CleanShot_2023-12-07_at_17.31.392x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Вставьте URL вебхука из Adapty в поле **Endpoint URL**. Выберите **Latest API version** в поле **Version** вебхука. Затем выберите следующие события: - charge.refunded - customer.subscription.created - customer.subscription.deleted - customer.subscription.paused - customer.subscription.resumed - customer.subscription.updated - invoice.created - invoice.updated - payment_intent.succeeded <img src="/assets/shared/img/cbc5404-CleanShot_2023-12-07_at_17.36.232x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Нажмите «Add endpoint», затем нажмите «Reveal» под разделом «Signing secret». Этот ключ используется для декодирования данных вебхука на стороне Adapty — скопируйте его после раскрытия: <img src="/assets/shared/img/0460cbb-CleanShot_2023-12-07_at_17.52.582x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. Наконец, вставьте этот ключ в App Settings → Stripe в Adapty в поле «Stripe Webhook Secret»: <img src="/assets/shared/img/055db20-CleanShot_2023-12-07_at_14.56.212x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Details> ## 2\. Создайте продукты в Stripe \{#2-create-products-on-stripe\} :::note Если вы настраиваете интеграцию в тестовом режиме, перед выполнением этого шага убедитесь, что Stripe также переключён в Test mode. ::: Перейдите в [Product catalog](https://dashboard.stripe.com/products?active=true) в Stripe и создайте продукты, которые хотите продавать, а также их тарифные планы. Обратите внимание, что Stripe позволяет создавать несколько тарифных планов для одного продукта — это удобно для настройки предложений без необходимости создавать дополнительные продукты. <img src="/assets/shared/img/b202e2e-CleanShot_2023-12-06_at_15.06.262x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::warning На данный момент Adapty поддерживает только тарифы **Flat rate** ($9,99/месяц) и **Package pricing** ($9,99/10 единиц), так как они аналогичны моделям из сторов. Варианты **Tiered pricing**, **Usage-based fee** и **Customer chooses price** не поддерживаются. ::: ## 3\. Добавьте продукты Stripe в Adapty \{#3-add-stripe-products-to-adapty\} :::warning Продукты обязательны! Обязательно создайте продукты Stripe в дашборде Adapty. Adapty отслеживает события только для транзакций, связанных с этими продуктами, поэтому не пропускайте этот шаг — иначе события транзакций не будут создаваться. ::: Мы относимся к Stripe так же, как к App Store и Google Play: это просто ещё один стор, где вы продаёте цифровые продукты. Настройка аналогична: просто добавьте продукты Stripe (а именно их `product_id` и `price_id`) в раздел Products в Adapty: <img src="/assets/shared/img/stripe-add-product.webp" style={{ border: 'none', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ID продуктов в Stripe выглядят как `prod_...`, а ID цен — как `price_...`. Их легко найти для каждого продукта в [Product Catalog](https://dashboard.stripe.com/products?active=true) в Stripe, открыв любой продукт: <img src="/assets/shared/img/14a72d7-CleanShot_2023-12-06_at_17.32.512x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> После добавления всех необходимых продуктов следующий шаг — сообщить Stripe, какой пользователь совершает покупку, чтобы Adapty мог её зафиксировать. ## 4\. Обогатите покупки на сайте идентификатором пользователя \{#4-enrich-purchases-made-on-the-web-with-your-user-id\} Adapty полагается исключительно на вебхуки от Stripe для предоставления и обновления уровней доступа пользователей. Однако для корректной работы интеграции вам нужно передавать дополнительную информацию со своей стороны при работе со Stripe. Чтобы уровни доступа были согласованы на всех платформах (веб и мобайл), необходимо использовать единый идентификатор пользователя, который Adapty сможет распознать из вебхуков. Это может быть email пользователя, номер телефона или любой другой ID из вашей системы авторизации. Определите, какой идентификатор вы хотите использовать для идентификации пользователей. Затем найдите в коде место, где инициируется платёж через Stripe, и добавьте этот идентификатор в объект `metadata` объекта [Stripe Subscription](https://docs.stripe.com/api/subscriptions/object#subscription_object-metadata) (`sub_...`) или [Checkout Session](https://docs.stripe.com/api/checkout/sessions/create#create_checkout_session-metadata) (`ses_...`) как `customer_user_id`: ```json showLineNumbers title="Stripe Metadata contents" {'customer_user_id': "YOUR_USER_ID"} ``` Это единственное дополнение в коде, которое вам нужно сделать. После этого Adapty будет разбирать все вебхуки от Stripe, извлекать `metadata` и корректно связывать подписки с вашими пользователями. :::warning Идентификатор пользователя обязателен В противном случае у нас нет возможности сопоставить пользователя и предоставить ему уровень доступа на мобильном устройстве. Если вы не передаёте `customer_user_id` в `metadata`, у вас будет возможность настроить Adapty так, чтобы он искал `customer_user_id` в других местах: в поле `email` объекта Customer в Stripe или в поле `client_reference_id` объекта Session в Stripe. Подробнее о настройке поведения при создании профиля читайте [ниже](stripe#profile-creation-behavior). ::: :::note Объект Customer в Stripe также обязателен Если вы используете Checkout Sessions, [убедитесь, что создаёте Customer в Stripe](https://docs.stripe.com/api/checkout/sessions/create#create_checkout_session-customer_creation), установив `customer_creation` в значение `always`. ::: ## 5\. Предоставьте доступ пользователям на мобильном устройстве \{#5-provide-access-to-users-on-the-mobile\} Чтобы мобильные пользователи, пришедшие с веба, могли получить доступ к платным функциям, просто вызовите `Adapty.activate()` или `Adapty.identify()` с тем же `customer_user_id`, который вы передали на предыдущем шаге (см. <InlineTooltip tooltip="Идентификация пользователей">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users) и [Unity](unity-identifying-users)</InlineTooltip> для получения подробной информации). ## 6\. Протестируйте интеграцию \{#6-test-your-integration\} Убедитесь, что вы выполнили все шаги выше как для Sandbox, так и для Production. Транзакции, совершённые в Test-режиме Stripe, будут считаться Sandbox-транзакциями в Adapty. :::info Готово! Теперь ваши пользователи могут оформлять покупки на сайте и получать доступ к платным функциям в приложении. А вся аналитика подписок будет собрана в одном месте. ::: ## Поведение при создании профиля \{#profile-creation-behavior\} Adapty должна привязать покупку к [профилю пользователя](profiles-crm), чтобы он был доступен на мобильном устройстве — поэтому по умолчанию профили создаются при получении вебхуков от Stripe. Вы можете выбрать, что использовать в качестве идентификатора пользователя в Adapty: 1. **По умолчанию и рекомендуется:** `customer_user_id`, переданный в metadata на [шаге 4 выше](stripe#4-enrich-purchases-made-on-the-web-with-your-user-id) 2. `email` из объекта Customer в Stripe (см. [документацию Stripe](https://docs.stripe.com/api/customers/object#customer_object-email)) 3. `client_reference_id` из объекта Session в Stripe (см. [документацию Stripe](https://docs.stripe.com/api/checkout/sessions/create#create_checkout_session-client_reference_id)) Вы можете настроить, какой идентификатор использовать, в [App Settings → Stripe](https://app.adapty.io/settings/stripe). :::warning **Примечание:** если конкретная транзакция из Stripe не содержит указанного идентификатора, профиль не будет создан вовсе. Транзакция останется анонимной до тех пор, пока её не подхватит какой-либо профиль (например, если вы воспользуетесь [S2S validate](api-adapty/operations/validateStripePurchase) и вручную сообщите нам об этой транзакции). Она отобразится в Analytics, но не в разделах, которые работают с подсчётом профилей (LTV, Cohorts, Conversions и т. д.), и не будет видна в Event feed. ::: Есть и четвёртый вариант — вообще не создавать профили, но это не рекомендуется из-за перечисленных выше ограничений в Analytics. ## Текущие ограничения \{#current-limitations\} ### Повышение, понижение тарифа и пропорциональный расчёт \{#upgrading-downgrading-and-proration\} Изменения подписки — например, переход на более дорогой или дешёвый тариф — могут приводить к пропорциональным начислениям. Adapty не учитывает их при расчёте дохода. Лучше всего отключить эти опции вручную через дашборд Stripe. Также можно отключить их, установив значение атрибута `proration_behaviour` в `none` через Stripe API. ### Отмена подписок \{#cancellations\} В Stripe есть два варианта отмены подписки: 1. Немедленная отмена: подписка отменяется сразу с пропорциональным расчётом или без него 2. Отмена в конце периода: подписка отменяется по истечении текущего расчётного периода (аналогично встроенным подпискам в сторах). Adapty поддерживает оба варианта, однако при расчёте дохода в случае немедленной отмены пропорциональный расчёт не учитывается. ### Проблемы с оплатой и льготный период \{#billing-issues-and-grace-period\} Когда у пользователя возникает проблема с оплатой, Adapty генерирует событие billing issue и доступ отзывается. Льготный период Stripe пока не поддерживается — это будет реализовано в будущих версиях. ### Возвраты \{#refunds\} Adapty отслеживает только полные возвраты. Частичные возвраты и пропорциональные расчёты в настоящее время не поддерживаются. ### Уникальность идентификаторов транзакций \{#transaction-id-uniqueness\} Adapty сопоставляет профили и транзакции с помощью `store_transaction_id` и `store_original_transaction_id`. Они **должны быть уникальными** в рамках тестовой и Production-среды. #### Почему это важно \{#why-this-matters\} Если один и тот же ID транзакции существует в обеих средах, Adapty считает их одной транзакцией, что приводит к: - Переносу тестовых уровней доступа и ID продуктов на Production-покупки - Некорректным ID продуктов и средам в ответах API - Нарушению привязки профилей и событий подписок #### Как обеспечить уникальность \{#how-to-ensure-uniqueness\} ID счетов (invoice) в Stripe могут совпадать в Test- и Live-средах. Чтобы избежать конфликтов между средами, выберите один из подходов: #### Вариант 1: Нумерация счетов на уровне аккаунта с префиксами для каждой среды \{#option-1-account-level-numbering-with-environment-prefixes\} Настройте префиксы отдельно для каждой среды: 1. В дашборде Stripe переключитесь в Test mode. 2. Перейдите в [Settings → Billing → Invoices](https://dashboard.stripe.com/settings/account/?support_details=true). 3. Установите **Invoice numbering** в значение **Sequentially across your account**. 4. Задайте **Invoice prefix** как TEST- (или другой префикс, уникальный для тестовой среды). 5. Переключитесь в Live mode и повторите шаги 2–4, используя LIVE- (или другой префикс, уникальный для боевой среды) в качестве префикса. #### Вариант 2: Нумерация счетов на уровне пользователя \{#option-2-customer-level-numbering\} Установите **Invoice numbering** в [**Stripe settings** -> **Billing** -> **Invoices**](https://dashboard.stripe.com/settings/account/?support_details=true) в значение **Sequentially for each customer (customer-level)**. Даже при такой настройке, если вы удалите счёт, Stripe может повторно использовать этот ID для новых счетов того же пользователя. Поэтому по возможности избегайте удаления счетов. ### Разовые покупки через Stripe Checkout или Payment Links \{#one-time-purchases-via-stripe-checkout-or-payment-links\} Adapty отслеживает разовые (не подписочные) покупки через Stripe Checkout (`mode=payment`) или Payment Links только в том случае, если Stripe генерирует счёт (invoice) для покупки. По умолчанию Stripe не создаёт счёт для разовых покупок через Checkout. В этом случае `payment_intent.succeeded` приходит без данных о счёте, чего недостаточно для записи транзакции в Adapty. Чтобы отслеживать разовые покупки через Checkout в Adapty, [включите создание счёта](https://docs.stripe.com/payments/checkout/receipts?payment-ui=stripe-hosted#paid-invoices-hosted) при создании сессии. Тогда Stripe сгенерирует счёт и отправит связанные события `invoice.created` и `invoice.updated`, которые Adapty обработает для записи транзакции. ## Получите больше от данных Stripe \{#get-more-from-your-stripe-data\} После интеграции со Stripe Adapty сразу готова предоставлять аналитику. Чтобы максимально использовать данные Stripe, вы можете настроить дополнительные интеграции Adapty для пересылки событий Stripe — и собрать всю аналитику подписок в едином дашборде Adapty. :::tip Для более детальной аналитики вы можете добавить `variation_id` в metadata Stripe, чтобы привязывать покупки к конкретным экземплярам пейвола. Это особенно полезно при реализации собственных веб-пейволов, когда вы хотите отслеживать, показ какого именно пейвола привёл к конверсии. Обратите внимание, что `variation_id` считывается из metadata только объектов Stripe Subscription (`sub_...`) и Checkout Session (`ses_...`): ```json showLineNumbers title="Stripe Metadata with variation_id" { 'customer_user_id': "YOUR_USER_ID", 'variation_id': "YOUR_VARIATION_ID" } ``` ::: Интеграции, которые можно использовать для пересылки и анализа событий Stripe: - [Amplitude](amplitude/) - [Webhook](webhook) - [Firebase](firebase-and-google-analytics) - [Mixpanel](mixpanel) - [Posthog](posthog) ### Поддерживаемые события Stripe \{#supported-stripe-events\} Adapty поддерживает следующие события Stripe: - charge.refunded - customer.subscription.created - customer.subscription.deleted - customer.subscription.paused - customer.subscription.resumed - customer.subscription.updated - invoice.created - invoice.updated - payment_intent.succeeded --- # File: paddle --- --- title: "Начальная интеграция с Paddle" description: "Интегрируйте Paddle с Adapty для удобной обработки платежей по подпискам." --- Adapty поддерживает сценарии web2app, отслеживая веб-платежи и подписки, оформленные через [Paddle](https://www.paddle.com/). Интеграция охватывает покупки, инициированные на веб-сайте, и синхронизирует их с доступом в мобильном приложении и аналитикой наряду со встроенными покупками из сторов. Это полезно в следующих сценариях: - Собирать данные о подписках из встроенных покупок и покупок на сайте в единой системе - Предоставлять доступ к платным функциям мобильного приложения пользователям, которые оформили покупку на сайте - Просматривать аналитику и данные о подписках из всех каналов продаж в одном дашборде :::note Apple теперь разрешает приложениям в US App Store размещать ссылки на внешние платёжные системы, однако приложения всё ещё могут быть обязаны предлагать встроенные покупки наряду с внешними вариантами. Ознакомьтесь с актуальными правилами App Store для своего региона и категории приложения. ::: :::note Эта интеграция предназначена для отслеживания и синхронизации веб-покупок в Paddle. Если вам нужно перенаправить пользователей из приложения на веб-страницу оформления заказа, используйте [веб-пейволы](web-paywall) Adapty. ::: Чтобы настроить интеграцию с Paddle, выполните следующие шаги: ## 1\. Подключите Paddle к Adapty \{#1-connect-paddle-to-adapty\} Интеграция использует вебхуки для отправки данных о подписках из Paddle в Adapty. Чтобы связать аккаунты Adapty и Paddle, вам нужно: 1. Предоставить API-ключи Paddle. 2. Добавить URL вебхука Adapty в Paddle. :::note Шаги ниже применимы как к Production, так и к Test-окружению. Вы можете настроить оба одновременно. Указанные ссылки ведут на Production-окружение — чтобы получить ссылки для Test-окружения, просто добавьте `sandbox-` в начало каждого URL. Например, используйте `https://sandbox-vendors.paddle.com/authentication-v2` вместо `https://vendors.paddle.com/authentication-v2`. ::: ### 1.1. Получите и добавьте API-ключи Paddle \{#get-and-add-paddle-api-keys\} 1. В Paddle перейдите в [Developer Tools → Authentication](https://vendors.paddle.com/authentication-v2) и нажмите **New API key**. <img src="/assets/shared/img/paddle-new-key.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Введите название ключа и установите срок его действия. Чтобы API-ключ работал с Adapty, необходимо предоставить ему разрешение **Read** для всех сущностей. Нажмите **Save**. <img src="/assets/shared/img/paddle-key.webp" style={{ border: 'none', /* border width and color */ width: '300px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Нажмите **Copy key**. <img src="/assets/shared/img/copy-paddle-key.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. В дашборде Adapty перейдите в [App Settings → Paddle](https://app.adapty.io/settings/paddle) и вставьте ключ в поле **Paddle API key**. :::warning Если вы задали срок действия для Paddle API key, вам нужно вручную сгенерировать новый ключ и обновить его в Adapty до истечения срока. Когда ключ истечёт, интеграция прекратит работу без каких-либо предупреждений, и пользователи не смогут совершать покупки. ::: <img src="/assets/shared/img/paddle-api-keys-adapty.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 1.2. Добавьте события, которые будут отправляться в Adapty \{#12-add-events-that-will-be-sent-to-adapty\} 1. Скопируйте **Webhook URL** с той же страницы **Paddle** в Adapty. 2. В Paddle перейдите в [**Developer Tools → Notifications**](https://vendors.paddle.com/notifications-v2) и нажмите **New destination**, чтобы добавить вебхук. <img src="/assets/shared/img/paddle-webhook.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Введите понятное название для вебхука. Рекомендуем включить в него «Adapty», чтобы легко найти его при необходимости. 4. Вставьте **Webhook URL** из Adapty в поле **URL**. Убедитесь, что используете вебхук для нужного окружения. 5. Установите **Notification type** в значение **Webhook**. <img src="/assets/shared/img/paddle-create-webhook.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. Выберите следующие события: - `subscription.created` - `subscription.updated` - `transaction.created` - `transaction.updated` - `adjustment.created` - `adjustment.updated` <img src="/assets/shared/img/paddle_events.png" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Нажмите **Save destination**, чтобы завершить настройку вебхука. ### 1.3. Получите и добавьте секретный ключ webhook \{#retrieve-and-add-the-webhook-secret-key\} 1. В окне **Notifications** нажмите на три точки рядом с только что созданным webhook и выберите **Edit destination**. 2. В панели **Edit destination** появится новое поле **Secret key**. Скопируйте его. <img src="/assets/shared/img/paddle-webhook-secret-key-copy.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. В Adapty перейдите в [App Settings → Paddle](https://app.adapty.io/settings/paddle) и вставьте ключ в поле **Notification secret key**. Этот ключ используется для проверки данных вебхука в Adapty. <img src="/assets/shared/img/paddle-webhook-secret-key.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 1.4. Сопоставление клиентов Paddle с профилями Adapty \{#14-match-paddle-customers-with-adapty-profiles\} Adapty должен связать каждую покупку с [профилем клиента](profiles-crm), чтобы её можно было использовать в вашем приложении. По умолчанию профили создаются автоматически, когда Adapty получает вебхуки от Paddle. Вы можете выбрать, какое значение использовать в качестве `customer_user_id` в Adapty: 1. **По умолчанию и рекомендуется:** `customer_user_id`, который вы передаёте в поле `custom_data` (см. [документацию Paddle](https://developer.paddle.com/build/transactions/custom-data)) 2. `email` из объекта Paddle Customer (см. [документацию Paddle](https://developer.paddle.com/paddle-js/methods/paddle-checkout-open/#parameters)) 3. Paddle Customer ID в формате `ctm-...` (см. [документацию Paddle](https://developer.paddle.com/paddle-js/methods/paddle-checkout-open/#parameters)) 4. Не создавать профили. Выберите этот вариант, если хотите самостоятельно управлять профилями пользователей. Вы можете настроить, какое значение использовать, в поле **Profile creation behavior** в [App Settings → Paddle](https://app.adapty.io/settings/paddle). <img src="/assets/shared/img/paddle-users.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 2. Добавьте продукты Paddle в Adapty :::warning Обязательно добавьте ваши продукты Paddle в дашборд Adapty или добавьте Paddle product ID к уже существующим продуктам. Adapty отслеживает события только для транзакций, связанных с этими продуктами. Если пропустить этот шаг, события транзакций создаваться не будут. ::: Paddle работает в Adapty так же, как App Store и Google Play — это ещё одна платформа для продажи цифровых продуктов. Чтобы настроить её, добавьте нужные значения `product_id` и `price_id` из Paddle в разделе [Products](https://app.adapty.io/products) в Adapty. <img src="/assets/shared/img/paddle-create-product.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> В Paddle идентификаторы продуктов выглядят как `pro_...`, а идентификаторы цен — как `pri_...`. Их можно найти в [каталоге продуктов Paddle](https://vendors.paddle.com/products-v2), открыв конкретный продукт: <img src="/assets/shared/img/paddle-product-price.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> После того как продукты добавлены, следующий шаг — убедиться, что Adapty сможет связать покупку с нужным пользователем. ## 3\. Предоставьте доступ пользователям на мобильном устройстве \{#3-provide-access-to-users-on-the-mobile\} Чтобы пользователи, совершившие покупку на сайте, получили доступ в мобильном приложении, вызовите `Adapty.activate()` или `Adapty.identify()` с тем же `customer_user_id`, который был передан при оформлении покупки. Подробнее см. в разделе [Идентификация пользователей](identifying-users). ## 4\. Протестируйте интеграцию \{#4-test-your-integration\} После завершения настройки можно протестировать интеграцию. Транзакции в Test-окружении Paddle будут отображаться в Adapty со статусом **Test**, а транзакции из Production — со статусом **Production**. Интеграция завершена. Пользователи могут оформлять подписки на вашем сайте и автоматически получать доступ к премиум-функциям в мобильном приложении, а вы отслеживаете всю аналитику подписок в едином дашборде Adapty. ## Важные замечания \{#important-considerations\} - В аналитике Adapty суммы транзакций включают налоги и комиссии Paddle, что отличается от дашборда Paddle, где суммы отображаются после вычета налогов и комиссий. Поэтому числа в Adapty будут выше, чем в вашем дашборде Paddle. - В отличие от других сторов, возвраты в Paddle затрагивают только конкретную транзакцию и не отменяют подписку автоматически. Подписка остаётся активной, если её не отменить явно. - Вы также можете передавать `variation_id` в поле `custom_data`, чтобы атрибутировать покупки конкретным экземплярам пейвола. Adapty обработает эти данные из вебхуков и учтёт их в аналитике. ### Платные триалы \{#paid-trials\} При работе с платными триалами в Paddle нужно создать два продукта в Adapty: 1. Создайте разовую покупку и свяжите её с ценой Paddle, которая списывает оплату за триальный период. 2. Затем создайте продукт-подписку (Monthly/Weekly/и т. д.) и свяжите его с ценой Paddle, в которой настроен бесплатный триал. С точки зрения Paddle, это один продукт с двумя ценами в рамках одной транзакции: одна цена — за триальный период (например, $0.99), другая — за бесплатный триал ($0.00). С точки зрения Adapty это создаёт два отдельных события: разовая покупка для пробного платежа и событие начала пробного периода для продукта-подписки. Например, когда пользователь начинает платный пробный период за $0,99 для подписки за $9,99/месяц, Paddle создаёт одну транзакцию с обеими ценами, тогда как Adapty обрабатывает это как разовую покупку на $0,99 (немедленный платёж) и событие начала пробного периода на $0,00 (будущая подписка за $9,99/месяц). :::note Когда пользователи отменяют платный пробный период, вы получаете события **Trial expired** и **Trial renewal canceled**. ::: ## Больше возможностей с данными Paddle \{#get-more-from-your-paddle-data\} :::important Чтобы события Paddle работали с интеграциями, ваши пользователи должны хотя бы раз войти в приложение через аккаунт App Store/Google Play. ::: После интеграции с Paddle Adapty сразу готов предоставлять аналитику. Чтобы максимально использовать данные Paddle, можно настроить дополнительные интеграции Adapty для передачи событий Paddle — это объединит всю аналитику подписок в едином дашборде Adapty. Интеграции для передачи и анализа событий Paddle: - [AppsFlyer](appsflyer) - [Webhook](webhook) - [Posthog](posthog) ## Текущие ограничения \{#current-limitations\} - **Отмены**: Paddle предлагает два варианта отмены подписки: 1. Немедленная отмена: подписка отменяется сразу. 2. Отмена в конце периода: подписка отменяется по истечении текущего расчётного периода (аналогично встроенным подпискам в сторах). - **Возвраты**: Adapty отслеживает полные и частичные возвраты средств. - **Grace period**: По умолчанию Paddle применяет фиксированный льготный период в 30 дней при проблемах с оплатой, в течение которого подписка остаётся активной. Вы можете [настроить продолжительность льготного периода и действие по его окончании (приостановка или отмена подписки)](https://developer.paddle.com/build/retain/configure-payment-recovery-dunning#prerequisites). **Пробные периоды**: если оплата не проходит после окончания пробного периода, статус подписки меняется на `past_due`. В production Paddle's Retain применяет окно повторных попыток оплаты (dunning window), чтобы попытаться восстановить платёж до того, как подписка будет отменена или приостановлена. В песочнице Retain недоступен, поэтому повторные попытки оплаты не предпринимаются, и подписка остаётся в статусе `past_due` бессрочно. --- **См. также:** - [Валидация покупки в Paddle, получение уровня доступа и импорт истории транзакций из Paddle через серверный API](api-adapty/operations/validatePaddlePurchase) --- # File: custom-store --- --- title: "Начальная интеграция с другими сторами" description: "Начальная интеграция Adapty с App Store: краткое руководство" --- Рады видеть вас в Adapty! Наша главная цель — помочь вам быстро стартовать и добиться наилучших результатов для вашего приложения. Начальная интеграция нужна только для [App Store](initial_ios), [Google Play](initial-android), [Stripe](stripe) и [Paddle](paddle), поскольку Adapty проверяет ваши приложения, продукты и предложения именно в этих сторах. Adapty не валидирует данные из других сторов и не обрабатывает покупки, совершённые через них. Тем не менее вы можете помечать продукты, проданные через другие сторы, чтобы Adapty предоставлял доступ к платному контенту после успешной покупки, отображал транзакции в аналитике и передавал их через интеграции. <img src="/assets/shared/img/Adapty-Communication-Scheme.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> :::important Убедитесь, что ваш бэкенд обрабатывает покупку и отправляет транзакцию в Adapty через [серверный API Adapty](getting-started-with-server-side-api). Adapty предоставит доступ, инициирует событие транзакции, отправит его в интеграции и отобразит в аналитике только после получения транзакции. ::: Чтобы пометить продукт как проданный через кастомный стор, выберите нужный стор при создании продукта. Если нужного стора нет в списке, создайте его следующим образом: 1. На странице **Products** откройте продукт, который хотите продавать через кастомный стор. 2. Выберите стор, через который будете продавать. Если его нет в списке, нажмите кнопку **Create Custom Store**. <img src="/assets/shared/img/create_custom-appstore.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Введите **Title** и **Store ID** стора. 4. Нажмите кнопку **Create store**. Если ваш бэкенд настроен правильно, Adapty будет получать транзакции продуктов из этого кастомного стора, отображать их в аналитике, в [**Event Feed**](event-feed) и [интеграциях](https://app.adapty.io/integrations), а также предоставлять доступ соответствующим образом. ## Максимальная польза от данных кастомного стора \{#get-more-from-your-custom-store-data\} :::important Чтобы события кастомного стора работали с интеграциями, ваши пользователи должны хотя бы один раз войти в приложение через аккаунт App Store или Google Play. ::: После настройки интеграции с кастомным стором Adapty сразу готов предоставлять аналитику. Чтобы извлечь максимум из ваших данных, настройте дополнительные интеграции Adapty для передачи событий кастомного стора — так вся аналитика по подпискам окажется в едином дашборде Adapty. Интеграции для передачи и анализа событий кастомного стора: - [AppsFlyer](appsflyer) - [Webhook](webhook) - [Posthog](posthog) --- # File: transfer-apps --- --- title: "Передача приложения другому владельцу" description: "Смените владельца приложения в Adapty" --- Передайте приложение другому владельцу, если ваша компания поглощена, вы продаёте приложение или реструктурируете бизнес. Процесс передачи включает согласованные изменения в Adapty, App Store Connect и Google Play Console, чтобы сервис работал без перебоев. ## Передача приложения \{#transfer-app-ownership\} Сначала выполните передачу в сторе, затем — в Adapty. Такой порядок гарантирует, что покупки продолжают работать на протяжении всего перехода. :::note Не удаляйте и не пересоздавайте продукты в процессе передачи. Не меняйте идентификаторы продуктов до тех пор, пока не убедитесь, что передача прошла успешно. ::: ### Передача App Store (iOS) \{#app-store-ios-transfer\} :::important Ключи API App Store Connect (Issuer ID, Key ID, файл .p8) привязаны к аккаунту, а не к приложению. После передачи необходимо сгенерировать новые ключи API в аккаунте нового владельца и обновить их в Adapty. App-specific shared secret продолжает валидировать чеки в течение периода передачи, однако новый владелец также должен перегенерировать его и обновить в Adapty после завершения передачи. ::: 1. **Новый владелец:** Создайте аккаунт в Adapty на [app.adapty.io](https://app.adapty.io), если у вас его ещё нет. 2. **Старый владелец:** Инициируйте передачу приложения в App Store Connect, следуя [руководству Apple по передаче](https://developer.apple.com/help/app-store-connect/transfer-an-app/overview-of-app-transfer). 3. **Новый владелец:** Примите передачу в App Store Connect. 4. **Старый владелец:** Напишите на [support@adapty.io](mailto:support@adapty.io), чтобы передать приложение в Adapty. Укажите название приложения и адрес электронной почты нового владельца. 5. **Новый владелец:** После получения приложения в Adapty пройдите [руководство по интеграции с App Store](initial_ios), чтобы сгенерировать и настроить все учётные данные в рамках вашего аккаунта. ### Перенос Google Play (Android) \{#google-play-android-transfer\} 1. **Новый владелец:** Создайте аккаунт Adapty на [app.adapty.io](https://app.adapty.io), если у вас его ещё нет. 2. **Оба владельца:** Убедитесь, что оба аккаунта Google Play Developer полностью зарегистрированы. 3. **Старый владелец:** Отправьте запрос на передачу через Google Play Console или Google Play Developer Support. Google может запросить дополнительные документы: номера DUNS, договоры или подтверждение сделки. 4. **Новый владелец:** Проверьте и подтвердите запрос на передачу. 5. **Google:** Команда поддержки Google обрабатывает передачу — обычно в течение нескольких рабочих дней, но срок может увеличиться в зависимости от верификации аккаунта, сложности подписок и настройки платежей. 6. **Старый владелец:** После того как Google завершит передачу, напишите на [support@adapty.io](mailto:support@adapty.io) с просьбой перенести приложение в Adapty. Укажите название приложения и email нового владельца. 7. **Новый владелец:** Получив приложение в Adapty, выполните [инструкцию по интеграции с Google Play](initial-android), чтобы сгенерировать и настроить все учётные данные в своём аккаунте. Передача включает пользователей, подписки, статистику, оценки и описание приложения в сторе. Непрерывность выставления счетов для существующих подписчиков сохраняется, но выплаты переключаются на торговый счёт нового владельца только после завершения передачи. Отчёты о выплатах и заказы до передачи остаются в исходном аккаунте. Подробные требования см. в [руководстве по передаче](https://support.google.com/googleplay/android-developer/answer/6230247) от Google. ## Снижение рисков и тайминг \{#risk-mitigation-and-timing\} **Что продолжает работать во время передачи:** - Покупки и продления (app-specific shared secret продолжает валидировать чеки в течение периода передачи) - Доступ для существующих подписчиков - SDK продолжает работать **Что временно перестаёт работать:** - Вызовы App Store Connect API (до настройки новых ключей) - Серверные уведомления (до перенастройки эндпоинта) - В аналитике возможны пробелы во время смены учётных данных **Рекомендуемое время для переноса:** - Проводите перенос в период низкой активности пользователей (3:00–6:00 по основному часовому поясу) - Убедитесь, что новый владелец готов сразу настроить учётные данные после принятия переноса стора - Закладывайте 15–30 минут между принятием переноса и завершением интеграции с Adapty **После завершения переноса:** - Немедленно протестируйте валидацию чеков - В течение 48 часов следите за успешностью авторебновлений - Убедитесь, что серверные уведомления доходят до ваших систем - Проверьте, что новые покупки отслеживаются корректно ## Проверка успешной передачи \{#verify-transfer-completed-successfully\} После завершения передачи как в Adapty, так и в сторе: 1. **Проверьте доступ к дашборду:** Новый владелец должен видеть приложение в своём дашборде Adapty. 2. **Проверьте подключение ключа API:** Убедитесь, что новый ключ API App Store Connect или сервисный аккаунт Google Play успешно подключается в Adapty. 3. **Протестируйте подключение SDK:** Запустите приложение и убедитесь, что SDK Adapty инициализируется без ошибок. --- # File: installation-of-adapty-sdks --- --- title: "Установка Adapty SDK" description: "Установите Adapty SDK для iOS, Android и кросс-платформенных приложений." --- У вас есть три способа начать работу в зависимости от ваших предпочтений: - **Следуйте платформенным гайдам по быстрому старту**: Гайды содержат готовые к использованию фрагменты кода, поэтому реализация не займёт много времени. - [iOS](ios-sdk-overview) - [Android](android-sdk-overview) - [React Native](react-native-sdk-overview) - [Flutter](flutter-sdk-overview) - [Unity](unity-sdk-overview) - [Kotlin Multiplatform](kmp-sdk-overview) - [Capacitor](capacitor-sdk-overview) - **Используйте LLM**: Наша документация оптимизирована для работы с языковыми моделями. Прочитайте наш [гайд](adapty-cursor) о том, как максимально эффективно использовать LLM с документацией Adapty. - **Изучите примеры приложений**: - [iOS (Swift)](https://github.com/adaptyteam/AdaptySDK-iOS/tree/master/Examples) - [Android (Kotlin)](https://github.com/adaptyteam/AdaptySDK-Android/tree/master/app) - [React Native (базовый пример на чистом RN)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/BasicExample) - [React Native (расширенный пример — удобен для разработки, так как позволяет работать с более сложными случаями)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/AdaptyDevtools) - [React Native (Expo dev build)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/FocusJournalExpo) - [React Native (Expo Go & Web)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/ExpoGoWebMock) - [Flutter (Dart)](https://github.com/adaptyteam/AdaptySDK-Flutter/tree/master/example) - [Unity (C#)](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Assets) - [Kotlin Multiplatform](https://github.com/adaptyteam/AdaptySDK-KMP/tree/main/example) - [Capacitor](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples) --- # File: sample-apps --- --- title: "Примеры приложений" description: "" --- Чтобы помочь вам начать работу с Adapty SDK, мы подготовили примеры приложений, которые демонстрируют интеграцию и использование ключевых возможностей. В них реализованы готовые примеры пейволов, покупок и отслеживания аналитики. <img src="/assets/shared/img/adapty-scheme.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Зачем использовать примеры приложений? \{#why-use-sample-apps\} - **Быстрая интеграция:** посмотрите, как Adapty SDK работает в реальном приложении. - **Лучшие практики:** следуйте рекомендованным паттернам реализации. - **Отладка и тестирование:** используйте примеры приложений для устранения проблем и экспериментов перед интеграцией Adapty в собственный проект. ## Доступные примеры приложений \{#available-sample-apps\} - [iOS (Swift)](https://github.com/adaptyteam/AdaptySDK-iOS/tree/master/Examples) - [Android (Kotlin)](https://github.com/adaptyteam/AdaptySDK-Android/tree/master/app) - [React Native (базовый пример на чистом RN)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/BasicExample) - [React Native (расширенный пример — удобен при разработке, позволяет работать с более сложными сценариями)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/AdaptyDevtools) - [React Native (Expo dev build)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/FocusJournalExpo) - [React Native (Expo Go & Web)](https://github.com/adaptyteam/AdaptySDK-React-Native/tree/master/examples/ExpoGoWebMock) - [Flutter (Dart)](https://github.com/adaptyteam/AdaptySDK-Flutter/tree/master/example) - [Unity (C#)](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Assets) - [Kotlin Multiplatform](https://github.com/adaptyteam/AdaptySDK-KMP/tree/main/example) - [Capacitor (React)](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-react-example) - [Capacitor (Vue.js)](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-vue-example) - [Capacitor (Angular)](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/basic-angular-example) - [Capacitor (расширенные инструменты разработки)](https://github.com/adaptyteam/AdaptySDK-Capacitor/tree/master/examples/adapty-devtools) --- # File: paywall-builder-templates --- --- title: "Создание флоу" description: "Начните новый флоу с готового шаблона из галереи или с минимального стартового варианта." --- Флоу можно создать из шаблона или с нуля. :::link Хотите узнать больше о создании флоу? Смотрите пошаговые видеоуроки в нашем [плейлисте на YouTube](https://www.youtube.com/playlist?list=PLMksWqaZiWtM). ::: ## Создание флоу \{#create-flow\} 1. Откройте страницу **Flows**. 2. Нажмите **Create flow**. 3. Выберите вариант: - **Browse templates** (открывает библиотеку шаблонов) - **Start from scratch** (создаёт пустой флоу) 4. Переименуйте флоу в редакторе. Нажмите на название флоу в заголовке и введите новое имя. :::warning Adapty допускает одинаковые названия флоу. Переименовывайте каждый новый флоу, иначе получите несколько флоу с названием **Untitled**, которые сложно различить. ::: ### Использование шаблона \{#use-a-template\} Библиотека шаблонов содержит несколько готовых шаблонов, которые служат отправной точкой для вашего флоу. Каждый из них — полноценный флоу с несколькими экранами, интерактивными элементами и настроенной навигацией. Любой элемент можно отредактировать под свои нужды. Чтобы применить шаблон: 1. В библиотеке шаблонов просмотрите карточки. На каждой карточке показаны скриншоты из флоу. 2. Нажмите **Use as template** на нужной карточке. Шаблон загружается в конструктор. Отсюда вы можете изменить любой элемент, экран или свойство. ### Начать с нуля \{#start-from-scratch\} Начать с нуля — создать флоу с одним пустым экраном. Оформите экран с помощью элементов из [библиотеки элементов](builder-elements). ## Смена шаблона \{#change-the-template\} Шаблон можно сменить прямо в конструкторе. Откройте панель **Screens** и нажмите кнопку **Templates** Templates, чтобы снова открыть библиотеку шаблонов, затем выберите новый шаблон. :::warning Применение нового шаблона заменяет текущий черновик флоу. Adapty запросит подтверждение — нажмите **Use template**, чтобы продолжить, или **Cancel**, чтобы оставить черновик. После подтверждения восстановить предыдущий черновик невозможно. Опубликованный флоу остаётся активным и не затрагивается. ::: ## Кастомные шрифты в шаблонах \{#custom-fonts-in-templates\} :::link Основная статья: [Кастомные шрифты во Flow Builder](using-custom-fonts-in-flow-builder) ::: Шаблоны, отмеченные чипом **Custom font**, используют кастомные шрифты. Эти шрифты не входят в состав SDK. Наведите курсор на чип, чтобы увидеть, какие шрифты использует шаблон. Чтобы типографика корректно отображалась на устройстве, добавьте файлы шрифтов в бандл приложения. Старые версии приложения, в которых нет этих шрифтов, будут использовать системный шрифт. Чтобы сменить шрифт, не затронув старые версии, продублируйте флоу, измените шрифт в копии и ограничьте копию [пользователями на версиях приложения, где есть этот шрифт](segments). --- # File: builder-ui --- --- title: "Интерфейс Flow Builder" description: "Обзор интерфейса и рабочей области Flow Builder." --- Основной интерфейс Flow Builder включает все инструменты, необходимые для добавления визуальных элементов, редактирования их свойств и изменения логики пользовательского флоу. В этой статье описан каждый раздел интерфейса: что он делает и где его найти. :::link Хотите узнать больше о создании флоу? Смотрите пошаговые видеоуроки в нашем [плейлисте на YouTube](https://www.youtube.com/playlist?list=PLMksWqaZiWtM). ::: ## Элементы управления проектом и полезные сочетания клавиш (верхняя панель инструментов) \{#project-controls-and-useful-shortcuts-top-toolbar\} * **Close** Close: Выйти из редактора флоу и вернуться на страницу флоу. * **App name** App: Показывает, к какому приложению относится флоу. * **All flows** Flows: Открыть список всех флоу для этого приложения. * **Flow status**: Иконка слева от названия флоу показывает текущий [статус флоу](builder-save-publish#flow-status): - **Draft** Draft - **Publishing** (крутящийся индикатор загрузки) - **Failed** Failed - или **Live** Live. * **Rename the flow**: Нажмите на название флоу, чтобы переименовать его. Несколько флоу могут иметь одинаковые названия — [давайте каждому новому флоу уникальное имя](paywall-builder-templates#create-flow). * **View mode toggle**: Переключайтесь между режимом дизайна Cursor и [режимом Remote Config](customize-flow-with-remote-config)Remote Config. * **Undo/Redo**: Нажмите на стрелки, чтобы отменить Undo или повторить Redo изменения во флоу. Также можно использовать ⌘Z / Ctrl+Z для отмены. * **Save draft / Publish**: Нажмите **Save draft**, чтобы сохранить прогресс без публикации (⌘ / Ctrl+S). Откройте выпадающий список Open dropdown, чтобы получить доступ к кнопке [**Publish**](builder-save-publish). Добавить флоу в [плейсмент](create-placement) можно только после публикации. ## Область предпросмотра (центр) \{#preview-area-center\} Центральная область рабочего пространства показывает, как выглядит ваш флоу на мобильном устройстве. * Чтобы выбрать элемент и изменить его свойства, нажмите на него. Чтобы выбрать дочерний элемент внутри контейнера, сначала нажмите на контейнер, затем на дочерний элемент. * Чтобы изменить свойства самого экрана, нажмите в пустое место за пределами любого элемента или выберите экран на панели Screens and Layers. * Чтобы изменить порядок элемента, перетащите его строку вверх или вниз на панели Screens and Layers. :::warning Редактор флоу создан для адаптивных макетов. Поэтому вы **не можете вручную изменять положение элементов** — можно только менять их порядок. Настройки макета каждого контейнера определяют, как распределяются находящиеся внутри него элементы. ::: ### Панель активного экрана (над превью устройства) \{#active-screen-bar-above-the-device-preview\} - **Screen name** — плашка с именем текущего экрана. - **Toggle animations** Toggle animations — включает или отключает предпросмотр анимаций элементов; они воспроизводятся непрерывно, пока не отключены. Отображается только если на активном экране есть хотя бы одна [анимация](builder-styling#animation). Не влияет на отображение анимаций на реальном устройстве. - **Add element** Plus — открывает [библиотеку элементов](builder-elements) для текущего экрана. Аналог кнопки **+** в верхней части панели «Screens and Layers» — удобно использовать, когда панель свёрнута. ### Элементы управления видом (нижняя панель инструментов) \{#view-controls-bottom-toolbar\} Инструменты нижней панели позволяют управлять предпросмотром. * **Device**: Выберите одну из доступных моделей iPhone или Android, чтобы изменить размеры области просмотра и отображение устройства. * **Screen orientation**: Переключайтесь между портретным Portrait и альбомным Landscape режимами, чтобы просмотреть флоу в разных ориентациях. * **Color scheme**: Переключайтесь между светлой Light mode и тёмной Dark mode темами, чтобы увидеть, как дизайн адаптируется к разным схемам оформления. * **Locale**: Выберите локаль, чтобы просмотреть флоу с локализованным контентом. * **View options**: Включайте или отключайте отображение рамки устройства и направляющих безопасной зоны. ## Свойства экрана и элементов (правая панель) \{#screen-and-element-properties-right-panel\} ### Настройки экрана и макет \{#screen-settings-and-layout\} :::link Основная статья: [Экраны и слои](paywall-layout-and-products) ::: Когда ни один элемент не выбран, правая панель позволяет настраивать свойства активного [экрана флоу](paywall-layout-and-products), в том числе: * Взаимодействие с системным UI (например, отображение строки состояния) * Правила автоматической компоновки * Фон (цвет, изображение или видео) * Размер отступов * Поведение вертикальной прокрутки Если экран содержит определённые элементы, например [интерактивные викторины](onboarding-quizzes), этот список расширится соответствующими свойствами. ### Свойства элемента \{#element-properties\} При выборе элемента правая панель позволяет изменить его стилевые и интерактивные свойства. #### Свойства дизайна \{#design-properties\} :::link Подробнее: [Расположение и позиционирование](manage-paywall-ui-elements), [Стили и внешний вид](builder-styling) ::: Вкладка **Design** позволяет настроить внешний вид и расположение выбранного элемента: * **Visibility**: Показать или скрыть элемент. Включите **Conditional** visibility, чтобы задать правила отображения элемента. * **Position**: Выберите тип позиционирования: Relative, Absolute или Fixed. * **Content** (только для текстовых элементов): редактируйте текстовое содержимое элемента, вставляйте [переменные](#variables) и управляйте локализациями. * **Typography** (только для текстовых элементов): настройте шрифт, начертание, размер, цвет, выравнивание, оформление и обрезку текста. * **Spacing**: задайте внешние и внутренние отступы элемента. * **Effects**: добавьте внешние тени, внутренние тени, размытие фона или размытие слоя. * **Animation**: добавьте анимационные эффекты (например, Pulse) и настройте их продолжительность и интенсивность. * **Appearance**: отрегулируйте прозрачность и угол поворота. * **Layout**: выберите направление раскладки (вертикальное или горизонтальное) и задайте распределение дочерних элементов. #### Свойства взаимодействий \{#interactions-properties\} :::link Подробнее: [Действия](onboarding-actions), [Навигация и взаимодействие](onboarding-navigation-branching) ::: Вкладка **Interactions** позволяет задать, что происходит при взаимодействии пользователя с выбранным элементом. Каждое взаимодействие состоит из **триггера** и одного или нескольких **действий**: * **Триггеры** определяют *когда* что-то происходит — например, **On Tap** (пользователь нажимает на элемент). * **Действия** определяют *что* происходит — например, переход на другой экран или изменение значения переменной. Добавьте несколько действий к одному триггеру, чтобы выполнять их последовательно. К одному элементу можно добавить несколько триггеров для выполнения нескольких действий по порядку. ## Левая панель \{#left-panel\} Левая панель меняет своё содержимое в зависимости от активной кнопки. Доступные разделы: * [Экраны и слои](#screens-and-layers) * [Добавить элемент](#element-selection) * [Продукты](#products) * [Стили](#saved-styles) * [Переменные](#variables) * [Локализация](#localization) ### Экраны и слои :::link Основная статья: [Экраны и слои](paywall-layout-and-products) ::: Кнопка Layers открывает панель «Экраны и слои» (отображается по умолчанию при открытии конструктора флоу). Здесь каждый экран представлен в виде дерева слоёв. Каждый элемент на экране — это слой, а контейнеры содержат вложенные дочерние элементы. Слои можно перетаскивать, чтобы изменить их порядок. ### Выбор элементов \{#element-selection\} :::link Основная статья: [Элементы](builder-elements) ::: Если нажать кнопку плюс Plus, в левой панели появится список доступных UI-элементов и их вариантов. Кликните на нужный элемент, чтобы добавить его на текущий экран как новый слой. ### Продукты \{#products\} :::link Основная статья: [Продукты](paywall-product-block) ::: Кнопка продуктов Products открывает список продуктов. В нём показано, какие продукты назначены каждому экрану вашего флоу. Список доступен только для чтения. Чтобы назначить продукты экрану, добавьте элемент «Продукт» и настройте его в правой панели. Чтобы создать или изменить продукты, используйте страницу **Products** в дашборде Adapty. ### Сохранённые стили \{#saved-styles\} :::info Подробнее: - [Стили и оформление](builder-styling) - [Текстовое содержимое](onboarding-text) - [Тёмный режим](paywall-dark-mode) ::: Кнопка «Стили» Styles открывает панель сохранённых стилей. Здесь можно редактировать и управлять глобальными стилями. Если несколько элементов вашего флоу используют одинаковую типографику или цвет, сохраните эти настройки как глобальный стиль — после этого их можно применять одним кликом. В настоящее время Flow Builder поддерживает два типа глобальных стилей — стили шрифтов и стили цветов. Для каждого стиля цвета можно задать отдельное значение для тёмного режима. ### Переменные \{#variables\} :::link Основная статья: [Переменные](onboarding-variables) ::: Кнопка с угловыми скобками Variables открывает панель переменных. Здесь можно создавать переменные для флоу и управлять ими. Во время выполнения SDK подставляет вместо плейсхолдеров переменных реальные значения — атрибуты пользователя, цены продуктов, локализованные строки и другое. Переменные сгруппированы на двух вкладках: * **Custom**: переменные, которые вы создаёте и контролируете через действия. * **Elements**: значения, определяемые взаимодействием пользователя — например, ответы на вопросы викторины, состояния переключателей или выбор вкладки. Переменные продукта — цена, название и другие данные продукта — в этой панели не отображаются. Ссылайтесь на них напрямую при редактировании текстового элемента. Используйте переменные для: * **Привязывать текст**: отображать динамический контент вместо статических строк. * **Управлять видимостью**: показывать или скрывать элементы на основе условий (например, скрывать кнопку апгрейда для премиум-пользователей). * **Взаимодействовать с пользователем**: считывать данные из полей ввода, например из форм или опросов. ### Локализация \{#localization\} :::link Основная статья: [Локализация](add-flow-remote-config-locale) ::: Вкладка Localization позволяет управлять всеми переводимыми элементами флоу. Здесь отображается таблица всех текстовых строк и изображений, сгруппированных по экранам, с отдельным столбцом для каждой локали. В этом разделе можно: * Добавлять новые локали и редактировать переведённые строки прямо в таблице. * Отслеживать статус перевода — каждая строка помечена как **Done** или **Missing**. * Фильтровать по экрану или показывать только незаполненные переводы. * Использовать **AI Translate** для автоматического перевода или **Import/Export** для массовой загрузки и выгрузки переводов. --- # File: flow-builder-recipes --- --- title: "Типовые рецепты для флоу" description: "Пошаговые гайды по созданию типовых шаблонов экранов в Flow Builder." --- В этом разделе описано, как создавать наиболее распространённые шаблоны экранов во Flow Builder — элемент за элементом, от выбора макета до настройки взаимодействий. Каждый гайд самодостаточен и использует стандартные элементы Flow Builder. <CustomDocCardList /> Посмотрите это вводное видео, чтобы создать базовый персонализированный флоу: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/aa-m459VIuY?si=zN_Co6B6qB88UPZP" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> :::link Хотите узнать больше о создании флоу? Смотрите пошаговые видеоуроки в нашем [плейлисте на YouTube](https://www.youtube.com/playlist?list=PLMksWqaZiWtM). ::: --- # File: basic-paywall-screen --- --- title: "Создание базового экрана пейвола" description: "Пошаговое руководство по созданию стандартного экрана пейвола во Flow Builder." --- Это самый распространённый шаблон пейвола. Используйте его как отдельный экран или разместите в конце многоэкранного [флоу](adapty-flow-builder). Стандартный экран пейвола содержит заголовок, описание ценности, список функций, список продуктов, кнопку покупки и ссылки в футере для восстановления покупок, пользовательского соглашения и политики конфиденциальности. ## Перед началом работы \{#before-you-start\} - [Создайте продукты](create-product) в дашборде Adapty. - [Подключите Adapty к App Store и Google Play](integrate-payments). ## 1. Настройте повторно используемые стили \{#1-set-up-reusable-styles\} Повторно используемые стили позволяют применять одинаковую типографику и цвета на всех экранах в один клик. Каждый новый флоу поставляется с набором стилей текста по умолчанию (H1, Body, Button Label и т. д.) — настройте их под своё оформление, прежде чем начать добавлять элементы. Добавьте цветовые стили для фирменных цветов, которые будете использовать на экране. Подробные инструкции: [Стили и оформление — Повторно используемые стили](builder-styling#reusable-styles). Чтобы настроить стили: 1. На левой панели откройте панель **Styles** Styles. 2. На вкладке **Text** нажмите на существующий стиль, чтобы изменить шрифт, начертание, размер и цвет. Новые стили добавляйте только если стандартных не хватает. 3. На вкладке **Colors** нажмите **Plus Create style** и добавьте цвета, которые планируете использовать на экране повторно. ## 2. Настройте макет экрана \{#set-up-the-screen-layout\} Сам экран служит контейнером для всего, что вы добавляете. Сначала настройте его макет, фон и отступы, чтобы элементы, которые вы добавите позже, распределились правильно. Полный список свойств экрана см. в разделе [Экраны и слои — Настройки экрана](paywall-layout-and-products#screen-settings). Чтобы настроить экран: 1. Нажмите на пустую область холста, чтобы выбрать экран. Правая панель переключится на настройки экрана. 2. В разделе **System UI** отключите **Safe area**, чтобы контент распространялся до краёв экрана. 3. В разделе **Layout** задайте направление **Vertical** Vertical и распределение **Space evenly**. 4. В разделе **Fill** выберите тип фона — сплошной цвет, градиент или изображение. В этом примере используется **Gradient** Gradient с двумя точками цвета. ## 3. Добавьте кнопку закрытия \{#add-the-close-button\} Кнопка закрытия скрывает пейвол. Пресет **Close** уже настроен — дополнительная настройка действия не требуется. 1. На канвасе нажмите **+**. 2. Выберите **Buttons** > **Close**. ## 4. Добавьте заголовок и свяжите его с кнопкой закрытия \{#add-the-title-and-pair-it-with-the-close-button\} H1 располагается рядом с кнопкой закрытия в верхней части экрана. Чтобы выровнять их по горизонтали, оберните оба элемента в горизонтальный контейнер. Чтобы добавить заголовок: 1. Нажмите **+** > **Text** > **H1**. 2. Выделив H1, откройте вкладку **Design** на правой панели и отредактируйте текст в поле **Content**. Чтобы сгруппировать заголовок с кнопкой закрытия: 1. На панели **Layers** нажмите меню с тремя точками Context menu на слое кнопки закрытия и выберите **Wrap** > **Wrap in Horizontal Container**. 2. Перетащите слой H1 в новый горизонтальный контейнер. Чтобы выровнять два элемента: 1. Отрегулируйте размер кнопки закрытия и размер шрифта H1 так, чтобы они комфортно располагались на одной строке. 2. Выделив горизонтальный контейнер, задайте в правой панели выравнивание и распределение элементов, чтобы они выстраивались правильно. ## 5. Добавьте описание ценности \{#add-the-value-description\} Короткая строка под заголовком объясняет пользователю, что он получит от подписки. 1. Нажмите **+** > **Text** > **Body**. 2. Выделив элемент body, отредактируйте текст в поле **Content** на вкладке **Design**. ## 6. Добавьте список возможностей \{#add-the-feature-list\} Список возможностей показывает, что входит в подписку. Каждая строка содержит иконку, заголовок и краткое описание. Полный набор пресетов списков см. в разделе [Элементы — List](builder-elements#list). Чтобы добавить список возможностей: 1. Нажмите **+** > **List** и выберите пресет списка. Icon List — самый распространённый вариант для пейволов. 2. Выбрав каждую строку, отредактируйте заголовок и описание в поле **Content**. 3. Чтобы добавить или удалить строки, выберите список и используйте элементы управления строками на панели **Layers**. ## 7. Добавьте список продуктов \{#add-the-product-list\} Список продуктов показывает варианты подписок, из которых пользователь может выбрать. Элемент Products отображает одну карточку на каждый продукт, назначенный экрану, и одна карточка автоматически помечается как выбранная по умолчанию. Подробнее об управлении продуктами — в разделе [Настройка покупок](paywall-product-block). Чтобы добавить и настроить продукты: 1. Нажмите **+** > **Products** и выберите шаблон макета. Vertical List — самый распространённый вариант. 2. Выберите каждую карточку продукта на холсте и укажите продукт в выпадающем списке на вкладке **Design**. В списке отображаются все продукты, настроенные в дашборде Adapty. 3. Чтобы изменить выбранный по умолчанию продукт, выберите нужную карточку и включите **Set as default product** на вкладке **Design**. 4. Чтобы настроить значок скидки, разверните карточку продукта на панели **Layers**, выберите слой значка и отредактируйте его текст в поле **Content**. Скройте значок на других карточках, нажав значок глаза Show рядом с каждым слоем значка. ## 8. Добавьте кнопку покупки \{#add-the-purchase-button\} Кнопка покупки запускает встроенную покупку для продукта, который пользователь выбрал на экране. Переменная `products.selectedProduct` всегда указывает на текущий выбранный продукт. Чтобы добавить кнопку покупки: 1. Нажмите **+** > **Buttons** и выберите подходящий пресет кнопки. 2. Выделив кнопку, откройте вкладку **Interactions** в правой панели. 3. Нажмите **Add trigger** > **On tap**, затем нажмите **Add action**. 4. Установите **Action** в значение **Purchase**, а **Product** — в `products.selectedProduct`. ## 9. Добавьте ссылки в футер \{#add-footer-links\} Футер содержит ссылки на условия использования и политику конфиденциальности (обязательные требования сторов), а также кнопку для восстановления предыдущих покупок. Чтобы добавить ссылки в футер: 1. Нажмите **+** > **Buttons** > **Links**. На экран добавится строка с кнопками Restore Purchases, Terms of Use и Privacy Policy. 2. На панели **Layers** выберите кнопку **Terms of Use**. Откройте вкладку **Interactions** — действие **Open URL** уже привязано. Нажмите на него и введите нужный URL. 3. Повторите то же самое для кнопки **Privacy Policy**, указав URL вашей политики конфиденциальности. 4. Кнопку **Restore Purchases** оставьте без изменений — её действие настроено заранее. :::tip Если какой-то элемент расположен слишком высоко или низко, или вы хотите добавить отступы, отрегулируйте его margin и padding. ::: ## Следующие шаги \{#next-steps\} - [Сохраните и опубликуйте флоу](builder-save-publish). - [Добавьте флоу в плейсмент](create-placement), чтобы начать показывать его пользователям. --- # File: show-plans-bottom-sheet --- --- title: "Показать все планы в нижнем листе" description: "Создайте пейвол-герой с одной кнопкой CTA, ссылкой «Показать все планы» и нижним листом, в котором отображается полный список продуктов." --- Этот шаблон сначала показывает одно выделенное предложение и ненавязчивую ссылку на полный список планов. При нажатии **Show all plans** снизу выезжает нижний лист с остальными продуктами, кнопкой покупки и ссылками в подвале. Используйте этот вариант, когда один тариф конвертирует значительно лучше остальных — нижний лист держит альтернативы на расстоянии одного касания, не перегружая основной экран. ## Перед началом работы \{#before-you-start\} - [Создайте продукты](create-product) в дашборде Adapty. - [Подключите Adapty к App Store и Google Play](integrate-payments). ## 1. Настройте макет экрана \{#1-set-up-the-screen-layout\} Используйте изображение-герой как фон экрана и сгруппируйте остальной контент внизу, чтобы изображение занимало верхнюю часть экрана. Полный список свойств экрана см. в разделе [Экраны и слои — Настройки экрана](paywall-layout-and-products#screen-settings). Чтобы настроить экран: 1. Нажмите на пустую область холста, чтобы выбрать экран. 2. В разделе **System UI** отключите **Safe area**, чтобы фоновое изображение растянулось до краёв экрана. 3. В разделе **Fill** выберите **Image** Image и загрузите фоновое изображение. 4. В разделе **Layout** настройте направление, отступы и выравнивание, чтобы расположить контент в нужном месте. В этом шаблоне направление **Vertical** Vertical с небольшим отступом и выравниванием **bottom-middle** группирует заголовок и кнопки в нижней части экрана. ## 2. Добавьте заголовок CTA \{#add-the-cta-heading\} Заголовок располагается в нижней части экрана, прямо над кнопкой подписки. Выше него занимает место hero-изображение. 1. Нажмите **+** > **Text** > **H1**. 2. Выделите H1, откройте вкладку **Design** и отредактируйте текст в поле **Content**. ## 3. Добавьте нижний лист и его заголовок \{#add-the-bottom-sheet-and-its-title\} Нижний лист — это контейнер-раскладка, который выезжает снизу экрана. Пока добавьте его видимым — вы наполните его содержимым в следующих шагах и скроете, как только всё будет на месте. Скрытые элементы недоступны для редактирования, поэтому лист должен оставаться видимым, пока вы его не заполните. Подробнее о нижних листах и других контейнерах-раскладках — в разделе [Элементы — Раскладка](builder-elements#layout). Чтобы добавить нижний лист и его заголовок: 1. Нажмите **+** > **Layout** > **Bottom Sheet**. 2. На панели **Layers** раскройте bottom sheet, выберите слой **Title** и отредактируйте поле **Content** на вкладке **Design** — например, `Choose your plan`. ## 4. Добавьте список продуктов внутрь нижнего листа \{#4-add-the-product-list-inside-the-bottom-sheet\} Разместите все продукты внутри нижнего листа. Один из них также будет определять цену, отображаемую на главной кнопке CTA. Подробнее об управлении продуктами — в разделе [Настройка покупок](paywall-product-block). Чтобы добавить и настроить продукты: 1. Нажмите **+** > **Products** и выберите пресет макета. В большинстве случаев хорошо подходит Vertical List. Элемент появится на экране, за пределами нижнего листа. 2. На панели **Layers** перетащите слой Products в контейнер **Content** внутри нижнего листа. 3. Выберите каждую карточку продукта на холсте и выберите продукт из выпадающего списка во вкладке **Design**. ## 5. Добавьте кнопку покупки в нижний лист \{#add-the-purchase-button-inside-the-bottom-sheet\} Нижний лист должен иметь собственную кнопку покупки, чтобы пользователь мог купить выбранный тариф из списка. 1. Нажмите **+** > **Buttons** и выберите подходящий пресет кнопки. 2. На панели **Layers** перетащите новую кнопку в контейнер **Content** внутри нижнего листа. 3. Выделив кнопку, откройте вкладку **Interactions** на правой панели. 4. Нажмите **Add trigger** > **On tap**, затем нажмите **Add action**. 5. Установите **Action** в значение **Purchase**, а **Product** — в `products.selectedProduct`. ## 6. Добавьте ссылки в подвале нижнего листа \{#add-the-footer-links-inside-the-bottom-sheet\} :::important Не используйте [встроенные ссылки](onboarding-text#inline-link) для текста внутри кнопок. Вместо этого настройте действие **Open URL** на самой кнопке. ::: Условия использования, политика конфиденциальности и восстановление покупок находятся в нижней части листа — основной экран остаётся незагромождённым. 1. Нажмите **+** > **Buttons** > **Links**. На пейвол добавится строка с кнопками Restore Purchases, Terms of Use и Privacy Policy. 2. На панели **Layers** перетащите строку Links в контейнер **Content** внутри нижнего листа. 3. На панели **Layers** выберите кнопку **Terms of Use**. Откройте вкладку **Interactions** и вставьте URL условий использования в поле **Open URL**. 4. Повторите то же самое для кнопки **Privacy Policy**, указав ссылку на политику конфиденциальности. 5. Кнопку **Restore Purchases** оставьте без изменений — её действие уже настроено заранее. ## 7. Скрыть нижний лист \{#hide-the-bottom-sheet\} Теперь, когда содержимое листа готово, скройте его, чтобы он не отображался на экране по умолчанию. Пользователи смогут открыть его, нажав **Show all plans** на последнем шаге. На панели **Layers** выберите нижний лист и установите его состояние **Hide** Hide. Лист останется в дереве слоёв, но больше не будет отображаться на холсте. ## 8. Добавьте основную кнопку подписки \{#add-the-main-subscribe-button\} Основная кнопка на экране оформляет подписку пользователя на месячный план в один тап. Её подпись использует переменную с ценой месячного продукта, так что кнопка всегда отображает актуальную стоимость. 1. На панели **Layers** кликните на экран, чтобы новые элементы добавлялись в корень, а не внутрь bottom sheet. 2. Нажмите **+** > **Buttons** и выберите пресет кнопки. 3. Выделив кнопку, откройте вкладку **Design** и поставьте курсор в поле **Content**. Нажмите Variable icon и выберите переменную с ценой основного продукта. Оберните её остальным текстом подписи — например, `Subscribe for {price}/month`. 4. Перейдите на вкладку **Interactions** и нажмите **Add trigger** > **On tap** > **Add action**. 5. Установите **Action** в значение **Purchase**, а **Product** — в нужный вам продукт. В отличие от кнопки в нижнем листе, эта кнопка привязана к конкретному продукту, а не к `products.selectedProduct`. ## 9. Добавьте ссылку «Показать все тарифы» \{#add-the-show-all-plans-link\} Текстовая ссылка под кнопкой подписки открывает нижний лист по нажатию. Добавьте её как текстовый элемент со стилем **Button Label** — это сохранит минимализм оформления и при этом позволит привязать действие. Подробнее о действии «Показать/скрыть» — в разделе [Actions — Show/hide elements](onboarding-actions#showhide-elements). Чтобы добавить ссылку: 1. Выбрав экран на панели **Layers**, нажмите **+** > **Text** > **Button Label**. 2. Выбрав текстовый элемент, измените поле **Content** на `Show all plans`. 3. Откройте вкладку **Interactions** и нажмите **Add trigger** > **On tap** > **Add action**. 4. Установите **Action** в значение **Show** и выберите элемент нижнего листа из выпадающего списка. ## Следующие шаги \{#next-steps\} - [Сохраните и опубликуйте свой флоу](builder-save-publish). - [Добавьте флоу в плейсмент](create-placement), чтобы начать показывать его пользователям. --- # File: paywall-with-tabs --- --- title: "Создание пейвола с вкладками" description: "Создайте экран пейвола с двумя вкладками, переключающимися между разными списками функций, группами продуктов и кнопками покупки." --- Этот шаблон использует вкладки для переключения между двумя вариантами одного предложения на одном экране. Каждая вкладка содержит собственный список функций, список продуктов и кнопку покупки. Нажатие на вкладку меняет отображаемый контент без перехода на другой экран — удобно для разделения планов по уровню, периоду оплаты или сегменту аудитории. ## Прежде чем начать \{#before-you-start\} - [Создайте продукты](create-product) в дашборде Adapty. - [Подключите Adapty к App Store и Google Play](integrate-payments). ## 1. Настройте макет экрана \{#1-set-up-the-screen-layout\} Экран служит контейнером для кнопки закрытия, заголовка, вкладок и содержимого вкладок. В этом примере фон — изображение, но сплошной цвет или градиент работают так же. Полный список свойств экрана см. в разделе [Экраны и слои — Настройки экрана](paywall-layout-and-products#screen-settings). Чтобы настроить экран: 1. Нажмите на пустую область холста, чтобы выделить экран. 2. В разделе **System UI** отключите **Safe area**, чтобы фон растянулся до краёв экрана. 3. В разделе **Fill** выберите тип фона и настройте его. В этом примере используется **Image** Image, но сплошной цвет или градиент настраиваются так же. 4. В разделе **Layout** задайте направление **Vertical** Vertical и настройте отступы и выравнивание, чтобы элементы располагались сверху вниз, а содержимое вкладки заполняло оставшееся пространство. ## 2. Добавьте кнопку закрытия \{#add-the-close-button\} Кнопка закрытия скрывает пейвол. Пресет **Close** уже настроен — никаких дополнительных действий не требуется. 1. На холсте нажмите **+**. 2. Выберите **Buttons** > **Close**. ## 3. Добавьте заголовок и разместите его рядом с кнопкой закрытия \{#add-the-title-and-pair-it-with-the-close-button\} Заголовок располагается рядом с кнопкой закрытия в верхней части экрана. Чтобы выровнять их по горизонтали, оберните оба элемента в горизонтальный контейнер. Чтобы добавить заголовок: 1. Нажмите **+** > **Text** > **H1**. 2. Выделив H1, откройте вкладку **Design** и отредактируйте текст в поле **Content**. Чтобы сгруппировать заголовок с кнопкой закрытия: 1. На панели **Layers** нажмите на меню с тремя точками Context menu на слое кнопки закрытия и выберите **Wrap** > **Wrap in Horizontal Container**. 2. Перетащите слой H1 в новый горизонтальный контейнер. Чтобы выровнять два элемента: 1. Отрегулируйте размер кнопки закрытия и размер шрифта H1 так, чтобы они комфортно размещались на одной строке. 2. Выделив горизонтальный контейнер, задайте выравнивание и распределение на правой панели, чтобы элементы выстроились корректно. ## 4. Добавьте вкладки и настройте их метки \{#4-add-the-tabs-and-configure-their-labels\} Элемент Tabs разбивает раздел экрана на переключаемые панели с контентом. Каждая вкладка получает собственный контейнер, который отображается, когда пользователь её выбирает. Подробнее об элементе Tabs — в разделе [Элементы — Tabs](builder-elements#tabs). Подробнее о группах с выбором — в разделе [Выбираемые элементы и группы](flow-selectable-elements). Чтобы добавить вкладки: 1. Нажмите **+** > **Tabs** и выберите пресет — Segment control, Button Tabs или Underline. 2. Выделив название каждого таба на холсте или в панели **Layers**, отредактируйте поле **Content** на вкладке **Design**, чтобы изменить подпись — например, `Premium` и `Pro`. ## 5. Добавьте список функций на первую вкладку \{#5-add-a-feature-list-to-the-first-tab\} Короткий компактный список функций внутри первой вкладки показывает пользователям, что входит в этот план. Полный набор пресетов списков описан в разделе [Элементы — Список](builder-elements#list). Чтобы добавить список функций: 1. Нажмите **+** > **List** и выберите пресет списка. Icon List — самый компактный вариант для пейволов. Элемент появится в конце дерева слоёв. 2. Выбрав каждую строку, отредактируйте заголовок в поле **Content**. 3. На панели **Layers** перетащите список в контейнер **Content** первой вкладки. ## 6. Добавьте список продуктов на первую вкладку \{#add-the-product-list-to-the-first-tab\} Список продуктов отображает варианты подписки для первой вкладки. Элемент Products отрисовывает по одной карточке на каждый продукт, назначенный экрану, и создаёт собственную группу выбора. Подробнее об управлении продуктами — в разделе [Настройка покупок](paywall-product-block). Чтобы добавить и настроить продукты: 1. Нажмите **+** > **Products** и выберите пресет макета. Vertical List хорошо подходит для стопки тарифов. Элемент появится в конце дерева слоёв. 2. Выберите каждую карточку продукта на холсте и выберите продукт из выпадающего списка на вкладке **Design**. 3. На панели **Layers** перетащите слой Products в контейнер **Content** первого таба. ## 7. Добавьте кнопку покупки на первый таб \{#add-the-purchase-button-to-the-first-tab\} Кнопка покупки запускает встроенную покупку выбранного пользователем продукта на первом табе. Её подпись использует цену выбранного продукта и автоматически синхронизируется с выбором пользователя. Подробнее об экшне «Покупка» читайте в разделе [Действия — Покупка](onboarding-actions#purchase). Чтобы добавить и настроить кнопку покупки: 1. Нажмите **+** > **Buttons** и выберите пресет кнопки. Элемент появится в конце дерева слоёв. 2. Выделив кнопку, откройте вкладку **Design** и поставьте курсор в поле **Content**. Нажмите на иконку переменной Variable icon, выберите `products.selectedProduct`, затем атрибут `prod_price` — полная переменная примет вид `products.selectedProduct.prod_price`. Добавьте остальной текст метки, например: `Subscribe for {prod_price}`. 3. Перейдите на вкладку **Interactions** и нажмите **Add trigger** > **On tap** > **Add action**. 4. Установите **Action** в значение **Purchase**, а **Product** — в `products.selectedProduct`. 5. На панели **Layers** перетащите кнопку в контейнер **Content** первой вкладки. ## 8. Скопируйте содержимое первой вкладки во вторую \{#copy-the-first-tabs-content-into-the-second-tab\} Вместо того чтобы собирать ту же структуру с нуля, скопируйте список функций, список продуктов и кнопку покупки из первой вкладки во вторую. После этого останется только обновить значения. Чтобы скопировать содержимое: 1. На панели **Layers** разверните контейнер **Content** первой вкладки. 2. Выделите каждый элемент внутри него (список функций, продукты, кнопку покупки), скопируйте с помощью ⌘C / Ctrl+C и вставьте с помощью ⌘V / Ctrl+V. Копии появятся в конце дерева слоёв. 3. Перетащите каждый скопированный элемент в контейнер **Content** второй вкладки. ## 9. Обновите содержимое второй вкладки \{#update-the-second-tabs-content\} Вторая вкладка пока повторяет первую. Обновите каждый элемент, чтобы он отражал второй тарифный план. Чтобы обновить вторую вкладку: 1. Отредактируйте список функций внутри второй вкладки так, чтобы строки соответствовали функциям второго плана. 2. Выберите каждую карточку продукта в элементе Products второй вкладки и назначьте продукты второго плана из выпадающего списка. Этот элемент Products автоматически становится отдельной выбираемой группой (`products2`). 3. Выберите кнопку покупки во второй вкладке. В поле **Content** на вкладке **Design** измените переменную цены с `products.selectedProduct.prod_price` на `products2.selectedProduct.prod_price`. 4. Перейдите на вкладку **Interactions** и обновите **Product** в действии **Purchase** с `products.selectedProduct` на `products2.selectedProduct`. ## 10. Добавьте общие ссылки в подвале \{#add-the-shared-footer-links\} Ссылки на условия использования, политику конфиденциальности и восстановление покупок должны отображаться вне зависимости от активной вкладки. Добавьте их на уровне экрана — за пределами обоих контейнеров с содержимым вкладок — чтобы они были общими для обеих вкладок. Чтобы добавить ссылки в подвале: 1. Нажмите **+** > **Buttons** > **Links**. Это добавит строку с Restore Purchases, Terms of Use и Privacy Policy в конец дерева слоёв — именно там, где нужно: на корневом уровне экрана, а не внутри вкладки. 2. На панели **Layers** выберите кнопку **Terms of Use**. Откройте вкладку **Interactions** и вставьте URL условий использования в поле **Open URL**. 3. Повторите то же самое для кнопки **Privacy Policy**, указав URL политики конфиденциальности. 4. Оставьте ссылку **Restore Purchases** без изменений — её действие уже настроено заранее. ## Следующие шаги \{#next-steps\} - [Сохраните и опубликуйте флоу](builder-save-publish). - [Добавьте флоу в плейсмент](create-placement), чтобы начать показывать его пользователям. --- # File: paywall-features-per-product --- --- title: "Отображение разных функций для каждого продукта" description: "Показывайте разный список функций в зависимости от того, какой продукт выбирает пользователь, используя условную видимость." --- Этот шаблон использует условную видимость, чтобы показывать разные списки функций для разных тарифов. На экране отображаются два продукта — например, Pro и Pro+ — и в зависимости от того, какой продукт выбрал пользователь, показывается соответствующий список. Один из продуктов помечен как дефолтный, поэтому его список функций виден при первой загрузке экрана. ## Перед началом работы \{#before-you-start\} - [Создайте продукты](create-product) в дашборде Adapty. - [Подключите Adapty к App Store и Google Play](integrate-payments). ## 1. Настройте макет экрана \{#1-set-up-the-screen-layout\} Экран служит контейнером для всего, что вы добавляете. В этом примере фоном является изображение, но сплошной цвет или градиент работают так же. Полный список свойств экрана см. в разделе [Экраны и слои — Настройки экрана](paywall-layout-and-products#screen-settings). Чтобы настроить экран: 1. Нажмите на пустую область холста, чтобы выделить экран. 2. В разделе **System UI** отключите **Safe area**, чтобы фон растянулся до краёв экрана. 3. В разделе **Fill** выберите тип фона и настройте его. В этом примере используется **Image** Image, но с однородным цветом или градиентом всё работает так же. 4. В разделе **Layout** установите направление **Vertical** Vertical и настройте отступы и выравнивание, чтобы элементы располагались сверху вниз, а контент заполнял оставшееся пространство. ## 2. Добавьте кнопку закрытия \{#add-the-close-button\} Кнопка закрытия скрывает пейвол. Пресет **Close** уже преднастроен — настраивать действие не нужно. 1. На холсте нажмите **+**. 2. Выберите **Buttons** > **Close**. ## 3. Добавьте заголовок и разместите его рядом с кнопкой закрытия \{#add-the-title-and-pair-it-with-the-close-button\} Заголовок располагается рядом с кнопкой закрытия в верхней части экрана. Чтобы выровнять их по горизонтали, оберните оба элемента в горизонтальный контейнер. Чтобы добавить заголовок: 1. Нажмите **+** > **Text** > **H1**. 2. Выделив H1, откройте вкладку **Design** и введите текст в поле **Content**. Чтобы сгруппировать заголовок с кнопкой закрытия: 1. На панели **Layers** нажмите на трёхточечное меню Context menu на слое кнопки закрытия и выберите **Wrap** > **Wrap in Horizontal Container**. 2. Перетащите слой H1 в новый горизонтальный контейнер. Чтобы выровнять два элемента: 1. Отрегулируйте размер кнопки закрытия и размер шрифта H1 так, чтобы они комфортно располагались на одной строке. 2. Выделив горизонтальный контейнер, задайте выравнивание и распределение в правой панели, чтобы элементы выстроились корректно. ## 4. Добавьте список продуктов \{#4-add-the-product-list\} Добавьте продукты, между которыми пользователь может выбирать. Отметьте один из них как выбранный по умолчанию, чтобы экран сразу отображался в осмысленном состоянии. Подробнее об управлении продуктами — в разделе [Настройка покупок](paywall-product-block). Чтобы добавить и настроить продукты: 1. Нажмите **+** > **Products** и выберите макет. Для этого шаблона хорошо подойдёт Vertical List. 2. Выберите каждую карточку продукта на холсте и укажите продукт в выпадающем списке на вкладке **Design**. 3. Выберите карточку, которая должна быть выбрана по умолчанию — например, Pro+ — и включите **Set as default product** на вкладке **Design**. ## 5. Добавьте список функций для первого продукта \{#add-the-feature-list-for-the-first-product\} Первый список функций описывает продукт по умолчанию. Он отображается только когда пользователь выбрал первый продукт. Подробнее об условной видимости см. в разделе [Условная видимость](onboarding-element-visibility). :::tip Вместо двух отдельных списков можно добавить один и сделать текстовые элементы внутри него условными — тогда один список будет адаптироваться к выбранному продукту. См. [Добавление условного текста](onboarding-text#add-conditional-text). ::: Чтобы добавить и настроить список функций: 1. Нажмите **+** > **List** и выберите пресет компактного списка. Icon List хорошо подходит для пейволов. 2. Выделив каждую строку, отредактируйте заголовок в поле **Content**, чтобы описать характеристики первого продукта. 3. Не снимая выделение со списка, откройте вкладку **Design**. В разделе **Visibility** выберите **Conditional** Conditional. 4. Настройте условие так, чтобы список отображался только тогда, когда первый продукт является выбранным в данный момент. Сопоставьте с переменной `products.selectedProduct.prod_title`. В поле **Value** нажмите иконку переменной `{}`, выберите карточку первого продукта, затем её атрибут `prod_title` — сравнение будет разрешено в заголовок этого продукта. ## 6. Добавьте список преимуществ для второго продукта \{#add-the-feature-list-for-the-second-product\} Повторите тот же подход для второго продукта. Два списка являются взаимоисключающими — в любой момент виден только один, в зависимости от того, какой продукт выбран. Чтобы добавить второй список преимуществ: 1. Нажмите **+** > **List** и выберите тот же компактный пресет для единообразия оформления. 2. Отредактируйте каждую строку, чтобы описать функции второго продукта. 3. В разделе **Visibility** выберите **Conditional** Conditional и настройте то же условие, что и в шаге 5, но укажите в picker переменной **Value** `prod_title` карточки второго продукта. ## 7. Добавьте кнопку покупки \{#add-the-purchase-button\} Кнопка покупки запускает встроенную покупку выбранного пользователем продукта. Надпись на кнопке отображает цену выбранного продукта и обновляется при переключении между тарифами. Подробнее о действии Purchase см. в разделе [Действия — Purchase](onboarding-actions#purchase). Чтобы добавить и настроить кнопку покупки: 1. Нажмите **+** > **Buttons** и выберите пресет кнопки. 2. Выделив кнопку, откройте вкладку **Design** и поставьте курсор в поле **Content**. Нажмите на иконку переменной Variable icon, выберите `products.selectedProduct`, затем атрибут `prod_price` — итоговая переменная примет вид `products.selectedProduct.prod_price`. Оберните её остальным текстом метки, например: `Subscribe for {prod_price}`. 3. Перейдите на вкладку **Interactions** и нажмите **Add trigger** > **On tap** > **Add action**. 4. Установите **Action** на **Purchase** и **Product** на `products.selectedProduct`. ## 8. Добавьте ссылки в футер \{#add-the-footer-links\} Условия использования, политика конфиденциальности и восстановление покупок располагаются под основным контентом. Чтобы добавить ссылки в футер: 1. Нажмите **+** > **Buttons** > **Links**. В конец дерева слоёв добавится строка с кнопками Restore Purchases, Terms of Use и Privacy Policy. 2. В панели **Layers** выберите кнопку **Terms of Use**. Откройте вкладку **Interactions** и вставьте ссылку на условия использования в поле **Open URL**. 3. Повторите то же самое для кнопки **Privacy Policy**, указав ссылку на политику конфиденциальности. 4. Кнопку **Restore Purchases** оставьте как есть — её действие уже настроено заранее. ## Следующие шаги \{#next-steps\} - [Сохраните и опубликуйте свой флоу](builder-save-publish). - [Добавьте флоу в плейсмент](create-placement), чтобы начать показывать его пользователям. --- # File: show-offer-on-close --- --- title: "Показ предложения при нажатии кнопки закрытия" description: "Перехватите первое нажатие кнопки закрытия, чтобы показать финальное предложение перед уходом пользователя с пейвола." --- Когда пользователь нажимает кнопку закрытия, он собирается уйти без оплаты. Этот рецепт перехватывает первое нажатие: вместо закрытия пейвола отображается оверлей с финальным предложением. Когда пользователь закрывает это предложение, кнопка закрытия работает в штатном режиме и закрывает флоу. Логика использует одну пользовательскую булеву переменную и одно условное действие: - `close_tapped` начинается как `False`. - Первое нажатие на закрытие показывает оверлей с предложением и устанавливает `close_tapped` в `True`. - Любое последующее нажатие на закрытие закрывает флоу. ## Перед началом \{#before-you-start\} - Создайте экран пейвола с кнопкой закрытия — например, следуйте гайду [Создание базового экрана пейвола](basic-paywall-screen). - [Создайте продукт](create-product) с [оффером](offers) для продвижения в оверлее. ## 1. Создайте переменную \{#1-create-the-variable\} Подробнее о пользовательских переменных см. в разделе [Переменные](onboarding-variables#custom-variables). 1. На левой панели нажмите иконку **{ }**, чтобы открыть **Variables**. 2. На вкладке **Custom** нажмите **+**. 3. Назовите переменную `close_tapped` и установите **Value Type** в значение **Boolean**. Оставьте **Initial Value** равным **False**. 4. Нажмите **Create variable**. ## 2. Создайте оверлей предложения \{#2-build-the-offer-overlay\} Оверлей — это контейнер, который прикреплён к экрану устройства и располагается поверх пейвола. Создайте его видимым — скрытые элементы нельзя редактировать, поэтому вы скроете его на шаге 4, когда контент будет на месте. 1. Нажмите **+** > **Layout** > **Vertical Container**. 2. Выделив контейнер, откройте вкладку **Design** и установите **Position** в значение **Fixed**. Задайте горизонтальное выравнивание **Left & Right** и вертикальное выравнивание **Top**. Поскольку вертикального центрирования нет, введите отступ сверху (например, `300`), чтобы сдвинуть оверлей к середине экрана. 3. В разделе **Fill** задайте фон — сплошной цвет или изображение. По умолчанию контейнер прозрачный, и без заливки пейвол будет просвечивать сквозь оверлей. 4. Добавьте содержимое оффера. Выделив контейнер на панели **Layers**, нажмите **+** > **Text** > **H2**. Чтобы показать цену со скидкой, вставьте [переменные оффера](onboarding-variables#product-variables), например `offer_price`, в поле **Content**. 5. Нажмите **+** > **Products**, выберите пресет макета и перетащите его в оверлей. Выделите карточку продукта на канвасе и выберите продукт и оффер на вкладке **Design**. 6. Нажмите **+** > **Buttons**, выберите пресет кнопки и перетащите его в оверлей. На вкладке **Interactions** нажмите **Add trigger** > **On tap** > **Add action**, затем установите **Action** в значение **Purchase**, а **Product** — в ваш продукт с оффером. ## 3. Добавьте кнопку закрытия на оверлей \{#3-add-the-dismiss-button-to-the-overlay\} У оверлея должна быть своя кнопка закрытия. Её действие должно скрывать оверлей, а не закрывать флоу. 1. Выделите оверлей, нажмите **+** > **Buttons** > **Close flow**. 2. Выделите кнопку и откройте вкладку **Interactions**. В пресете уже настроено действие **Close Flow**. Нажмите на него и измените тип на **Hide element**. В качестве цели укажите контейнер оверлея. :::important Не оставляйте предустановленное действие **Close Flow** на кнопке закрытия оверлея — оно закроет весь флоу, а не скроет оверлей. ::: ## 4. Скройте оверлей \{#hide-the-overlay\} Оверлей должен оставаться невидимым до первого нажатия на кнопку закрытия. На панели **Layers** выберите контейнер оверлея и установите его состояние в **Hide** Hide. Оверлей останется в дереве слоёв, но больше не будет отображаться на холсте. ## 5. Настройте кнопку закрытия \{#5-set-up-the-close-button\} Замените стандартное действие кнопки закрытия на условное действие с ветвлением по `close_tapped`. Подробнее об условных действиях см. в разделе [Действия — Условные действия](onboarding-actions#conditional-actions). 1. Выберите кнопку закрытия пейвола — ту, что на экране, а не кнопку закрытия оверлея. 2. Откройте вкладку **Interactions**, нажмите на предварительно настроенное действие **Close Flow** и измените его тип на **Conditional Action**. 3. В блоке **if** нажмите **Add condition** и задайте: `close_tapped` **Equals** **False**. 4. В блоке **then** добавьте два действия: - **Set Variable**: установите `close_tapped` в **True**. - **Show element**: Укажите цель — оверлей с предложением. 5. В блоке **else** добавьте действие **Close Flow**. Теперь первое нажатие на закрытие показывает предложение. После того как пользователь отклоняет его, повторное нажатие на закрытие соответствует ветке **else** и закрывает флоу. ## Следующие шаги \{#next-steps\} - [Сохраните и опубликуйте флоу](builder-save-publish). - [Добавьте флоу в плейсмент](create-placement), чтобы начать показывать его пользователям. --- # File: strikethrough-price --- --- title: "Показать зачёркнутую цену со значком скидки" description: "Зачеркните цену месячного плана рядом с эффективной месячной ценой годового плана, чтобы скидка была видна с первого взгляда." --- Зачёркнутая цена рядом с настоящей делает скидку заметной с первого взгляда. В этом рецепте карточка годового продукта переделывается так, чтобы показывать, сколько стоила бы подписка в месяц по месячному плану — зачёркнутым текстом — рядом с эффективной месячной ценой годового плана, а сверху — значок скидки. Зачёркнутое форматирование применяется ко всему текстовому элементу — нельзя зачеркнуть переменную внутри более длинного текста. Поэтому зачёркнутая цена живёт в отдельном текстовом элементе, рядом с ценой со скидкой в горизонтальном контейнере. :::tip Если достаточно завышенной «старой» цены того же продукта — например, двойная цена, зачёркнутая — добавьте готовый элемент [Старая цена](onboarding-text#add-an-old-price). Этот рецепт подходит для случая, когда зачёркнутая цена — это реальная цена другого продукта. ::: ## Перед началом работы \{#before-you-start\} - Создайте экран пейвола с продуктами — например, следуя гайду [Создание базового экрана пейвола](basic-paywall-screen). В этом рецепте предполагается, что пейвол предлагает годовой и месячный продукт. ## 1. Добавьте строки с ценами \{#1-stack-the-price-rows\} Карточка годовой подписки начинается с одной строки цены — переменная `prod_price` продукта, за которой следует `/year`, что отображается, например, как `$29.99/year`. Добавьте вторую строку выше — с ценой за месяц. 1. На панели **Layers** выберите текст с ценой внутри карточки годового продукта. Он уже находится в вертикальном контейнере карточки, поэтому копия разместится под ним. 2. Нажмите меню с тремя точками Context menu на слое и выберите **Duplicate**. Копия появится под оригиналом. На следующем шаге верхний текст станет строкой с месячной ценой, нижний — оставит годовую. ## 2. Разделите строку с ценой за месяц на два элемента \{#2-split-the-monthly-row-into-two-prices\} Строка с ценой за месяц содержит два текстовых элемента: зачёркнутую цену месячного плана и цену годового плана в пересчёте на месяц. :::important В выборщике переменных отображаются только цены продуктов, присутствующих на экране флоу. Чтобы использовать продукт, которого нет во флоу, создайте пустой экран, добавьте на него элемент **Products** и назначьте нужный продукт. Убедитесь, что ни одно навигационное действие не ведёт на этот экран. ::: 1. Выберите верхний текстовый элемент, нажмите на его меню с тремя точками и выберите **Wrap** > **Wrap in Horizontal Container**. 2. Нажмите на меню с тремя точками на текстовом слое внутри нового контейнера и выберите **Duplicate**. 3. Выберите первый текстовый элемент и очистите его поле **Content**. Нажмите на иконку переменной Variable icon, выберите месячный продукт, затем его атрибут `prod_price_per_month`. Введите `/month` после переменной. 4. На вкладке **Design** в разделе **Typography** установите **Decoration** в значение **Strikethrough** Strikethrough (см. [Стили и внешний вид — Decoration](builder-styling#decoration)). 5. Выберите второй текстовый элемент и замените его содержимое аналогичным образом, но выберите атрибут `prod_price_per_month` годового продукта. Введите `/month` после переменной. ## 3. Приглушите цену годовой подписки \{#tone-down-the-yearly-price\} Продублированные ценники унаследовали оригинальное оформление, поэтому все три цены сейчас выглядят одинаково — одного размера и жирности. Сделайте годовую цену менее заметной, чтобы строка с месячной ценой выделялась. 1. На канвасе выберите нижний текстовый элемент — цену годовой подписки. 2. В панели инструментов над элементом откройте выпадающий список стилей текста и выберите более скромный стиль — например, **Caption**. Или выберите другой стиль в разделе **Typography** на вкладке **Design**. ## 4. Добавьте значок скидки \{#add-the-discount-badge\} Значок выделяет размер экономии рядом с ценами. 1. Нажмите **+** > **Badge**. 2. На панели **Layers** перетащите значок внутрь годового продукта — между вертикальным контейнером с ценами и радиокнопкой. 3. Выберите текстовый слой значка и отредактируйте поле **Content** — например, `Save 75%`. Переменной для процента скидки нет — рассчитайте её из своих цен и введите как статичный текст. Теперь карточка годового плана сравнивает цену с месячным: зачёркнутая цена месячного плана, эффективная ежемесячная цена годового плана и значок скидки — всё в одной строке, а полная годовая цена — ниже. ## Следующие шаги \{#next-steps\} - [Сохраните и опубликуйте флоу](builder-save-publish). - [Добавьте флоу в плейсмент](create-placement), чтобы начать показывать его пользователям. --- # File: onboarding-flow-tutorial --- --- title: "Создайте персонализированный онбординг-флоу" description: "Подробное руководство по созданию многоэкранного онбординг-флоу — экраны, контент, навигация и условные переходы — на конкретном примере." --- Многоэкранный флоу в Flow Builder — это последовательность экранов, связанных навигационными действиями. Флоу может быть линейным или ветвиться в зависимости от действий пользователя на предыдущих экранах. В этом гайде разобран весь процесс от начала до конца: создание экранов, наполнение их контентом, настройка навигации и добавление условного ветвления — на примере четырёхэкранного онбординга. В примере используется: - **Поле для ввода имени**, которое превращает имя пользователя в переменную для персонализации. - **Квиз с одним вариантом ответа**, от которого зависит, какой экран увидит пользователь следующим. - **Два разветвлённых пути** с индивидуальным текстом для каждого сегмента аудитории. - **Пейвол** как финальный экран. Этот же подход применяется к любому флоу, который персонализирует контент на основе ввода пользователя. Предпочитаете видеоформат? В этом туториале показан весь процесс от начала до конца: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/aa-m459VIuY?si=zN_Co6B6qB88UPZP" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ## Перед началом работы \{#before-you-start\} - [Создайте продукты](create-product) в дашборде Adapty. В примере используются два — годовая и месячная подписка. - [Подключите Adapty к App Store и Google Play](integrate-payments). ## 1. Настройте многоразовые стили \{#1-set-up-reusable-styles\} Многоразовые стили позволяют одним кликом применить единообразную типографику и цвета ко всем экранам. В цветовых стилях задаются светлый и тёмный варианты, поэтому флоу автоматически поддерживает обе темы. Подробные инструкции см. в разделе [Стили и оформление — Многоразовые стили](builder-styling#reusable-styles). Для настройки стилей: 1. На левой панели откройте панель **Styles** Styles. 2. На вкладке **Colors** нажмите **Plus Create style** и добавьте цвета, которые будете переиспользовать. Для каждого цвета выберите значение в режиме Light, затем переключитесь на вкладку Dark и выберите значение для тёмного режима. 3. На вкладке **Text** нажмите на существующий стиль, чтобы отредактировать шрифт, начертание и размер, или нажмите **Plus Create style**, чтобы добавить собственные пресеты. ## 2. Создайте экраны \{#2-create-the-screens\} Флоу — это последовательность экранов. Настройте первый экран с общей основой — макетом, фоном и безопасной зоной — а затем продублируйте его для остальных. Так все экраны будут иметь одну основу, и вам нужно будет настроить её только один раз. Подробнее об управлении экранами читайте в [Экраны и слои — Управление экранами](paywall-layout-and-products#manage-screens). Чтобы настроить экраны: 1. Нажмите на пустую область холста на первом экране, чтобы открыть настройки экрана. 2. В разделе **System UI** отключите **Safe area**, чтобы фоновые элементы и элементы у краёв экрана могли выходить за пределы безопасной зоны. 3. В разделе **Fill** выберите тип фона и настройте его — например, **Image** Image, который будет отображаться за каждым экраном флоу. 4. В разделе **Layout** задайте направление **Vertical** Vertical и выберите подходящее распределение элементов. 5. В разделе **Screens** левой панели нажмите меню с тремя точками Context menu на первом экране и выберите **Duplicate**. Повторяйте, пока не получите четыре экрана — второй путь ветвления мы добавим позже, дублируя первый. 6. Переименуйте каждый экран в соответствии с его ролью — в нашем примере: `Welcome`, `Quiz`, `Rock path` и `Paywall`. ## 3. Создайте экран-приветствие \{#build-the-introduction-screen\} Первый экран обычно задаёт общий тон — заголовок, список возможностей и призыв к действию, открывающий остальную часть флоу. В нашем примере это экран Welcome. Нажмите на экран **Welcome** в панели **Screens**, затем добавьте элементы: 1. Добавьте главное изображение. Нажмите **+** > **Media** > **Image**, загрузите изображение и при необходимости настройте отступы. 2. Добавьте заголовок: нажмите **+** > **Text**, выберите стиль заголовка из сохранённых текстовых стилей и отредактируйте поле **Content**. 3. Добавьте список функций. Нажмите **+** > **List** > **Icon Cards**, затем отредактируйте иконку и подпись в каждой карточке. 4. Добавьте основную кнопку навигации внизу экрана. Действие для неё будет настроено на шаге навигации. ## 4. Создание экрана ввода и викторины \{#4-build-the-input-and-quiz-screen\} Второй экран собирает данные от пользователя. В нашем примере он запрашивает имя и один ответ на вопрос, который определяет, какой путь увидит пользователь дальше. Подробнее о вводе данных и викторинах читайте в разделах [Поля ввода и формы](builder-inputs-and-forms) и [Опросы и викторины](onboarding-quizzes). Нажмите на экран **Quiz** на панели **Screens**, затем добавьте элементы. Каждая группа на экране — вступление, вопрос + поле ввода, вопрос + викторина — располагается в своём вертикальном контейнере, чтобы связанные элементы держались вместе визуально. 1. Добавьте заголовок и текст вступления. Нажмите **+** > **Text** > **H1** для заголовка и **+** > **Text** > **Body** для основного текста. 2. Сгруппируйте вступление. Нажмите **+** > **Layout** > **Vertical Container**, перетащите новый контейнер в верхнюю часть дерева слоёв, затем перетащите H1 и body внутрь него. 3. Добавьте первый вопрос и поле ввода. Нажмите **+** > **Text** для подписи вопроса, затем **+** > **Inputs** > **Text** для поля ввода. 4. Укажите **Element ID** поля ввода на вкладке **Design** — в нашем примере это `name`. Это сделает значение доступным как переменную, на которую могут ссылаться другие экраны. 5. Сгруппируйте подпись и поле ввода в вертикальный контейнер так же, как в intro. 6. Добавьте второй вопрос и квиз. Нажмите **+** > **Text** для подписи, затем **+** > **Quiz** и выберите пресет макета, например Icon Options. Настройте варианты ответов — в нашем примере `Rock` и `Hip hop`. 7. Сгруппируйте подпись и квиз в вертикальный контейнер таким же образом. 8. Задайте ID вариантов ответов. Выберите каждый вариант квиза, откройте вкладку **Interactions** и задайте его **Element ID**. Эти ID используются в условной навигации далее. 9. Переключите квиз в режим одиночного выбора: кликните на пустое место холста, чтобы открыть **Screen settings**, прокрутите вниз до **Selectable Groups**, кликните на название группы квиза и установите тип **Single choice**. 10. Добавьте основную кнопку внизу — это кнопка «Далее», которая запускает ветвление. ## 5. Создайте первую ветку \{#build-the-first-branching-path\} Каждый экран ветки подстраивает контент под определённый сегмент аудитории. В нашем примере ветка Rock включает контент, связанный с роком: плейлисты, исполнителей и рекомендации. Подробнее о переменных читайте в разделе [Переменные](onboarding-variables). Чтобы создать экран: 1. На панели **Screens** нажмите на экран **Rock path**. 2. Добавьте заголовок. Поставьте курсор в поле **Content** в том месте, где должна появиться персонализация, нажмите на иконку переменной Variable icon и откройте вкладку **Elements**. Выберите экран, в котором находится поле ввода — в нашем примере **Quiz** — затем выберите переменную значения этого поля. В пикере она будет отображена как `<elementId>.value` — в нашем примере `name.value`. Во время выполнения заголовок будет обновляться в соответствии с тем, что ввёл пользователь. 3. Добавьте основной текст в виде дополнительных текстовых элементов, адаптированных под сегмент аудитории для этого пути. 4. Добавьте основную кнопку внизу. ## 6. Создайте второй вариант ветки \{#build-the-second-branching-path\} Экраны для разных веток обычно используют одинаковую структуру — меняется только текст. Продублируйте первый экран ветки и обновите содержимое. Чтобы продублировать и обновить: 1. На панели **Screens** выберите первый экран ветки и нажмите ⌘D / Ctrl+D, чтобы продублировать его. Копия появится в конце списка экранов. 2. Переименуйте копию — в нашем примере это `Hip hop path` — и перетащите её на нужное место в списке экранов, так чтобы она находилась рядом с экраном, который был продублирован. 3. Обновите основной текст для другого сегмента аудитории. Персонализированный заголовок продолжает работать — переменная переносится автоматически. ## 7. Создайте пейвол \{#build-the-paywall\} Финальный экран — это пейвол, где пользователь может оформить подписку. Подробное описание механики пейвола смотрите в статье [Создание базового экрана пейвола](basic-paywall-screen). Версия ниже — краткая выжимка. Нажмите на экран **Paywall** в панели **Screens**, затем добавьте элементы: 1. Добавьте **Horizontal Container** вверху и поместите внутрь кнопку **Close**. Пресет Close уже преднастроен. 2. Добавьте главное изображение, заголовок (с той же переменной персонализации, что и на экранах пути) и подзаголовок в качестве вспомогательного текста. 3. Добавьте продукты: нажмите **+** > **Products** и выберите **Vertical List**. Назначьте каждой карточке продукт через выпадающий список во вкладке **Design**. 4. Нажмите на карточку продукта по умолчанию и включите **Set as default product**, чтобы он был выбран при загрузке экрана. 5. Добавьте кнопку покупки. Нажмите **+** > **Buttons** и выберите пресет. На вкладке **Interactions** нажмите **Add trigger** > **On tap** > **Add action** и установите **Action** в значение **Purchase**, а **Product** — в `products.selectedProduct`. 6. Добавьте на экран шаблон **Button** > **Links**. Он включает три ссылки в футере: Restore Purchases, Terms of Use и Privacy Policy. Ссылка «Восстановить» настроена заранее. Чтобы настроить остальные ссылки, выберите элемент кнопки, откройте вкладку **Interactions** и укажите цель для действия **Open URL**. ## 8. Настройка навигации между экранами \{#wire-navigation-between-the-screens\} Экраны не связаны между собой автоматически. Используйте триггеры **On tap** и действия **Navigate to**, чтобы подключить основную кнопку каждого экрана к следующему экрану. Если экран ветвится в зависимости от ввода пользователя, вместо этого используется **Conditional action**. Подробнее о навигации и условных действиях — в разделах [Навигация и взаимодействие](onboarding-navigation-branching) и [Действия — Условные действия](onboarding-actions#conditional-actions). Чтобы настроить навигацию для примера флоу: 1. **Статическая навигация с экрана вступления.** Откройте экран Welcome, выберите основную кнопку и перейдите на вкладку **Interactions**. Нажмите **Add trigger** > **On tap** > **Add action**, установите **Action** в значение **Navigate to** и выберите следующий экран — в нашем примере это экран Quiz. 2. **Условная навигация из квиза.** Откройте экран Quiz, выберите кнопку Next и добавьте триггер **On tap** с действием **Conditional action**. Настройте правило IF/ELSE: - В пикере переменных откройте вкладку **Elements**, выберите экран **Quiz** и укажите `quiz.selectedOptionId`. - Используйте оператор **Equals** и сравните значение с ID одного из вариантов — в нашем примере это вариант Rock. - **IF** условие совпадает — выполните **Navigate to** и выберите первый экран пути. - **ELSE** — выполните **Navigate to** и выберите второй экран пути. 3. **Статическая навигация с каждой ветки на пейвол.** Повторите шаг 1 для каждого экрана ветки, указав пейвол в качестве назначения. ## Следующие шаги \{#next-steps\} - [Сохраните и опубликуйте флоу](builder-save-publish). - [Добавьте флоу в плейсмент](create-placement), чтобы начать показывать его пользователям. - Для флоу с таргетингом по аудитории (вместо ветвления внутри флоу) создайте сегменты аудитории и назначьте разные флоу на странице Placement. :::link Хотите узнать больше о создании флоу? Смотрите пошаговые видеоуроки в нашем [плейлисте на YouTube](https://www.youtube.com/playlist?list=PLMksWqaZiWtM). ::: --- # File: migrate-to-flows --- --- title: "Миграция на флоу" description: "Перенесите отдельный онбординг и пейвол в единое флоу Adapty — что изменится и как запустить это, не нарушив работу пользователей на старых версиях приложения." --- В Adapty *флоу* объединяет онбординг и пейвол в единую сущность за одним плейсментом. Флоу заменяет отдельные онбординг и пейвол, которые вы сегодня создаёте и показываете по отдельности. Это руководство объясняет, что меняется при переходе на флоу и как внедрить это изменение, не нарушая работу пользователей на старых версиях приложения. :::important Флоу в настоящее время поддерживаются на iOS, Android, React Native, Flutter и Capacitor SDK v4 и выше. Поддержка других платформ и фреймворков появится в ближайшее время. ::: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/8Cby6lVGI0o?si=rYA1HtdayyF1ffWd" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ## Флоу vs. онбординги и пейволы \{#flows-vs-onboardings-and-paywalls\} С отдельными онбордингами и пейволами вы поддерживаете два билдера и два плейсмента. Переход пользователя от онбординга к пейволу приходится обрабатывать в собственном коде. Флоу заменяет оба — вводные экраны, квиз и экран покупки — собраны в одном редакторе и отдаются из одного плейсмента. В таблице ниже сравниваются оба варианта: | | Флоу | Пейвол в Paywall Builder | Онбординг | |---|---|---|---| | Несколько экранов | Да | Нет — один экран | Да | | Отрисовка | Нативная | Нативная | WebView | | Продукты и плейсмент | Один плейсмент; продукты добавляются непосредственно во флоу | Один плейсмент; продукты добавляются непосредственно в пейвол | Один плейсмент, но собственных продуктов нет — для продаж нужно создать отдельный пейвол и подключить его через отдельный плейсмент | ## Нужна ли вам миграция? \{#should-you-migrate\} Существующие онбординги и пейволы продолжают работать, и Adapty их поддерживает. Однако новые функции теперь появляются во флоу, а не в отдельных конструкторах онбордингов и пейволов. **Если вы строите продукт на перспективу, флоу — лучший фундамент** — переходите на них, когда это вписывается в ваш план релизов. ## Как выполнить миграцию \{#how-to-migrate\} Миграция состоит из четырёх шагов. Основная часть работы — это разовое обновление SDK: создание и предпросмотр флоу не требуют написания кода. 1. **[Создайте флоу](#build-your-flow)**: Создайте флоу в редакторе без кода — разработчик не нужен. 2. **[Предпросмотр на устройстве](#preview-on-device)**: Проверьте флоу на реальном устройстве через мобильное приложение Adapty — сборка приложения не нужна. 3. **[Создайте новый плейсмент для флоу](#create-a-new-placement-for-your-flow)**: Создайте новый плейсмент для флоу с уникальным ID и решите, как он будет сосуществовать с существующими плейсментами. 4. **[Обновите SDK](#update-the-sdk)**: Обновитесь до iOS, Android, React Native или Capacitor SDK v4, получите флоу из плейсмента и проверьте покупку в песочнице. Это основная задача для разработчика. ### Создайте флоу \{#build-your-flow\} На странице **Flows** нажмите **Create flow**, чтобы приступить к созданию онбординга и пейвола в виде единого флоу. Подробнее о билдере: - **[Документация по флоу](adapty-flow-builder)**: Знакомит с билдером и его возможностями. - **[Типовые рецепты флоу](flow-builder-recipes)**: Пошаговые гайды для самых распространённых экранов. - **Ask AI**: Если застряли — используйте чат на любой странице документации. :::note Создание флоу из готового шаблона или с помощью ИИ пока недоступно — обе функции скоро появятся. Сейчас каждый новый флоу начинается с нескольких часто используемых экранов, которые вы можете редактировать и оформлять под свои нужды. ::: ### Предпросмотр на устройстве \{#preview-on-device\} Вы можете просматривать флоу на реальном устройстве, не меняя приложение. Скачайте [приложение Adapty](https://apps.apple.com/us/app/adapty/id6739359219) из App Store. Затем в конструкторе флоу нажмите **Test on device**, выберите локаль и отсканируйте QR-код на устройстве. Так вы увидите реальные экраны, ветвление, тексты и дизайн. :::note В режиме предпросмотра Adapty не может получить доступ к вашим продуктам в сторах, поэтому отображаемые цены ненастоящие. Реальные покупки проверяются позже — в сборке v4 с аккаунтом песочницы. Подробнее см. в разделе [Обновление SDK](#update-the-sdk). ::: ### Создайте новый плейсмент для вашего флоу \{#create-a-new-placement-for-your-flow\} Плейсмент обслуживает только один тип контента — флоу, пейвол или онбординг. Преобразовать существующий плейсмент онбординга или пейвола в плейсмент флоу нельзя (см. [типы плейсментов](create-placement)). Для флоу нужен отдельный новый плейсмент. **Присвойте новому плейсменту флоу полностью уникальный идентификатор.** Он не может совпадать с идентификатором существующего плейсмента пейвола или онбординга. :::warning Не отключайте старые плейсменты во время перехода Пользователи на старых версиях приложения имеют ID плейсментов онбординга и пейвола, скомпилированные в приложении. Они продолжают вызывать методы онбординга и пейвола и видят ваши существующие онбординг и пейвол до обновления. Выводите старые плейсменты из эксплуатации только после того, как уровень принятия SDK v4 станет достаточно высоким. ::: Вам не нужно переводить все плейсменты на флоу сразу. В SDK v4 метод `getFlow` получает данные как из плейсментов с флоу, так и из плейсментов с пейволами, поэтому в приложении везде вызывается один и тот же метод. Оставьте пейволы Paywall Builder там, где они вам нужны, а флоу используйте в остальных плейсментах. В переходный период каждый тип плейсмента отслеживает собственные метрики. Пока в работе находятся и старая, и новая версии приложения, данные распределяются между двумя наборами плейсментов. Старые плейсменты онбординга и пейвола охватывают более ранние версии; новый плейсмент флоу — SDK v4+. Сравнивайте их как отдельные когорты и ожидайте, что доля плейсмента флоу будет расти по мере обновления пользователей. Вы можете проводить A/B-тесты с флоу: запустите [обычный A/B-тест](ab-tests) на нескольких вариантах флоу в плейсменте флоу. Кросс-плейсментные A/B-тесты пока доступны только для пейволов, поэтому запустить их на плейсментах флоу пока нельзя. Сравнение нового флоу со старым пейволом — это сравнение когорт, а не единый тест: они находятся на разных типах плейсментов. ### Обновите SDK \{#update-the-sdk\} Когда плейсмент с флоу готов, укажите на него в приложении. Флоу работают только на Adapty SDK v4 и выше. Обновите SDK и получите флоу из нового плейсмента с помощью `getFlow`. Инструкции по обновлению для каждой платформы — в гайде по миграции на v4: [iOS](migration-to-ios-sdk-v4), [Android](migration-to-android-sdk-v4), [React Native](migration-to-react-native-sdk-v4) или [Capacitor](migration-to-capacitor-sdk-v4). После того как флоу подключён, проверьте его как любой другой платёжный флоу: запустите на устройстве или симуляторе и совершите покупку в песочнице ([iOS](ios-test) / [Android](testing-on-android)), чтобы убедиться, что продукты, сама покупка и уровень доступа работают корректно. :::note Флоу видят только пользователи, установившие приложение с SDK v4+. Те, кто пользуется более старой версией приложения, продолжают видеть ваш прежний онбординг и пейвол — именно поэтому старые плейсменты остаются активными во время перехода. То же самое касается платформ, которые пока не поддерживают флоу. ::: --- # File: paywall-layout-and-products --- --- title: Экраны и слои description: "Управляйте экранами и иерархией элементов на каждом экране во Flow Builder." --- Флоу состоит из одного или нескольких экранов. Каждый экран представляет один шаг в пути пользователя — например, пейвол, квиз или слайд с информацией о продукте. Элементы на каждом экране организованы в иерархию слоёв. Для управления экранами, слоями и элементами откройте стандартный вид **Screens and Layers**. В нём отображается последовательность экранов и структура слоёв каждого экрана. ## Управление экранами \{#manage-screens\} В верхней части левой панели перечислены все экраны флоу. Каждый элемент отображает пронумерованную метку и миниатюру предварительного просмотра. * **Выбрать экран**: Нажмите на запись экрана, чтобы сделать его активным. Визуальный редактор отобразит выбранный экран, а раздел Layers ниже обновится и покажет его иерархию слоёв. * **Добавить экран**: Нажмите кнопку Plus в верхней части раздела Screens, чтобы добавить новый пустой экран во флоу. * **Открыть библиотеку шаблонов**: Нажмите кнопку Templates в верхней части раздела Screens, чтобы просмотреть и применить [шаблоны флоу](paywall-builder-templates). * **Изменить порядок экранов**: Перетащите записи экранов, чтобы изменить их порядок во флоу. :::important Если в вашем флоу есть незаполненные пустые экраны, опубликовать его не получится. Удалите все черновые экраны перед публикацией. ::: ### Действия с экранами \{#screen-actions\} Нажмите на иконку с тремя точками Context в строке экрана, чтобы открыть контекстное меню. | Действие | Горячая клавиша | Описание | |----------|-----------------|----------| | **Play Animation** | | Предпросмотр анимаций, настроенных на этом экране | | **Copy** | ⌘C / Ctrl+C | Копировать экран в буфер обмена | | **Paste here** | ⌘V / Ctrl+V | Вставить ранее скопированный экран | | **Duplicate** | ⌘D / Ctrl+D | Создать копию экрана и добавить её во флоу | | **Rename** | | Изменить отображаемое имя экрана | | **Delete** | ⌘⌫ / Ctrl+Del | Удалить экран из флоу | :::tip Буфер обмена сохраняется между флоу. Скопируйте экран или элемент из одного флоу, откройте другой и вставьте его туда. ::: :::warning Когда вы удаляете экран, любое действие [Navigate to Screen](onboarding-navigation-branching), которое указывало на него, **теряет цель**, но само действие **не удаляется**. Назначьте новый экран назначения или удалите действие — иначе вы не сможете [предпросмотреть или опубликовать флоу](builder-save-publish#publish-a-flow). ::: ## Навигация между экранами \{#navigate-between-screens\} :::link Основная статья: [Навигация и взаимодействие](onboarding-navigation-branching) ::: Порядок экранов в списке сам по себе не определяет навигацию. Чтобы связать экраны между собой, используйте взаимодействия с элементами: настройте кнопку на переход пользователя к другому экрану. ## Настройки экрана \{#screen-settings\} Чтобы просмотреть свойства и настройки активного экрана, нажмите на пустую область в превью экрана. Правая панель переключится в режим настроек экрана. ### Системный интерфейс \{#system-ui\} Управляет тем, как экран взаимодействует с аппаратными элементами устройства. * **Safe area** добавляет отступы, которые не дают контенту заходить за вырез экрана и системные панели. * **Status bar** показывает и скрывает системную строку состояния (время, заряд батареи, иконки сигнала). ### Включение экрана в индикатор прогресса \{#include-screen-in-progress-indicator\} Если вы добавили элемент [Индикатор прогресса](builder-loaders-and-progress-bars#progress-indicators) в флоу, Adapty отображает его на каждом экране. Снимите флажок **Include screen in progress indicator**, чтобы убрать индикатор прогресса с конкретного экрана. Это удобно для приветственных экранов, финального пейвола или любого шага, который не должен учитываться в прогрессе. ### Макет экрана \{#screen-layout\} :::link Полная статья: [Макет и позиционирование](manage-paywall-ui-elements) ::: Раздел **Layout** определяет, как экран распределяет дочерние элементы. Эти свойства доступны для любого контейнерного элемента. * **Free**: Дочерние элементы располагаются независимо друг от друга. * **Vertical**: Элементы выстраиваются сверху вниз, как колонка в flexbox. * **Horizontal**: Элементы выстраиваются слева направо, как строка в flexbox. Для вертикальных и горизонтальных раскладок можно также настроить отступы и выравнивание. * **Alignment**: Положение элемента вдоль поперечной оси. * **Gap**: Пространство между соседними элементами. * **Distribution**: Распределение пространства между дочерними элементами и вокруг них. #### RTL-раскладка \{#rtl-layout\} Установите флажок **Mirror for RTL**, чтобы зеркально отразить раскладку для письменных систем с направлением письма справа налево. Порядок элементов в горизонтальных контейнерах изменится на обратный. ### Фон экрана \{#screen-background\} :::link Основная статья: [Фоны](paywall-head-picture) ::: **Fill** задаёт [фон экрана](paywall-head-picture): сплошной цвет, градиент, изображение или видео. Фон заполняет весь экран устройства, включая области за вырезом и системными панелями — даже если включена **Safe area**. #### Зацикливание фонового видео \{#loop-background-video\} Включите переключатель **Loop**, чтобы фоновое видео воспроизводилось непрерывно по кругу. #### Назначение пользовательского медиа-идентификатора \{#assign-a-custom-media-id\} Как и в случае с [любым изображением или видео](custom-media), вы можете присвоить фону экрана пользовательский медиа-идентификатор для обращения к нему в SDK. ### Отступы экрана \{#screen-spacing\} Задаёт внутренние отступы экрана для каждой стороны (сверху, справа, снизу, слева). ### Прокрутка \{#scroll\} Управляет поведением при переполнении. Включите **Vertical scroll**, чтобы содержимое экрана прокручивалось, если оно выходит за границы видимой области. ### Группы выбора \{#selectable-groups\} :::link Основная статья: [Выбираемые элементы и группы](flow-selectable-elements) ::: В разделе **Selectable groups** перечислены все группы выбора на текущем экране — из [квизов](onboarding-quizzes), [продуктов](paywall-product-block), [вкладок](builder-tabs), [переключателей триала](builder-toggles) или любого [пользовательского выбираемого элемента](flow-selectable-elements#make-an-element-selectable). Нажмите на запись группы, чтобы переименовать её, изменить тип, просмотреть переменные, которые она предоставляет, или удалить её. ## Управление слоями \{#manage-layers\} Каждый элемент на экране представлен в виде слоя. Раздел **Layers** отображает порядок элементов на активном экране. :::important Слои флоу не перекрываются так, как слои в графических редакторах. Вместо этого они представляют отдельные компоненты экрана. Элементы перекрываются *только* при использовании [абсолютного или фиксированного позиционирования](manage-paywall-ui-elements). Порядок их наложения определяется свойством `z-index`, а не их позицией в дереве слоёв. ::: Древовидная структура отражает отношения родитель-потомок. Нажмите на стрелку рядом с любым родительским слоем, чтобы развернуть или свернуть его дочерние элементы. Создавать слои напрямую нельзя. Каждый элемент, добавленный через представление [Добавить элемент](builder-elements), появляется как новый слой в дереве. * **Выбрать слой**: кликните по слою, чтобы выделить его. Визуальный редактор подсветит соответствующий элемент на канвасе, а правая панель отобразит его свойства [дизайна](builder-styling) и [взаимодействия](onboarding-navigation-branching). * **Изменить порядок слоёв**: перетащите слои внутри дерева, чтобы изменить их порядок в родительском контейнере. Порядок в дереве соответствует визуальному порядку на экране. * **Показать или скрыть слой**: наведите курсор на слой, чтобы справа от него появилась иконка глаза Eye. Кликните по ней, чтобы переключить видимость слоя. Скрытые слои остаются в дереве, но не отображаются ни в визуальном редакторе, ни на устройстве. Чтобы управлять видимостью с помощью логики во время выполнения, используйте [условную видимость](onboarding-element-visibility). * **Свернуть все слои**: нажмите кнопку свернуть Collapse в правом верхнем углу секции Layers, чтобы свернуть всё дерево. ### Действия со слоями \{#layer-actions\} Нажмите на иконку с тремя точками Context, чтобы открыть контекстное меню. | Действие | Горячая клавиша | Описание | |--------|----------|-------------| | **Copy** | ⌘C / Ctrl+C | Копировать слой в буфер обмена | | **Paste here** | ⌘V / Ctrl+V | Вставить скопированный ранее слой как дочерний | | **Duplicate** | ⌘D / Ctrl+D | Создать копию слоя в том же контейнере | | **Rename** | | Изменить отображаемое имя слоя. По умолчанию слои используют в качестве имени своё содержимое или тип компонента | | **Delete** | ⌘⌫ / Ctrl+Del | Удалить слой и все его дочерние элементы | | **Wrap** | | Обернуть слой в новый контейнер: **Wrap in Horizontal Container** или **Wrap in Vertical Container** | | **Unwrap / Ungroup** | | Удалить обёртку-контейнер и переместить его дочерние элементы на уровень выше | | **Move up** | ↑ | Переместить слой на одну позицию вверх в родительском контейнере | | **Move down** | ↓ | Переместить слой на одну позицию вниз в родительском контейнере | --- # File: manage-paywall-ui-elements --- --- title: "Макет и позиционирование" description: "Размещайте элементы на экране с помощью макета, режима позиционирования, размеров и отступов." --- Конструктор флоу создаёт адаптивные макеты. Здесь вы не перетаскиваете элементы на точные координаты — вместо этого вы вкладываете их в **контейнеры**, которые автоматически выстраивают дочерние элементы. Контейнер определяет направление элементов (вертикальное или горизонтальное), их выравнивание и отступы. Отдельные элементы могут дополнительно настраивать свои размеры и отступы или — при необходимости — выходить из потока с помощью абсолютного или фиксированного позиционирования. :::link О визуальных свойствах — заливке, рамках и эффектах — читайте в разделе [Стили и оформление](builder-styling). ::: <Tabs groupId="video"> <TabItem value="align" label="Выравнивание и позиционирование"> <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/aRS4Bzb6W4I?si=qH7B6t3kMab70gBi" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ` </TabItem>` `<TabItem value="layout" label="Layout, sizing & spacing">` <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/WQ9fpxrndok?si=ROMdIPvJ32tSwUX6" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> </TabItem> </Tabs> ## Компоновка \{#layout\} Компоновка — это основной инструмент для расположения элементов на экране. Каждый контейнер автоматически распределяет дочерние элементы по заданным правилам — направлению, выравниванию и отступам. Доступные в конструкторе элементы компоновки: * **[Вертикальный контейнер](builder-containers#containers)**: Размещает дочерние элементы сверху вниз * **[Горизонтальный контейнер](builder-containers#containers)**: Размещает дочерние элементы слева направо * **[Разделитель](builder-containers#dividers)**: Визуальный разделитель между элементами * **[Карусель](builder-containers#carousel)**: Горизонтально прокручиваемый набор слайдов * **[Bottom Sheet](builder-containers#bottom-sheet)**: Выдвигающаяся панель, которая показывает дополнительный контент при нажатии кнопки Контейнеры — это основные строительные блоки экрана. Вы можете вкладывать их друг в друга, чтобы создавать сложные макеты. У каждого контейнера есть раздел **Layout** на правой панели, который управляет расположением дочерних элементов. Чтобы сгруппировать элементы в новый контейнер, используйте действие слоя **Wrap** [layer action](paywall-layout-and-products#layer-actions). Чтобы убрать контейнер и поднять его дочерние элементы на уровень выше, используйте **Unwrap**. :::link Подробнее об иерархии экранов и слоёв — в разделе [Экраны и слои](paywall-layout-and-products). ::: ### Direction * Free **Free**: Без автоматического расположения. Дочерние элементы позиционируются независимо (полезно, когда дочерние элементы используют абсолютное позиционирование) * Vertical **Vertical**: Дочерние элементы выстраиваются сверху вниз, как строки в столбце * Horizontal **Horizontal**: Дочерние элементы располагаются слева направо, как элементы в строке ### Порядок элементов \{#element-order\} Дочерние элементы отображаются в том порядке, в каком они расположены на панели **Layers**. В вертикальном контейнере верхний элемент списка оказывается в верхней части экрана, в горизонтальном — в левой. Перетаскивайте элементы на панели **Layers** для изменения порядка или используйте **Move Up** и **Move Down** в [действиях слоя](paywall-layout-and-products#layer-actions). ### Выравнивание \{#alignment\} Сетка выравнивания определяет, как дочерние элементы располагаются вдоль поперечной оси контейнера. В вертикальном контейнере выравнивание задаёт горизонтальное положение дочерних элементов (по левому краю, по центру или по правому краю). В горизонтальном — вертикальное (по верхнему краю, по середине или по нижнему краю). ### Распределение \{#distribution\} Распределение определяет, как пространство делится между дочерними элементами вдоль главной оси: * **Gap** Gap (по умолчанию): фиксированное расстояние в пикселях между соседними дочерними элементами * **Space Between**: дочерние элементы распределяются до краёв, а между ними появляются равные промежутки * **Space Around**: каждый дочерний элемент окружён равными промежутками, при этом у краёв промежутки вдвое меньше * **Space Evenly**: равные промежутки перед всеми дочерними элементами, между ними и после них ### Обрезка контента \{#clip-content\} Визуально скрывает контент, выходящий за границы контейнера. Отключите эту опцию, если нужно разрешить переполнение — например, для бейджа, который намеренно выступает за край карточки. ## Позиция \{#position\} По умолчанию позиция каждого элемента определяется автоматически на основе лейаута контейнера. Переключатель **Position** позволяет вывести элемент из обычного потока и задать его положение вручную. ### Относительное (по умолчанию) \{#relative-default\} Элемент остаётся в обычном потоке разметки. Его положение определяется правилами родительского контейнера — свободно перетаскивать его нельзя. Используйте **Margin** для настройки отступов вокруг относительного элемента. Относительное позиционирование подходит для большинства элементов контента: текстовых блоков, изображений, карточек, кнопок и элементов списков. ### Абсолютное позиционирование \{#absolute\} Элемент выходит из обычного потока и накладывается поверх другого контента. Он больше не влияет на расположение соседних элементов. При выборе **Absolute** появляются дополнительные настройки: * **Поля смещения** (T, L, R, B): задают расстояние в пикселях от элемента до каждого края родительского контейнера * **Сетка привязки**: нажмите на точку сетки 3×3, чтобы выбрать угол, край или центр родительского элемента, к которому будет привязан элемент * **Горизонтальная привязка** Horizontal positioning (Left / Center / Right) и **Вертикальная привязка** Vertical positioning (Top / Center / Bottom): выпадающие списки, которые управляют той же точкой привязки, что и сетка * **Z-index**: числовое поле, которое определяет [порядок наложения](#stacking-order) элемента относительно соседних. Элементы с большим значением отображаются поверх остальных Используйте абсолютное позиционирование для декоративных наложений, значков, кнопок закрытия и иконок, размещённых поверх изображений. :::tip Чтобы растянуть абсолютный элемент на всю ширину родителя, установите горизонтальный якорь на **Left**, затем добавьте смещение **Right** равное 0. Элемент прикрепится к обоим краям. ::: ### Фиксированный \{#fixed\} Элемент полностью игнорирует родительский контейнер и прикрепляется к экрану. Он остаётся видимым при прокрутке — контент страницы движется под ним. Фиксированное позиционирование использует те же элементы управления, что и Absolute (отступы, сетка привязки, Z-index). Все отступы отсчитываются относительно безопасной области экрана, а не родительского элемента. Например, отступ 0 от нижнего края удерживает элемент выше индикатора Home. Чтобы отсчитывать отступы от физических краёв экрана, включите [Ignore safe area](#ignore-safe-area). Используйте фиксированное позиционирование для элементов, которые должны перекрывать прокручиваемый контент, а не резервировать под него место — плавающие кнопки закрытия или восстановления, постоянные верхние баннеры, кнопки прокрутки вверх и навигационные панели. Для выделенной нижней зоны действий используйте [Footer](builder-containers#footer). ### Игнорирование безопасной зоны \{#ignore-safe-area\} Безопасная зона — это часть экрана, свободная от выреза камеры, строки состояния и индикатора возврата домой. По умолчанию позиционируемые элементы остаются внутри неё. Установите флажок **Ignore safe area** под селектором типа позиции, чтобы отступы элемента отсчитывались от физических краёв экрана. Тогда элемент сможет заходить за вырез камеры и индикатор возврата домой. Для полноэкранного медиа: установите позиционирование **Fixed**, задайте все четыре отступа равными 0 и выберите **Ignore safe area**. Медиа займёт весь экран от края до края. Флажок работает только для абсолютного и фиксированного позиционирования. Для относительных элементов он недоступен, а при переключении обратно в **Relative** настройка сбрасывается. ## Размер \{#sizing\} У каждого элемента есть элементы управления **Width** и **Height**. Нажмите на выпадающий список, чтобы выбрать режим задания размера: * **Fill**: Элемент растягивается, занимая всё доступное пространство родителя. Показанное значение в пикселях — это вычисленный результат. * **Hug**: Элемент сжимается, подстраиваясь под размер содержимого. Показанное значение в пикселях — это вычисленный результат. * **Fixed**: Элемент использует точное значение в пикселях, которое вы задаёте, независимо от размера родителя или содержимого. Единственный доступный режим для элементов с абсолютным или фиксированным позиционированием. ## Отступы \{#spacing\} Задайте значения отступов отдельно для каждой стороны элемента. * **Margin**: Пространство между элементом и соседними элементами. Не выходит за границы родительского контейнера, какое бы значение ни было задано. * **Padding**: Пространство между границей элемента и его содержимым. У текстовых элементов есть только margin. У экранов — только padding. Для контейнеров и других элементов с дочерним содержимым доступны оба варианта. ## Порядок наложения \{#stacking-order\} Относительные элементы никогда не перекрывают друг друга — каждый контейнер выстраивает дочерние элементы последовательно. Перекрытие возникает только тогда, когда элемент выходит из обычного потока с помощью позиционирования **Absolute** или **Fixed**. Когда элементы всё же перекрываются, последующие сиблинги на панели **Layers** отображаются поверх предыдущих — даже если последующий сиблинг относительный, а предыдущий абсолютный. **Absolute** и **Fixed** элементы получают поле **Z-index** для более точного управления: чем выше значение, тем выше приоритет. У **Relative** элементов Z-index отсутствует — порядок в стеке определяется только их позицией в списке слоёв. Чтобы изменить порядок элементов, используйте [действия со слоями](paywall-layout-and-products#layer-actions) **Move up** и **Move down**. --- # File: builder-styling --- --- title: "Стили и внешний вид" description: "Настройте визуальное оформление элементов — заливку, рамки, эффекты, типографику, состояния и стили, применяемые ко всему проекту." --- Вкладка **Design** на правой панели управляет визуальным оформлением каждого элемента. Доступные свойства зависят от типа элемента, однако большинство из них имеют общие настройки стилей. :::link О размерах, отступах и позиционировании читайте в разделе [Макет и позиционирование](manage-paywall-ui-elements). ::: ## Видимость \{#visibility\} Переключатель **Visibility** определяет, отображается ли элемент на экране. * Show **Show** (по умолчанию): Элемент всегда виден. * Conditional **Conditional**: Элемент виден только при выполнении определённых условий. Подробнее см. в разделе [Условная видимость](onboarding-element-visibility). * Hide **Hide**: Элемент всегда скрыт. Используйте это, чтобы временно убрать элемент из флоу, не удаляя его. <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/3w3YSOmI3tQ?si=vPhoQGt44SI285Ru" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ## Заливка \{#fill\} Раздел **Fill** управляет фоном элемента. Доступны четыре типа заливки: сплошной цвет, градиент, изображение и видео. Используйте это свойство, чтобы задать главное изображение / видео для всего экрана. * **Solid color** Solid color. Используйте палитру цветов, введите hex-значение или задайте [цветовой стиль проекта](#color-styles). Настройте **прозрачность**, чтобы сделать фон полупрозрачным. * **Gradient** Gradient. Добавьте градиентную заливку с двумя и более цветовыми точками. Перетаскивайте точки для настройки перехода, а угол градиента задаёт его направление. * **Image** Image или **Video** Video. Установите [изображение / видео](custom-media) в качестве фона элемента. ## Граница \{#border\} По умолчанию граница отключена. Нажмите Plus рядом с **Border** на правой панели, чтобы добавить её. Чтобы убрать границу, нажмите Close рядом с заголовком **Border**. Если граница добавлена, настройте: * **Color**: используйте палитру цветов, введите hex-значение или назначьте [глобальный цветовой стиль](#color-styles). Отрегулируйте **opacity**, чтобы сделать границу полупрозрачной. * **Width**: толщина границы в пикселях. ## Углы \{#corners\} Раздел **Corners** управляет радиусом скругления углов. * **Radius slider**: задаёт одинаковый радиус для всех четырёх углов * **Per-corner toggle** Per Corner: включите, чтобы задать отдельный радиус для каждого угла ## Эффекты \{#effects\} Нажмите кнопку плюса Plus рядом с **Effects**, чтобы добавить один или несколько визуальных эффектов: * **Drop shadow**: тень за элементом * **Inner shadow**: тень внутри границ элемента * **Background blur**: размытие фона * **Layer blur**: размытие элемента и его дочерних объектов На один элемент можно наложить несколько эффектов. Переключите видимость Show, чтобы временно отключить эффект. ## Анимация \{#animation\} Нажмите кнопку Plus рядом с **Animation**, чтобы добавить анимационный эффект. На данный момент доступна только анимация **Pulse** — элемент ритмично увеличивается и уменьшается, привлекая к себе внимание. Настройте анимацию Pulse с помощью следующих параметров: | Параметр | Описание | |-----------|-------------| | Scale amount (%) | Насколько элемент увеличивается относительно исходного размера | | Duration (ms) | Продолжительность одного цикла анимации | | Delay between loops (ms) | Пауза между повторениями | | Shadow color | Цвет пульсирующей тени | | Shadow size (px) | Размер пульсирующей тени | ### Предпросмотр анимации \{#preview-the-animation\} По умолчанию в билдере отображаются статичные экраны — анимации не воспроизводятся, пока вы не включите их. Есть два способа: - Нажмите кнопку **Toggle animations** Toggle animations над превью устройства. Она включает или отключает анимации на экране — после включения они воспроизводятся непрерывно, пока вы не нажмёте снова. Кнопка появляется только тогда, когда на активном экране есть хотя бы одна анимация. - Откройте [контекстное меню](paywall-layout-and-products#screen-actions) экрана (значок с тремя точками рядом со слоем экрана) и выберите **Play Animation**. ## Внешний вид \{#appearance\} * **Opacity**: от 0% (прозрачный) до 100% (непрозрачный) * **Rotation**: введите значение в градусах для поворота элемента ## Свойства типографики (текстовые элементы) \{#typography-properties-text-elements\} Для текстовых элементов отображается раздел **Typography** со следующими настройками: ### Шрифт \{#font\} :::link См. также: [Кастомные шрифты](using-custom-fonts-in-flow-builder) ::: Нажмите на выпадающий список шрифтов Font select, чтобы открыть выбор шрифта. В нём есть две вкладки: * **Styles**: список сохранённых [текстовых стилей](#text-styles) вашего проекта. Выберите стиль, чтобы применить все его параметры типографики сразу. * **Fonts**: список доступных семейств шрифтов. Используйте поиск или прокрутку, чтобы найти нужный. Встроенные шрифты могут **отображаться по-разному на разных устройствах** — для единообразного отображения загрузите [кастомный шрифт](using-custom-fonts-in-flow-builder). ### Размер и насыщенность \{#size-and-weight\} :::warning Для [пользовательских шрифтов](using-custom-fonts-in-flow-builder) элементы управления **Weight**, **Bold** и **Italic** влияют только на встроенный предпросмотр редактора. Чтобы отображать разные насыщенности и начертания, загружайте каждый вариант шрифта как отдельный файл. ::: * **Weight**: выберите насыщенность шрифта из выпадающего списка * **Size**: выберите размер из выпадающего списка или введите произвольное значение ### Цвет \{#color\} Нажмите на образец цвета, чтобы открыть палитру. Введите HEX-значение, выберите цвет вручную или воспользуйтесь [переиспользуемыми стилями](#reusable-styles). Регулируйте ползунок прозрачности, чтобы сделать текст полупрозрачным. ### Выравнивание \{#alignment\} Две группы элементов управления выравниванием: * **По горизонтали**: По левому краю Align left, По центру Align center, или По правому краю Align right * **По вертикали**: По верхнему краю Align top, По середине Align middle, или По нижнему краю Align bottom ### Оформление \{#decoration\} * **None** None: Без оформления (по умолчанию) * **Underline** Underline: Добавляет подчёркивание текста * **Strikethrough** Strikethrough: Добавляет зачёркивание текста ### Усечение \{#truncation\} Включите усечение, чтобы обрезать текст, превышающий значение **Max Lines**. Это удобно при поддержке нескольких языков: если переведённая строка длиннее оригинала, усечение не даст ей сломать вёрстку. :::note Когда вы выбираете текстовый элемент, над ним на холсте появляется **встроенная панель инструментов**. Она даёт быстрый доступ к шрифту, начертанию, размеру и выравниванию — без прокрутки правой панели. ::: ## Настройки состояний (интерактивные элементы) \{#state-specific-settings-interactive-elements\} Интерактивные элементы поддерживают несколько визуальных состояний. При выборе такого элемента в правой панели появляется раздел **States**. Переключайтесь между состояниями, чтобы настроить отдельные визуальные свойства для каждого из них. Каждое состояние может переопределять любое визуальное свойство — заливку, границу, цвет типографики, прозрачность и другие. ### Выбираемые состояния \{#selectable-states\} :::link Основная статья: [Выбираемые элементы](flow-selectable-elements) ::: Элементы, входящие в группу выбора (варианты квиза, продукты, вкладки, переключатели пробного периода), по умолчанию имеют два состояния: * **Default**: обычный вид элемента * **Selected**: вид элемента, когда пользователь выбрал этот вариант. Переопределите такие свойства, как заливка, цвет границы и цвет текста, чтобы выделить активный выбор Чтобы задать стиль для неактивного состояния выбираемого элемента, добавьте третье состояние вручную. Откройте **States settings** Settings и добавьте **Disabled state**. Состояние **Disabled** зависит от условий. Выберите его и нажмите **Set conditions** set conditions, чтобы указать, при каких условиях элемент становится неактивным во время выполнения, — например, когда обязательное поле пустое. ### Состояния полей ввода \{#input-states\} Поля ввода поддерживают несколько состояний: * **Default**: обычный вид без фокуса * **Active**: поле в фокусе, готово к вводу * **Invalid**: введённое значение не прошло валидацию * **Disabled**: поле неактивно и недоступно для взаимодействия ### Другие элементы с управлением состоянием \{#other-state-bearing-elements\} Некоторые элементы поддерживают стилизацию по состояниям, выходящую за рамки стандартной схемы **Default / Selected / Disabled**: - **[Шаги индикатора прогресса](builder-loaders-and-progress-bars#step-states)** — три состояния для каждого шага: **Completed**, **Current** и **Upcoming**. - **[Точки карусели](builder-containers#dots)** — два варианта цвета: **Color** для неактивных точек и **Active Color** для точки текущего слайда. ## Повторно используемые стили \{#reusable-styles\} Панель **Styles** Styles в левом сайдбаре позволяет задавать повторно используемые стили, которые применяются ко всему флоу. Доступны два типа стилей: текстовые стили и стили цветов. Для поддержки тёмной темы необходимо использовать именно стили цветов. ### Стили текста \{#text-styles\} :::link Основная статья: [Текстовый контент](onboarding-text) ::: Стили текста хранят полный набор настроек типографики — гарнитуру, начертание, размер, межстрочный интервал, выравнивание и оформление. Каждый шаблон флоу включает стандартные пресеты, и вы можете создавать собственные стили. Чтобы создать стиль текста: 1. Откройте панель **Styles** Styles и выберите вкладку **Text**. 2. Нажмите **Plus Create style**. 3. Введите название и настройте параметры типографики. 4. Нажмите **Create**. Чтобы применить текстовый стиль, выберите текстовый элемент и выберите стиль из выпадающего списка шрифтов в разделе **Typography**. ### Цветовые стили \{#color-styles\} Цветовые стили — это именованные цвета, на которые можно ссылаться в любом месте флоу. У каждого цветового стиля есть название (например, «Primary text» или «Brand»), значение hex и счётчик использования, показывающий, сколько элементов на него ссылаются. Чтобы создать цветовой стиль: 1. Откройте панель **Styles** Styles и выберите вкладку **Colors**. 2. Нажмите **Plus Create style**. 3. Введите название и выберите цвет. Когда вы обновляете цветовой стиль, все элементы, которые на него ссылаются, обновляются автоматически. ### Тёмная тема \{#dark-mode\} :::link Основная статья: [Тёмная тема](paywall-dark-mode) ::: При необходимости для каждого цветового стиля можно добавить два варианта — один для светлой темы Light mode и один для тёмной темы Dark mode. SDK автоматически применяет нужный вариант в зависимости от текущей цветовой схемы устройства. Для предпросмотра тёмной темы в Paywall Builder используйте **переключатель темы** Dark mode на [нижней панели инструментов](builder-ui#view-controls-bottom-toolbar). --- # File: paywall-product-block --- --- title: "Настройка покупок" description: "Назначайте продукты экранам, добавляйте элементы продуктов и подключайте кнопку покупки во Flow Builder." --- Чтобы настроить покупки на экране, добавьте кнопку покупки и настройте для неё действие **Purchase**. Действие может быть привязано к конкретному продукту или к тому продукту, который пользователь выбирает в элементе Products на экране. <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/LLIZCd94PlE?si=t_8BitA1FBpbd8ue" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ## Добавление продуктов \{#add-products\} Элемент продукта — это визуальная карточка, отображающая продукт на холсте. Чтобы добавить элемент продукта: 1. На холсте нажмите **+** на нужном экране. 2. Выберите **Products**. 3. Выберите пресет раскладки: вертикальный список, горизонтальный список, карусель функций, карточки функций, список баннеров или нижний лист. 4. Выберите каждую карточку продукта и назначьте ей продукт в выпадающем списке на панели **Design**. :::important Элемент продукта без привязанного продукта [блокирует предпросмотр и публикацию](builder-save-publish#troubleshooting). Назначьте продукт или удалите элемент. ::: Чтобы показать зачёркнутую базовую цену на карточке, добавьте внутрь неё [элемент старой цены](onboarding-text#add-an-old-price). :::note Вы также можете привязать действие **Purchase** напрямую к взаимодействию **On tap** карточки продукта. Тогда нажатие на карточку запускает покупку — без необходимости в отдельной кнопке покупки. ::: :::important Если вы удалите группу продуктов и замените её новой, убедитесь, что все действия и переменные ссылаются на новую группу. Ссылки, оставшиеся на удалённую группу, [блокируют предпросмотр и публикацию](builder-save-publish#troubleshooting). ::: ## Добавить кнопку покупки \{#add-a-purchase-button\} Кнопка покупки запускает действие **Purchase** при нажатии. Чтобы добавить кнопку покупки: 1. На холсте нажмите **+** на экране. 2. Выберите **Button** и укажите пресет кнопки. 3. Выделив кнопку, откройте вкладку **Interactions** в правой панели. 4. Нажмите **Add trigger** > **On tap**, затем нажмите **Add action**. 5. Установите **Action** в значение **Purchase**, затем задайте **Product** как одно из: - `products.selectedProduct`: покупает продукт, который пользователь выбрал в элементе Products на экране. - Конкретный продукт: всегда покупает именно этот продукт, независимо от выбора на экране. ### Отображение цены на кнопке \{#show-the-price-on-the-button\} Чтобы вставить цену выбранного продукта в подпись кнопки, используйте переменную: 1. Выделите кнопку и откройте вкладку **Design** на правой панели. 2. В поле **Content** установите курсор в то место, где должна отображаться цена. 3. Нажмите на иконку переменной, выберите `products.selectedProduct`, затем атрибут `prod_price`. Полная переменная принимает вид `products.selectedProduct.prod_price`. 4. Добавьте статический текст вокруг переменной — например, `Subscribe for {prod_price}`. Подпись обновляется при каждом выборе другого продукта пользователем. ## Восстановление покупок \{#restore-purchases\} Чтобы пользователи могли восстановить прежние покупки, добавьте на экран кнопку или ссылку для восстановления. Чтобы добавить элемент восстановления покупок: 1. На холсте нажмите **+** на экране. 2. Выберите **Button**, затем выберите **Links** для текстовой ссылки или любой другой тип кнопки для стилизованной кнопки. 3. Выделив элемент, откройте вкладку **Interactions** на правой панели и нажмите **Add trigger**. 4. Выберите **On tap** и нажмите **Add action**. 5. В выпадающем списке **Action** выберите **Restore purchases**. ## Отображение дополнительных элементов в зависимости от выбранного продукта \{#display-additional-elements-based-on-the-selected-product\} Если на экране есть продукты, можно показывать или скрывать другие элементы в зависимости от того, какой продукт выбирает пользователь. Чтобы настроить условную видимость: 1. В элементе **Products** выберите карточку продукта. 2. Откройте вкладку **Interactions** на правой панели и нажмите **Add trigger**. 3. Выберите **On tap** и нажмите **Add action**. 4. В выпадающем меню **Action** выберите **Show** или **Hide**. 5. Выберите элемент, который нужно показать или скрыть при выборе этого продукта. ## Обзор продуктов во флоу \{#review-products-in-flow\} Панель **Products** в левом сайдбаре связывает существующие продукты с каждым экраном флоу. У каждого экрана есть два раздела: - **Default** — один продукт, выбранный по умолчанию при загрузке экрана. - **Other** — дополнительные продукты, доступные на том же экране. --- # File: flow-selectable-elements --- --- title: "Выбираемые элементы и группы" description: "Делайте элементы выбираемыми, организуйте их в группы и используйте их состояние в условиях флоу." --- Выбираемые элементы — это элементы флоу, на которые пользователь может нажать, чтобы выбрать или снять выбор. Их состояние может управлять навигацией, видимостью и другой логикой во флоу. Вот что можно сделать: - [Использовать стандартные выбираемые элементы](#default-selectable-elements) — варианты квиза, продукты, вкладки и переключатели пробного периода доступны из коробки - [Сделать любой элемент выбираемым](#make-an-element-selectable) — превратите любой элемент в выбираемый и назначьте его в группу - [Создавать группы и управлять ими](#create-a-group) — организуйте выбираемые элементы в группы с одиночным, множественным выбором или переключателем - [Использовать состояние выбора в условиях](#use-selectable-state-in-conditions) — обращайтесь к значениям группы в условиях на любом экране флоу ## Элементы, выбираемые по умолчанию \{#default-selectable-elements\} Некоторые типы элементов являются выбираемыми по умолчанию — они уже входят в автоматически создаваемые группы и не требуют дополнительной настройки: - **Варианты квиза**: каждый ответ квиза — это выбираемый элемент внутри группы квиза. См. [Квизы](onboarding-quizzes). - **Продукты**: карточки продуктов в группе продуктов. См. [Блок продуктов](paywall-product-block). - **Табы**: элементы табов внутри группы табов. См. [Табы](builder-tabs). - **Переключатели триала**: контейнер, который входит в группу и получает состояние «выбран». См. [Переключатели](builder-toggles). ## Сделайте элемент выбираемым \{#make-an-element-selectable\} <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/btpZPOm9VRY?si=1P959iwNfIJ1ZP7N" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> В некоторых случаях вам может понадобиться сделать дополнительные элементы выбираемыми. Например, можно добавить чекбокс **Больше не спрашивать**, который работает как элемент внутри группы-опроса. Чтобы сделать элемент выбираемым: 1. Выберите элемент на экране или в панели **Layers**. 2. Справа переключитесь на панель **Interactions**. 3. Выберите **Turn into selectable element**. 4. В выпадающем списке **Group** выберите существующую группу или [создайте новую](#create-a-group). 5. Задайте **Element ID** — уникальный идентификатор этого элемента внутри группы. 6. Если вы хотите, чтобы этот элемент был выбран по умолчанию, установите флажок **Set as default in group**. ## Создать группу \{#create-a-group\} Группы организуют выбираемые элементы на экране и определяют логику выбора — один вариант, несколько или переключение. Чтобы создать группу: 1. Выберите элемент и [сделайте его выбираемым](#make-an-element-selectable). 2. В выпадающем списке **Group** выберите **Create group**. 3. Введите **Group name**. 4. Выберите [тип группы](#group-types). Группа станет доступна в выпадающем списке **Group** для других выбираемых элементов на том же экране. ## Типы групп \{#group-types\} :::important Большинство [пресетов квизов](onboarding-quizzes) по умолчанию используют тип **multi-choice**. Измените [тип группы](#manage-groups), чтобы разрешить выбор только одного ответа. ::: - **Single choice**: В группе можно выбрать только один элемент. При выборе нового элемента предыдущий снимается. - **Multi-choice**: Можно одновременно выбрать несколько элементов. - **Toggle**: Каждый элемент независимо переключается между выбранным и невыбранным состоянием при каждом нажатии. ## Управление группами \{#manage-groups\} Чтобы просматривать и редактировать группы, откройте панель **Screen settings** и найдите раздел **Selectable groups**. В нём перечислены все группы на текущем экране. Нажмите на идентификатор группы, чтобы: - Изменить идентификатор группы - Изменить [тип группы](#group-types) - Посмотреть, как элементы группы используются в условиях ## Использование состояния выбора в условиях \{#use-selectable-state-in-conditions\} Вы можете обращаться к состоянию выбора группы в условиях на любом экране флоу — не только на том, где определена группа. Например: `IF quiz.photo is selected, THEN navigate to the Photo screen`. :::important Все элементы группы должны находиться на одном экране. Нельзя добавлять элементы с разных экранов в одну группу. Однако вы можете ссылаться на значения группы в условиях на любом экране флоу. ::: Используйте состояние выбора вместе с: - **[Условные действия](onboarding-actions#conditional-actions)**: Направляйте пользователей на разные экраны или запускайте разные действия в зависимости от выбранных элементов. - **[Динамическая навигация](onboarding-navigation-branching)**: Разветвляйте флоу на основе ответов в квизе, состояний переключателей или других выборов. - **[Условная видимость](onboarding-element-visibility)**: Показывайте или скрывайте элементы в зависимости от того, что пользователь выбрал на предыдущих экранах. --- # File: builder-element-states --- --- title: "Состояния элементов" description: "Задавайте стили элементов для каждого состояния и используйте условие для отключения элемента во время выполнения." --- Интерактивные элементы флоу меняют внешний вид в зависимости от действий пользователя: выбранный вариант квиза становится **Selected**, сфокусированный ввод — **Active**. Некоторые состояния управляются условиями — например, кнопку можно **отключить** (disable). Настройте стиль каждого состояния отдельно, чтобы давать пользователям визуальную обратную связь без изменений в коде приложения. <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/gdsNfHpKAqQ?si=VY5mqZgH1j0RB6fE" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ## Доступные состояния по типу элемента \{#available-states-by-element-kind\} | Тип элемента | Встроенные состояния | Добавляемые состояния | |---|---|---| | [Выбираемые элементы](#selectable-element-states) | **Default**, **Selected** | **Disabled** | | [Поля ввода](#input-states) | **Default**, **Active**, **Invalid** | **Disabled** | | [Любой элемент с взаимодействием по нажатию](#condition-driven-disabled-state) — кнопки, изображения, иконки, контейнеры и т. д. | **Default** | **Disabled** | | [Шаги индикатора прогресса](#step-states-for-progress-indicators) | **Completed**, **Current**, **Upcoming** | — | **Добавляемые** состояния не отображаются по умолчанию — откройте **States settings** Settings чтобы их добавить. Они [**управляются условиями**](#condition-driven-disabled-state): вы сами определяете, когда они активируются. ## Как стилизовать состояние \{#how-to-style-a-state\} 1. Выберите элемент. Раздел **States** в правой панели отображает список состояний, которые поддерживает этот элемент. 2. В разделе **States** активируйте нужное состояние. При необходимости добавьте [состояние Disabled на основе условий](#condition-driven-disabled-state). 3. Измените любое свойство — заливку, рамку, типографику и т. д. Изменение применится только к этому состоянию. Вложенные элементы становятся состоятельными вместе с родительским. Любое изменение дочернего элемента применяется только в рамках активного состояния родителя. 4. Builder применяет подходящий стиль во время выполнения. ## Состояния выбираемого элемента \{#selectable-element-states\} Выбираемые элементы — варианты квиза, продукты, вкладки, переключатели триала и любой [кастомный выбираемый элемент](flow-selectable-elements#make-an-element-selectable) — имеют два состояния из коробки: - **Default**: Стандартный вид элемента в состоянии покоя. - **Selected**: Применяется, когда пользователь нажимает на элемент. Builder возвращается к Default, когда пользователь снимает выбор с элемента. В группе с одним выбором выбор одного элемента снимает выбор с остальных. Группы с несколькими вариантами позволяют выбрать несколько элементов одновременно. Переключатели независимы — выбор одного не влияет на соседние. См. [типы групп](flow-selectable-elements#group-types). :::tip Нужно одинаково стилизовать одно состояние для нескольких элементов (например, варианты квиза)? Сначала настройте стиль одного элемента, затем продублируйте его. Стилизация состояния не переносится между соседними элементами — дублирование является текущим обходным решением. ::: ## Состояния поля ввода \{#input-states\} - **Default**: Стандартный вид поля в покое. - **Active**: Применяется, пока поле в фокусе. - **Invalid**: Применяется, когда содержимое поля не проходит валидацию. Например, если в поле email нет символа `@`. См. [Валидация полей ввода](builder-inputs-and-forms#input-validation). - **Disabled**: Поле неактивно и недоступно для взаимодействия. Это состояние добавляется вручную; см. [Состояние Disabled на основе условий](#condition-driven-disabled-state). Каждое состояние настраивается так же, как у выбираемого элемента: активируйте нужное состояние и измените свойства. ## Состояние Disabled на основе условий \{#condition-driven-disabled-state\} Состояние Disabled запрещает пользователю взаимодействовать с элементом. В отличие от Default, Selected, Active или Invalid, состояние Disabled не активируется само по себе — для него нужно задать условие-триггер вручную. Disabled доступно для: - **Inputs**: Любое [поле ввода](builder-inputs-and-forms) — текст, email, пароль, число, телефон, дата и/или время. - **Selectable elements**: Варианты квиза, продукты, вкладки, переключатели пробного периода и любой [пользовательский выбираемый элемент](flow-selectable-elements#make-an-element-selectable). - **Любой элемент с действием по нажатию**: Например, кнопка, изображение или иконка, которые запускают навигационное действие. ### Добавьте состояние Disabled \{#add-the-disabled-state\} Чтобы добавить и настроить состояние Disabled: 1. Выберите нужный элемент. 2. В разделе **States** нажмите **Settings** Settings. 3. Выберите **Add Disabled state**. Состояние Disabled появится в разделе **States**. 4. Рядом с новым состоянием Disabled нажмите **Edit conditional state** Edit conditional state. 5. Добавьте условие. Если нужно заблокировать кнопку **submit** до тех пор, пока введённые данные не пройдут валидацию, сравните переменную `isValid` элемента ввода со значением `false`. 6. Оформите состояние Disabled визуально, чтобы обозначить ограничение (например, уменьшите прозрачность). { } SDK оценивает условие во время выполнения и автоматически применяет состояние Disabled — изменения в коде приложения не требуются. ## Состояния шагов для индикаторов прогресса \{#step-states-for-progress-indicators\} :::link Основная статья: [Индикаторы прогресса](builder-loaders-and-progress-bars#step-states) ::: Индикаторы прогресса показывают пользователю, насколько далеко он продвинулся в флоу онбординга. Каждый шаг имеет три состояния: - **Completed**: Шаги, которые пользователь уже прошёл. - **Current**: Текущий шаг пользователя. - **Upcoming**: Шаги, до которых пользователь ещё не дошёл. --- # File: builder-containers --- --- title: "Элементы компоновки: контейнеры, карусели, нижние листы" description: "Группируйте элементы в контейнеры, карусели и нижние листы во Flow Builder." --- Элементы компоновки объединяют другие элементы и управляют тем, как они располагаются на экране. :Flow Builder включает пять типов элементов компоновки: - **Containers**: размещают дочерние элементы вдоль оси — вертикально или горизонтально - **Carousel**: прокручиваемый контейнер, показывающий по одному слайду за раз - **Bottom Sheet**: панель, выезжающая снизу экрана и отображающаяся поверх основного контента - **Footer**: панель, закреплённая в нижней части экрана, за пределами области прокрутки - **Dividers**: тонкие линии, разделяющие строки или столбцы :::link **Tabs** тоже относятся к этой категории, но описаны в отдельной статье. Подробнее см. [Tabs](builder-tabs). ::: ## Контейнеры \{#containers\} :::link Основная статья: [Позиционирование элементов](manage-paywall-ui-elements) ::: Контейнеры группируют элементы по вертикали или горизонтали. **Vertical Container** располагает элементы в строки, а **Horizontal Container** — в столбцы. :::tip Вкладывайте контейнеры друг в друга, чтобы создавать более сложные макеты. ::: ### Изменение направления контейнера \{#change-container-direction\} Направление контейнера можно изменить в любой момент. Переключайтесь между **Vertical**, **Horizontal** и **Free** в разделе **Layout** на правой панели — удалять и пересоздавать контейнер не нужно. Отступы, выравнивание и распределение элементов настраиваются там же, в разделе **Layout**. Дочерние элементы отображаются в том порядке, в котором они расположены на панели **Layers** — перетащите их, чтобы изменить порядок. ### Обёртывание и разворачивание \{#wrap-and-unwrap\} Чтобы превратить существующий элемент в контейнер, выделите его и воспользуйтесь действием слоя **Wrap** [layer action](paywall-layout-and-products#layer-actions). Перетащите дополнительные элементы в новый контейнер с панели **Layers**. Чтобы удалить контейнер и поднять его дочерние элементы на уровень выше, используйте **Unwrap**. ## Карусель \{#carousel\} **Карусель** — это контейнер с горизонтальной прокруткой, показывающий один слайд за раз. Пользователь листает слайды свайпом, либо карусель переключает их автоматически по таймеру. Карусель содержит набор слоёв **Slide**. Когда слайд активен, элементы на этом слое отображаются на экране. В отличие от вкладок, активный слайд карусели не является [выбираемым элементом группы](flow-selectable-elements) — на слайды нельзя ссылаться в условиях или динамическом тексте. Используйте карусель для визуальной смены слайдов, но не для ветвления логики на основе выбора пользователя. ### Изменение активного слайда \{#change-active-slide\} Когда вы выбираете карусель, конструктор показывает всплывающую панель управления с выпадающим списком **Slide** и кнопкой **+ Add Slide**. - Нажмите **+ Add Slide**, чтобы добавить новый пустой слайд. - Используйте выпадающий список **Slide**, чтобы переключить активный слайд на канвасе, — или кликните на соответствующий слой Slide в панели **Layers**. Чтобы изменить порядок слайдов, перетащите их внутри элемента Carousel в панели Layers. {/* TODO: on-device GIF */} ### Свойства #### Автопрокрутка \{#auto-scroll\} Автопрокрутка автоматически листает слайды — пользователю не нужно свайпать, чтобы увидеть весь контент. Поведение управляется двумя параметрами: - **Delay** — сколько времени каждый слайд остаётся видимым (мс). - **Duration** — сколько времени занимает переход между слайдами (мс). #### Размер карусели \{#carousel-sizing\} Специальные элементы управления определяют размер карусели и расстояние между соседними слайдами. Установите для **Height** значение **Fixed**, чтобы макет не смещался при пролистывании слайдов с разным объёмом контента. #### Размер слайда \{#slide-sizing\} Параметры **Width** и **Height** для каждого слайда. По умолчанию установлено Fill, чтобы слайд повторял размеры карусели. Задайте фиксированную ширину, чтобы создать эффект «заглядывания», при котором соседние слайды частично видны. #### Точки \{#dots\} Индикатор страниц в нижней части карусели. Показывает пользователю количество слайдов и активный слайд. Отключите переключатель **Show dots**, чтобы скрыть индикатор слайдов. Когда точки видны, их внешний вид определяется следующими параметрами: - **Color** — цвет неактивной точки. - **Active Color** — цвет точки для текущего видимого слайда. - **Size** — диаметр каждой точки в пикселях. - **Gap** — расстояние между соседними точками. - **Padding** — отступ между рядом точек и контентом карусели выше. ## Bottom Sheet :::link Пошаговое руководство: [Показать все тарифы в Bottom Sheet](show-plans-bottom-sheet) ::: **Bottom Sheet** — это панель-слой, которая выезжает снизу экрана поверх основного контента. Шторка всегда размывает фон позади себя — отключить это нельзя. Рекомендуется открывать её по тапу — например, по ссылке **Show all plans** — а не при загрузке экрана. ### Структура \{#structure\} Bottom Sheet содержит два верхнеуровневых слоя: - **Heading** — контейнер в верхней части шторки, по умолчанию содержит текстовый слой **Title** и кнопку закрытия **Close button** Close. Отредактируйте или удалите их при необходимости. - **Content** — основной контейнер. Добавляйте в него продукты, кнопки, ссылки и другие элементы. {/* TODO: on-device GIF */} ### Начальная видимость \{#initial-visibility\} По умолчанию нижний лист появляется сразу при рендеринге экрана. Чтобы открывать его по требованию: 1. **Сначала наполните контент листа** — скрытые слои недоступны для редактирования, поэтому лист должен оставаться видимым, пока вы не закончите его заполнять. 2. На панели **Layers** выберите нижний лист. 3. Установите **Visibility** в значение **Hide** Hide. Лист остаётся в дереве слоёв, но перестаёт отображаться на экране. ### Открытие скрытого bottom sheet \{#triggering-the-bottom-sheet\} Чтобы открыть скрытый bottom sheet, привяжите действие **Show** к другому элементу: 1. Выберите элемент-триггер (например, кнопку или текстовую ссылку). 2. Откройте вкладку **Interactions** на правой панели. 3. Нажмите **Add trigger** > **On tap**, затем **Add action**. 4. Установите **Action** в значение **Show** и выберите bottom sheet из выпадающего списка. ## Подвал \{#footer\} **Footer** — это закреплённый контейнер, занимающий нижнюю часть экрана. Он может быть любой высоты и содержать что угодно — от одной кнопки до нескольких строк текста. Используйте подвал для элементов, которые должны оставаться на месте, пока остальное содержимое прокручивается: кнопки призыва к действию, юридические тексты, ссылки. В отличие от обычных элементов, подвал распространяется на нижнюю безопасную зону устройства: его фон доходит до самого края экрана. Только один футер допускается на экране. Нельзя дублировать существующий футер или добавлять новый. ### Футер vs. обычный фиксированный элемент \{#footer-vs-a-regular-fixed-element\} Оба остаются на экране, пока контент прокручивается. Выбирайте вариант, который подходит для вашей задачи: - **Используйте Footer** для основной нижней панели экрана (кнопка CTA, юридический текст, ссылки). Он резервирует под себя место, так что контент всегда прокручивается выше него и никогда не перекрывается им, а нижняя безопасная зона закрывается автоматически. - **Используйте [фиксированный элемент](manage-paywall-ui-elements)** для того, что должно плавать поверх прокручиваемого контента, а не резервировать место, или для того, что прикрепляется к краю, отличному от нижнего, — плавающая кнопка закрытия/восстановления, постоянный баннер сверху, кнопка прокрутки наверх. Отступы для безопасной зоны в этом случае вы настраиваете самостоятельно. ## Разделители \{#dividers\} **Horizontal Divider** и **Vertical Divider** — тонкие линии для разделения контента. Используйте Horizontal Divider для разделения строк, а Vertical Divider — для разделения колонок внутри горизонтального контейнера. Толщину, цвет и длину можно настроить на правой панели. --- # File: using-custom-fonts-in-flow-builder --- --- title: "Кастомные шрифты в Flow Builder" description: "Загружайте и используйте кастомные шрифты в Flow Builder." --- При создании флоу вы можете захотеть использовать кастомный шрифт, соответствующий стилю остального приложения. Вот как добавить кастомные шрифты и применить их во флоу. :::tip [Настройте шрифты](onboarding-text) на панели **Styles** перед тем, как приступать к дизайну флоу. Тогда все изменения будут применяться глобально. ::: ## Встроенные шрифты \{#built-in-fonts\} Когда вы создаёте флоу в Builder, Adapty по умолчанию использует системный шрифт. Как правило, это SF Pro на iOS и Roboto на Android, хотя конкретный шрифт зависит от устройства. Также можно выбрать один из распространённых шрифтов: Arial, Times New Roman, Courier New, Georgia и Helvetica. Для каждого из них доступно несколько вариантов начертания. Эти шрифты не входят в состав SDK Adapty и используются только для предварительного просмотра. Мы не можем гарантировать их корректную работу на всех устройствах. Тем не менее, по результатам нашего тестирования, большинство устройств распознают эти шрифты без каких-либо дополнительных настроек. Вы также можете [ознакомиться со шрифтами, доступными на iOS по умолчанию](https://developer.apple.com/fonts/system-fonts/). ## Добавление пользовательского шрифта \{#add-a-custom-font\} :::warning Загружаемый файл используется **только для предварительного просмотра в редакторе** — Adapty не передаёт его на устройства пользователей. Чтобы шрифт отображался на устройстве, [включите файл в бандл приложения](#add-the-font-files-to-your-apps-bundle). Без этого SDK во время выполнения заменит шрифт на SF Pro (iOS) или Roboto (Android). ::: Если вам нужны шрифты, отличные от системных, вы можете добавить пользовательский шрифт. Чтобы добавить пользовательский шрифт: 0. Если шрифт вариативный, разделите его на файлы с одним стилем и уникальными именами. Элементы управления жирностью, полужирным и курсивом не применяются к пользовательским шрифтам. Adapty регистрирует только один стиль на файл пользовательского шрифта. Чтобы [стилизовать текст](onboarding-text), переключите шрифт на нужный вариант. 1. Выберите **Upload new font** в любом из выпадающих списков шрифтов. 2. В окне **Add custom font** заполните следующие поля: :::warning Значения **Font name in Builder**, **iOS font name** и **Android font name** должны быть уникальными среди всех файлов пользовательских шрифтов в приложении. ::: - **Font name in Builder**: Введите отображаемое имя шрифта. Оно будет появляться в выпадающих списках шрифтов по всему Builder. - **iOS font name**: Введите PostScript-имя шрифта. Его можно найти в Font Book → PostScript name или через [`UIFont` API](https://developer.apple.com/documentation/uikit/uifont). - **Android font name**: Введите имя файла из `res/font/`. Используйте только строчные буквы, цифры и символы подчёркивания. - **Font file**: Перетащите файл шрифта или нажмите **Select files**. Поддерживаемые форматы: `.ttf`, `.otf`, `.woff`, `.woff2`. 3. Нажмите **Save font**. Загружая файл шрифта в Adapty, вы подтверждаете, что имеете право использовать его в своём приложении. ### Удаление пользовательского шрифта \{#delete-a-custom-font\} Если удалить пользовательский шрифт из дашборда, все ссылки на него во всех черновых и опубликованных флоу автоматически заменятся системным шрифтом — без предупреждения и без возможности отмены. Перед удалением убедитесь, что ни один активный флоу не использует этот шрифт. ## Пользовательские шрифты в шаблонах флоу \{#custom-fonts-in-flow-templates\} [Библиотека шаблонов флоу](paywall-builder-templates) включает шаблоны с пользовательскими шрифтами. Наведите курсор на чип **Custom font** на карточке шаблона, чтобы увидеть, какие шрифты в нём используются. Adapty не включает эти шрифты в поставку. Вам нужно найти их самостоятельно, ориентируясь на названия шрифтов, указанные в чипе. Некоторые шрифты могут требовать коммерческой лицензии. Когда файлы шрифтов готовы, следуйте инструкции по подключению ниже. ## Добавьте файлы шрифтов в бандл вашего приложения \{#add-the-font-files-to-your-apps-bundle\} Если вы уже используете кастомный шрифт в других местах приложения, просто добавьте шрифты для пейвола таким же способом. Если нет — убедитесь, что файл шрифта включён в проект и бандл приложения. Как это сделать: - На iOS: [в официальной документации Apple](https://developer.apple.com/documentation/uikit/adding-a-custom-font-to-your-app) - На Android: [в официальной документации Android](https://developer.android.com/develop/ui/views/text-and-emoji/fonts-in-xml) --- # File: paywall-head-picture --- --- title: "Фоны" description: "Заполните экран однотонным цветом, градиентом, изображением или видеофоном во Flow Builder." --- Задайте фон на любом экране через панель **Fill** в разделе [**Screen settings**](paywall-layout-and-products#screen-settings). Доступно четыре типа фона: однотонный цвет, градиент, изображение или видео. ## Изображение \{#image\} Загрузите файл `.JPG`, `.PNG`, `.GIF` или `.WEBP` размером до 20 МБ. Изображение масштабируется и покрывает весь фон. :::note Фоновые изображения и видео занимают весь экран, включая области за вырезом камеры и системными панелями — даже если включена **Safe area**. Держите важный контент подальше от краёв, чтобы он не обрезался. ::: Чтобы менять фоновое изображение во время выполнения из кода приложения, включите [custom media ID](custom-media#custom-media-id). ## Видео \{#video\} Загрузите файл `.MP4` или `.WEBM` размером до 50 МБ. В превью отображается стоп-кадр, но на устройстве видео воспроизводится в реальном времени. Включите **Loop**, чтобы видео воспроизводилось непрерывно по кругу. Чтобы менять фоновое видео во время выполнения, включите [custom media ID](custom-media#custom-media-id). ## Однотонный цвет \{#solid-color\} Введите hex-значение и задайте непрозрачность от 0 до 100%. Выберите сохранённый [стиль цвета](builder-styling) из палитры, чтобы применить фирменный цвет — фон автоматически адаптируется к светлой и тёмной темам. ## Градиент \{#gradient\} Создайте линейный градиент с несколькими точками: - **Direction** — поворачивайте градиент от 0 до 360°. - **Stops** — перетаскивайте по полосе для изменения позиции. Нажмите на точку, чтобы изменить её hex-значение и непрозрачность. --- # File: custom-media --- --- title: "Изображения, видео и иконки" description: "Добавляйте элементы изображения, видео и иконок на экраны в Flow Builder, а также заменяйте медиафайлы во время выполнения с помощью пользовательских медиаидентификаторов." --- Flow Builder включает три типа медиаэлементов в категории **Media**: Image, Video и Icon. :::tip Чтобы изображение или видео занимало весь экран — включая области за вырезом камеры и индикатором домашнего экрана — расположите его как фиксированное с нулевыми отступами со всех сторон и выберите **Ignore safe area**. См. [Макет и позиционирование](manage-paywall-ui-elements#ignore-safe-area). ::: ## Изображение \{#image\} Загрузите файл `.JPG`, `.PNG` или `.GIF` размером до 20 МБ. - **Aspect** — управляет тем, как изображение вписывается в контейнер: - **Fit** — масштабирует изображение так, чтобы оно целиком помещалось в контейнер без обрезки. - **Fill** — растягивает изображение, заполняя контейнер. - **Cover** — масштабирует изображение так, чтобы оно покрывало контейнер, обрезая при необходимости. По умолчанию. - **Use custom media ID** — см. раздел [Custom media ID](#custom-media-id) ниже. ## Видео \{#video\} Загрузите файл `.MP4` или `.WEBM` размером до 50 МБ и продолжительностью не более 30 секунд. Минимальное разрешение видео — 640x640 пикселей. - **Aspect** — Fit, Fill или Cover. По умолчанию — Fill. - **Loop** — непрерывное воспроизведение видео. Включено по умолчанию. - **Use custom media ID** — см. [Кастомный медиа-ID](#custom-media-id) ниже. В превью редактора видео не воспроизводится — на холсте отображается стоп-кадр. На устройстве в рантайме видео по умолчанию воспроизводится без звука. При включённом Loop повторяется бесконечно. ### Запустить действие по окончании видео \{#trigger-an-action-when-the-video-ends\} :::link Основная статья: [Действия](onboarding-actions) ::: Элемент Video поддерживает триггер **On playback finished**, который срабатывает, когда видео заканчивается. Настройте его в панели **Interactions**, чтобы перейти на другой экран, показать CTA или выполнить любое другое действие. ## Иконка \{#icon\} Выбирайте иконки из встроенной библиотеки [Tabler Icons](https://tabler.io/icons) — тысячи иконок в двух стилях: - **Stroke** — только контур. - **Filled** — сплошная заливка. Ищите иконку по ключевому слову в пикере. Цвет иконки задаётся в пикере **Color** — выберите сохранённый [стиль цвета](builder-styling) или укажите произвольный. ## Пользовательский медиа-ID \{#custom-media-id\} :::important Вы также можете задать пользовательский медиа-ID для [фона](paywall-head-picture) в виде изображения или видео. ::: Присвойте элементу изображения или видео пользовательский медиа-ID, чтобы заменять его во время выполнения из кода приложения. Используйте это для [персонализированных визуальных материалов](get-pb-paywalls#customize-assets) — например, для отображения выбранного пользователем аватара. Медиафайл, загруженный в Flow Builder, используется как резервный вариант. Если ваш код не предоставляет медиафайл для этого ID во время выполнения, вместо него отображается резервный вариант. Чтобы включить пользовательский медиа-ID для элемента изображения или видео: 1. Установите флажок **Use custom media ID** под областью загрузки. 2. Введите медиа-ID. 3. Загрузите резервное изображение или видео. В коде приложения получайте медиафайлы по их ID — см. [Настройка ресурсов](get-pb-paywalls#customize-assets) для SDK API. --- # File: paywall-buttons --- --- title: "Кнопки во Flow Builder" description: "Добавляйте и настраивайте кнопки действий во Flow Builder." --- :::info Этот раздел описывает новый Flow Builder, который работает с версией SDK Adapty 4.0 и выше. ::: Кнопки — это интерактивные элементы в Flow Builder, реагирующие на нажатия пользователя. Используйте их для: - CTA-кнопок покупки, привязанных к продуктам и автоматически обрабатывающих транзакции - Навигации — перемещения пользователей между экранами (Далее, Назад, Закрыть, Пропустить) - Служебных ссылок — Восстановить покупки, Условия использования и Политика конфиденциальности :::tip Размещайте CTA-кнопки покупки, ссылки восстановления и юридические ссылки внутри [Footer](builder-containers#footer) — он закреплён в нижней части экрана и перекрывает прокручиваемые элементы под ним. ::: ## Добавление кнопок \{#add-buttons\} Чтобы добавить кнопку: 1. Нажмите **+** и выберите **Button**. 2. Выберите тип кнопки. <img src="/assets/shared/img/button-type.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Кнопки покупок, ссылки и кнопки закрытия поставляются с предварительно настроенными действиями. Для ссылок [задайте URL для перехода](#links). Для кнопок других типов откройте панель **Interactions**. Там в разделе **Button triggers** настройте [действия](onboarding-actions), которые должна выполнять кнопка. <img src="/assets/shared/img/button-action.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Настройте [оформление кнопки](builder-styling) в панели **Design**. ## Типы кнопок \{#button-types\} ### Кнопки покупки \{#purchase-buttons\} :::link Чтобы кнопки покупки работали, привяжите продукты к экранам и добавьте элемент **Products**. Смотрите [гайд](paywall-product-block). ::: Кнопка покупки запускает встроенную покупку для того продукта, который пользователь выбрал на экране. SDK обрабатывает транзакцию автоматически — вам не нужно писать для этого код в приложении. Чтобы добавить кнопку покупки: 1. Нажмите **+** и выберите **Button**, затем выберите пресет кнопки. 2. Выделив кнопку, откройте вкладку **Interactions** на правой панели. 3. Нажмите **Add trigger** > **On tap**, затем нажмите **Add action**. 4. Установите **Action** на **Purchase**, а **Product** на `products.selectedProduct`. Переменная `products.selectedProduct` всегда указывает на текущий выбранный продукт на экране. :::tip Вы можете привлечь больше внимания к кнопкам покупки, добавив анимацию. Paywall Builder в настоящее время поддерживает тип анимации **Pulse**. Настройте стиль анимации в панели **Design**. ::: <img src="/assets/shared/img/purchase-button.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Ссылки \{#links\} :::important Кнопки **Terms of Use** и **Privacy Policy** имеют встроенное действие **Open URL**. Укажите целевой URL именно там. Незаполненные Open URL и [встроенные ссылки](onboarding-text#inline-link) блокируют предпросмотр и публикацию. ::: Чтобы соответствовать требованиям некоторых сторов, вы можете добавить ссылки на: - Условия использования - Политику конфиденциальности - Восстановление покупок Чтобы добавить ссылки: 1. Нажмите **+** и выберите **Button > Links**. На экране появится строка инлайн-кнопок с предустановленными действиями: восстановление покупок или открытие URL. Если часть кнопок не нужна, удалите их на панели слоёв. <img src="/assets/shared/img/add-links.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Теперь настройте действия кнопок: - Кнопка **Restore purchases** уже обрабатывает восстановление покупок. - Для каждой оставшейся ссылки: 1. Нажмите на кнопку, чтобы выбрать её, и перейдите на вкладку **Interactions** справа. 2. Вставьте URL в поле. 3. По умолчанию URL открывается во встроенном браузере для удобства пользователей. Если вы хотите перенаправлять пользователей во внешний браузер, установите флажок **Open in external browser**. <img src="/assets/shared/img/pb-links.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Кнопка закрытия флоу \{#close-flow\} Кнопка **Close** закрывает флоу автоматически. Чтобы добавить кнопку закрытия, нажмите **+** и выберите **Button > Close flow**. <img src="/assets/shared/img/close-flow.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::tip Используйте позиционирование **Absolute**, чтобы разместить кнопку закрытия в углу экрана. ::: Вы также можете настроить любую другую кнопку для закрытия флоу с помощью [действий](onboarding-actions). ### Пользовательские кнопки \{#custom-buttons\} Любую добавленную кнопку можно настроить на выполнение нужного действия при нажатии: - Перейти на следующий экран - Показать предупреждение - Задать [переменную](onboarding-variables) - [Показать или скрыть элементы экрана](onboarding-element-visibility) - Открыть URL - Восстановить покупки - Выполнить условные действия --- # File: builder-tabs --- --- title: "Вкладки" description: "Добавьте навигацию по вкладкам, переключающую панели контента в флоу." --- **Вкладки** делят раздел экрана на переключаемые панели контента — пользователь нажимает на заголовок вкладки, и панель ниже обновляется соответственно. {/* TODO: on-device GIF */} ## Добавление, удаление и выбор вкладок \{#add-remove-and-select-tabs\} Каждая вкладка состоит из двух частей: - **Заголовок вкладки** — кликабельная метка (Tab 1, Tab 2 и т. д.). - **Содержимое вкладки** — один контейнер на каждую вкладку. Всё, что вы помещаете в контейнер, отображается при выборе соответствующей вкладки. Нажмите **Add tab**, чтобы добавить новую вкладку. Для каждой новой вкладки автоматически создаётся соответствующий контейнер содержимого. Чтобы определённая вкладка была активной при первом открытии экрана, включите переключатель **Selected by default**. ## Стилизация вкладок \{#style-the-tabs\} ### Шаблоны \{#templates\} Flow Builder предлагает три готовых шаблона для вкладок: - **Segment control** — переключатель в виде «пилюли» с закруглёнными углами вокруг активной вкладки. - **Button Tabs** — отдельные вкладки в виде кнопок. - **Underline** — текстовые метки с подчёркиванием активной вкладки. ### Состояния вкладок \{#tab-states\} У каждой вкладки есть переключатель состояний (**Default / Selected**), позволяющий отдельно настроить стили активного и неактивного состояния — типографику, цвета, заливку и рамку для каждого из них. ## Выбираемая группа \{#selectable-group\} Вкладки — это **одиночная выбираемая группа**: в каждый момент активна ровно одна вкладка. Управлять группой можно в панели **Screen settings** в разделе [Selectable groups](paywall-layout-and-products#selectable-groups). Группа предоставляет две переменные: - `tabs.selectedOptionId` — ID выбранной вкладки. Используйте в условиях. - `tabs.selectedOptionTitle` — метка выбранной вкладки. Используйте в динамическом тексте. Замените `tabs` на свой **Group ID**, если переименовали группу. Подробнее см. в разделе [Выбираемые элементы и группы](flow-selectable-elements). --- # File: builder-toggles --- --- title: "Переключатели" description: "Добавьте переключатели в платёжные флоу." --- :::warning Apple может отклонить приложение, если в нём используется предварительно включённый переключатель пробного периода. Переключатель, установленный в положение «вкл.» по умолчанию, может быть расценён как манипулятивный тёмный паттерн согласно рекомендациям App Store Review Guidelines — это подразумевает согласие пользователя на пробный период без явного выбора. Чтобы избежать отклонения, установите переключатель в положение **выкл.** по умолчанию и позвольте пользователям самостоятельно подключаться к пробному периоду. ::: Переключатель пробного периода — это бинарный переключатель, позволяющий пользователям выбирать между стандартными продуктами и продуктами с пробным периодом на пейволе. При изменении его состояния может запускаться действие — например, замена групп продуктов, обновление переменных или показ/скрытие элементов — мгновенно. Чтобы добавить переключатель пробного периода, нажмите **+** на целевом экране и выберите **Trial toggle**. Каждый переключатель пробного периода является выбираемым элементом типа **Toggle**. Каждому выбираемому элементу назначается переменная, отражающая его состояние — например, переключатель с именем `trial` получает переменную `trial.is_selected` со значением `True` или `False`. Чтобы другие элементы реагировали на состояние переключателя, задайте условное [действие](onboarding-actions) или [условную видимость](onboarding-element-visibility) на основе этой переменной. --- # File: builder-reviews-and-testimonials --- --- title: "Отзывы и рекомендации" description: "Добавляйте отзывы, рейтинги и социальное доказательство на пейвол." --- Категория элементов **User Engagement** предоставляет четыре шаблона для демонстрации отзывов, рейтингов и социального доказательства на пейволе. Каждый шаблон — это полностью редактируемая композиция: замените текст-заполнитель и примените свои [цветовые стили](builder-styling) и [типографику](onboarding-text), чтобы всё выглядело единообразно с остальным флоу. ## Обзор \{#review\} Карточка с оценкой, цитатой и подписью автора. Используется для демонстрации запоминающейся пользовательской цитаты. ## Рейтинг \{#rating\} Счётчик и строка со звёздами, например «17000+ рейтингов». Используйте, чтобы подчеркнуть количество оценок. ## Рейтинг приложения \{#app-rating\} Отображает итоговую оценку с указанием количества отзывов, например «4.9 / На основе 1000+ отзывов». Используйте, чтобы подчеркнуть высокий общий рейтинг. ## Социальное доказательство \{#social-proof\} Группа аватаров с указанием количества участников — например, «Присоединяйтесь к 50 000+ пользователей». Используйте, чтобы подчеркнуть масштаб сообщества. --- # File: flow-timer --- --- title: "Таймер обратного отсчёта" description: "Добавьте таймер обратного отсчёта на пейвол." --- **Таймер обратного отсчёта** ведёт отсчёт от заданной продолжительности до нуля — когда он достигает нуля, отображение замирает. ## Шаблоны \{#templates\} Категория предлагает четыре визуальных варианта: - **Blocks** — дни, часы, минуты и секунды в отдельных подписанных ячейках. - **Inline Units** — однострочный текст с суффиксами единиц времени. - **Inline** — только цифры. - **Badge** — отображение цифр в виде таблетки. ## Настройки \{#settings\} ### Установите продолжительность \{#set-the-duration\} В разделе **Countdown** правой панели укажите начальную продолжительность в днях, часах, минутах и секундах. ### Настройка поведения \{#configure-the-behavior\} Выпадающий список **Behavior** управляет тем, когда запускается таймер: - **Every appear** — перезапускается каждый раз, когда пользователь открывает экран. Поведение по умолчанию. - **First appear** — запускается при первом открытии экрана в текущей сессии приложения. Продолжает отсчёт, если пользователь возвращается в той же сессии; сбрасывается при новом запуске приложения. - **First appear (persisted)** — запускается при первом открытии экрана и продолжает отсчёт между запусками приложения. ### Запустить действие по окончании таймера \{#trigger-an-action-when-the-timer-ends\} :::link Основная статья: [Действия](onboarding-actions) ::: Добавьте триггер **On timer end**, чтобы запустить действие, когда обратный отсчёт достигает нуля, — например, перейти на другой экран или скрыть значок скидки. --- # File: onboarding-quizzes --- --- title: "Квизы во флоу" description: "Добавляйте интерактивные квизы в флоу Adapty, чтобы собирать предпочтения пользователей и выстраивать персонализированные флоу — без написания кода." --- Квизы позволяют предлагать пользователям готовые варианты ответов. В отличие от полей ввода, у квизов нет текстовых полей — пользователь выбирает из тех вариантов, которые вы задали. Используйте их для сбора предпочтений, сегментации пользователей или разветвления флоу в зависимости от выбранных ответов. ### Добавить викторину \{#add-a-quiz\} 1. Нажмите **+** в верхнем левом углу. 2. Выберите **Quiz**. 3. Выберите тип викторины: - **Icon/image/emoji options:** вертикальный список вариантов для выбора, каждый из которых содержит иконку, изображение или эмодзи рядом с текстовой подписью. - **Icon/image/emoji grid:** сетка вариантов для выбора, каждый из которых содержит иконку, изображение или эмодзи. - **Rating:** шкала, по которой пользователи могут выразить оценку — числовую или в виде звёзд. ### Настройка условной навигации \{#set-up-conditional-navigation\} Чтобы направлять пользователей по разным маршрутам в зависимости от их выбора, задайте условное действие на **кнопке навигации**, а не на варианте ответа: 1. Выберите кнопку навигации. 2. На панели **Interactions** добавьте триггер **On Tap** с действием **Conditional**. 3. В диалоге **Edit Action** настройте строку **if**: - Слева нажмите `{}` и выберите **Elements → Screen → `<quizElementId>.selectedOptionId`**, чтобы сослаться на выбор пользователя. - Оставьте оператор `=`. - Справа введите elementId для сравнения — например, `rock`. 4. В блоке **then** задайте действие **Navigate to** и выберите целевой экран. 5. В блоке **else** задайте резервный пункт назначения **Navigate to** или нажмите **+ Add else/if**, чтобы добавить дополнительные условия для других вариантов. :::link Ознакомьтесь с соответствующими гайдами, чтобы узнать, как использовать ответы на вопросы викторины: - [Условная навигация](onboarding-navigation-branching) - [Переменные](onboarding-variables) - [Действия](onboarding-actions) ::: ### Изменение типа квиза \{#change-quiz-type\} По умолчанию квиз работает в режиме **multi choice** — пользователи могут выбрать несколько вариантов одновременно. Переключите на **single choice**, если нужно, чтобы выбирался только один вариант. 1. Выберите экран с квизом. 2. В **Screen settings** прокрутите до **Selectable groups** и нажмите на свой квиз. 3. В диалоге **Edit group** откройте **Group type** и выберите: - **Single choice** — можно выбрать только один вариант. - **Multi choice** — можно выбрать несколько вариантов. 4. Нажмите **Save**. --- # File: builder-inputs-and-forms --- --- title: "Инпуты и формы во Flow Builder" description: "Добавляйте интерактивные элементы форм: текстовые поля и чекбоксы." --- Используйте инпуты для сбора данных от пользователей — например, имени, адреса электронной почты или даты рождения. Сохраняйте ответы и обращайтесь к ним в других частях флоу, например, чтобы обращаться к пользователю по имени на следующем экране. ## Добавление поля ввода \{#add-an-input\} 1. Нажмите **+** в верхнем левом углу. 2. Выберите **Input**. 3. Выберите тип поля ввода: - **Text:** Любой короткий текст. - **Email:** Адрес электронной почты с опциональной проверкой формата. - **Password:** Защищённый ввод текста с настраиваемыми требованиями. - **Number:** Числовые значения с настраиваемым форматом. - **Phone number:** Номера телефонов. - **Date:** Открывает выбор даты. - **Time:** Открывает выбор времени. - **Date and time:** Открывает комбинированный выбор. ## Настройка поля ввода \{#configure-an-input\} :::link Подробнее о визуальных настройках — макете, стиле и видимости — читайте в разделе [Стили и оформление](builder-styling). ::: Для всех типов полей ввода в вкладке **Design** доступны следующие настройки: - **Type:** Измените тип ввода (Text, Email, Password, Number, Phone number, Date, Time или Date and time). - **Element ID:** Идентификатор, который используется для обращения к значению поля в других частях флоу. См. [Использование значений полей ввода](#use-input-values) ниже. - **Placeholder:** Текст-подсказка, отображаемый внутри пустого поля. - **State:** Определите, как поле выглядит в разных ситуациях. Переключайтесь между **Default**, **Active**, **Invalid** и **Disabled** и применяйте разные стили к каждому состоянию. - **Typography:** Стиль текста для значения, отображаемого в поле. - **Leading and trailing icons:** Добавьте иконки внутри поля ввода. Некоторые настройки доступны только для определённых типов полей ввода: | Настройка | Типы полей ввода | |----------------------------------|-------------------------------| | Clear button | Text, Email | | Validate email format | Email | | Show password icon | Password | | Edit password requirements | Password | | Number format | Number | | Date/time format | Date, Time, Date and time | | Min and max date | Date, Date and time | ## Использование значений полей ввода \{#use-input-values\} Каждое поле ввода автоматически становится переменной — никаких дополнительных настроек или действия **On Submit** не требуется. На значение ссылаются через **Element ID** поля, который задаётся в **Input Settings**. Чтобы использовать значение поля ввода в другом месте флоу (например, для персонализации текста, заполнения другого поля или условной навигации), вставьте переменную и выберите: **Element > Screen > `<elementId>.value`** :::link Ознакомьтесь с соответствующими гайдами, чтобы понять, как использовать сохранённые входные значения: - [Условная навигация](onboarding-navigation-branching) - [Переменные](onboarding-variables) ::: ## Валидация ввода \{#input-validation\} Поведение валидации зависит от типа поля. Каждое поле предоставляет переменную Boolean только для чтения, `<elementId>.isValid`, которая отражает, соответствует ли введённое значение правилам валидации этого поля. Используйте её в условных действиях или условной видимости — например, чтобы скрывать кнопку «Далее», пока формат email не будет корректным. :::note - Переменная `isValid` доступна только для чтения — задать её значение нельзя. - Пустое поле всегда считается валидным. - Для текстовых полей правила валидации отсутствуют. `textInput.isValid` всегда возвращает `True`. ::: | Тип ввода | Поведение валидации | |---|---| | Text | Встроенных правил валидации нет. | | Email | Опционально. Включите **Validate email format** в панели **Design**, чтобы проверять введённое значение на соответствие формату электронной почты. | | Phone number | Встроенная проверка формата номера телефона. Не настраивается в Builder — правило применяется во время выполнения. | | Password | Настраивается. См. раздел [Требования к паролю](#password-requirements) ниже. | | Number | На основе формата. Введённое значение должно соответствовать выбранному числовому формату. См. раздел [Числовой формат](#number-format) ниже. | | Date, Time, Date and time | Встроенная. Пикер принимает только корректные значения даты или времени. | [Визуальное состояние](builder-styling#input-states) **Invalid** активируется, когда пользователь отправляет форму — например, нажимая Enter или Done на клавиатуре. До этого момента поле ввода отображается в состоянии **Active** или **Default**. ### Требования к паролю \{#password-requirements\} Поля для ввода пароля поддерживают настраиваемые правила валидации. Нажмите **Edit password requirements** на панели **Design**, чтобы открыть редактор правил. Активные правила отображаются в виде живого списка под полем ввода — каждый пункт отмечается галочкой, как только соответствующее условие выполнено. Доступные правила: - **Min length** — минимальное количество символов. По умолчанию: 8. - **Max length** — максимальное количество символов. По умолчанию: 32. - **Uppercase letter** — хотя бы одна заглавная буква A–Z. - **Lowercase letter** — хотя бы одна строчная буква a–z. - **Number** — хотя бы одна цифра. - **Special character** — хотя бы один неалфавитно-цифровой символ (например, `!@#$%`). Пароль считается действительным только при соблюдении всех включённых правил. ### Формат числа \{#number-format\} Выпадающий список **Format** в настройках поля **Number** определяет, как интерпретируется введённое значение: - **Integer** — только целые числа (например, `4`). - **Decimal (Point)** — десятичные дроби с точкой в качестве разделителя (например, `4.89`). - **Decimal (Comma)** — десятичные дроби с запятой в качестве разделителя (например, `4,89`). Значения, не соответствующие выбранному формату, считаются недопустимыми. ## Запуск действий по событиям ввода \{#trigger-actions-on-input-events\} :::link Основная статья: [Действия](onboarding-actions) ::: Вы можете запускать действия в ответ на действия пользователя через панель **Interactions**: - **On changed** — срабатывает, когда пользователь изменяет значение поля ввода. Доступно для всех типов полей. - **On submit** — срабатывает, когда пользователь отправляет текстовый ввод, нажав Enter или Done на клавиатуре. Для выборщиков даты и времени этот триггер недоступен. --- # File: onboarding-navigation-branching --- --- title: "Навигация и ветвление" description: "Проведите пользователей через экраны флоу с помощью статических маршрутов и динамического ветвления." --- Навигация и ветвление позволяют провести пользователей через каждый шаг флоу: используйте статические маршруты для перехода всех пользователей к основным экранам и динамическую навигацию для адаптации флоу на основе их выборов. :::link Навигация — это тип действия. Подробнее о действиях читайте в разделе [Действия](onboarding-actions). ::: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/OLl-WziDMhU?si=_eUtsmbEuFAaLj1r" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ## Навигация между экранами \{#navigate-between-screens\} Вы можете настроить статическую и динамическую навигацию с помощью различных элементов флоу. ### Статическая навигация \{#static-navigation\} Статическая навигация направляет всех пользователей на один и тот же целевой экран. Чтобы её настроить: 1. Выберите любой элемент, на который пользователи могут нажать: кнопку, вариант ответа в квизе или переключатель. 2. Откройте панель **Interactions** справа. Нажмите **Add trigger**. Чтобы перенаправить пользователей сразу при нажатии на вариант квиза — без дополнительного нажатия кнопки — выберите здесь элемент варианта квиза вместо кнопки. 3. Настройте триггер **On tap**: - **Action**: выберите **Navigate to screen**. - **Destination**: выберите экран назначения. ### Динамическая навигация \{#dynamic-navigation\} Динамическая навигация направляет пользователей в зависимости от их ответов в квизе, состояния переключателей и кастомных атрибутов. Любой [выбираемый элемент](flow-selectable-elements) может быть условием для динамической навигации. Чтобы её настроить: 1. Выберите элемент, который будет осуществлять навигацию. 2. Откройте панель **Interactions** справа. Нажмите **Add trigger**. Чтобы перенаправить пользователей сразу при нажатии на вариант квиза — без дополнительного нажатия кнопки — выберите здесь элемент варианта квиза вместо кнопки. 3. Настройте триггер **On tap**: - **Action**: выберите **Conditional**. - **Conditions**: задайте условия навигации. Подробнее [здесь](onboarding-actions#conditional-actions). ## Закрытие флоу \{#close-flow\} Если пользовательский сценарий предполагает закрытие флоу, вы можете настроить это с помощью кнопок или квизов с одиночным ответом: 1. Добавьте и выберите элемент, который должен закрывать флоу при нажатии. 2. Откройте панель **Interactions** справа. Нажмите **Add trigger**. 3. Настройте триггер **On tap**: - **Action**: выберите **Close flow**. --- # File: onboarding-actions --- --- title: "Действия" description: "Определяйте действия, запускаемые взаимодействиями пользователя в конструкторе." --- Панель **Interactions** позволяет задавать, как элементы флоу реагируют на события — нажатия, появление элементов, отправку форм. Для каждого события вы назначаете одно или несколько действий: переход между экранами, отображение или скрытие элементов, открытие URL, установка переменных и другое. Используйте условия, чтобы настроить флоу под данные конкретного пользователя. Каждое взаимодействие строится по трёхзвенной цепочке: 1. **Element**: Компонент экрана, с которого начинается взаимодействие — кнопка, вариант ответа в квизе, поле ввода или любой другой элемент. 2. **Trigger**: Событие, запускающее логику, — нажатие, появление элемента или отправка формы. 3. **Action**: Задача, которую флоу выполняет в ответ. Один триггер может последовательно запускать несколько действий. ## Настройка взаимодействий \{#set-up-interactions\} Чтобы настроить взаимодействие: 1. Выберите элемент на экране или в панели **Layers**. 2. Справа переключитесь на панель **Interactions** и нажмите **Add trigger**. 3. В разделе **Button triggers** выберите [тип триггера](#trigger-types). 4. Нажмите **Add action**, кликните на название действия и выберите [тип действия](#action-types) из выпадающего списка в окне **Edit action**. 5. Настройте свойства действия в зависимости от выбранного [типа действия](#action-types). 6. При необходимости нажмите **Add action**, чтобы добавить ещё действия для того же триггера. ## Типы триггеров \{#trigger-types\} Триггеры срабатывают в ответ на действия пользователя, изменения состояния элементов или загрузку экрана. **On screen appear** является универсальным; остальные привязаны к конкретным элементам. | Триггер | Срабатывает когда... | Поддерживается на | |---|---|---| | **On screen appear** | Экран загружается | Все элементы | | **On tap** | Пользователь нажимает на элемент | [Кнопки](paywall-buttons), [варианты квиза](onboarding-quizzes), [переключатели](builder-toggles), [таймеры обратного отсчёта](flow-timer), [видео](custom-media) | | **On changed** | Пользователь изменяет значение поля (вводит текст, выбирает дату или время) | Все [поля ввода](builder-inputs-and-forms) | | **On submit** | Пользователь отправляет текстовый ввод, нажав Enter или Done на клавиатуре | [Текстовые поля ввода](builder-inputs-and-forms) | | **On timer end** | Элемент [Countdown](flow-timer) достигает нуля | [Countdown](flow-timer) | | **On playback finished** | [Видео](custom-media) воспроизводится до конца | [Video](custom-media) | Для элементов без встроенных взаимодействий (например, [Loader](builder-loaders-and-progress-bars)), **On screen appear** — единственный доступный триггер. ## Типы действий \{#action-types\} :::important **Любое навигационное действие**, которое переводит пользователя на другой экран, всегда должно быть последним в списке действий. Действия, которые стоят после него (например, «Set Variable»), могут не выполниться, поскольку приложение уже сменило экран. ::: ### Перейти на экран \{#navigate-to-screen\} Это основное действие для перемещения пользователей между экранами. Оно переводит пользователя на указанный экран назначения. Для этого действия нужно только задать экран назначения. Если вы хотите включить динамическую навигацию, см. [Навигация и ветвление](onboarding-navigation-branching) или раздел [Условные действия](#conditional-actions). ### Переход к следующему экрану \{#navigate-next\} Переводит пользователя на следующий экран в порядке экранов флоу. Используйте это для линейных флоу, где порядок экранов в редакторе совпадает с тем порядком, в котором вы хотите показывать их пользователям. ### Возврат назад \{#navigate-back\} Возвращает пользователя на предыдущий экран в истории навигации, а не на предыдущий экран в последовательности. ### Открыть URL \{#open-url\} :::tip Используйте [встроенные ссылки](onboarding-text#inline-link) для добавления ссылок в текст. ::: Открывает указанный веб-адрес. Используйте это действие, чтобы направить пользователей на веб-страницы, статьи или профили в социальных сетях за пределами нативных экранов приложения. Для этого действия можно настроить два параметра: - **URL address**: укажите URL-адрес. Кроме того, его можно сделать динамическим — например, чтобы направлять пользователей на разные страницы в зависимости от их ответа в квизе или введённых данных. Для этого нажмите Variable icon и выберите нужную переменную. - **Open in external browser**: задайте, где открывать внешние ссылки. По умолчанию они открываются во встроенном браузере, чтобы пользователь оставался в приложении. Установите флажок **Open in external browser**, если хотите открывать ссылки во внешнем браузере. ### Закрыть флоу \{#close-flow\} Закрывает текущий флоу. ### Показ/скрытие элементов \{#showhide-elements\} Показывает или скрывает определённый элемент на экране. Это действие переопределяет начальное состояние, заданное в **Visibility** в панели **Design**. Если в **Visibility** выбрано **Hide**, действие **Show** сделает элемент видимым. :::important Действие **Show** или **Hide** без указанного целевого элемента [блокирует предпросмотр и публикацию](builder-save-publish#troubleshooting). Выберите цель или удалите действие. ::: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/3w3YSOmI3tQ?si=vPhoQGt44SI285Ru" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> ### Показать уведомление \{#show-alert\} Отображает нативное системное всплывающее окно. Чтобы продолжить, пользователь должен нажать **Ok**. Для уведомлений необходимо задать **Title** и **Message**. В обоих полях можно использовать переменные, чтобы сделать содержимое динамическим. Для этого нажмите Variable icon и выберите нужную переменную. :::important Действие **Show alert** с пустой или незаполненной конфигурацией [блокирует предпросмотр и публикацию](builder-save-publish#troubleshooting). Заполните оба поля или удалите это действие. ::: ### Задать переменную \{#set-variable\} Обновляет значение переменной во флоу. Перед добавлением этого действия создайте переменные на панели **Variables** слева (см. [Переменные](onboarding-variables)). Нажмите **Add variable** и задайте столько переменных и значений, сколько нужно. :::important Действие **Set variable** без назначения [блокирует предпросмотр и публикацию](builder-save-publish#troubleshooting). Настройте хотя бы одно назначение или удалите это действие. ::: ### Покупка \{#purchase\} Запускает флоу покупки прямо из кнопки или элемента взаимодействия в вашем онбординге. Используйте это, чтобы пользователи могли оформить подписку или купить продукт, не покидая флоу. Для этого действия можно настроить два варианта поведения: - **In-app store**: Инициирует нативную покупку. Установите **Product** на конкретный продукт или на `products.selectedProduct`, чтобы использовать текущий выбор пользователя на экране. - **Web payment**: Отправляет пользователя на [веб-пейвол](web-paywall) вместо инициирования нативной покупки. Используйте этот вариант, когда хотите обрабатывать транзакцию за пределами приложения, например для веб-предложений подписок. :::important Действие **Purchase** без целевого **Product** или **Web Paywall URL** [блокирует предпросмотр и публикацию](builder-save-publish#troubleshooting). Назначьте цель или удалите это действие. ::: ### Восстановление покупок \{#restore-purchases\} Запускает процесс восстановления покупок на устройстве. Пользователи нажимают на это, когда ранее оформили подписку на другом устройстве или после переустановки приложения и хотят вернуть доступ к своим правам. Настраивать здесь нечего — Adapty обрабатывает восстановление через нативный механизм стора. Действие **Restore purchases** также предварительно настроено на ссылке **Restore** в пресете кнопки **Links** (см. [Настройка покупок](paywall-product-block#restore-purchases)). ## Пользовательские действия \{#custom-actions\} Пользовательское действие передаёт именованный **Action ID**, который обрабатывает ваш код. Используйте его, когда встроенные типы действий не покрывают нужную функциональность. Adapty отправляет триггер — ваше приложение реализует поведение: 1. В билдере вы назначаете **Action ID** взаимодействию элемента. 2. Когда пользователь вызывает это взаимодействие, флоу передаёт ID в ваше приложение. 3. Приложение сопоставляет ID и выполняет ваш код. ### Настройте пользовательское действие \{#set-up-a-custom-action\} 1. В окне **Edit action** укажите **Action ID** — строку, по которой ваше приложение распознает действие (например, `show_discount`). 2. В коде приложения реализуйте обработчик для этого Action ID. Подробности и примеры кода — в разделе [Обработка действий пейвола](handle-paywall-actions). :::important Действие **Custom** без **Action ID** [блокирует предпросмотр и публикацию](builder-save-publish#troubleshooting). Укажите Action ID или удалите действие. ::: ### Что можно делать с кастомными действиями \{#what-you-can-do-with-custom-actions\} Само по себе кастомное действие ничего не делает. Вы задаёте статический Action ID в конструкторе, а код приложения определяет, что происходит при его получении. Все примеры ниже работают по одной схеме: назначьте ID во флоу, затем обработайте его в коде. - **Запустить внутреннее событие**: Отправьте ID, например `viewed_special_offer`, а затем залогируйте событие в аналитике при его получении. - **Запросить системное разрешение**: Отправьте ID, например `request_location`, а затем вызовите системный диалог разрешения из приложения. Если разрешение нельзя выдать через диалог — откройте системные настройки телефона. Adapty не показывает диалог — это делает ваше приложение. - **Запустить нативную аутентификацию**: Отправьте ID, например `login_google`, а затем откройте свой экран входа. Флоу не может авторизовать пользователя самостоятельно. - **Применить бизнес-логику**: Отправьте ID, например `apply_discount`, а затем разблокируйте контент или измените состояние приложения на своей стороне. - **Передать ответ на вопрос викторины в приложение**: Назначьте разный Action ID каждому варианту ответа (например, `goal_weight_loss` и `goal_muscle`), а затем читайте ID в коде. Используйте его, чтобы задать [пользовательский атрибут](setting-user-attributes#custom-user-attributes), по которому впоследствии можно строить сегменты. Поскольку действие передаёт только фиксированный ID, это единственный способ сообщить о выборе — флоу не может отправить выбранное значение. :::important Пользовательское действие срабатывает в момент выбора ответа. Если пользователь меняет ответ, флоу также отправляет новый Action ID. Ваше приложение получает оба сигнала по порядку — например, `goal_weight_loss`, затем `goal_muscle`. Сделайте обработчик идемпотентным, чтобы последний сигнал был приоритетным. ::: ### Что не могут делать пользовательские действия \{#what-custom-actions-cant-do\} Пользовательские действия статичны. Action ID фиксируется на этапе создания флоу — он не может читать [переменные](onboarding-variables) или [пользовательский ввод](builder-inputs-and-forms). Когда действие срабатывает, приложение получает только этот ID, но не email, номер телефона или другие данные, которые ввёл пользователь. Поля ввода остаются внутри флоу в виде переменных для ветвления и персонализации. Чтобы использовать эти значения в приложении, собирайте их через собственный интерфейс или API. Пользовательские действия также работают в одностороннем порядке. Приложение не может вернуть результат во флоу, и флоу не ждёт завершения вашего кода. Если за пользовательским действием следует действие **Navigate next**, пользователь переходит на следующий экран даже если ваш код завершился ошибкой — например, когда пользователь закрыл экран входа, не авторизовавшись. В сочетании со статическим Action ID это исключает возможность валидации пользовательского ввода в приложении — например, проверки SMS-кода и разветвления флоу в зависимости от результата. Если дальнейший сценарий флоу зависит от того, что сделал ваш код, [разделите флоу между двумя плейсментами](#continue-the-flow-based-on-the-result). ### Продолжение флоу в зависимости от результата \{#continue-the-flow-based-on-the-result\} Если часть экранов должна появляться только после успешного выполнения кастомного действия — например, экраны после входа в аккаунт — разбейте флоу на два [плейсмента](placements) и дайте приложению решать, когда показывать вторую часть: 1. В первом плейсменте создайте флоу, который заканчивается кастомным действием (например, `login`). 2. В приложении обработайте Action ID: покажите экран входа и проверьте, вошёл ли пользователь. 3. Если пользователь вошёл, покажите флоу из второго плейсмента с продолжением экранов. Таким образом, ваше приложение управляет переходом на основе фактического результата, а не флоу, переходящего вперёд независимо от исхода. ## Условные действия \{#conditional-actions\} <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/xmWSEPxnI0s?si=mazHQHE89qEDxvPA" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> Используйте условные действия, чтобы разветвлять флоу по разным путям в зависимости от данных пользователя. Вот несколько распространённых сценариев: - На экране есть квиз, и вы хотите направлять пользователей на разные экраны в зависимости от их ответов. В этом случае добавьте условное действие к кнопке. - Вы хотите предлагать разные продукты и офферы разным группам пользователей. Разместите их на разных экранах и настройте условия для кнопки навигации. - Вы хотите пропускать определённые шаги для пользователей, которые уже прошли туториал в предыдущей сессии. Условные действия работают как цепочка if / else-if / else. Приложение читает правила сверху вниз и останавливается на первом совпадении: 1. **IF**: Флоу проверяет основное условие. - Условие истинно? Флоу сразу выполняет действия из блока THEN и останавливается. - Условие ложно? Флоу переходит к следующему разделу. 2. **ELSE IF**: Здесь можно добавить дополнительные проверки (например, «Если не Premium, то является ли пользователь Trial?»). 3. **ELSE** (Фолбэк): Если ни одно из правил выше не сработало, флоу выполняет действия из этого финального раздела. :::important - Если правило добавлено, но ему не назначено действие, совпадение с условием ни к чему не приводит. - Незавершённое правило (без оператора или значения) [блокирует предпросмотр и публикацию](builder-save-publish#troubleshooting). ::: Для каждого правила выберите переменную для проверки и действие, которое нужно выполнить. Одному правилу можно назначить несколько действий. :::important Флоу выполняет только одно правило — первое, с которым совпало условие. Если нужно выполнить и **IF**, и **ELSE IF** одновременно, добавьте оба действия в **IF**. ::: Чтобы узнать, как сделать элементы выбираемыми и организовать их в группы для использования в условиях, см. [Выбираемые элементы и группы](flow-selectable-elements). ## Устранение неполадок \{#troubleshooting\} Любое действие с незаполненными обязательными полями блокирует предпросмотр и публикацию. Полный список см. в разделе [Сохранение и публикация флоу](builder-save-publish#troubleshooting). --- # File: builder-loaders-and-progress-bars --- --- title: "Индикаторы прогресса и загрузки" description: "Отображение прогресса по шагам и состояния занятости в флоу." --- Категория **Progress** предоставляет два типа элементов: один для пошагового прогресса по многоэкранному флоу, другой — для индикации состояния загрузки на месте. ## Индикаторы прогресса \{#progress-indicators\} ### Стили индикатора \{#indicator-styles\} Элемент **Progress** показывает, на каком экране многошагового флоу находится пользователь. Категория предоставляет три визуальных варианта: - **Linear** — Одна полоса, которая заполняется по мере продвижения пользователя. - **Segmented** — Отдельные полосы для каждого шага, заполняющиеся одна за другой. - **Connectors** — Подписанные круги, соединённые линиями (например, Шаг 1, Шаг 2, Шаг 3 по порядку). ### Сопоставление шагов с экранами \{#match-steps-to-screens\} По умолчанию индикатор прогресса отслеживает каждый экран во флоу. Чтобы ограничить его подмножеством, выберите нужные экраны в выпадающем списке **Screens**. Либо откройте экран, который хотите исключить, и снимите флажок **Include screen in progress indicator**. Отключите тогл **One segment per screen**, если вам нужен более точный контроль над количеством шагов. :::warning Позиция шага следует за списком экранов, а не за реальным порядком, в котором их видит пользователь. В нелинейных флоу отображаемый шаг индикатора может перескакивать вперёд или возвращаться назад. ::: ### Состояния шагов \{#step-states\} У каждого шага три состояния — **Completed**, **Current** и **Upcoming**. Выберите шаг внутри индикатора прогресса, чтобы отредактировать стили состояния в правой панели. Используйте **Apply changes to all states**, чтобы применить изменения ко всем трём состояниям. Редактирование одного шага затрагивает все шаги в одном индикаторе. ### Макет и позиционирование \{#layout-and-positioning\} Индикатор прогресса — это глобальный элемент, поэтому его нельзя разместить внутри [контейнера](builder-containers). Также нельзя задать его позицию вручную — по умолчанию используется абсолютное позиционирование. Когда пользователь прокручивает экран, индикатор остаётся на месте, а контент прокручивается под ним. Чтобы управлять отступами вокруг индикатора, используйте элементы управления **Margin** и **Padding** в разделе **Spacing**, а не пытайтесь перемещать сам элемент. Если макет выглядит неправильно, скорректируйте отступы как у индикатора, так и у соседнего элемента. ## Загрузчики \{#loaders\} **Загрузчик** — это анимированный элемент, который показывает, что что-то происходит: например, обрабатываются ответы пользователя на вопросы квиза для формирования персонального плана. В категории доступны три шаблона: - **Spinner** — Круговой спиннер. - **Spinner with label** — Круговой спиннер с подписью (например, «Загрузка...»). - **Loader** — Горизонтальная полоса, которая заполняется по мере выполнения задачи. {/* - **Loader with label** — Горизонтальная полоса с подписью и процентами (например, «Анализ... 47%»). */} :::warning Лоадер требует **триггера** для появления и скрытия. Откройте вкладку **Interactions**, чтобы настроить эту логику — например, показывать его после того, как пользователь прошёл квиз. ::: --- # File: onboarding-variables --- --- title: "Переменные" description: "Используйте переменные для отображения динамических данных в ваших флоу." --- Переменные позволяют отображать динамический контент во флоу — цены на продукты, детали предложений и другие данные, которые меняются в зависимости от контекста конкретного пользователя. Используйте их для управления видимостью элементов и персонализации содержимого экранов. Чтобы открыть панель переменных, нажмите значок **{ }** на левой панели. Панель содержит три вкладки: - **[Пользовательские](#custom-variables)**: Переменные, которые вы создаёте и настраиваете самостоятельно. - **[Продуктовые](#product-variables)**: Встроенные переменные, подтягивающие локализованные данные о продукте и офере из стора. - **[Элементные](#element-variables)**: Переменные, привязанные к состояниям элементов на холсте. ## Пользовательские переменные \{#custom-variables\} ### Создание кастомной переменной \{#create-a-custom-variable\} 1. На панели переменных нажмите **+**. 2. Введите имя переменной. 3. Выберите тип: String, Number или Boolean. 4. Задайте начальное значение — это значение, которое переменная принимает при запуске флоу. 5. Нажмите **Create variable**. :::tip Используйте точки в именах, чтобы группировать связанные переменные — например, `user.score` или `user.goal`. ::: ### Обновление переменной через взаимодействие \{#update-a-variable-via-an-interaction\} :::link Подробнее — в статье [Действия](onboarding-actions). ::: Вы можете обновить значение переменной в рантайме, добавив действие **Set up variables** к любому элементу. 1. Выберите элемент на канвасе. 2. На вкладке **Interactions** нажмите **Add trigger**. 3. Выберите **On tap** и нажмите **Add action**. В выпадающем списке **Action type** выберите **Set up variables**. 4. Нажмите **Add variable**. Выберите переменную и задайте новое значение. :::tip Например, можно присвоить разные значения переменной `user.goal` в зависимости от ответа пользователя в квизе, а затем использовать эту переменную для перехода на нужный экран. ::: ## Переменные продукта \{#product-variables\} Переменные продукта берут локализованные данные напрямую из сторов. Используйте их в текстовых полях для отображения локализованных цен, названий и деталей офферов, а также в условиях для показа или скрытия контента в зависимости от доступности оффера. | Переменная | Описание | Пример | | :--- | :--- | :--- | | `prod_title` | Локализованное название продукта | Premium Subscription | | `prod_price` | Локализованная цена за один расчётный период | $9.99 | | `prod_price_per_day` | Цена подписки, делённая на количество дней в расчётном периоде. Пустая для разовых покупок. | $0.33 | | `prod_price_per_week` | Цена подписки, делённая на количество недель в расчётном периоде. Пустая для разовых покупок. | $2.33 | | `prod_price_per_month` | Цена подписки, приведённая к одному месяцу. Пустая для разовых покупок. | $9.99 | | `prod_price_per_year` | Цена подписки, приведённая к одному году. Пустая для разовых покупок. | $119.88 | | `offer_price` | Локализованная цена introductory offer или promotional offer. Пустая, если пользователь не имеет права на оффер. | $0.99 | | `offer_billing_period` | Локализованный расчётный период оффера. Совпадает с `offer_full_duration` для триальных и pay-upfront офферов. Пустая, если пользователь не имеет права на оффер. | 1 week | | `offer_full_duration` | Локализованная полная длительность оффера. Пустая, если пользователь не имеет права на оффер. | 1 month | | `is_free_trial` | Возвращает `true`, если пользователь имеет право на оффер с бесплатным пробным периодом. | true | | `is_pay_up_front` | Возвращает `true`, если пользователь имеет право на pay-up-front оффер. | true | | `is_pay_as_you_go` | Возвращает `true`, если пользователь имеет право на pay-as-you-go оффер. | true | :::tip Используйте `is_free_trial`, `is_pay_up_front` и `is_pay_as_you_go` с условной видимостью, чтобы показывать или скрывать элементы в зависимости от того, какой оффер доступен пользователю. Например, показывайте таймлайн бесплатного пробного периода только когда `is_free_trial` равно `true`. ::: Значения переменных оффера зависят от типа оффера, на который имеет право пользователь. Для примера рассмотрим недельную подписку «Premium Subscription» за $5 с тремя возможными офферами: - **Pay As You Go**: Первые 3 недели за $3 (списывается еженедельно), затем $5/неделю. - **Pay Up Front**: Первые 3 недели за $8 (списывается сразу), затем $5/неделю. - **Free Trial**: Первая неделя бесплатно, затем $5/неделю. В этом примере `prod_title` возвращает «Premium Subscription», а `prod_price` возвращает $5. Значения переменных оффера зависят от того, на какой оффер имеет право пользователь: | Переменная | Pay As You Go | Pay Upfront | Free Trial | | :--- | :--- | :--- | :--- | | `offer_price` | $3 | $8 | $0 | | `offer_billing_period` | 1 неделя | 3 недели | 1 неделя | | `offer_full_duration` | 3 недели | 3 недели | 1 неделя | Для предложений Pay Upfront и Free Trial `offer_billing_period` и `offer_full_duration` возвращают одинаковое значение. Для Pay As You Go они различаются, поскольку расчётный период составляет одну неделю, а полная продолжительность — три недели. :::note Подробнее о предложениях и их настройке см. в разделе [Предложения](offers). ::: ## Переменные элементов \{#element-variables\} Переменные элементов фиксируют выбор пользователя — что он выбрал в квизах, на какой вкладке находится и включён ли переключатель триала. Тип переменной зависит от группы: - **Одиночный выбор**: Квизы с одиночным выбором и вкладки: - `selected_id`: ID элемента для использования в условиях - `selected_title`: заголовок элемента для использования в динамическом тексте - **Множественный выбор**: Квизы с множественным выбором: - `selected_ids`: ID элементов для использования в условиях - `selected_titles`: заголовки элементов для использования в динамическом тексте - **Переключатель**: Переключатель триала: - `is_selected`: булево значение Распространённые сценарии использования: - Отображение разного контента в зависимости от того, включён ли переключатель пробного периода. - [Переход пользователей на разные экраны](onboarding-navigation-branching) в зависимости от их ответов в квизе ## Использование переменных в тексте \{#use-variables-in-text\} Чтобы вставить переменную в текстовый элемент: 1. Выберите текстовый элемент на холсте. 2. На вкладке **Design** найдите поле **Content** и введите текст. 3. Нажмите на иконку **{ }** в поле. 4. Выберите переменную из списка. :::tip Переменные можно также использовать в других элементах: - Используйте переменные в ссылках и алертах, чтобы сделать их динамическими - Создавайте динамические условия на основе переменных. Например, условие может выглядеть так: `if experience.current > experience.target, navigate to...` ::: ### Переменные стилей \{#style-variables\} Применить форматирование к отдельной переменной нельзя. Если выделить переменную в поле **Content** и попытаться сделать её жирной, курсивной, подчёркнутой, зачёркнутой или изменить её цвет — ничего не произойдёт. Форматирование применяется только ко всему текстовому блоку целиком. Чтобы стилизовать текст, используйте раздел **Typography** на вкладке **Design** или выберите сохранённый [стиль текста](onboarding-text#set-up-text-styles). ### Повторное использование контента на разных экранах \{#reuse-content-across-screens\} Некоторый контент повторяется на нескольких экранах флоу — например, подпись кнопки «Продолжить», призыв к действию или дисклеймер, который показывается на нескольких экранах. То же касается более длинных текстов — например, описания функции, которое используется на нескольких экранах. Вместо того чтобы вводить этот контент в каждый элемент вручную, сохраните его в кастомной переменной. Это удобно, когда вы направляете разных пользователей на разные экраны, но хотите, чтобы формулировки везде были одинаковыми. 1. [Создайте пользовательскую переменную](#create-a-custom-variable) типа String и задайте её начальное значение как текст, который хотите переиспользовать. Например, назовите её `button.navigation` и установите значение `Continue`. 2. Вставьте эту переменную в поле **Content** каждого элемента, где должен появляться этот текст. Чтобы изменить текст везде сразу, достаточно один раз обновить начальное значение переменной. Все элементы, использующие её, обновятся автоматически — редактировать каждый экран вручную не нужно. --- # File: onboarding-element-visibility --- --- title: "Условная видимость" description: "Показывайте или скрывайте элементы в зависимости от условий." --- <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/3w3YSOmI3tQ?si=vPhoQGt44SI285Ru" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> Вы можете управлять отображением элемента, добавив к нему условие. Элемент с условием виден только тем пользователям, которые соответствуют заданным критериям. :::important Если вы показываете или скрываете элемент с помощью действия **Show** или **Hide** ([action](onboarding-actions)), это действие переопределяет условие **Visibility**, заданное для данного элемента. Используйте условия **Visibility** для элементов, которые должны всегда отображаться или скрываться на основе фиксированного критерия. Используйте действия, когда видимость должна меняться в зависимости от взаимодействия пользователя — например, для отображения кнопки после того, как пользователь ответил на вопрос викторины. ::: Чтобы добавить условие к элементу: 1. Выберите элемент на канвасе или в панели слоёв. 2. В разделе **Visibility** правой панели выберите **Conditional**. 3. Настройте условие, выбрав тип свойства на одной из трёх вкладок: - **Custom**: переменные, которые вы создаёте и которыми управляете; их значения можно обновлять через действия пользователя. Подробнее — в разделе [Variables](onboarding-variables). - **Products**: свойства продуктов в вашем флоу, например цена или название. - **Elements**: состояния других элементов флоу, например активен ли переключатель триала. 4. Введите **Value** для сравнения. 5. При необходимости нажмите на оператор, чтобы изменить его. 6. (Опционально) Нажмите **Add condition**, чтобы добавить ещё условия. С помощью селектора укажите, должны ли выполняться все условия сразу или достаточно любого одного. --- # File: paywall-dark-mode --- --- title: "Тёмная тема" description: "Настройте тёмную тему для флоу в Adapty, чтобы улучшить пользовательский опыт." --- Флоу в Adapty поддерживают тёмную тему из коробки. По умолчанию у цветовых стилей есть светлый и тёмный вариант — когда вы применяете цветовой стиль к элементу, флоу автоматически использует нужное значение в зависимости от текущего режима устройства. Adapty предоставляет набор предустановленных цветовых стилей, а также вы можете создавать собственные. ## Настройка цветовых стилей \{#configure-color-styles\} Каждый **цветовой стиль** определяет светлый и тёмный вариант цвета. Когда элемент использует именованный стиль, он автоматически переключается между ними. Управлять цветовыми стилями можно в разделе **Style** > **Colors** на левой панели. Чтобы добавить цветовой стиль: 1. В разделе **Style** > **Colors** нажмите **Create style**. 2. Выберите светлый и тёмный варианты цвета. Чтобы переименовать стиль, нажмите **⋮** рядом с ним и выберите **Rename**. ## Настройка темы строки состояния \{#set-the-status-bar-theme\} Если **Status bar** включён в панели **Screen settings**, вы можете задать его тему отдельно: выберите **Light**, **Dark** или **Auto** в параметрах **Status bar theme**. ## Предпросмотр светлой и тёмной темы \{#preview-light--dark-modes\} Чтобы посмотреть, как выглядит ваш флоу в каждом режиме, воспользуйтесь переключателем в виде иконки солнца/луны в нижней части области предпросмотра. ## Удаление тёмной темы \{#remove-dark-mode\} Чтобы полностью отключить поддержку тёмной темы, в панели **Style** > **Colors** нажмите **⋮** > **Delete dark theme**. --- # File: add-paywall-locale-in-adapty-paywall-builder --- --- title: "Добавление локализации в Flow Builder" description: "Добавляйте локализованный контент в Flow Builder от Adapty, чтобы охватить пользователей по всему миру на их родном языке." --- Локализация флоу делает их доступными на нескольких языках. В Flow Builder локализация организована по экранам — каждый из них показывает процент выполнения для отслеживания прогресса перевода. :::tip Завершите настройку флоу в локали по умолчанию, прежде чем добавлять другие языки. ::: ## Добавление и настройка локализации \{#add-and-set-up-localization\} 1. На левой панели нажмите Localizations. Затем нажмите **Add locale**. Выберите языки для добавления. 2. Каждый добавленный язык отображается в виде столбца в таблице локализации, предзаполненного значениями языка по умолчанию. 3. Чтобы сосредоточиться только на незаполненных полях, включите переключатель **Missing only** на левой панели. Таблица отфильтруется и покажет только непереведённые строки. ## Экспорт и импорт для внешнего перевода \{#export-and-import-for-external-translation\} Вы можете экспортировать файл локализации, чтобы передать его переводчикам, и импортировать готовый перевод. На верхней панели инструментов нажмите **Import / Export**. ### Формат экспортируемого файла \{#export-file-format\} При экспорте создаётся файл `.tsv` (значения, разделённые табуляцией), где каждая строка соответствует одному переводимому элементу. Столбцы: | Столбец | Описание | |--------|-------------| | `Screen` | Экран, которому принадлежит элемент (например, `Welcome`, `Quiz`) | | `Element` | Автоматически сгенерированный идентификатор элемента на этом экране. Его можно изменить в **Interactions** > **Element ID**. | | `Property` | Тип свойства (например, `content`) | | `[default_locale]` | Код языка по умолчанию (например, `en`) | | `[locale]` | По одному столбцу на каждую добавленную локаль (например, `fr`, `es`) | Пример: :::note Оставляйте столбцы локалей пустыми для непереведённых строк — Adapty будет считать их отсутствующими. ::: ### Требования к файлу импорта - **Формат**: `.tsv` (значения, разделённые табуляцией) - **Заголовки**: должны содержать столбцы `Screen`, `Element`, `Property` и хотя бы один столбец с локалью - **Названия столбцов с локалями**: должны совпадать с кодами локалей, уже добавленных во флоу. Если файл содержит коды локалей, отсутствующие во флоу, импорт завершится ошибкой. - **Частичный импорт**: можно включить только часть строк — строки, не вошедшие в файл, сохранят текущие значения ## Перевод вручную \{#translate-manually\} Вы также можете вводить переводы напрямую в любую ячейку таблицы локализации. Чтобы управлять конкретной строкой, откройте её контекстное меню (**⋮**): - **Reset to default**: Сбрасывает перевод строки до значений языка по умолчанию. ## Предварительный просмотр локализации \{#preview-the-localization\} Чтобы проверить переводы, переключите активную локаль в Flow Builder и просмотрите каждый экран. --- # File: add-flow-remote-config-locale --- --- title: "Локализация флоу через Remote Config" description: "Добавьте локали в Remote Config флоу, чтобы отдавать разные значения в зависимости от языка или региона пользователя." --- Remote Config флоу может хранить отдельный JSON-payload для каждой локали. Во время выполнения SDK возвращает payload, соответствующий локали пользователя, — так можно отдавать переведённые тексты, разные изображения и другие зависящие от локали значения без выпуска новой версии приложения. ## Добавление локали \{#add-a-locale\} Чтобы добавить локаль в Remote Config флоу: 1. Откройте флоу в Flow Builder. 2. Нажмите на иконку Remote Config над превью экрана. 3. Нажмите **Add locale** над редактором. 4. Заполните поля диалогового окна: - **Code**: код локали, например `en`, `fr` или `de`. - **Name**: отображаемое имя, например English или French. Adapty добавит новый столбец в JSON-редактор для этой локали. ## Редактирование значений для каждой локали \{#edit-values-per-locale\} Каждый столбец локали принимает данные в формате JSON. Используйте одинаковые ключи во всех столбцах, а значения переводите для каждой локали. Например, столбец для английского языка: ```json showLineNumbers { "title": "Try for free!", "cta": "Continue", "trial_days": 7 } ``` И столбец для испанского: ```json showLineNumbers { "title": "¡Prueba gratis!", "cta": "Continuar", "trial_days": 7 } ``` Столбцы независимы друг от друга — редактирование одного не затрагивает остальные. ## Считайте соответствующую локаль в приложении \{#read-the-matching-locale-in-your-app\} SDK предоставляет по одной записи `AdaptyRemoteConfig` для каждой локали в `AdaptyFlow.remoteConfigs`. Выберите запись, чья `locale` соответствует пользователю, затем читайте `dictionary` или `jsonString`, чтобы использовать значения в рантайме. ## Резервное копирование и перенос локалей \{#back-up-or-move-locales\} Используйте меню **Import/Export** над редактором, чтобы создать резервную копию Remote Config или скопировать его между флоу. Экспортируемый JSON-файл содержит данные всех локалей сразу. Подробнее о формате файла — в разделе [Настройка флоу с помощью Remote Config](customize-flow-with-remote-config). --- # File: customize-flow-with-remote-config --- --- title: "Настройка флоу с помощью Remote Config" description: "Настройте флоу в Flow Builder с помощью JSON-payload Remote Config." --- :::important Этот гайд посвящён Remote Config для Flow Builder. Для классических пейволов, созданных без Flow Builder, см. [Дизайн пейвола с Remote Config](customize-paywall-with-remote-config). ::: Remote Config позволяет хранить произвольный JSON, который SDK читает во время выполнения. Используйте его, чтобы менять заголовки, изображения, шрифты, цвета или флаги фич без выпуска новой версии приложения. ## Работа с Remote Config \{#work-with-remote-config\} Чтобы открыть Remote Config для флоу, нажмите на иконку Remote Config над превью экрана в редакторе флоу. В режиме **JSON** можно вводить любые данные в формате JSON. Редактор показывает по одной колонке на каждую добавленную локаль: :::warning Если Remote Config содержит невалидный JSON, флоу нельзя ни **сохранить**, ни **опубликовать**. Полный список проблем, блокирующих предпросмотр и публикацию, см. в разделе [Сохранение и публикация флоу](builder-save-publish#troubleshooting). ::: Позже вы можете получить эти данные из SDK через массив `remoteConfigs` объекта `AdaptyFlow`. Adapty хранит по одной записи `AdaptyRemoteConfig` на каждую локаль; выберите ту, что соответствует локали пользователя, и прочитайте либо распарсенный `dictionary`, либо сырую строку `jsonString`, чтобы адаптировать флоу во время выполнения. Вот несколько примеров использования Remote Config. <Tabs> <TabItem value="Titles" label="Заголовки" default> ```json showLineNumbers { "screen_title": "Today only: Subscribe, and get 7 days for free!" } # Test titles or other texts ``` </TabItem> <TabItem value="Images" label="Изображения"> ```json showLineNumbers { "background_image": "https://adapty.io/media/paywalls/bg1.webp" } # Test images on your flow ``` </TabItem> <TabItem value="Fonts" label="Шрифты"> ```json showLineNumbers { "font_family": "San Francisco", "font_size": 16 } # Test fonts ``` </TabItem> <TabItem value="Color" label="Цвет"> ```json showLineNumbers { "subscribe_button_color": "purple" } # Test colors of buttons, texts etc. ``` </TabItem> <TabItem value="HTML" label="HTML"> ```json showLineNumbers { "photo_gallery": "https://adapty.io/media/paywalls/link-to-html-snippet.html" } # Any HTML code that can be displayed in the flow ``` </TabItem> <TabItem value="Soft/Hard Paywall" label="Мягкий/жёсткий пейвол"> ```json showLineNumbers { "hard_paywall": true } # By setting it to true, you disallow skipping the paywall without subscribing # You have to handle this logic in your app ``` </TabItem> <TabItem value="Translations" label="Переводы"> ```json showLineNumbers { "title": { "en": "Try for free!", "es": "¡Prueba gratis!", "ru": "Попробуй бесплатно!" } } ``` </TabItem> </Tabs> Вы можете комбинировать любые из этих паттернов или задавать собственные ключи для тестирования альтернативных текстов, макетов или поведения. Затем [создайте плейсмент](create-placement) и добавьте в него флоу. Потом отобразите флоу в вашем приложении: [iOS](present-remote-config-paywalls) или [Android](present-remote-config-paywalls-android). ## Добавление локали \{#add-a-locale\} Чтобы локализовать флоу, нажмите **Add locale** над редактором и выберите локали. Adapty добавит в редактор новый столбец для этой локали. Редактируйте каждый столбец независимо — во время выполнения SDK возвращает запись `AdaptyRemoteConfig`, у которой `locale` совпадает с выбором пользователя. ## Импорт и экспорт JSON \{#import-and-export-json\} Используйте меню **Import/Export** над редактором, чтобы создать резервную копию, поделиться или массово отредактировать Remote Config сразу для всех локалей. - **Export JSON**: скачивает один JSON-файл со всеми локалями. - **Import JSON**: загружает JSON-файл в том же формате. Загруженный файл заменяет текущий Remote Config. В файле локали используются как ключи верхнего уровня, а содержимое каждой локали — как значение: ```json showLineNumbers { "en": { "title": "Get Premium", "cta": "Continue", "trial_days": 7, "features": ["sync", "export", "ai"] }, "fr": { "title": "Passez à Premium", "cta": "Continuer", "trial_days": 7, "features": ["synchronisation", "exportation", "IA"] } } ``` Каждый блок локали следует той же структуре JSON, которую вы вводите непосредственно в столбец локали. --- # File: paywall-device-compatibility-preview --- --- title: "Предпросмотр флоу" description: "Просмотрите совместимость флоу на разных устройствах для оптимального отображения." --- Есть два способа просмотреть, как выглядит ваш флоу на разных типах экранов: - **Предпросмотр на устройствах**: проверьте, как флоу отображается на реальных устройствах на любом этапе разработки. - **Предпросмотр в дашборде Adapty**: просматривайте флоу прямо в процессе дизайна. ## Предпросмотр на устройствах \{#preview-on-devices\} Чтобы посмотреть флоу на реальном устройстве: 1. [Скачайте приложение Adapty из App Store](https://apps.apple.com/us/app/adapty/id6739359219). 2. В конструкторе флоу нажмите **Test on device**. 3. Выберите локаль флоу. 4. Отсканируйте QR-код камерой устройства или откройте ссылку. Флоу откроется в мобильном приложении Adapty. :::note В тестовом режиме Adapty не может получить доступ к вашим продуктам в сторах, поэтому цены, отображаемые во флоу, ненастоящие. ::: ### Устранение неполадок \{#troubleshooting\} Вы не можете опубликовать или просмотреть флоу, если присутствует хотя бы одна из следующих проблем. - Взаимодействие с неполной конфигурацией. Распространённые случаи: - Действие **Open URL** без целевого URL. - Действие **Navigate to screen** без указания экрана назначения — также возникает, если экран назначения был удалён после настройки действия. - **Условное действие** без оператора или значения. - Действие **Set Variable** без назначенной переменной или значения. - Действие **Purchase** без продукта (встроенный стор) или без URL Web Paywall (веб-оплата). - Действие **Custom** без Action ID. - Действие **Show alert** с пустым полем Title или Message. - Действие **Show** или **Hide** без выбранного элемента. - **Экран без элементов**. - Элемент продукта **без привязанного продукта** — может возникнуть, если удалить связанный продукт. - Невалидный JSON в **Remote Config** нарушает весь процесс доставки — сохранить даже черновик не получится. ## Предпросмотр в дашборде Adapty \{#preview-in-the-adapty-dashboard\} :::tip Чтобы убедиться, что флоу готов к публикации, [просмотрите его на реальном устройстве](#preview-on-devices) и убедитесь, что он отображается без ошибок. ::: Вы можете предварительно просмотреть флоу на разных типах экранов прямо в области предпросмотра во Flow Builder. Это поможет убедиться, что флоу хорошо выглядит на различных устройствах и размерах экрана. С помощью элементов управления предпросмотром под областью предпросмотра вы можете: - Выбрать устройство для предпросмотра флоу. - Переключаться между горизонтальным и вертикальным режимами предпросмотра. - Переключаться между светлой и тёмной темами. - Переключаться между локалями. :::tip - Всегда предпросматривайте разные локали, так как разные языки могут иметь разную длину слов, и макет экрана может выглядеть по-разному. - Для предпросмотра кастомных переменных задайте им начальные значения. Например, если вы добавили переменную `name`, можно задать начальное значение `Jane Doe`, чтобы увидеть её в предпросмотре. ::: --- # File: builder-save-publish --- --- title: "Сохранение и публикация флоу" description: "Сохраняйте флоу как черновики и публикуйте их для пользователей" --- [Flow Builder](adapty-flow-builder) разделяет сохранение и публикацию. Черновики сохраняют вашу работу в дашборде Adapty, а публикация делает текущую версию доступной пользователям через SDK. В этой статье рассказывается о том, как и когда использовать каждое из этих действий. ## Сохранение флоу в виде черновика \{#save-a-flow-as-a-draft\} :::warning Недействительный [Remote Config](customize-flow-with-remote-config) не позволит сохранить черновик. ::: Flow Builder автоматически сохраняет прогресс раз в минуту. Чтобы сохранить черновик вручную, нажмите **Save draft** в правом верхнем углу Flow Builder или используйте сочетание клавиш **Cmd/Ctrl + S**. Черновики видны только в дашборде. Они не влияют на то, что видят пользователи в приложении, даже если флоу уже привязан к [плейсменту](placements). ## Публикация флоу \{#publish-a-flow\} Публикация делает текущую версию флоу доступной для пользователей через SDK. После публикации новая версия заменяет любую ранее опубликованную версию того же флоу. :::note Чтобы добавить флоу в [плейсмент](placements), сначала опубликуйте его. Флоу в статусе Draft добавить нельзя. ::: Чтобы опубликовать флоу, нажмите **Publish to Live** в правом верхнем углу Flow Builder. Дальнейшее зависит от того, привязан ли флоу к плейсменту: - **Флоу уже привязан к плейсменту**: пользователи увидят новую версию при следующем обращении к этому плейсменту. - **Флоу не привязан к плейсменту**: добавьте флоу в [плейсмент](create-placement), чтобы начать показывать его пользователям. :::tip Флоу готов к публикации, когда каждое действие, экран и элемент продукта полностью настроены. Типичные ошибки описаны в разделе [Устранение неполадок](#troubleshooting). ::: :::warning [Пользовательские шрифты](using-custom-fonts-in-flow-builder) не поставляются вместе с флоу — каждый файл шрифта нужно добавить в бандл приложения. Без файла пользователи увидят системный шрифт. Чтобы изменить шрифт в опубликованном флоу без поломки старых версий: продублируйте флоу, измените шрифт в копии и назначьте её [аудитории](add-audience-paywall-ab-test) для версий приложения, в которых есть этот шрифт. ::: ## Статус флоу \{#flow-status\} Каждый флоу отображает статус в списке флоу. Статус отражает, на каком этапе жизненного цикла сохранения и публикации находится флоу. | Статус | Значение | | :----- | :------ | | **Draft** | Флоу ни разу не публиковался. Существует только черновик, пользователи его не видят. Нужно сначала опубликовать черновик, чтобы добавить флоу в [плейсмент](placements). | | **Dirty** | Флоу был опубликован, но в нём есть сохранённые правки, которые ещё не опубликованы. Пользователи видят последнюю опубликованную версию, пока вы не опубликуете снова. | | **Publishing** | Публикация в процессе. | | **Failed** | Последняя попытка публикации завершилась ошибкой. Пользователи продолжают видеть последнюю опубликованную версию, если она есть. | | **Published** | Последняя сохранённая версия активна. Неопубликованных правок нет. | | **Archived** | Флоу удалён. | ## Устранение неполадок \{#troubleshooting\} Вы не можете опубликовать или просмотреть флоу, если присутствует хотя бы одна из следующих проблем. - Взаимодействие с неполной конфигурацией. Распространённые случаи: - Действие **Open URL** без целевого URL. - Действие **Navigate to screen** без указания экрана назначения — также возникает, если экран назначения был удалён после настройки действия. - **Условное действие** без оператора или значения. - Действие **Set Variable** без назначенной переменной или значения. - Действие **Purchase** без продукта (встроенный стор) или без URL Web Paywall (веб-оплата). - Действие **Custom** без Action ID. - Действие **Show alert** с пустым полем Title или Message. - Действие **Show** или **Hide** без выбранного элемента. - **Экран без элементов**. - Элемент продукта **без привязанного продукта** — может возникнуть, если удалить связанный продукт. - Невалидный JSON в **Remote Config** нарушает весь процесс доставки — сохранить даже черновик не получится. Перед публикацией проверьте флоу в [приложении Adapty](paywall-device-compatibility-preview) — это поможет выявить проблемы заранее. Если флоу не загружается в превью, изучите сообщение об ошибке для получения подробностей. --- # File: flow-metrics --- --- title: "Метрики флоу" description: "Отслеживайте и анализируйте метрики производительности флоу для увеличения дохода от подписок." --- Adapty собирает ряд метрик, которые помогают оценить эффективность ваших флоу. В отличие от метрик пейвола, метрики флоу включают отслеживание завершения, поэтому вы можете видеть, на каком экране пользователи уходят. Все метрики обновляются в реальном времени, кроме просмотров — они обновляются раз в несколько минут. В этом документе описаны доступные метрики, их определения и способы расчёта. :::important Выручка флоу рассчитывается по всем транзакциям, совершённым после того, как флоу был показан. ::: Метрики флоу доступны в списке флоу и дают общее представление об эффективности всех ваших флоу. В этом сводном представлении для каждого флоу показаны агрегированные метрики — это позволяет сравнивать их результативность и находить точки роста. Для более детального анализа конкретного флоу перейдите к метрикам детальной страницы флоу. Там вы найдёте исчерпывающие метрики для выбранного флоу с более глубоким разбором его показателей. ## Управление метриками \{#metrics-controls\} Система отображает метрики за выбранный период и группирует их по параметру левого столбца с тремя уровнями вложенности. Для опубликованных флоу метрики охватывают период с даты публикации флоу по текущую дату. Черновики и архивные флоу включаются в таблицу метрик, но если данных нет, они отображаются без метрик. ### Варианты отображения данных метрик \{#view-options-for-metrics-data\} На странице флоу доступны два варианта отображения данных метрик: - Отображение по плейсментам: метрики группируются по [плейсментам](placements), связанным с флоу. Используйте этот вариант, чтобы сравнить эффективность одного и того же флоу в разных плейсментах. - Отображение по аудиториям: метрики группируются по целевой [аудитории](audience) флоу. Используйте этот вариант для оценки метрик по отдельным сегментам аудитории. Выпадающий список в верхней части страницы флоу позволяет выбрать нужный вариант отображения. ### Фильтрация метрик по дате установки \{#filter-metrics-by-install-date\} Флажок **Filter metrics by install date** позволяет анализировать данные на основе того, когда пользователи установили приложение, а не когда произошли транзакции или просмотры. Это удобно для оценки эффективности привлечения пользователей по конкретной когорте. ### Временные диапазоны \{#time-ranges\} Вы можете анализировать данные метрик за нужный период — дни, недели, месяцы или произвольный диапазон дат. ### Фильтры и группировки \{#filters-and-groups\} Adapty предоставляет инструменты для фильтрации и настройки анализа метрик под ваши нужды. На странице метрик доступны различные временные диапазоны, варианты группировки и возможности фильтрации. - Фильтровать по: атрибуции (источник, группа объявлений, набор объявлений, креатив, кампания), стране, стору. - Группировать по: флоу (по умолчанию), стране или стору. Варианты группировки отображаются в выпадающем списке только при наличии данных по соответствующему измерению — например, если все просмотры флоу поступают из одной страны, группировка по стране предложена не будет. Дополнительную информацию о доступных элементах управления, фильтрах, параметрах группировки и способах их использования можно найти в [этой документации](controls-filters-grouping-compare-proceeds). ### График одной метрики \{#single-metric-chart\} Раздел с графиком отображает данные в виде столбчатой диаграммы. График помогает быстро увидеть: - Точные числа по каждой метрике. - Данные за конкретный период. Рядом с графиком отображается итоговая сумма — общая картина с первого взгляда. Нажмите на иконку со стрелкой, чтобы развернуть график. ### Итоговая сводка метрик \{#total-metrics-summary\} Рядом с графиком отдельной метрики находится раздел итоговой сводки метрик. В нём отображаются накопленные значения выбранных метрик на конкретный момент времени. Отображаемую метрику можно изменить с помощью выпадающего меню. ## Определения метрик \{#metrics-definitions\} ### Просмотры и уникальные просмотры \{#views--unique-views\} **Просмотры** — это количество раз, когда пользователи начинали ваш флоу (достигали первого экрана). Если один пользователь начал флоу дважды, это засчитается как два просмотра, но один уникальный просмотр. Метрика показывает, как часто ваш флоу был показан. ### Завершения и уникальные завершения \{#completions--unique-completions\} **Завершения** — это количество раз, когда пользователи доходят до последнего экрана вашего флоу. Если кто-то прошёл его дважды, это считается двумя завершениями, но одним уникальным завершением. ### Уникальный процент завершений \{#unique-completions-rate\} Количество уникальных завершений, разделённое на количество уникальных просмотров. Используйте эту метрику, чтобы понять, как пользователи продвигаются по флоу, и определить, на каком этапе они уходят. :::note Adapty конвертирует другие валюты в USD по курсу [currencylayer.com](https://currencylayer.com/) (обновляется каждые 8 часов). Курс **фиксируется на момент транзакции** — последующие изменения не влияют на результат конвертации. ::: ### Выручка \{#revenue\} **Revenue** показывает общий доход в USD от покупок и продлений, связанных с флоу. Это сумма до каких-либо вычетов, включая комиссию App Store / Play Store. ### Выручка \{#proceeds\} [**Proceeds**](analytics-cohorts#revenue-vs-proceeds) — это сумма, которую вы получаете после вычета комиссии App Store / Play Store, но до налогов. :::important Сообщите Adapty, если ваше приложение участвует в программе сниженной комиссии. Для корректных расчётов укажите статус участия в программах [Small Business Program](app-store-small-business-program) и [Reduced Service Fee](google-reduced-service-fee) в [настройках приложения](general). ::: ### Чистая выручка \{#net-proceeds\} Ваш итоговый доход после вычета комиссий стора и налогов. ### ARPPU ARPPU — это средняя выручка на одного платящего пользователя. Рассчитывается как общая выручка, делённая на количество уникальных платящих пользователей. $15 000 выручки / 1 000 платящих пользователей = $15 ARPPU. ### ARPU \{#arpu\} ARPU — это средняя выручка на одного пользователя, просмотревшего флоу. Рассчитывается как общая выручка, делённая на количество уникальных просмотров. ### ARPAS ARPAS — это средняя выручка на одного активного подписчика. Рассчитывается как общая выручка, делённая на количество подписчиков, которые активировали пробный период или подписку. Например: $5 000 выручки / 1 000 подписчиков = $5 ARPAS. ### CR покупок и уникальный CR покупок \{#cr-purchases--unique-cr-purchases\} **Конверсия в покупки (CR purchases)** показывает, какой процент просмотров флоу завершается покупкой. Например, 10 покупок из 100 просмотров — это конверсия 10%. **Уникальный CR покупок** показывает, какой процент уникальных пользователей, просмотревших флоу, совершили покупку. При этом каждый пользователь учитывается только один раз, сколько бы раз он ни видел флоу. ### CR trials и unique CR trials \{#cr-trials--unique-cr-trials\} **Conversion rate to trials** показывает, какой процент просмотров флоу заканчивается запуском триала. Например, 10 триалов из 100 просмотров — это 10% конверсия. **Unique CR trials** измеряет, какой процент уникальных пользователей, просмотревших флоу, запустили триал, считая каждого пользователя только один раз вне зависимости от того, сколько раз они его видели. ### Покупки \{#purchases\} **Purchases** учитывает все транзакции во флоу, кроме продлений. В их числе: - Новые прямые покупки. - Конвертации триалов, активированных во флоу. - Изменения плана (апгрейды, даунгрейды, кросс-грейды). - Восстановления подписок во флоу — например, когда подписка возобновляется после истечения без автопродления. Эта метрика даёт полную картину новой транзакционной активности из вашего флоу. ### Пробные периоды \{#trials\} **Trials** — количество пользователей, которые начали бесплатный пробный период через ваш флоу. Используйте эту метрику, чтобы отслеживать, насколько хорошо ваше пробное предложение привлекает пользователей до того, как они решат платить. ### Отменённые триалы \{#trials-cancelled\} **Trials cancelled** показывает, сколько пользователей отключили автопродление в период пробного доступа. Это помогает понять, сколько людей решили не переходить на платную подписку после знакомства с сервисом. ### Возвраты \{#refunds\} **Возвраты** показывают, сколько покупок и подписок было возвращено с возвратом средств, независимо от причины. ### Процент возвратов \{#refund-rate\} **Процент возвратов** показывает долю первых покупок, по которым был оформлен возврат. Пример: 5 возвратов из 1 000 первых покупок = 0,5%. Продления в этом расчёте не учитываются. --- # File: fallback-flows --- --- title: "Резервные флоу" description: "Настройте локальные резервные флоу в Adapty, чтобы флоу оставался видимым при отсутствии подключения к интернету." --- Чтобы обеспечить бесперебойный пользовательский опыт, важно настроить **резервные версии** ваших [флоу](adapty-flow-builder). Когда приложение запрашивает флоу, SDK Adapty обращается к нашим серверам для получения его конфигурации. Если устройство не может подключиться к Adapty (проблемы с сетью, недоступность сервера), SDK переходит к локальным данным: - Если пользователь уже видел этот флоу, SDK отдаёт кешированную копию. - Если кеша нет, SDK загружает резервный файл конфигурации, встроенный в приложение. Adapty автоматически генерирует эти резервные файлы. Резервный бандл флоу общий с пейволами — один JSON-файл на платформу содержит резервные варианты и для флоу, и для пейволов. SDK читает нужный раздел в зависимости от запроса. :::important Резервные флоу входят в состав **Adapty SDK 4.0+**. Если в диалоге загрузки выбрать более раннюю версию SDK, файл будет содержать только варианты пейволов и онбордингов — без флоу. Убедитесь, что ваше приложение использует версию SDK с поддержкой флоу, прежде чем полагаться на резервный флоу. ::: ## Перед началом работы \{#before-you-start\} 1. Создайте [флоу](adapty-flow-builder) во Flow Builder. 2. [Создайте плейсмент](create-placement) для флоу. ## Скачайте резервный файл \{#download-the-fallback-file\} 1. Откройте страницу **[Placements](https://app.adapty.io/placements)**. 2. Нажмите кнопку **Fallbacks** в правом верхнем углу. 3. Выберите нужную платформу из выпадающего списка. 4. Выберите версию SDK, которая соответствует той, что поставляется с вашим приложением. Выберите **Adapty SDK v4.0.0 and higher** (или более позднюю версию), чтобы получить бандл, включающий флоу. Браузер скачает JSON-файл для каждой платформы — например, `ios_4_0_0_fallback.json`. <details> <summary>Пример записи резервного флоу (нажмите, чтобы раскрыть)</summary> ```json "PLACEMENT_ID": { "data": [ { "developer_id": "PLACEMENT_ID", "variation_id": "cb1c0ef8-aecd-4a53-a6f3-b98266e66884", "flow_id": "daf25858-3fa2-4981-8500-9c8a30e5b7e6", "flow_name": "FLOW_NAME", "flow_version_id": "FLOW_VERSION_ID", "placement_audience_version_id": "a9eb3ab8-3178-477d-84d4-ef9d3978e48b", "audience_name": "All Users", "ab_test_name": "", "cross_placement_info": null, "weight": 100, "variations": [ { "variation_id": "cb1c0ef8-aecd-4a53-a6f3-b98266e66884", "paywall_id": "PAYWALL_ID", "paywall_name": "PAYWALL_NAME", "ab_test_name": "", "products": [], "revision": 1, "custom_payload": null, "weight": 100 } ], "remote_configs": [] } ], "meta": { "placement": { "developer_id": "PLACEMENT_ID", "is_tracking_purchases": true, "audience_name": "All Users", "placement_audience_version_id": "a9eb3ab8-3178-477d-84d4-ef9d3978e48b", "revision": 0, "ab_test_name": "" } } } ``` Точная структура может меняться между версиями SDK. Всегда используйте файл, сгенерированный Adapty для вашей версии SDK, а не создавайте его вручную. </details> ## После загрузки \{#after-the-download\} Добавьте файл в код приложения, затем следуйте платформенному гайду по настройке. Те же API, которые загружают резервные пейволы, также загружают резервные флоу, если ваше приложение использует SDK с поддержкой флоу: - [iOS](ios-use-fallback-paywalls) - [Android](android-use-fallback-paywalls) - [React Native](react-native-use-fallback-paywalls) - [Capacitor](capacitor-use-fallback-paywalls) ## Ограничения \{#limitations\} Резервные флоу жёстко закодированы и хранятся локально, поэтому они не обладают всеми динамическими возможностями живых флоу: - **Один вариант на плейсмент.** Если у плейсмента несколько флоу (разные аудитории, варианты A/B-теста), резервный файл использует вариант с наибольшим весом или наиболее широкой аудиторией. - **Без A/B-тестирования.** A/B-тест живого флоу разрешается на сервере; резервный вариант всегда обслуживает один выбранный вариант. - **Без удалённых обновлений.** Обновление резервного файла требует нового релиза приложения. Изменения, которые вы обычно вносите через Remote Config, вносите через живой флоу. - **Только локаль по умолчанию.** Резервный файл использует локаль `en`; локализованные варианты не включаются в бандл. --- # File: create-product --- --- title: "Создание продукта" description: "Пошаговое руководство по созданию новых продуктов-подписок в Adapty для более эффективного управления доходами." --- Способ создания продуктов в Adapty зависит от того, есть ли они уже в сторах: - **[Если продуктов ещё нет в App Store и/или Google Play — создайте их в Adapty и сразу опубликуйте в сторах](#create-product-and-push-to-store)**. - **[Если продукты уже есть в App Store и/или Google Play — создайте их в Adapty и подключите существующие продукты из сторов.](#create-product-and-connect-existing-store-products)** :::tip Вы также можете создавать продукты программно через [Developer CLI](developer-cli-reference#adapty-products-create). ::: ## Создайте продукт и добавьте его в стор \{#create-product-and-push-to-store\} :::warning Прежде чем начать, убедитесь, что вы настроили интеграцию со сторами, которые вам нужны: - [App Store](initial_ios) - [Google Play](initial-android) Если вы настраивали интеграцию с App Store некоторое время назад, убедитесь, что вы [добавили ключ App Store Connect API](app-store-connection-configuration#step-6-add-app-store-connect-api-key). ::: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/qUpC2XG-r5E?si=7Komyv4_PUQ4FaEH" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> Чтобы добавить новый продукт в приложение: 1. Перейдите в раздел **[Products](https://app.adapty.io/products)** в главном меню Adapty. <img src="/assets/shared/img/products-tab.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите **Create product** в правом верхнем углу. Adapty поддерживает все типы продуктов: подписки, нерасходуемые покупки \(включая пожизненный доступ\) и расходуемые покупки. 3. Выберите **Create a new product and push to stores**. <img src="/assets/shared/img/push-to-stores.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Введите следующие данные: - **Product name**: введите название продукта, которое будет отображаться в дашборде Adapty. Это название нужно в первую очередь для вас, поэтому выбирайте то, которое вам удобнее всего использовать в дашборде Adapty. - **Access Level**: выберите [уровень доступа](access-level), к которому относится продукт. Уровень доступа определяет, какие функции станут доступны после покупки продукта. Обратите внимание, что в этом списке отображаются только уже созданные уровни доступа. Уровень доступа `premium` создаётся в Adapty по умолчанию, но вы также можете [добавить дополнительные уровни доступа](access-level). - **Subscription duration**: выберите длительность подписки из списка. - **Weekly/Monthly/2 Months/3 Months/6 Months/Annual**: длительность подписки. - **Lifetime**: используйте этот период для продуктов, которые открывают премиум-функции приложения навсегда. - **Non-Subscriptions**: для продуктов, которые не являются подписками и поэтому не имеют длительности, используйте non-subscriptions. Это могут быть продукты для разблокировки дополнительных функций, расходуемые покупки и т. д. - **Consumables**: расходуемые покупки можно приобретать несколько раз. Они расходуются в процессе использования приложения. Примеры — внутриигровая валюта и дополнения. Учтите, что расходуемые покупки не влияют на уровни доступа. Чтобы предоставить уровень доступа при разовой покупке, используйте **Non-Subscriptions**. - **Price (USD)**: цена продукта в долларах США. Эта цена будет использоваться как базовая для автоматического расчёта и установки цен во всех странах. Позже вы сможете [настроить цену для отдельных стран и регионов](edit-product#set-country-specific-prices). <img src="/assets/shared/img/create-product-push.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Нажмите **Save & Continue**. 6. Настройте информацию о продукте для App Store, если планируете публиковаться там: - **Product ID**: Создайте постоянный уникальный идентификатор продукта. - **Product group**: Выберите существующую группу продуктов, созданную в App Store Connect, или нажмите **Create new Product Group** и задайте её название. После того как Adapty создаст её, вы сможете выбрать её из выпадающего списка. - **Screenshot**: Загрузите скриншот встроенной покупки, на котором чётко показан предлагаемый товар или услуга. Этот скриншот используется только для проверки в App Store и не отображается в App Store. Требования к размеру и формату скриншота смотрите [здесь](https://developer.apple.com/help/app-store-connect/reference/app-information/screenshot-specifications/). <img src="/assets/shared/img/push-app-store.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Нажмите **Push data to App Store**. :::warning Если это ваш первый продукт для данного приложения, вам нужно вручную отправить его на проверку в App Store Connect. В дальнейшем этого не потребуется. После завершения проверки статус продукта в Adapty обновится автоматически. ::: 8. Настройте информацию о продукте для Google Play, если планируете публикацию там: - **Base Product ID**: Создайте постоянный уникальный идентификатор продукта. - **Subscription**: Выберите существующую группу подписок, созданную в Google Play Console, или нажмите **Create new Product Group** и задайте её название и ID. После того как Adapty создаст её, вы сможете выбрать её из выпадающего списка. :::note Льготный период и период удержания аккаунта будут автоматически установлены по умолчанию согласно правилам Play Store. Изменить их можно позже в Google Play Console. ::: <img src="/assets/shared/img/push-google-play.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 9. Нажмите **Push data to Play Store**. 10. Для iOS настройте introductory offer — бесплатный пробный период — выбрав **Free duration** из выпадающего списка. На этом начальном этапе можно добавить introductory offer с бесплатным пробным периодом. После того как основной продукт будет одобрен сторами, вы сможете [добавить другие офферы](offers) (например, promotional или win-back), привязав их существующие ID из консоли стора. <img src="/assets/shared/img/intro.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important Introductory offer не синхронизируются с Google Play автоматически. В отличие от App Store, в Google Play нет отдельного типа «introductory offer» — пробные периоды и скидочные предложения настраиваются как **офферы** базового плана. [Создайте оффер в Google Play Console и привяжите его к продукту Adapty](google-play-offers). ::: 11. Наконец, нажмите **Save**, чтобы подтвердить создание продукта. ## Создайте продукт и подключите существующие продукты из стора \{#create-product-and-connect-existing-store-products\} :::warning Перед началом убедитесь, что вы: - Настроили интеграцию со сторами, которые вам нужны: - [App Store](initial_ios) - [Google Play](initial-android) - Создали продукты в нужных сторах: - [App Store](app-store-products) - [Google Play](android-products) **Если у вас ещё нет продуктов**, воспользуйтесь гайдом [Загрузить в сторы](#create-product-and-push-to-store) — он позволяет создать продукты одновременно в Adapty и сторах. ::: <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/nlkdKCF0SwY?si=VVigzHcpv3waKJmI" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> Чтобы добавить новый продукт в приложение: 1. Перейдите в **[Products](https://app.adapty.io/products)** через главное меню Adapty. <img src="/assets/shared/img/products-tab.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите **Create product** в правом верхнем углу. Adapty поддерживает все типы продуктов: подписки, неизрасходуемые покупки \(включая пожизненный доступ\) и расходуемые покупки. 3. Выберите **Connect an existing store product**. <img src="/assets/shared/img/existing-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Введите следующие данные: - **Product name**: введите название продукта, которое будет использоваться в дашборде Adapty. Название нужно прежде всего вам, поэтому выбирайте то, которое удобнее всего использовать в дашборде Adapty. - **Access Level ID**: Выберите [уровень доступа](access-level), к которому относится продукт. Уровень доступа определяет, какие функции открываются после покупки продукта. Обратите внимание, что в этом списке отображаются только ранее созданные уровни доступа. Уровень доступа `premium` создаётся в Adapty по умолчанию, но вы также можете [добавить дополнительные уровни доступа](access-level). - **Subscription duration** (Длительность подписки): выберите длительность подписки из списка. - **Weekly/Monthly/2 Months/3 Months/6 Months/Annual**: длительность подписки. - **Lifetime**: используйте пожизненный период для продуктов, которые навсегда открывают премиум-функции приложения. - **Non-Subscriptions**: для продуктов, которые не являются подписками и поэтому не имеют длительности, используйте non-subscriptions. Они могут открывать дополнительные функции, расходуемые покупки и т. д. - **Consumables**: расходуемые покупки можно приобретать несколько раз. Они расходуются в ходе использования приложения. Примеры: внутриигровая валюта и дополнения. Учтите, что расходуемые покупки не влияют на уровни доступа. Чтобы предоставить уровень доступа через разовую покупку, используйте **Non-Subscriptions**. - **Price (USD)**: цена продукта в долларах США. Если ваш продукт уже есть в сторе, это значение не влияет на его реальную цену в сторе — можно выбрать любое значение из списка. Позже вы можете [настроить цены для разных регионов](edit-product#set-country-specific-prices) прямо в дашборде Adapty. <img src="/assets/shared/img/product-info.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Нажмите **Continue**. 6. Настройте информацию о продукте из каждого стора: - **App Store:** - **App Store Product ID:** Уникальный идентификатор для доступа к продукту на устройствах. Выберите его из списка. Если его нет в списке, проверьте настройки в App Store Connect и убедитесь, что идентификатор корректен и принадлежит этому приложению. - **Play Store:** - **Google Play Product ID:** Идентификатор продукта в Play Store. Выберите его из списка. Если его нет в списке, проверьте настройки в Google Play Console и убедитесь, что идентификатор корректен и принадлежит этому приложению. - **Base Plan ID:** Идентификатор базового плана продукта в Play Store. При добавлении Product ID подписки в Play Store необходимо указать Base Plan ID. Базовый план определяет основные параметры подписки: расчётный период, тип продления (автоматическое или предоплаченное) и цену. Обратите внимание: в Adapty каждая комбинация одной и той же подписки с разными базовыми планами считается отдельным продуктом. - **Legacy fallback product**: Резервный продукт используется исключительно в приложениях на устаревших версиях Adapty SDK (2.5 и ниже). Отметив продукт как обратно совместимый в Google Play Console, вы позволяете Adapty определить, можно ли его приобрести в старых версиях SDK. В этом поле укажите значение в формате `<subscription_id>:<base_plan_id>`. - **Stripe**: - **Stripe Product ID**: Уникальный идентификатор продукта в Stripe. - **Stripe Price ID**: В Stripe объекты цен содержат не только сумму — они также включают поведение налогов, объёмные тарифы и интервалы подписки. Поскольку у одного продукта может быть несколько цен, при создании продукта в Adapty укажите нужный идентификатор цены. - **Paddle**: - **Paddle Product ID**: Уникальный идентификатор продукта в Paddle. - **Paddle Price ID**: В Paddle объекты цен содержат не только сумму — они также включают поведение налогов, объёмные тарифы и интервалы подписки. Поскольку у одного продукта может быть несколько цен, при создании продукта в Adapty укажите нужный идентификатор цены. 7. **Опционально:** вы можете добавить продукты из любого стороннего стора, нажав **Add custom store**. В окне **Manage custom store info** можно выбрать существующий кастомный стор или добавить новый и привязать к нему продукт. Имейте в виду, что Adapty отслеживает транзакции только из App Store, Google Play и Stripe. Для кастомных сторов вам нужно будет отправлять транзакции через метод Set transaction серверного API Adapty. 8. Нажмите **Save product**, чтобы завершить создание продукта. Синхронизация статуса продукта может занять до пяти минут — подождите, пока данные обновятся в таблице. 9. При необходимости вы можете [создать офферы](create-offer) для продукта. Чтобы добавить офферы, нажмите **Yes, add offers**. В противном случае нажмите **No, thanks**. :::note Introductory offers создаются в Adapty только при публикации продукта в сторе. При импорте или для ранее созданных продуктов introductory offers не синхронизируются и не отображаются в Adapty, однако в приложении будут работать корректно. ::: ## Дальнейшие шаги \{#next-steps\} Поздравляем! Вы добавили продукты в Adapty. Что дальше? - Если вы ещё не настроили introductory/promotional офферы, вы можете [сделать это](offers) сейчас. - Если офферы уже настроены или вы решили пропустить этот шаг, переходите к [настройке пейволов](quickstart-paywalls) для включения встроенных покупок. - Если нужно внести изменения в продукты стора (например, установить региональные цены или настроить льготный период), сделайте это в App Store Connect или Google Play Console. - Прочитайте, как можно [редактировать продукты](edit-product) позже. --- # File: edit-product --- --- title: "Редактирование продукта" description: "Изменяйте и управляйте продуктами-подписками в Adapty для точного отслеживания дохода." --- В Adapty вы можете редактировать название продукта, уровень доступа, региональное ценообразование и привязанные идентификаторы стора, а также просматривать журнал аудита для отслеживания изменений цен. Длительность подписки нельзя изменить после создания продукта — для этого нужно создать новый продукт. :::warning Редактирование продуктов допустимо, однако важно учитывать, что изменения в продуктах, уже используемых в активных пейволах, могут привести к расхождениям в аналитике. **Не рекомендуется редактировать уровень доступа, App Store Product ID и Play Store Product ID**, так как это может негативно повлиять на точность аналитики. Вносите эти изменения только если допустили ошибку, например опечатку в идентификаторе продукта. Если продукт больше не используется и вы хотите заменить его другим, настоятельно рекомендуем создать новый продукт и обновить пейволы и A/B-тесты соответствующим образом. ::: ## Редактирование продукта \{#edit-product\} Чтобы отредактировать продукт: 1. Перейдите в раздел **[Products](https://app.adapty.io/products)** в главном меню Adapty. 2. Нажмите на строку продукта в таблице или кликните на три точки рядом с продуктом и выберите **Edit**. 3. В открывшемся окне **Edit** внесите нужные изменения. Подробнее о доступных параметрах читайте в разделе [Создание продукта](create-product). 4. Нажмите **Save**. :::warning Изменения в App Store Connect или Google Play Console не синхронизируются с Adapty. Цена, отображаемая в Adapty, устанавливается при создании продукта и не обновляется при изменении цены в сторе. Это не влияет на аналитику доходов — Adapty получает данные о выручке напрямую из сторов. Поле с ценой на дашборде носит исключительно справочный характер. ::: :::note Если вы измените уровень доступа, изменение применится только к новым подпискам. Для существующих подписчиков текущий уровень доступа остаётся неизменным и обновится автоматически при следующем продлении подписки. ::: <img src={require('./img/edit-product.png').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Установка цен для разных стран \{#set-country-specific-prices\} Вы можете задать разные цены для разных регионов прямо в дашборде Adapty — они автоматически применятся к вашим продуктам в App Store Connect и/или Google Play Console. Чтобы установить цены для разных стран: 1. [Откройте продукт для редактирования](#edit-product). 2. Нажмите **Download**, чтобы выгрузить текущие цены из сторов в нужном формате, или создайте новый CSV-файл. <img src={require('./img/download-prices.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Обновите цены в CSV-файле, соблюдая [формат](#csv-file-format). Если цена для какой-либо страны не изменилась или не включена в файл, ничего не произойдёт. При загрузке CSV Adapty сравнивает цены и обновляет только те, которые отличаются. 4. В окне **Edit** нажмите **Upload** и выберите CSV-файл. <img src={require('./img/upload-prices.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Если вы хотите, чтобы изменения применились и к существующим подписчикам, выберите **Apply to existing subscribers**. 6. Просмотрите изменения, которые будут применены, и нажмите **Save changes**. <img src={require('./img/country-level-price.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Формат CSV-файла \{#csv-file-format\} :::tip Если у вас похожие продукты в одном приложении или вы хотите установить одинаковые цены в разных приложениях, можно использовать один и тот же CSV-файл. ::: Проще всего редактировать цены в CSV, [скачав файл с текущими ценами и отредактировав его напрямую](#set-country-specific-prices). Если вы создаёте файл самостоятельно, он должен содержать следующие столбцы: - `region_name` - `region_code` - `app_store_currency` - `app_store_requested_price` - `play_store_currency` - `play_store_requested_price` Пример: ``` region_name,region_code,app_store_currency,app_store_requested_price,play_store_currency,play_store_requested_price United States,US,,8.99,,8.99 United Arab Emirates,AE,USD,8.99,AED,39.99 Germany,DE,USD,8.99,USD,8.99 ``` ## Просмотр журнала аудита \{#view-audit-log\} Adapty записывает все изменения цен для каждого продукта, поэтому вы можете отслеживать, кто и когда вносил изменения. Чтобы открыть журнал аудита: 1. Откройте **[Products](https://app.adapty.io/products)** в главном меню Adapty. 2. Нажмите на три точки рядом с продуктом и выберите **Audit log**. В таблице журнала аудита отображается каждое изменение цены с датой, именем и ролью участника команды, а также количеством изменений. Чтобы скачать подробную CSV-разбивку события, нажмите на значок загрузки в соответствующей строке. <img src={require('./img/audit-log.webp').default} style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: delete-product --- --- title: "Удаление продукта" description: "Узнайте, как удалить продукт с подпиской в Adapty, не нарушая поток доходов вашего приложения." --- Удалить можно только те продукты, которые не используются в пейволах. Чтобы удалить продукт: 1. Перейдите в раздел **[Products](https://app.adapty.io/products)** в главном меню Adapty. 2. Нажмите кнопку **3-dot** рядом с продуктом и выберите **Delete**. <img src="/assets/shared/img/delete-product.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Введите название продукта, который вы собираетесь удалить. <img src="/assets/shared/img/b945add-delete_product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Нажмите **Delete forever**. --- # File: add-product-to-paywall --- --- title: "Добавление продукта на пейвол" description: "Узнайте, как добавлять продукты на пейволы в Adapty и управлять ими." --- Чтобы продукт отображался на [пейволе](paywalls) и был доступен для выбора пользователями вашего приложения, выполните следующие шаги: 1. При [настройке пейвола](create-paywall) нажмите **Add product** под заголовком **Products**. 2. В открывшемся выпадающем списке выберите продукты, которые будут показаны пользователям. Список содержит только ранее созданные продукты. Порядок продуктов сохраняется на стороне SDK, поэтому при настройке пейвола важно учитывать желаемый порядок их отображения. При необходимости можно также указать offer для продукта. <img src="/assets/shared/img/0479b51-ad_product_to_paywall.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Нажмите **Create as draft** или **Save and publish** в зависимости от статуса пейвола. Обратите внимание: после создания пейвола не рекомендуется редактировать, добавлять или удалять продукты, поскольку это может повлиять на метрики пейвола. --- # File: virtual-currencies --- --- title: "Виртуальные валюты" description: "Определяйте внутриигровые валюты в Adapty, привязывайте их к продуктам для автоматического начисления кредитов и отслеживайте баланс каждого пользователя." --- <CustomDocCardList ids={['virtual-currency-quickstart', 'create-virtual-currency', 'virtual-currency-balance']} /> Начисляйте пользователям виртуальную валюту — AI-токены, кредиты или монеты — при покупке продуктов или продлении подписок. Определите валюту один раз, привяжите её к нужным продуктам, и Adapty будет автоматически зачислять её каждому пользователю и отслеживать баланс. Приложение читает и расходует этот баланс через [серверный API](getting-started-with-server-side-api) — например, чтобы списывать токены за каждую генерацию. ## Как это работает \{#how-it-works\} 1. [Создайте виртуальную валюту](create-virtual-currency): Откройте **Products** и перейдите на вкладку **Virtual currency**. Нажмите **New virtual currency**. Задайте валюту: укажите код, название и при необходимости описание. 2. **Привяжите валюту к продуктам**: Свяжите валюту с разовыми или подписочными продуктами и укажите, сколько кредитов даёт каждая покупка. 3. **Кредиты начисляются автоматически**: Когда пользователь покупает привязанный разовый продукт или продлевает привязанную подписку, Adapty начисляет настроенное количество кредитов на его баланс. 4. **Читайте и списывайте баланс**: Приложение считывает баланс каждого пользователя и начисляет или списывает кредиты через серверный API. 5. **Отслеживайте каждое изменение**: Все изменения баланса отображаются в [профиле пользователя](virtual-currency-balance). Полное руководство — от настройки валюты до списания кредитов через API — см. в [кратком руководстве по виртуальной валюте](virtual-currency-quickstart). ## Варианты использования \{#use-cases\} | Тип приложения | Как использовать виртуальные валюты | |----------------|--------------------------------------| | **ИИ-приложения** (генерация изображений, видео или текста) | Списывайте кредиты за каждую генерацию. Включите ежемесячный лимит кредитов в подписку и продавайте наборы кредитов как разовые покупки. | | **Приложения с короткими сериалами и видео** | Списывайте монеты за разблокировку эпизода. Предоставляйте подписчикам регулярный лимит монет и продавайте наборы монет как разовые покупки. | | **Изучение языков и образование** | Давайте бесплатным пользователям ограниченный запас сердечек или жизней через серверный API. Предоставляйте больший лимит или полностью отключайте проверку баланса на платном тарифе. | | **Мобильные игры** | Используйте мягкую и твёрдую валюту одновременно: начисляйте золото за игровые достижения через серверный API, продавайте кристаллы за деньги и конвертируйте одно в другое в рамках единой атомарной транзакции. | | **Приложения для повышения продуктивности** | Тарифицируйте ресурсоёмкие действия — например, OCR или экспорт — с помощью кредитов. Давайте бесплатным пользователям небольшой лимит, предоставляйте больше по подписке и продавайте наборы кредитов для пакетной обработки. | :::tip Для подписочных начислений включите [истечение срока действия кредитов](create-virtual-currency#link-products). Когда неиспользованные кредиты сбрасываются при каждом обновлении, начисление остаётся весомым аргументом продолжать подписку, а активные пользователи покупают пакеты кредитов вместо того, чтобы расходовать накопленный запас. ::: ## Ограничения \{#limitations\} - **Для доступа с нескольких устройств нужна идентификация**: баланс принадлежит одному профилю. Анонимный пользователь хранит свой баланс на том устройстве, где он его накопил, поэтому идентифицируйте пользователей, чтобы баланс был доступен на любом устройстве. См. [Балансы, профили и устройства](virtual-currency-balance#balances-profiles-and-devices). - **Только на стороне сервера**: метода SDK для чтения или списания баланса пока нет, поэтому вашему приложению нужен бэкенд. Он читает балансы и начисляет или списывает кредиты через серверный API. - **До 20 валют на приложение**: в одном приложении можно создать до 20 виртуальных валют. --- # File: virtual-currency-quickstart --- --- title: "Быстрый старт: виртуальная валюта" description: "Настройка виртуальной валюты с нуля: создайте токен-валюту, начисляйте токены с подпиской и списывайте их через серверный API." --- :::link Основная статья: [Виртуальные валюты](virtual-currencies) ::: Это руководство поможет настроить токен-валюту с нуля: подписка начисляет пользователям 1000 токенов в месяц, а платные действия в приложении списывают токены. Adapty хранит все балансы — вашему бэкенду остаётся только читать и тратить их. 1. Следуйте [гайду по созданию виртуальной валюты](create-virtual-currency), чтобы создать валюту `TOKENS`. Привяжите её к подписке Pro и установите **Credit per cycle** равным 1000. Для продажи дополнительных токенов также привяжите разовые продукты с наборами токенов. 2. Вызовите [List virtual currency balances](api-adapty/operations/listVirtualCurrencyBalances), чтобы получить баланс пользователя, например, перед запуском генерации: ```bash title="Read balances" curl https://api.adapty.io/api/v2/server-side-api/vc/balances/ \ -H "Authorization: Api-Key {your secret key}" \ -H "adapty-customer-user-id: user-42" ``` В ответе перечислены все валюты пользователя, например 1000 `TOKENS`: ```json title="Response" { "data": [ { "code": "TOKENS", "name": "Tokens", "balance": 1000, "held": 0, "available": 1000 } ] } ``` 3. Когда пользователь запускает генерацию, спишите токены, вызвав [Create virtual currency transaction](api-adapty/operations/createVirtualCurrencyTransaction) с отрицательным `amount`. В этом примере одна генерация изображения стоит 100 токенов: ```bash title="Spend tokens" curl -X POST https://api.adapty.io/api/v2/server-side-api/vc/transactions/ \ -H "Authorization: Api-Key {your secret key}" \ -H "adapty-customer-user-id: user-42" \ -H "Content-Type: application/json" \ -d '{"items": [{"currency_code": "TOKENS", "amount": -100}]}' ``` Транзакция атомарна и возвращает обновлённый баланс. Если у пользователя недостаточно средств, запрос возвращает `insufficient_balance` и ничего не меняется. Добавьте заголовок `Idempotency-Key`, чтобы безопасно повторять запросы. 4. Для проведения win-back promo начислите токены через тот же эндпоинт с положительным значением `amount`. Начисленные таким образом кредиты не имеют срока действия. 5. Отслеживайте все изменения в [профиле](virtual-currency-balance) пользователя или в [истории транзакций](api-adapty/operations/listVirtualCurrencyTransactions) — это позволит в любой момент провести аудит экономики. --- # File: create-virtual-currency --- --- title: "Создание виртуальной валюты" description: "Создайте виртуальную валюту в Adapty, привяжите её к продуктам, чтобы покупки начисляли кредиты, и настройте срок их действия." --- :::link Основная статья: [Виртуальные валюты](virtual-currencies) ::: Чтобы продавать кредиты виртуальной валюты, сначала определите валюту, а затем привяжите её к продуктам. Это делается в два шага — оба описаны в этой статье. После привязки продукта каждая покупка или продление будет автоматически начислять кредиты. ## Создание виртуальной валюты \{#create-a-virtual-currency\} В дашборде Adapty откройте **Products** > [**Virtual currency**](https://app.adapty.io/virtual-currency). Чтобы создать виртуальную валюту: 1. Нажмите **New virtual currency**. Откроется панель на шаге **General**. 2. Заполните данные о валюте: - **Code**: Уникальный **постоянный** идентификатор валюты в вашем приложении, например `COINS`. Идентифицирует валюту в серверном API и отслеживает баланс каждого пользователя. Используйте латинские буквы, цифры и знак подчёркивания, не более 32 символов. Строчные буквы автоматически преобразуются в заглавные. - **Name**: Отображаемое название, например `Gold`. Его можно изменить позже. - **Description**: Необязательная заметка о валюте. 3. Нажмите **Continue**, чтобы перейти к шагу **Link Products**. :::important После создания валюты изменить её **Code** невозможно. **Name** при этом можно редактировать в любое время. ::: При добавлении новой валюты начальный баланс каждого профиля пользователя равен 0. ## Привязка продуктов \{#link-products\} Привяжите продукт к виртуальной валюте, чтобы покупка или продление подписки начисляло кредиты в этой валюте. Один продукт можно привязать к нескольким валютам, а одну валюту — к нескольким продуктам. Привязать продукты можно сейчас, на шаге **Link Products**, или позже при редактировании валюты. На шаге **Link Products** в разделе **Associated products** отображаются продукты, которые начисляют данную валюту. Чтобы добавить продукт, нажмите **Add associated products** и выберите нужный продукт. Каждое начисление добавляется к балансу пользователя, а не заменяет его, поэтому кредиты от разных продуктов накапливаются. Настройки кредитов зависят от типа продукта: - **Продукты с подпиской** имеют три настройки кредитов: - **Credit per cycle** (обязательно): кредиты, начисляемые при каждом продлении, включая продление, которое переводит пробный период в платный. - **Credit on trial start** (необязательно): отдельная разовая сумма, начисляемая при начале бесплатного пробного периода. - **Credits expire at the end of each billing cycle** (переключатель): если включён, Adapty сбрасывает неиспользованные кредиты до 0 при каждом продлении, до начисления кредитов нового цикла. Если выключен, кредиты накапливаются между циклами и не истекают. - **Разовые покупки**: задайте **Credit amount** — количество кредитов, начисляемых за каждую покупку. Разовые кредиты не истекают. Нажмите **Save**, чтобы создать валюту с привязанными продуктами. Привязки продуктов действуют только в будущем. Новый привязанный продукт начнёт начислять кредиты только со следующей покупки или продления. Adapty не начисляет валюту автоматически текущим подписчикам — им нужно сначала продлить подписку. При удалении привязки новые начисления прекращаются, но уже начисленные кредиты сохраняются. Если переключатель срока действия включён, кредиты с истечением срока следуют жизненному циклу подписки: - В период повторных попыток оплаты или льготного периода Adapty не начисляет новые кредиты. - После отмены подписки кредиты сохраняются до конца оплаченного периода. - Когда подписка истекает, Adapty сбрасывает оставшиеся кредиты до 0. ## Дальнейшие шаги \{#next-steps\} После того как вы создадите валюту и привяжете продукты, покупки будут автоматически начислять кредиты. Что можно сделать дальше: - Отслеживать баланс каждого пользователя на странице [Virtual currency balance](virtual-currency-balance). - Начислять, списывать и считывать балансы в рантайме через серверный API: [Create virtual currency transaction](api-adapty/operations/createVirtualCurrencyTransaction) и [List virtual currency balances](api-adapty/operations/listVirtualCurrencyBalances). Чтобы посмотреть готовый пример, следуйте [Virtual currency quickstart](virtual-currency-quickstart). --- # File: virtual-currency-balance --- --- title: "Баланс виртуальной валюты" description: "Смотрите балансы виртуальной валюты пользователя в его профиле и просматривайте историю каждого изменения баланса." --- :::link Основная статья: [Виртуальные валюты](virtual-currencies) ::: В каждом профиле пользователя в [Profiles/CRM](profiles-crm) отображается баланс виртуальной валюты и история всех изменений. ## Просмотр балансов в профиле пользователя \{#view-balances-in-a-user-profile\} Откройте [профиль](https://app.adapty.io/profiles/users) пользователя. Карточка **Virtual currency** отображает все валюты, которыми владеет пользователь. Каждая строка содержит: - **Код** валюты, например `COINS`. - Текущий **баланс** в виде целого числа. Балансы обновляются по мере того, как пользователь зарабатывает, тратит или получает кредиты. Баланс не может опуститься ниже 0: транзакция, пытающаяся потратить больше, чем есть у пользователя, завершается ошибкой `insufficient_balance` и ничего не меняет. У профиля без балансов отображается пустая карточка. Чтобы считать баланс из собственного кода, вызовите [List virtual currency balances](api-adapty/operations/listVirtualCurrencyBalances) в серверном API. ## Просмотр изменений баланса в истории событий \{#review-balance-changes-in-the-event-history\} Каждое изменение баланса отображается в истории событий профиля, начиная с самого последнего. Каждая запись содержит название изменения и перечень затронутых валют. В истории отображается три типа событий виртуальной валюты: - **Virtual currency credited**: покупка, продление или начало пробного периода начислили кредиты. - **Virtual currency transaction**: серверный вызов API пополнил или списал баланс. - **Virtual currency expired**: срочные кредиты обнулились в конце расчётного периода или после окончания подписки. Для каждой изменённой валюты в записи указывается: - **Virtual currency**: Название и код валюты, например `Gold Coins (COINS)`. - **Amount**: Изменение со знаком — положительное для пополнения, отрицательное для списания. - **Balance after**: Баланс валюты после применения изменения. Одно событие может изменить несколько валют одновременно — например, одна [транзакция](api-adapty/operations/createVirtualCurrencyTransaction) списывает одну валюту и пополняет другую для конвертации между ними. ## Балансы, профили и устройства \{#balances-profiles-and-devices\} Баланс всегда принадлежит ровно одному [профилю](profiles-crm) и никогда не переходит к другому. Adapty не делится балансами между профилями и не переносит их — в отличие от возможности [делиться уровнями доступа между аккаунтами](profiles-crm#sharing-paid-access-between-user-accounts). На практике это означает: - **Идентифицированные пользователи**: идентификатор пользователя всегда указывает на один и тот же профиль, поэтому пользователь видит одинаковый баланс на каждом устройстве, где выполнен вход. - **Анонимные пользователи**: анонимный профиль может накапливать и тратить кредиты, но существует только на одном устройстве. Когда тот же пользователь открывает приложение на другом устройстве без входа в систему, Adapty создаёт новый анонимный профиль с нулевым балансом. Чтобы запустить систему виртуальной валюты на разных устройствах, идентифицируйте пользователей. Это можно сделать в коде приложения через SDK (см. [Идентификация пользователей](ios-quickstart-identify) в быстром старте) или на стороне бэкенда через [серверный API](getting-started-with-server-side-api). Позднее определение пользователя имеет свою особенность: если пользователь получает кредиты анонимно, а затем входит с customer user ID, который уже принадлежит другому профилю, устройство переключится на тот профиль с его балансом. Кредиты, накопленные анонимно, останутся на старом профиле и станут недоступны. Если customer user ID новый, он привязывается к текущему профилю, и пользователь сохраняет баланс. Для надёжности идентифицируйте пользователей до того, как они смогут зарабатывать или покупать кредиты. В вызовах [server-side API](getting-started-with-server-side-api) идентифицируйте профиль с помощью заголовка `adapty-profile-id` или `adapty-customer-user-id` — оба указывают на один профиль. Для анонимных профилей используйте `adapty-profile-id`. --- # File: app-store-offers --- --- title: "Офферы в App Store" description: "Настройте и управляйте офферами App Store для повышения удержания пользователей." --- :::info Настройте [продукты в сторе](quickstart-products) перед тем, как следовать этому гайду. ::: Офферы в App Store — это специальные предложения, пробные периоды или скидки для авторегенерируемых подписок. Они включают скидки и пакетные предложения, которые помогают привлекать новых пользователей и повышать конверсию. Существует четыре типа офферов в App Store, и Adapty поддерживает их все: - **[Introductory offers](#introductory-offers) для новых пользователей**: - Бесплатные или со скидкой периоды подписки - Доступны только новым пользователям (тем, кто ни разу не активировал introductory offer и не имел подписки) - Привязывать их к продуктам в Adapty не нужно. Adapty автоматически применяет офферы для подходящих пользователей при покупке продукта. - **[Promotional](#promotional-offers) и [win-back](#win-back-offers) офферы**: - Adapty применяет эти офферы автоматически в момент покупки, но сначала нужно настроить их в продуктах и пейволах. - Promotional offers включают бесплатные периоды подписки, скидки в процентах и скидки с фиксированной ценой. Подходящим может быть любой пользователь. - Win-back offers включают бесплатные периоды подписки или скидки в процентах. Доступны только ушедшим пользователям. - **Промокоды**: подробнее см. в разделе [Использование промокодов в iOS](making-purchases#redeem-offer-codes-in-ios). :::important Чтобы использовать предложения App Store, загрузите [ключ подписки](app-store-connection-configuration#step-4-for-trials-and-special-offers--set-up-promotional-offers) в дашборд Adapty. ::: ## Introductory offers Adapty автоматически применяет introductory offers на iOS, если пользователь соответствует условиям. Чтобы включить introductory offers для продаваемых продуктов, достаточно создать их в App Store Connect: 1. Откройте приложение в App Store Connect и перейдите в **Monetization > Subscriptions**. 2. Выберите группу подписок и найдите нужную подписку. У подписки должна быть настроена длительность. 3. Нажмите **View all Subscription Pricing** и перейдите на вкладку **Introductory offers**. Нажмите **Set up introductory offer**. 4. Выберите страны и регионы, в которых будет доступен introductory offer. 5. Выберите даты начала и окончания introductory offer. Если у introductory offer нет конкретной даты окончания, выберите **No end date**. Нажмите **Next**. 6. Выберите тип introductory offer. В зависимости от выбора нужно также указать продолжительность и цену предложения. Подробнее читайте в [документации Apple](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-introductory-offers-for-auto-renewable-subscriptions). 7. Проверьте выбранные параметры и нажмите **Confirm**. По завершении настройки ничего дополнительно делать в Adapty не нужно. Предложение автоматически активируется для подходящих пользователей при покупке продукта. Убедитесь, что пейвол с этим продуктом показывается только тем пользователям, которые имеют право на получение предложения. ## Promotional offers Adapty автоматически применяет promotional offers для подходящих пользователей. Сначала настройте офферы в App Store Connect, затем добавьте их к продукту и пейволу в Adapty: 1. Откройте своё приложение в App Store Connect и выберите **Monetization > Subscriptions** в левом меню. 2. Выберите группу подписок и перейдите к нужной подписке. У подписки должна быть настроена длительность. 3. Нажмите **View all Subscription Pricing** и перейдите на вкладку **Promotional offers**. Нажмите **Set up promotional offer**. 4. Укажите детали promotional offer. Эти значения нельзя изменить после создания, поэтому выбирайте их внимательно. - **Promotional offer reference name**: Название promotional offer. Пользователи его не увидят. - **Promotional offer identifier**: Идентификационный код promotional offer. Он понадобится для добавления предложения в Adapty. 5. Выберите тип promotional offer. Тип определяет, платят ли пользователи сниженную цену или получают бесплатный период. Для скидки выберите **Pay as you go** или **Pay up front**. Для бесплатного периода подписки выберите **Free**. Затем задайте продолжительность и цену предложения. Подробнее читайте в [документации Apple](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-promotional-offers-for-auto-renewable-subscriptions). 6. При необходимости задайте разные цены для разных стран и регионов и нажмите **Next**. 7. Проверьте выбор и нажмите **Confirm**. 8. [Добавьте promotional offer](create-offer) в Adapty. ## Win-back offer :::important Прежде чем создать win-back offer, ваша подписка должна пройти проверку App Review. ::: Adapty автоматически применяет win-back offer, если пользователи соответствуют условиям. Сначала настройте офферы в App Store Connect, затем добавьте их к продукту и пейволу в Adapty: 1. Откройте своё приложение в App Store Connect и перейдите в **Monetization > Subscriptions** через меню слева. 2. Выберите группу подписок и найдите нужную подписку. У подписки должна быть настроена длительность. 3. Нажмите **View all Subscription Pricing** и перейдите на вкладку **Win-back offers**. Нажмите **Create offer**. 4. Заполните детали win-back offer. Эти значения нельзя изменить после создания. - **Reference name**: название оффера. Пользователи его не увидят. - **Offer identifier**: идентификационный код оффера. Он понадобится для добавления оффера в Adapty. 5. Настройте тип оффера, длительность и цену. Подробнее в [документации Apple](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-win-back-offers). 6. Проверьте настройки и нажмите **Confirm**. 7. [Добавьте оффер](create-offer) в Adapty. ## Следующие шаги \{#next-steps\} После добавления офферов продолжите настройку: - Если у вас есть **приложения в Google Play**, настройте [офферы Google Play](google-play-offers). - Если у вас есть **promotional или win-back офферы**, [добавьте их в Adapty](create-offer). - Если у вас только **introductory offers** и нет promotional или win-back офферов — всё готово. Эти разделы также могут быть полезны: - [Работа с офферами в Paywall Builder Adapty](create-offer#paywall-builder) - [Как Adapty работает с офферами](create-offer#how-adapty-works-with-offers) --- # File: google-play-offers --- --- title: "Офферы в Google Play" description: "Настройте офферы Google Play для улучшения монетизации и удержания пользователей." --- В Google Play офферы любого типа (бесплатные пробные периоды или скидки) добавляются как **offers**. Чтобы создать оффер, сначала нужно создать подписку и добавить автоматически возобновляемый базовый план. Офферы всегда создаются для базовых планов в подписках. На скриншоте ниже видна подписка `premium_access`(1) с двумя базовыми планами: `1-month` (2) и `1-year` (3). <img src="/assets/shared/img/c0b1dfa-001930-November-03-XYnbieeu.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Чтобы создать оффер в Google Play Console: 1. Нажмите **Add offer** и выберите базовый план из списка. <img src="/assets/shared/img/75a5d69-eb0bc9a-001931-November-03-eQdthUMx.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Введите ID оффера. Он будет использоваться в аналитике и дашборде Adapty, поэтому дайте ему понятное имя. <img src="/assets/shared/img/ff282c2-c0b1dfa-001930-November-03-XYnbieeu.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Выберите критерии доступности: 1. **New customer acquisition**: оффер будет доступен только новым подписчикам, если они ранее не использовали этот оффер. Это наиболее распространённый вариант, который рекомендуется использовать по умолчанию. 2. **Upgrade**: оффер будет доступен пользователям, переходящим с другой подписки. Используйте его, когда хотите продвигать более дорогие планы существующим подписчикам — например, при переходе с бронзового на золотой уровень подписки. 3. **Developer determined**: вы можете управлять тем, кто может использовать этот оффер, через код приложения. Будьте осторожны при использовании в продакшене — есть риск мошенничества: пользователи могут снова и снова активировать бесплатную или скидочную подписку. Хороший сценарий для этого типа оффера — возврат отписавшихся пользователей. <img src="/assets/shared/img/ee302dc-a506e5a-001934-November-03-TVBLOz2L.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Добавьте до двух ценовых фаз к офферу. Доступны три типа фаз: 1. **Free trial**: подписка бесплатна в течение заданного периода (минимум 3 дня). Это наиболее распространённый тип оффера. 2. **Single payment**: подписка дешевле при единовременной оплате. Например, обычно месячный план стоит $9.99, но с этим типом оффера первые три месяца обойдутся в $19.99 — скидка 30%. 3. **Discounted recurring payment**: подписка дешевле в течение первых `n` периодов. Например, обычно месячный план стоит $9.99, но с этим типом оффера каждый из первых трёх месяцев стоит $4.99 — скидка 50%. Оффер может иметь две фазы. В таком случае первая фаза должна быть Free trial, а вторая — либо Single payment, либо Discounted recurring payment. Они применяются именно в этом порядке. <img src="/assets/shared/img/d6267f3-a48f79e-001936-November-03-A13wutRh.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important Обратите внимание: пейволы, созданные с помощью Adapty Paywall Builder, отображают только первую фазу многофазного оффера Google подписки. При этом при совершении покупки пользователем все фазы оффера будут применены согласно настройкам в Google Play. ::: 5. Активируйте оффер, чтобы использовать его в приложении. <img src="/assets/shared/img/d3fc09b-f149ba6-001937-November-03-MO9Gz3ap.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. Перейдите к [добавлению оффера в Adapty](create-offer). :::note ID офферов могут совпадать для разных базовых планов. ::: ## Следующие шаги \{#next-steps\} После добавления офферов продолжите настройку: - Если у вас есть **приложения в App Store**, перейдите к [гайду по App Store](app-store-offers). - Если у вас **только приложения в Google Play**, следуйте [этому гайду](create-offer), чтобы добавить офферы в Adapty. --- # File: create-offer --- --- title: "Добавление офферов в Adapty" description: "Создавайте специальные офферы для подписок и управляйте ими с помощью инструментов Adapty." --- Adapty позволяет предлагать пробные периоды или скидки новым, текущим или ушедшим подписчикам. После настройки в App Store Connect или Google Play Console нужно добавить их в Adapty в два шага: 1. [Добавьте офферы к продуктам в Adapty, используя идентификаторы офферов из сторов.](#1-create-offer) 2. [Отобразите оффер во флоу или на пейволе.](#2-display-offer) :::warning Introductory offers (App Store) применяются автоматически, если пользователь имеет на них право. Не добавляйте их к продуктам в Adapty. В этом гайде объясняется, как настроить promotional offers (App Store), win-back offers (App Store) и все офферы Google Play. ::: ## 0. Прежде чем начать \{#before-you-start\} Прежде чем настраивать офферы в Adapty, убедитесь в следующем: 1. Вы создали все необходимые офферы в сторе: - [App Store](app-store-offers) - [Google Play](google-play-offers) 2. Вы создали [продукты](create-product) в Adapty и добавили их идентификаторы. 3. Для App Store: вы загрузили [ключ встроенных покупок для promotional offer](app-store-connection-configuration#step-4-for-trials-and-special-offers--set-up-promotional-offers). ## 1. Добавьте оффер к продукту в Adapty \{#1-add-offer-to-product-in-adapty\} Как только ваш promotional offer (для Play Store и App Store) или win-back offer (для App Store) настроен в сторах, добавить его в Adapty несложно: 1. Откройте [**Products**](https://app.adapty.io/products) в главном меню Adapty. Найдите продукт, к которому хотите добавить оффер. 2. Найдите нужный продукт. В колонке **Actions** нажмите кнопку **3-dot** рядом с продуктом и выберите **Edit**. 3. В окне **Edit product** нажмите **+** и выберите **Add offers**. 4. Нажмите **Add offer**. 5. Введите данные offer для продукта. Поля для offer: - **Offer name**: Дайте офферу название, чтобы легко находить его в Adapty. Используйте любое удобное для вас название. - **App Store Offer type**: Выберите тип оффера App Store: Promotional или Win-back. (Introductory offers добавлять не нужно — они применяются автоматически, если доступны.) - **App Store Offer ID**: Уникальный идентификатор оффера, [который вы задали в App Store](app-store-products). - **Play Store Offer ID**: Аналогично — уникальный идентификатор оффера, [который вы задали в Play Store](android-products). :::tip Если поле **App Store Offer ID** или **Play Store Offer ID** неактивно, перейдите на вкладку **Products** и выберите ID продукта. ::: 6. (Опционально) Добавьте другие офферы, нажав **Add offer**. 7. Нажмите **Save**, чтобы сохранить офферы для продукта. ## 2. Покажите предложение \{#2-display-offer\} Once an offer is attached to a product, surface it where users see that product — in a flow or in a paywall. ### Добавление оффера во флоу \{#add-offer-to-flow\} В [Flow Builder](adapty-flow-builder) оффер привязывается к продукту в элементе Products. Сначала добавьте элемент продукта и назначьте ему продукты — см. [Настройка покупок](paywall-product-block). Чтобы привязать оффер: 1. На холсте выберите карточку продукта, для которой нужно показать оффер. 2. На правой панели в разделе **Product** выберите продукт, затем выберите оффер из выпадающего списка **Select offer (optional)**. ### Добавление оффера на пейвол \{#add-offer-to-paywall\} :::info Нельзя добавлять офферы на пейволы в статусе **live**. Если вы хотите добавить оффер на существующий пейвол, [создайте его копию](duplicate-paywalls) и настройте продукты в новом пейволе. ::: Чтобы оффер стал виден и доступен для выбора пользователями в [пейволе](paywalls), выполните следующие шаги: 1. При создании или редактировании пейвола на вкладке **General** добавьте продукт, к которому вы только что добавили офер. 2. Выберите ранее созданный офер для этого продукта из списка **Offer**. Список доступен только для продуктов, у которых есть оферы. 3. При необходимости добавьте другие продукты и оферы, но для каждого продукта можно добавить только один офер. ## Как Adapty работает с офферами \{#how-adapty-works-with-offers\} Обратите внимание на то, как работают офферы в Adapty: - Когда пользователь имеет право на оффер, Adapty автоматически применяет настроенный вами оффер при совершении покупки. - Если продукт содержит как introductory offer, так и promotional offers, настроенные в App Store, подходящие пользователи сначала получат introductory offer. После окончания его периода, если пользователь по-прежнему имеет право на promotional offer и вы настроили этот оффер в Adapty, он будет применён при следующей попытке приобрести продукт. - Если вам нужен более тонкий контроль над применением офферов или необходимо продавать продукт без офферов в отдельных случаях, у вас есть несколько вариантов: - Настройте критерии eligibility в App Store или Google Play Console - Создайте отдельный продукт без офферов в App Store или Google Play Console - Создайте отдельный продукт без офферов в Adapty, добавьте пейволы с обоими вариантами продукта в [плейсмент](placements) и используйте [сегменты](segments) аудитории, чтобы управлять тем, какой пейвол показывается разным пользователям. Например, можно создать сегменты на основе **Subscription product** или **Paid access level**, либо использовать [кастомные атрибуты](profiles-crm) для реализации собственной логики. --- # File: create-access-level --- --- title: "Создание уровня доступа" description: "Создавайте и назначайте уровни доступа в Adapty для более точной сегментации пользователей." --- Уровни доступа позволяют управлять тем, что пользователи могут делать в вашем приложении, без жёсткой привязки к конкретным идентификаторам продуктов. Каждый продукт определяет, на какой срок пользователь получает тот или иной уровень доступа. Таким образом, при каждой покупке Adapty предоставляет доступ к приложению на определённый период (для подписок) или навсегда (для покупок с пожизненным доступом). При создании приложения в дашборде Adapty автоматически генерируется уровень доступа `premium`. Он является уровнем доступа по умолчанию и не может быть удалён. :::tip Вы также можете создавать уровни доступа программно с помощью [Developer CLI](developer-cli-reference#adapty-access-levels-create). ::: Чтобы создать новый уровень доступа: 1. Перейдите в раздел **[Products](https://app.adapty.io/access-levels)** из главного меню Adapty и выберите вкладку **Access levels**. <img src="/assets/shared/img/access-level-list.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите **Create access level**. <img src="/assets/shared/img/b8646ca-image.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. В открывшемся окне **Create access level** задайте идентификатор уровня доступа. Этот ID будет использоваться в вашем мобильном приложении для предоставления доступа к дополнительным функциям после покупки, а также для отличия одного уровня доступа от других. Выбирайте понятный и легко читаемый идентификатор. 4. Нажмите **Create access level**, чтобы подтвердить создание уровня доступа. --- # File: assigning-access-level-to-a-product --- --- title: "Привязка уровня доступа к продукту" description: "Привязывайте уровни доступа к продуктам для удобного управления подписками." --- Каждый [продукт](product) должен быть связан с уровнем доступа — это гарантирует, что после покупки пользователь получит доступ к нужному контенту. Adapty автоматически определяет длительность подписки и использует её как дату истечения уровня доступа. Если пользователь приобретает продукт с пожизненным доступом, уровень доступа остаётся активным бессрочно. Чтобы привязать уровень доступа к продукту: 1. При [настройке продукта](create-product) выберите нужный уровень доступа из списка **Access Level ID**. <img src="/assets/shared/img/access-level-product.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите **Save**. --- # File: give-access-level-to-specific-customer --- --- title: "Выдать уровень доступа конкретному пользователю" description: "Назначайте определённые уровни доступа пользователям с помощью инструментов Adapty." --- Вы можете вручную изменить уровень доступа для конкретного пользователя прямо в дашборде Adapty. Это удобно, например, в сценариях поддержки — скажем, если вы хотите продлить премиум-доступ пользователю на неделю в знак благодарности за отличный отзыв. ## Выдать уровень доступа конкретному пользователю в дашборде Adapty \{#give-access-level-to-a-specific-customer-in-the-adapty-dashboard\} 1. Перейдите в раздел **[Profiles and Segments](https://app.adapty.io/placements)** главного меню Adapty. <img src="/assets/shared/img/profiles-list.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите на пользователя, которому хотите предоставить доступ. 3. Нажмите **Add access level**. <img src="/assets/shared/img/add-access-level.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Выберите уровень доступа для предоставления и срок его действия для данного пользователя. 5. Нажмите **Apply**. ## Выдать уровень доступа конкретному пользователю через API \{#give-access-level-to-a-specific-customer-via-api\} Вы также можете назначить уровень доступа пользователю со своего сервера через Adapty API. Это удобно, если у вас есть бонусы за рефералов или другие события, связанные с вашими продуктами. Подробнее — на странице [Выдача уровня доступа через серверный API](api-adapty/operations/grantAccessLevel). --- # File: local-access-levels --- --- title: "Локальные уровни доступа" description: "Управляйте уровнями доступа в случае временных сбоев." --- :::important Обратите внимание на следующее: - Локальные уровни доступа поддерживаются в Adapty SDK начиная с версии 3.12. - По умолчанию локальные уровни доступа отключены на Android из соображений безопасности. Если они вам нужны, включите их при инициализации SDK: [Android](sdk-installation-android#enable-local-access-levels), [React Native](sdk-installation-reactnative), [Flutter](sdk-installation-flutter#enable-local-access-levels-android). ::: Каждый настроенный вами продукт связан с [**уровнем доступа**](access-level). Когда пользователь совершает покупку, Adapty SDK присваивает уровень доступа [профилю](profiles-crm) пользователя — именно по нему вы определяете, может ли пользователь получить доступ к платному контенту в приложении. Adapty SDK очень надёжен, и его серверы крайне редко бывают недоступны. Но даже в этом редком случае ваши пользователи ничего не заметят. Если пользователь совершил покупку, но Adapty не может получить ответ, SDK переключается на прямую проверку покупок в сторе. В этом случае уровень доступа предоставляется локально в приложении — никаких дополнительных настроек для этого не требуется. SDK делает всё это автоматически в фоновом режиме, и пользователи получат доступ к тому, за что заплатили, как обычно. Обратите внимание на особенности работы локальных уровней доступа: - Когда пользователи снова выходят в сеть, информация о транзакциях автоматически передаётся на серверы Adapty, которые применяют транзакции к профилю пользователя и возвращают обновлённый профиль в SDK. - Обновлённые данные не появятся в аналитике Adapty до тех пор, пока данные не будут переданы. - Локальные уровни доступа работают только когда серверы Adapty недоступны. В остальных случаях SDK использует кешированные данные. - Локальные уровни доступа не работают для расходуемых покупок, за исключением случаев, когда расходуемому продукту в дашборде Adapty назначен тип подписки (ежемесячная, ежегодная, еженедельная и т. д.). --- # File: choose-meaningful-placements --- --- title: "Выбирайте значимые плейсменты" description: "Оптимизируйте плейсменты флоу и пейволов в Adapty для повышения вовлечённости пользователей и увеличения дохода." --- При [создании плейсментов](create-placement) важно учитывать логику флоу вашего приложения и пользовательский опыт, который вы хотите создать. В большинстве приложений достаточно не более 5 [плейсментов](placements), чтобы при этом сохранить возможность проводить эксперименты. Вот пример того, как можно организовать плейсменты: <img src="/assets/shared/img/placement-flows.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. **Онбординг-флоу:** Это первое взаимодействие пользователей с вашим приложением. Отличный момент, чтобы показать ценность продукта — объедините здесь флоу, онбординг и пейвол-плейсменты. Более 80% подписок оформляется именно во время онбординга, поэтому важно делать акцент на самых прибыльных подписках. С Adapty вы легко настроите разные [флоу](adapty-flow-builder), [онбординги](onboardings) и [пейволы](paywalls) для разных аудиторий и запустите A/B-тесты, чтобы найти лучший вариант для своего приложения. Например, можно запустить A/B-тест для пользователей из США, показывая более дорогие подписки в 50% случаев. 2. **Настройки приложения:** Если пользователь не оформил подписку во время онбординга, создайте флоу или пейвол-плейсмент внутри приложения — в настройках или после выполнения какого-то целевого действия. Поскольку пользователи внутри приложения обдумывают подписку более взвешенно, продукты здесь могут быть немного дешевле, чем на этапе онбординга. 3. **Промо:** Если пользователь так и не оформил подписку после нескольких просмотров флоу или пейвола, возможно, цены для него слишком высоки или он в целом скептически относится к подпискам. В таком случае покажите ему специальное предложение с самой доступной подпиской или даже с продуктом с пожизненным доступом. Это поможет привлечь тех, кто чувствителен к цене или сомневается в необходимости подписки. В большинстве приложений логика и плейсменты схожи — они следуют пути пользователя и ключевым точкам, в которых можно показывать флоу, пейволы, онбординги или A/B-тесты для роста конверсий и дохода. Вы можете настраивать их в каждом плейсменте, чтобы экспериментировать и оптимизировать стратегии монетизации. --- # File: create-placement --- --- title: "Создание плейсмента" description: "Создавайте плейсменты в Adapty и управляйте ими для улучшения работы флоу и пейволов." --- [Плейсмент](placements) — это конкретное место в мобильном приложении, где можно показать флоу, пейвол, онбординг или A/B-тест. Например, выбор подписки может появляться во флоу при запуске приложения, а расходуемый продукт (например, золотые монеты) — когда у пользователя заканчиваются монеты в игре. В разных плейсментах или для разных сегментов пользователей можно показывать одни и те же или разные флоу, пейволы, онбординги или A/B-тесты — в Adapty такие сегменты называются «аудиториями». Советы по выбору подходящих плейсментов читайте в разделе [Выбор значимых плейсментов](choose-meaningful-placements). :::tip Вы также можете создавать плейсменты программно с помощью [Developer CLI](developer-cli-reference#adapty-placements-create). ::: :::info Хотя процесс создания плейсментов похож для флоу, пейволов и онбордингов, нельзя создать один плейсмент, который обслуживает более одного типа — каждый тип плейсмента обрабатывает разные метрики. ::: ## Создание и настройка плейсмента \{#create-and-configure-a-placement\} 1. Перейдите в раздел **[Placements](https://app.adapty.io/placements)** главного меню Adapty. Выберите вкладку **Flows**, **Paywalls** или **Onboardings** в зависимости от типа плейсмента, который хотите создать. 2. Нажмите **Create placement**. <img src="/assets/shared/img/create-placement-2.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Введите **Placement name** — внутренний идентификатор в дашборде Adapty. При необходимости его можно изменить позже. 4. Введите **Placement ID** — этот идентификатор используется в SDK для вызова [флоу](adapty-flow-builder), [пейволов](paywalls), [онбордингов](onboardings) и [A/B-тестов](ab-tests) плейсмента. Изменить его позже не получится, так как он уникален для каждого плейсмента. Затем назначьте плейсменту флоу, пейвол, онбординг или A/B-тест. Adapty поддерживает [аудитории](audience) — сегменты пользователей на основе [сегментов](segments) — так что вы можете показывать разный контент разным группам пользователей. Если таргетинг не нужен, аудитория *All users* по умолчанию охватывает всех. :::note Прежде чем продолжить, убедитесь, что вы создали флоу, пейвол, онбординг или A/B-тест, который хотите запустить, а также аудиторию, которую хотите указать. ::: 1. В окне **Placements/ Your placement** добавьте флоу, пейвол, онбординг или A/B-тест для отображения аудитории *All users* по умолчанию. Для этого нажмите кнопку **Run flow**, **Run paywall** или **Run A/B test** (название зависит от типа плейсмента), затем выберите нужный флоу, пейвол, онбординг или A/B-тест из выпадающего списка. 2. Если вы хотите использовать в плейсменте несколько аудиторий для создания персонализированного контента для разных групп пользователей, нажмите кнопку **Add audience** и выберите нужный сегмент пользователей из списка. <Zoom> <img src="/docs/img/placement-add-audience.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. Теперь добавьте флоу, пейвол, онбординг или A/B-тест, который будет показываться этой аудитории. 4. Добавьте столько аудиторий, сколько нужно. 5. Если у вас более одной аудитории, убедитесь, что приоритеты аудиторий расставлены верно. 6. Нажмите кнопку **Save and publish**. Как только плейсмент сохранён и опубликован, всё готово — используйте **Placement ID** в коде приложения, чтобы получить и отобразить его. ## Следующие шаги \{#next-steps\} Отображение пейволов в приложении: [iOS](ios-present-paywalls) | [Android](android-present-paywalls) | [React Native](react-native-present-paywalls) | [Flutter](flutter-present-paywalls) | [Unity](unity-present-paywalls) | [Kotlin Multiplatform](kmp-present-paywalls) | [Capacitor](capacitor-present-paywalls) Отображение онбординга в приложении: [iOS](ios-present-onboardings) | [Android](android-present-onboardings) | [React Native](react-native-present-onboardings) | [Flutter](flutter-present-onboardings) | [Unity](unity-present-onboardings) | [Kotlin Multiplatform](kmp-present-onboardings) | [Capacitor](capacitor-present-onboardings) --- # File: edit-placement --- --- title: "Редактирование плейсмента" description: "Узнайте, как редактировать плейсменты в Adapty для оптимизации флоу, видимости пейволов и вовлечённости пользователей." --- [Плейсмент](placements) — это конкретное место в вашем мобильном приложении, где может отображаться флоу, пейвол, онбординг или A/B-тест. Например, выбор подписки может появляться во флоу при запуске приложения, а расходуемый продукт (например, золотые монеты) — когда у пользователя в игре заканчиваются монеты. Вы можете показывать одни и те же или разные флоу, пейволы, онбординги или A/B-тесты в нескольких плейсментах или для разных пользовательских сегментов, которые в Adapty называются аудиториями. Чтобы отредактировать существующий плейсмент: 1. Перейдите в раздел **[Placements](https://app.adapty.io/placements)** главного меню Adapty. Переключитесь на вкладку **Flows**, **Paywalls** или **Onboardings** в зависимости от типа плейсмента, который нужно изменить. 2. Нажмите на нужный плейсмент. 3. Нажмите **Edit placement** в правом верхнем углу. 4. Внесите необходимые изменения. Подробнее о доступных параметрах читайте в разделе [Создание плейсмента](create-placement). 5. Нажмите кнопку **Save and publish**, чтобы подтвердить изменения. --- # File: export-placements --- --- title: "Экспорт плейсмента" description: "Узнайте, как экспортировать плейсменты в Adapty для оптимизации флоу, видимости пейволов и взаимодействия с пользователями." --- Когда вы работаете с несколькими флоу, пейволами и онбордингами, важно отслеживать, что именно и каким пользователям показывается. Вы можете экспортировать все настройки [плейсментов](placements) в CSV-файл, чтобы увидеть, какой флоу/пейвол/онбординг отображается для каждой аудитории, и проверить конфигурацию после внесения изменений или проведения экспериментов. :::tip Если вам удобнее, вы можете [экспортировать плейсменты через server-side API](api-export-analytics/operations/retrievePlacementInfo). ::: Чтобы экспортировать плейсменты флоу, пейволов или онбордингов: 1. Перейдите в раздел **[Placements](https://app.adapty.io/placements)** главного меню. Переключитесь на вкладку **Flows**, **Paywalls** или **Onboardings** — плейсменты для каждого типа экспортируются отдельно. 2. Нажмите **Export to CSV**. <img src="/assets/shared/img/export-placement.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Экспортированный CSV-файл содержит следующую информацию о ваших плейсментах: - ID плейсмента - Название плейсмента - Название аудитории - Название сегмента - Название кросс-плейсментного A/B-теста - Название A/B-теста - Название флоу, пейвола или онбординга (в зависимости от вкладки, из которой был выполнен экспорт) :::note Кросс-плейсментные A/B-тесты не поддерживаются для плейсментов с флоу, поэтому этот столбец будет пустым в экспортах флоу. ::: --- # File: delete-placement --- --- title: "Удаление плейсмента" description: "Узнайте, как удалить плейсмент в Adapty, не затрагивая работу флоу или пейвола." --- [Плейсмент](placements) — это конкретное место в вашем мобильном приложении, где может отображаться флоу, пейвол, онбординг или A/B-тест. :::danger Хотя у вас есть возможность удалить любой плейсмент, крайне важно не удалять плейсмент, который активно используется в вашем мобильном приложении. Удаление активного флоу или плейсмента пейвола приведёт к тому, что резервный пейвол будет показываться постоянно, если вы его [настроили](fallback-paywalls), и вы никогда не сможете заменить его динамическим флоу или пейволом в уже выпущенных версиях приложения. ::: Чтобы удалить существующий плейсмент: 1. Перейдите в **[Placements](https://app.adapty.io/placements)** из главного меню Adapty. Переключитесь на вкладку **Flows**, **Paywalls** или **Onboardings** в зависимости от типа плейсмента, который нужно удалить. 2. Нажмите кнопку **3-dot** рядом с плейсментом и выберите **Delete**. <img src="/assets/shared/img/delete-placement.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. В открывшемся окне **Delete placement** введите название плейсмента, который вы собираетесь удалить. <img src="/assets/shared/img/8177c51-delete_placement.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Нажмите кнопку **Delete forever**, чтобы подтвердить удаление. --- # File: add-audience-paywall-ab-test --- --- title: "Добавление аудитории и флоу, пейвола или A/B-теста к плейсменту" description: "Запускайте A/B-тесты для флоу и пейволов с разными сегментами аудитории в Adapty." --- :::note Прежде чем продолжить, убедитесь, что вы создали флоу, пейвол, онбординг или A/B-тест, который хотите запустить, а также аудиторию, которую хотите указать. ::: 1. В окне **Placements/ Your placement** добавьте флоу, пейвол, онбординг или A/B-тест для отображения аудитории *All users* по умолчанию. Для этого нажмите кнопку **Run flow**, **Run paywall** или **Run A/B test** (название зависит от типа плейсмента), затем выберите нужный флоу, пейвол, онбординг или A/B-тест из выпадающего списка. 2. Если вы хотите использовать в плейсменте несколько аудиторий для создания персонализированного контента для разных групп пользователей, нажмите кнопку **Add audience** и выберите нужный сегмент пользователей из списка. <Zoom> <img src="/docs/img/placement-add-audience.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. Теперь добавьте флоу, пейвол, онбординг или A/B-тест, который будет показываться этой аудитории. 4. Добавьте столько аудиторий, сколько нужно. 5. Если у вас более одной аудитории, убедитесь, что приоритеты аудиторий расставлены верно. 6. Нажмите кнопку **Save and publish**. **Аудитории** в Adapty — это группы пользователей, определяемые с помощью [сегментов](segments). Они позволяют показывать флоу, пейволы, онбординги и A/B-тесты именно тем пользователям, которым они предназначены. Создавайте сегменты с фильтрами, чтобы каждая группа видела нужный контент. Когда вы добавляете аудиторию в [плейсмент](placements), вы нацеливаете флоу, пейволы, онбординги или A/B-тесты на конкретную группу пользователей. Привязка аудитории к плейсменту гарантирует, что нужные пользователи увидят нужный контент в нужный момент своего пути в приложении. Откройте плейсмент, в который хотите добавить флоу, пейвол, онбординг или A/B-тест, либо создайте новый в меню [Placements](https://app.adapty.io/placements). :::note Прежде чем продолжить, убедитесь, что вы создали флоу, пейвол, онбординг или A/B-тест, который хотите запустить, а также аудиторию, которую хотите указать. ::: 1. В окне **Placements/ Your placement** добавьте флоу, пейвол, онбординг или A/B-тест для отображения аудитории *All users* по умолчанию. Для этого нажмите кнопку **Run flow**, **Run paywall** или **Run A/B test** (название зависит от типа плейсмента), затем выберите нужный флоу, пейвол, онбординг или A/B-тест из выпадающего списка. 2. Если вы хотите использовать в плейсменте несколько аудиторий для создания персонализированного контента для разных групп пользователей, нажмите кнопку **Add audience** и выберите нужный сегмент пользователей из списка. <Zoom> <img src="/docs/img/placement-add-audience.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. Теперь добавьте флоу, пейвол, онбординг или A/B-тест, который будет показываться этой аудитории. 4. Добавьте столько аудиторий, сколько нужно. 5. Если у вас более одной аудитории, убедитесь, что приоритеты аудиторий расставлены верно. 6. Нажмите кнопку **Save and publish**. --- # File: change-audience-priority --- --- title: "Изменение приоритета аудитории в плейсменте" description: "Настройте приоритеты аудиторий в Adapty для таргетирования пользователей с персонализированными предложениями." --- Если в одном [плейсменте](placements) настроено несколько аудиторий, пользователь может одновременно попадать в несколько из них. Например, если вы создали аудитории «Новички», «Бегуны» и общую аудиторию «Все пользователи», важно определить, какую из них проверять первой, когда пользователь подходит под несколько критериев. <img src="/assets/shared/img/afee54f-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> В этом случае используется приоритет аудитории. Приоритет аудитории — это числовой порядок, где #1 — наивысший. Он определяет последовательность, в которой проверяются аудитории. Проще говоря, приоритет аудитории помогает Adapty решить, какую аудиторию применить первой при выборе пейвола, онбординга или A/B-теста для отображения. Если приоритет аудитории низкий, подходящие пользователи могут быть пропущены и направлены в другую аудиторию с более высоким приоритетом. Кросс-плейсментные аудитории, то есть созданные для [кросс-плейсментных A/B-тестов](ab-tests#ab-test-types), всегда имеют приоритет над обычными аудиториями. Аудитория «Все пользователи» всегда имеет наименьший приоритет, поскольку является резервной и включает всех, кто не попал ни в одну другую аудиторию. Чтобы изменить приоритеты аудиторий в плейсменте: 1. При создании нового или редактировании существующего плейсмента нажмите **Edit priority**. Кнопка отображается только если в плейсмент добавлено не менее трёх аудиторий («Все пользователи» и ещё две). Если аудиторий меньше, порядок очевиден — аудитория «Все пользователи» идёт последней. <img src="/assets/shared/img/edit-priority.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. В открывшемся окне **Edit audience priorities** перетащите аудитории в нужном порядке. <img src="/assets/shared/img/reorder_audiences.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Нажмите кнопку **Save**. --- # File: placement-metrics --- --- title: "Метрики плейсментов" description: "Анализируйте метрики плейсментов в Adapty для улучшения эффективности пейволов." --- С Adapty вы можете создавать и управлять несколькими плейсментами в приложении, каждый из которых связан с отдельными пейволами или A/B-тестами. Это позволяет таргетировать конкретные сегменты пользователей, экспериментировать с различными предложениями или моделями ценообразования и оптимизировать стратегию монетизации приложения. Для сбора ценной информации о работе ваших плейсментов и вовлечённости пользователей Adapty отслеживает различные взаимодействия и транзакции, связанные с отображаемыми пейволами. Система аналитики фиксирует такие метрики, как просмотры, уникальные просмотры, покупки, триалы, возвраты, конверсию и выручку. Собранные метрики непрерывно обновляются в реальном времени и доступны для анализа в удобном дашборде Adapty. Вы можете настраивать временной диапазон анализа, применять фильтры по различным параметрам и сравнивать метрики по плейсментам, сегментам пользователей или продуктам. Метрики плейсментов доступны в списке плейсментов, где можно получить общее представление о работе всех ваших плейсментов. Этот обзорный вид предоставляет агрегированные метрики по каждому плейсменту, что позволяет сравнивать их эффективность и выявлять тенденции. Для более детального анализа каждого плейсмента можно перейти к подробным метрикам плейсмента. На этой странице вы найдёте исчерпывающие метрики для выбранного плейсмента. Они дают глубокое понимание того, как работает конкретный плейсмент, и помогают оценить его эффективность и принимать решения на основе данных. <img src="/assets/shared/img/3e711fc-CleanShot_2023-07-26_at_14.55.042x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Фильтрация метрик по дате установки \{#filter-metrics-by-install-date\} Метрики пейволов, триалов и покупок можно группировать по двум разным датам: - **Дата события** — когда был просмотрен пейвол, начался триал или совершена покупка. - **Дата установки** — когда пользователь впервые открыл приложение. Два режима могут показывать очень разные цифры для одного и того же периода. Чекбокс **Filter metrics by install date** определяет, какой из них использует дашборд: - **Не отмечен (по умолчанию)**: метрики группируются по дате события. - **Отмечен**: метрики группируются по дате установки. **Пример.** Вы задаёте период с 1 по 30 апреля и смотрите на триалы. - **Не отмечен**: показывает триалы, которые *начались* в апреле, независимо от того, когда эти пользователи установили приложение. - **Отмечен**: показывает триалы от пользователей, которые *установили* приложение в апреле, независимо от того, когда у них начались триалы. Используйте режим по дате установки, чтобы оценить эффективность привлечения пользователей для конкретной когорты. Используйте режим по дате события, чтобы измерить активность пейвола или онбординга за конкретный период. ### Управление метриками \{#metrics-controls\} Система отображает метрики за выбранный период и организует их по параметру левого столбца с четырьмя уровнями вложенности. #### Варианты отображения данных метрик \{#view-options-for-metrics-data\} На странице метрик плейсмента доступны два варианта отображения данных метрик: по пейволам и по аудиториям. <img src="/assets/shared/img/9d26b32-Export-1690376094858.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> В режиме группировки по пейволам метрики сгруппированы по плейсментам, связанным с пейволом. Это позволяет анализировать метрики в разрезе разных плейсментов. В режиме группировки по аудиториям метрики сгруппированы по целевой аудитории пейвола. Это позволяет оценивать метрики для разных сегментов аудитории. #### Временные диапазоны \{#time-ranges\} Вы можете выбрать один из нескольких временных периодов для анализа данных метрик — дни, недели, месяцы или произвольный диапазон дат. <img src="/assets/shared/img/15d2c3e-CleanShot_2023-07-26_at_16.49.272x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> #### Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: Adapty предлагает мощные инструменты для фильтрации и настройки анализа метрик под ваши задачи. На странице метрик доступны различные временные диапазоны, параметры группировки и варианты фильтрации. - ✅ Фильтрация по: аудитории, пейволу, группе пейволов, плейсменту, стране, стору. - ✅ Группировка по: сегменту, стору и продукту #### График отдельной метрики \{#single-metrics-chart\} Один из ключевых компонентов страницы метрик плейсмента — раздел с графиком, который наглядно отображает выбранные метрики и упрощает их анализ. Раздел с графиком на странице метрик плейсментов включает горизонтальную столбчатую диаграмму, которая наглядно отображает значения выбранной метрики. Каждый столбец соответствует отдельному значению метрики и масштабируется пропорционально, что позволяет сразу оценить данные. Горизонтальная ось показывает анализируемый временной диапазон, а вертикальный столбец — числовые значения метрик. Суммарное значение всех метрик отображается рядом с графиком. <img src="/assets/shared/img/4623c5b-Export-1690375597411.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Кроме того, нажав на значок стрелки в правом верхнем углу раздела графика, можно развернуть вид и отобразить выбранные метрики на полной строке графика. #### Сводка общих метрик \{#total-metrics-summary\} Рядом с графиком отдельной метрики отображается раздел сводки по всем метрикам — он показывает накопленные значения выбранных метрик на конкретный момент времени. Отображаемую метрику можно изменить через выпадающее меню. <img src="/assets/shared/img/0f647cf-CleanShot_2023-07-26_at_14.55.492x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Определения метрик \{#metrics-definitions\} Раскройте потенциал метрик плейсментов с помощью наших подробных определений. От выручки до коэффициентов конверсии — получайте ценные инсайты, которые прокачают ваши стратегии монетизации и помогут приложению расти. <img src="/assets/shared/img/771a0f0-Export-1690375049771.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::note Adapty конвертирует другие валюты в USD по курсу [currencylayer.com](https://currencylayer.com/) (обновляется каждые 8 часов). Курс **фиксируется на момент транзакции** — последующие изменения не влияют на результат конвертации. ::: #### Выручка \{#revenue\} Эта метрика отображает общую сумму дохода в USD от покупок и продлений в рамках конкретных плейсментов. Обратите внимание: при расчёте дохода комиссия App Store или Google Play не учитывается — сумма рассчитывается до вычета каких-либо комиссий. #### Proceeds \{#proceeds\} Эта метрика отражает фактическую сумму в USD, которую владелец приложения получает от покупок и продлений в конкретных плейсментах после вычета комиссии Apple App Store или Google Play Store. Она показывает чистую выручку, которая напрямую влияет на доход приложения. Подробнее о том, как рассчитываются поступления, читайте в [документации](analytics-cohorts#revenue-vs-proceeds) Adapty. #### ARPPU ARPPU означает Average Revenue Per Paying User — средний доход на одного платящего пользователя в рамках конкретных плейсментов. Рассчитывается как общий доход, делённый на количество уникальных платящих пользователей. Например, если общий доход составляет $15 000, а платящих пользователей 1 000, то ARPPU равен $15. #### ARPAS ARPAS, или Average revenue per active subscriber (средний доход на одного активного подписчика), показывает средний доход, получаемый с одного активного подписчика в рамках конкретных плейсментов. Рассчитывается как отношение общего дохода к числу подписчиков, активировавших пробный период или подписку. Например, если общий доход составляет $5 000, а подписчиков 1 000, то ARPAS равен $5. Эта метрика помогает оценить средний потенциал монетизации в расчёте на одного подписчика. #### ARPU \{#arpu\} Только для плейсментов онбординга. ARPU — это средняя выручка на пользователя, просмотревшего онбординг. Рассчитывается как общая выручка, делённая на количество уникальных просмотров. #### Уникальный CR до покупки \{#unique-cr-to-purchases\} Уникальный коэффициент конверсии до покупки рассчитывается как отношение количества покупок в определённых плейсментах к количеству уникальных просмотров. Он отражает соотношение покупок к уникальному числу просмотров, позволяя оценить эффективность конвертации уникальных посетителей в конкретных плейсментах в платящих пользователей. #### CR до покупки \{#cr-to-purchases\} Конверсия в покупки рассчитывается путём деления количества покупок в конкретных плейсментах на общее количество просмотров пейволов. Она показывает, какой процент просмотров в конкретных плейсментах завершается покупкой, и позволяет оценить эффективность вашего пейвола с точки зрения конвертации пользователей в платящих клиентов. #### Уникальный CR в триалы \{#unique-cr-to-trials\} Уникальная конверсия в триалы рассчитывается как отношение числа запущенных триалов в конкретных плейсментах к числу уникальных просмотров. Она показывает, какой процент уникальных просмотров в конкретных плейсментах завершился активацией триала, и помогает оценить эффективность пейвола в части конверсии уникальных посетителей в пользователей триала. #### Покупки \{#purchases\} Покупки представляют собой совокупный итог различных транзакций, совершённых на пейволе в конкретных плейсментах. В эту метрику включены следующие транзакции (продления не учитываются): - Новые покупки, совершённые непосредственно в конкретных плейсментах. - Конверсии триалов, изначально активированных в конкретных плейсментах. - Даунгрейды, апгрейды и кросс-грейды подписок, оформленных в конкретных плейсментах. - Восстановления подписок в конкретных плейсментах — например, когда подписка возобновляется после истечения без автопродления. Учитывая все эти типы транзакций, метрика покупок даёт полное представление об общей активности по привлечению пользователей и монетизации в конкретных плейсментах. #### Триалы \{#trials\} Метрика «Trials» отражает общее количество пробных периодов, активированных в конкретных плейсментах. Она показывает, сколько пользователей начали пробный период через ваш пейвол в этих плейсментах. Метрика помогает отслеживать эффективность пробного предложения и даёт представление о вовлечённости пользователей и конверсии из пробных периодов в платные подписки. #### Trials canceled \{#trials-canceled\} Метрика «отменённые триалы» показывает количество триалов в конкретных плейсментах, в которых была отключена функция автопродления. Это происходит, когда пользователи вручную отказываются от триала, давая понять, что не планируют продолжать подписку после окончания пробного периода. Отслеживание отменённых триалов даёт ценную информацию о поведении пользователей и позволяет понять, с какой частотой они отказываются от триала в конкретных плейсментах. #### Возвраты \{#refunds\} Метрика возвратов отражает количество возвращённых покупок и подписок в рамках конкретных плейсментов. Сюда входят транзакции, которые были отменены или возвращены по различным причинам: по запросу пользователя, из-за проблем с оплатой или по другим основаниям согласно применимой политике возвратов. #### Процент возвратов \{#refund-rate\} Процент возвратов рассчитывается как отношение количества возвратов в конкретных плейсментах к числу первичных покупок (продления не учитываются). Например, если было 5 возвратов и 1 000 первичных покупок, процент возвратов составит 0,5%. #### Просмотры \{#views\} Метрика просмотров показывает общее количество раз, когда пользователи видели пейвол в конкретных плейсментах. Каждое посещение пейвола считается отдельным просмотром. Отслеживание просмотров помогает понять уровень вовлечённости и активность пользователей на пейволе, даёт представление об их поведении и об эффективности размещения и дизайна пейвола в конкретных разделах приложения. #### Уникальные просмотры \{#unique-views\} Метрика уникальных просмотров отражает количество уникальных случаев, когда пользователи просматривали пейвол в конкретных плейсментах. В отличие от общего числа просмотров, где каждое посещение считается отдельным, уникальные просмотры учитывают каждого пользователя только один раз — вне зависимости от того, сколько раз он открывал пейвол в этих плейсментах. Отслеживание уникальных просмотров даёт более точное представление о вовлечённости пользователей и охвате пейвола в конкретных плейсментах, поскольку в фокусе — отдельные пользователи, а не общее число визитов. #### Завершения и уникальные завершения \{#completions--unique-completions\} Только для плейсментов с онбордингом. Завершения — это количество раз, когда пользователи проходят плейсмент с онбордингом от первого до последнего экрана. Если кто-то прошёл его дважды, это два **завершения**, но одно **уникальное завершение**. #### Процент уникальных завершений \{#unique-completions-rate\} Только для плейсментов с онбордингом. Количество уникальных завершений, делённое на количество уникальных просмотров. Эта метрика помогает понять, как пользователи взаимодействуют с плейсментом онбординга, и при необходимости внести изменения, если вы замечаете, что пользователи его игнорируют. --- # File: create-paywall --- --- title: "Создание пейвола" description: "Узнайте, как создавать высококонверсионные пейволы с помощью Paywall Builder от Adapty." --- [Пейвол](paywalls) — это конфигурация в Adapty, определяющая, какие продукты предлагать пользователям. В Adapty пейволы — единственный способ получать продукты в приложении. Пейвол нужен вне зависимости от того, как вы его отображаете: - [**Paywall Builder**](adapty-paywall-builder): Дизайн экрана в визуальном редакторе без кода. Adapty отрисовывает его и обрабатывает покупки. - **Кастомный пейвол**: Реализуйте собственный UI и используйте конфигурацию пейвола для получения продуктов. После создания назначьте пейвол [плейсменту](placements) — плейсменты определяют, какой пейвол видят пользователи. Продукты опубликованного пейвола зафиксированы, поэтому его метрики всегда отражают одну и ту же комбинацию, что позволяет сравнивать эффективность разных наборов продуктов и цен. :::tip Вы также можете создавать пейволы программно с помощью [Developer CLI](developer-cli-reference#adapty-paywalls-create). ::: <details> <summary>Перед созданием пейволов (нажмите, чтобы развернуть)</summary> 1. [Создайте хотя бы один продукт](create-product). 2. (опционально) [Создайте оффер](create-offer). </details> ## Создание пейвола \{#create-paywall\} Чтобы создать новый пейвол в дашборде Adapty: 1. Перейдите в раздел [**Paywalls**](https://app.adapty.io/paywalls) в главном меню Adapty. На этой странице отображается обзор всех ваших пейволов и их метрик. 2. Нажмите **Create paywall**. 3. На странице **Paywalls / New paywall** введите **Paywall name** — это имя будет использоваться для идентификации пейвола в дашборде Adapty. 4. Нажмите **Add product**. 5. Выберите продукты, которые будут показываться пользователям. :::note - Порядок продуктов в этом списке сохранится в SDK, поэтому расставьте их в нужном порядке. - После того как пейвол показан в продакшене, изменить продукты на нём невозможно, так как это повлияет на метрики пейвола. ::: 6. Если вы предлагаете бесплатные пробные периоды или другие офферы для продуктов, добавьте их здесь, иначе они не будут доступны. Выберите оффер, [созданный ранее](create-offer) для этого продукта, из списка **Offer**. Список доступен только для продуктов, у которых есть офферы. 7. Нажмите **Create as a draft**, чтобы подтвердить создание пейвола. Пейвол создан! <img src="/assets/shared/img/create-paywall.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '900px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Дальнейшие шаги \{#next-steps\} После создания первого пейвола: 1. Добавьте его в [плейсмент](placements). Идентификаторы плейсментов — единственные захардкоженные сущности. Они используются для получения продуктов для продажи. 2. Дальнейшая работа с пейволом зависит от вашей реализации: - Если вы хотите использовать [Adapty Paywall Builder](adapty-paywall-builder), оформите пейвол в визуальном редакторе без кода. Adapty отрисует пейвол и обработает логику покупки, а вам останется только отобразить его в коде приложения. - Если вы используете кастомный пейвол, воспользуйтесь нашими гайдами по реализации встроенных покупок с Adapty для вашей платформы: - [iOS](ios-implement-paywalls-manually) - [Android](android-implement-paywalls-manually) - [React Native](react-native-implement-paywalls-manually) - [Flutter](flutter-implement-paywalls-manually) - [Unity](unity-implement-paywalls-manually) - [Kotlin Multiplatform](kmp-implement-paywalls-manually) --- # File: customize-paywall-with-remote-config --- --- title: "Дизайн пейвола с Remote Config" description: "Настройте свой пейвол с помощью Remote Config в Adapty для более точного таргетинга." --- :::important Этот гайд описывает Remote Config для классических пейволов. Для Flow Builder см. [Настройка флоу с помощью Remote Config](customize-flow-with-remote-config). ::: Remote Config пейвола — мощный инструмент, предоставляющий гибкие возможности настройки. Он позволяет использовать произвольные JSON-данные для точной конфигурации пейволов. С его помощью можно задавать такие параметры, как заголовки, изображения, шрифты, цвета и многое другое. <details> <summary>Прежде чем начать настройку пейвола (нажмите, чтобы развернуть)</summary> 1. [Создайте продукт](create-product). 2. [Создайте пейвол и добавьте в него продукт](create-paywall). </details> Чтобы начать настройку пейвола с помощью Remote Config: 1. Откройте раздел [**Paywalls**](https://app.adapty.io/paywalls) в главном меню Adapty. 2. Нажмите на пейвол, чтобы открыть его. <img src="/assets/shared/img/remote-config.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Перейдите на вкладку **Remote config**. <img src="/assets/shared/img/remote-config-3.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Remote Config имеет 2 режима просмотра: - [Таблица](customize-paywall-with-remote-config#table-view-of-the-remote-config) - [JSON](customize-paywall-with-remote-config#json-view-of-the-remote-config) Оба режима — **Table** и **JSON** — содержат одинаковые элементы конфигурации. Разница лишь в удобстве: режим таблицы предоставляет контекстное меню, которое может пригодиться для исправления ошибок локализации. Переключаться между режимами можно в любой момент, нажав на вкладку **Table** или **JSON**. Какой бы вид настройки пейвола вы ни выбрали, позже вы сможете получить эти данные из SDK с помощью свойств `remoteConfig` или `remoteConfigString` объекта `AdaptyPaywall` и внести нужные изменения в пейвол. Вы также можете программно обновлять значения Remote Config через [серверный API](api-adapty/operations/updatePaywall), чтобы динамически изменять конфигурацию пейвола без ручного обновления в дашборде. Вот несколько примеров того, как можно использовать Remote Config. <Tabs groupId="current-os" queryString> <TabItem value="Titles" label="Заголовки" default> ```json showLineNumbers { "screen_title": "Today only: Subscribe, and get 7 days for free!" } # Test titles or others texts ``` </TabItem> <TabItem value="Images" label="Изображения" default> ```json showLineNumbers { "background_image": "https://adapty.io/media/paywalls/bg1.webp" } # Test images on your paywall ``` </TabItem> <TabItem value="Fonts" label="Шрифты" default> ```json showLineNumbers { "font_family": "San Francisco", "font_size": 16 } # Test fonts ``` </TabItem> <TabItem value="Color" label="Цвета" default> ```json showLineNumbers { "subscribe_button_color": "purple" } # Test colors of buttons, texts etc. ``` </TabItem> <TabItem value="HTML" label="HTML" default> ```json showLineNumbers { "photo_gallery": "https://adapty.io/media/paywalls/link-to-html-snippet.html" } # Any HTML code that can be displayed on the paywall ``` </TabItem> <TabItem value="Soft/Hard Paywall" label="Мягкий/Жёсткий пейвол" default> ```json showLineNumbers { "hard_paywall": true } # By setting it to true, you disalow skipping paywall without subscribing # You have to handle this logic in your app ``` </TabItem> <TabItem value="Translations" label="Переводы" default> ```json showLineNumbers { "title": { "en": "Try for free!", "es": "¡Prueba gratis!", "ru": "Попробуй бесплатно!" } } ``` </TabItem> </Tabs> Вы можете комбинировать разные варианты и придумывать собственные. Так можно тестировать разные заголовки, тексты, изображения, шрифты, цвета и многое другое. ### JSON-режим Remote Config \{#json-view-of-the-remote-config\} В режиме **JSON** можно вводить любые данные в формате JSON: <img src="/assets/shared/img/3356ff5-remote_config_JSON.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Табличное представление Remote Config \{#table-view-of-the-remote-config\} Если вы редко работаете с кодом и вам нужно подправить отдельные значения JSON, воспользуйтесь режимом **Table** в Adapty. <img src="/assets/shared/img/4c27b2f-remote_config_table.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Это копия вашего JSON в формате таблицы, которую удобно читать и понимать. Цветовая подсветка помогает различать типы данных. Чтобы добавить ключ, нажмите кнопку **Add row**. Мы автоматически проверяем соответствие значений и типов и показываем предупреждение, если ваши изменения могут привести к невалидному JSON. <img src="/assets/shared/img/ef682d8-add_raw.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Дополнительные параметры строк особенно полезны при [локализации пейволов](add-remote-config-locale): <img src="/assets/shared/img/17bcf80-remote_config_table_options.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Теперь нужно [создать плейсмент](create-placement) и добавить в него пейвол. После этого вы сможете <InlineTooltip tooltip="отображать пейволы с Remote Config">[iOS](present-remote-config-paywalls), [Android](present-remote-config-paywalls-android), [React Native](present-remote-config-paywalls-react-native), [Flutter](present-remote-config-paywalls-flutter), и [Unity](present-remote-config-paywalls-unity)</InlineTooltip> в своём мобильном приложении. --- # File: add-paywall-locale-in-adapty-paywall-builder --- --- title: "Добавление локализации в Flow Builder" description: "Добавляйте локализованный контент в Flow Builder от Adapty, чтобы охватить пользователей по всему миру на их родном языке." --- Локализация флоу делает их доступными на нескольких языках. В Flow Builder локализация организована по экранам — каждый из них показывает процент выполнения для отслеживания прогресса перевода. :::tip Завершите настройку флоу в локали по умолчанию, прежде чем добавлять другие языки. ::: ## Добавление и настройка локализации \{#add-and-set-up-localization\} 1. На левой панели нажмите Localizations. Затем нажмите **Add locale**. Выберите языки для добавления. 2. Каждый добавленный язык отображается в виде столбца в таблице локализации, предзаполненного значениями языка по умолчанию. 3. Чтобы сосредоточиться только на незаполненных полях, включите переключатель **Missing only** на левой панели. Таблица отфильтруется и покажет только непереведённые строки. ## Экспорт и импорт для внешнего перевода \{#export-and-import-for-external-translation\} Вы можете экспортировать файл локализации, чтобы передать его переводчикам, и импортировать готовый перевод. На верхней панели инструментов нажмите **Import / Export**. ### Формат экспортируемого файла \{#export-file-format\} При экспорте создаётся файл `.tsv` (значения, разделённые табуляцией), где каждая строка соответствует одному переводимому элементу. Столбцы: | Столбец | Описание | |--------|-------------| | `Screen` | Экран, которому принадлежит элемент (например, `Welcome`, `Quiz`) | | `Element` | Автоматически сгенерированный идентификатор элемента на этом экране. Его можно изменить в **Interactions** > **Element ID**. | | `Property` | Тип свойства (например, `content`) | | `[default_locale]` | Код языка по умолчанию (например, `en`) | | `[locale]` | По одному столбцу на каждую добавленную локаль (например, `fr`, `es`) | Пример: :::note Оставляйте столбцы локалей пустыми для непереведённых строк — Adapty будет считать их отсутствующими. ::: ### Требования к файлу импорта - **Формат**: `.tsv` (значения, разделённые табуляцией) - **Заголовки**: должны содержать столбцы `Screen`, `Element`, `Property` и хотя бы один столбец с локалью - **Названия столбцов с локалями**: должны совпадать с кодами локалей, уже добавленных во флоу. Если файл содержит коды локалей, отсутствующие во флоу, импорт завершится ошибкой. - **Частичный импорт**: можно включить только часть строк — строки, не вошедшие в файл, сохранят текущие значения ## Перевод вручную \{#translate-manually\} Вы также можете вводить переводы напрямую в любую ячейку таблицы локализации. Чтобы управлять конкретной строкой, откройте её контекстное меню (**⋮**): - **Reset to default**: Сбрасывает перевод строки до значений языка по умолчанию. ## Предварительный просмотр локализации \{#preview-the-localization\} Чтобы проверить переводы, переключите активную локаль в Flow Builder и просмотрите каждый экран. --- # File: add-remote-config-locale --- --- title: "Локализация пейволов с помощью Remote Config" description: "Добавляйте локали Remote Config для персонализации пейволов Adapty." --- Адаптация пейволов под разные языки — необходимость в мире с разнообразными культурами. Локализация позволяет создавать персонализированный опыт для пользователей из конкретных регионов. Для каждого пейвола можно добавить версии на разных языках, чтобы ваш продукт был близок местной аудитории. Если вы не используете Adapty Paywall Builder для оформления пейволов, вы всё равно можете локализовать свои пейволы и управлять локализациями без повторного деплоя приложения: 1. Создайте Remote Config с переменными в дашборде Adapty. Переменные могут представлять текст, медиафайлы или другие типы контента. 2. Задайте значения переменных для каждой локали. 3. Обработайте переменные в коде приложения. 4. При получении пейвола с продуктами и передаче локали вы получите корректные значения переменных. Таким образом, локализации не вшиты в код приложения, и вы можете изменять их в любой момент. Как в табличном представлении, так и в формате JSON можно легко настраивать параметры для каждого языка. Например, переводить строковые ключи, переключать булевы значения (например, `TRUE` для английского, `FALSE` для итальянского) или даже менять фоновые изображения. ## Настройка локализации для пейволов на базе Remote Config \{#set-up-localization-for-remote-configured-paywalls\} 1. Перейдите в раздел [**Paywalls**](https://app.adapty.io/paywalls) в Adapty. 2. Нажмите на пейвол, чтобы открыть его. 3. Перейдите на вкладку **Remote config**. <img src="/assets/shared/img/switch_to_remote_config.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Нажмите **Locales** и выберите языки, которые хотите поддержать. Сохраните изменения, чтобы добавить эти локали к пейволу. <img src="/assets/shared/img/add_locale.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Теперь вы можете переводить контент вручную, с помощью ИИ или экспортировать файл локализации для внешних переводчиков. ## Перевод пейволов с помощью ИИ \{#translate-paywalls-with-ai\} Перевод с помощью ИИ — быстрый и эффективный способ локализовать пейвол. Вы можете переводить значения типа **String** и **List**. По умолчанию все строки выбраны (выделены фиолетовым). Строки, которые уже переведены, отмечены зелёным и по умолчанию не включаются в новый перевод. Невыбранные и непереведённые строки отображаются серым. <img src="/assets/shared/img/localization-table.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <img src="/assets/shared/img/localization-json.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. Выберите строки для перевода. Рекомендуется снять галочки со строк, содержащих идентификаторы, URL и переменные, чтобы ИИ их не переводил. 2. Выберите языки для перевода. <img src="/assets/shared/img/localization-table-language.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Нажмите **AI Translate**, чтобы применить переводы. Выбранные строки будут переведены и добавлены к пейволу, переведённые строки станут зелёными. ## Экспорт файлов локализации для внешнего перевода \{#exporting-localization-files-for-external-translation\} Хотя локализация с помощью ИИ становится всё популярнее, вы можете предпочесть более надёжный подход — профессиональных переводчиков или проверенное переводческое агентство. В таком случае вы можете экспортировать файлы локализации, передать их переводчикам, а затем импортировать готовые переводы обратно в Adapty. Кнопка **Export** создаёт отдельные файлы `.json` для каждого языка, упакованные в один архив. Если нужен только один файл, его можно экспортировать напрямую из меню конкретного языка. <img src="/assets/shared/img/localization-single-export.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> После получения переведённых файлов используйте кнопку **Import**, чтобы загрузить их все сразу или по отдельности. Adapty автоматически проверит файлы на соответствие правильному формату. ### Формат файла для импорта \{#import-file-format\} Чтобы импорт прошёл успешно, файл должен соответствовать следующим требованиям: - **Имя файла и расширение:** Имя файла должно совпадать с представляемой локалью и иметь расширение `.json`. Проверить и скопировать название локали можно в дашборде Adapty. Если имя не распознано, импорт завершится ошибкой. <img src="/assets/shared/img/locale-name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> - **Валидный JSON:** Файл должен быть валидным JSON. Если это не так, импорт завершится ошибкой. ## Ручная локализация \{#manual-localization\} Иногда может потребоваться подправить переводы, добавить разные изображения для конкретных локалей или настроить Remote Config напрямую. 1. Выберите элемент, который хотите перевести, и введите новое значение. Можно обновить значения типа **String** и **List** или заменить изображения на более подходящие для данной локали. <img src="/assets/shared/img/032b429-remote_config_localization.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Воспользуйтесь контекстным меню в английской локали для эффективного решения проблем с локализацией: - **Copy this value to all locales**: перезаписывает все изменения, внесённые в не-английских локалях для выбранной строки, заменяя их значением из английской локали. - **Revert all row changes to original values**: отменяет все изменения, внесённые в текущей сессии, и восстанавливает значения до последнего сохранённого состояния. <img src="/assets/shared/img/d7e70f1-remote_confi_loc_table_options.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> После добавления локалей к пейволу убедитесь, что коды локалей корректно реализованы в коде приложения. См. <InlineTooltip tooltip="гайды по использованию локализаций и кодов локалей в приложении">[iOS](localizations-and-locale-codes), [Android](android-localizations-and-locale-codes)</InlineTooltip> --- # File: web-paywall-configuration --- --- title: "Настройка веб-пейвола" --- После нажатия **Create web paywall** на странице **Web paywall** вы будете перенаправлены на отдельную страницу, где можно настроить дизайн веб-пейвола и способ оплаты. ## Настройка способа оплаты \{#set-up-a-payment-method\} Сначала нужно подключить платёжного провайдера, который будет обрабатывать покупки. Доступные варианты: - Stripe - Paddle - Paypal - Solidgate :::important Чтобы аналитика веб-пейвола в Adapty работала корректно, необходимо [добавить продукты](product) вместе с соответствующими идентификаторами продуктов из Stripe/Paddle/другого платёжного провайдера. ::: Чтобы настроить платёжного провайдера: 1. На странице со списком веб-пейволов нажмите **Settings** и перейдите на вкладку **Integrations**. 2. Выберите платёжного провайдера и следуйте инструкциям по интеграции на экране. <img src="/assets/shared/img/web-paywall-configuration-1.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. ⚠️ Если вы выбрали Stripe, убедитесь, что используете ключи из окружения **Test Mode**, несмотря на то что в интерфейсе написано **Sandbox**. Иначе веб-пейвол не будет работать. **Sandbox** в Stripe пока не поддерживается. <img src="/assets/shared/img/web-paywall-configuration-stripe.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Настройка верификации домена для Apple Pay \{#set-up-apple-pay-domain-verification\} В разделе **Settings > Domains** выберите основного платёжного провайдера для верификации домена. Затем подтвердите домены своего пейвола у соответствующего провайдера: **Stripe**: 1. Перейдите в [настройки доменов способов оплаты](https://dashboard.stripe.com/settings/payment_method_domains) и нажмите **Add a new domain**. 2. Добавьте `app.funnelfox.com` и ваш личный субдомен пейвола (он выглядит как `paywalls-....fnlfx.com`). Чтобы найти субдомен, перейдите в **Settings > Domains** и скопируйте значение **Hosted subdomain**. **Paddle**: 1. В консоли Paddle перейдите в **Checkout > Website approval** и нажмите **Add a new domain**. 2. Добавьте `app.funnelfox.com` и ваш личный субдомен пейвола (он выглядит как `paywalls-....fnlfx.com`). Чтобы найти субдомен, перейдите в **Settings > Domains** и скопируйте значение **Hosted subdomain**. Процесс проверки в Paddle выполняется вручную, поэтому нужно подождать, пока домены не перейдут из статуса `Pending` в `Approved`. **FunnelFox Billing**: Следуйте [инструкциям по интеграции FunnelFox Billing](https://funnelfox.com/docs/billing/integration-billing-funnelfox). **SolidGate**: 1. В вашем дашборде Solidgate перейдите в **Developers > Apple Pay Domains**. 2. Нажмите **+ Add new domain** и вставьте домен вашего проекта (из **Settings > Domains** в FunnelFox). При необходимости добавьте также ваш кастомный домен. 3. Чтобы использовать Apple Pay в режиме предварительного просмотра, также добавьте `http://app.funnelfox.com/`. ## Создание и настройка веб-пейвола \{#create-and-configure-a-web-paywall\} 1. На странице со списком веб-пейволов нажмите **Create a paywall**. 2. Введите название пейвола и нажмите **Create**. <img src="/assets/shared/img/web-paywall-configuration-2.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Вы будете перенаправлены на базовый шаблон с двумя вариантами подписки и кнопкой покупки через Apple Pay. На первом экране отображается список планов подписки. Второй и третий экраны — это экраны оформления заказа. Каждый экран соответствует одному из предлагаемых вами планов. Если у вас только один план, удалите лишний экран. Если планов больше, нужно продублировать экраны оформления заказа. Последний экран, который видит пользователь после успешной покупки, — это место, где нужно чётко указать, что он может вернуться в ваше приложение. <img src="/assets/shared/img/web-paywall-configuration-10.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Настройте список планов: добавьте или удалите планы и цены. Все цены и планы на экране не добавляются автоматически, поэтому их нужно настраивать вручную. <img src="/assets/shared/img/web-paywall-configuration-8.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Добавьте или настройте экран оформления заказа для каждого плана. Рекомендуем добавлять итоговую сумму на каждый экран оформления, чтобы пользователи видели стоимость до нажатия кнопки покупки. 6. На экранах оформления заказа уже есть кнопка Apple Pay. Чтобы она работала, настройте на каждом экране следующее: 1. **Product type**: выберите, хотите ли вы добавить пробный период или скидку. 2. **Trial period**: укажите длительность пробного периода. 3. **Product**: выберите продукт из вашего платёжного провайдера. :::important Убедитесь, что продукт добавлен в Adapty. Иначе результат покупки будет установлен по умолчанию. ::: 4. **Subscription discount**: при необходимости выберите купон из вашего платёжного провайдера. <img src="/assets/shared/img/web-paywall-configuration-6.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Теперь нужно связать планы с экранами оформления заказа. На экране выбора плана нажмите кнопку **Continue** и выберите целевой экран для каждого плана. <img src="/assets/shared/img/web-paywall-configuration-9.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Когда пейвол будет готов, нужно получить его ссылку для активации в Adapty. Способ получения ссылки зависит от того, тестируете вы его или запускаете в продакшн: 1. **Для тестирования в песочнице**: нажмите **Preview** в правом верхнем углу и скопируйте ссылку. 2. **Для продакшна**: нажмите **Publish** в правом верхнем углу. Нажмите **Home** и скопируйте ссылку из столбца **URL**. <img src="/assets/shared/img/web-paywall-configuration-11.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Готово! Используйте эту ссылку, чтобы [продолжить настройку](web-paywall#step-2-trigger-the-paywall). --- # File: fallback-paywalls --- --- title: "Резервные пейволы" description: "Используйте резервные пейволы для обеспечения бесперебойного пользовательского опыта в Adapty." --- Чтобы обеспечить бесперебойный пользовательский опыт, важно настроить **резервные версии** ваших [пейволов](paywalls) и [онбордингов](onboardings). Когда приложение загружает пейвол, SDK Adapty запрашивает данные конфигурации с наших серверов. Но что произойдёт, если устройство не может подключиться к Adapty из-за проблем с сетью или сбоя серверов? * Если пользователь уже открывал пейвол ранее и устройство кешировало его данные, приложение загружает данные пейвола **из кеша**. * Если устройство не кешировало пейвол, приложение ищет локально сохранённый файл конфигурации. Это позволяет отобразить пейвол без ошибки. Adapty автоматически генерирует резервные файлы конфигурации, которые вы можете скачать и использовать. Каждый файл содержит платформозависимые конфигурации для *всех* ваших плейсментов. ## С чего начать \{#get-started\} 1. [Скачайте резервный файл конфигурации](/local-fallback-paywalls) из Adapty. 2. Настройте резервные пейволы с помощью SDK Adapty: * [iOS](ios-use-fallback-paywalls) * [Android](android-use-fallback-paywalls) * [React Native](react-native-use-fallback-paywalls) * [Flutter](flutter-use-fallback-paywalls) * [Unity](unity-use-fallback-paywalls) * [Kotlin Multiplatform](kmp-use-fallback-paywalls) * [Capacitor](capacitor-use-fallback-paywalls) ## Ограничения \{#limitations\} Резервные пейволы хранятся локально и имеют фиксированную конфигурацию, поэтому им недоступны динамические возможности обычных пейволов Adapty. * Резервные пейволы не поддерживают [локализацию](paywall-localization). При генерации файла конфигурации Adapty использует локаль по умолчанию — `en`. * Для каждого плейсмента может быть только один резервный пейвол. Если в вашей настройке предусмотрены разные конфигурации пейволов для разных [аудиторий](audience), Adapty использует конфигурацию, предназначенную для «Всех пользователей». * Резервные пейволы не поддерживают [A/B-тесты](ab-tests). Если пейвол участвует в A/B-тесте, в резервный файл конфигурации будет включён вариант с наибольшим весом. * Резервными пейволами нельзя [управлять удалённо](customize-paywall-with-remote-config). Для обновления файла конфигурации необходимо выпустить новую версию приложения в App Store / Google Play. --- # File: local-fallback-paywalls --- --- title: "Скачать резервные пейволы" description: "Используйте локальные резервные пейволы в Adapty, чтобы обеспечить бесперебойный флоу подписок." --- Adapty автоматически генерирует JSON-файлы конфигурации для ваших [резервных пейволов](/fallback-paywalls) — по одному на каждую платформу. Эти файлы также содержат резервные данные для онбордингов. Если в одном плейсменте несколько пейволов или онбордингов, в резервной версии будет включён вариант с наибольшим весом или наиболее широкой аудиторией. Adapty обновляет эти файлы при каждом изменении пейволов или онбордингов. Чтобы скачать файлы резервных конфигураций, выполните следующие шаги: 1. Откройте страницу **[Placements](https://app.adapty.io/placements)**. 2. Нажмите кнопку **Fallbacks**. 3. Выберите целевую платформу (*iOS* или *Android*) из выпадающего списка. 4. Выберите версию SDK, чтобы начать загрузку. <img src="/assets/shared/img/9c63367-placements.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## После загрузки \{#after-the-download\} Следуйте руководству по настройке для вашей платформы: * [iOS](ios-use-fallback-paywalls) * [Android](android-use-fallback-paywalls) * [React Native](react-native-use-fallback-paywalls) * [Flutter](flutter-use-fallback-paywalls) * [Unity](unity-use-fallback-paywalls) * [Kotlin Multiplatform](kmp-use-fallback-paywalls) * [Capacitor](capacitor-use-fallback-paywalls) --- # File: paywall-metrics --- --- title: "Метрики пейвола" description: "Отслеживайте и анализируйте метрики производительности пейволов для повышения дохода от подписок." --- Adapty собирает ряд метрик, которые помогают лучше оценивать эффективность пейволов. Все метрики обновляются в реальном времени, за исключением просмотров — они обновляются раз в несколько минут. Все метрики, кроме просмотров, привязаны к продукту в рамках пейвола. В этом документе описаны доступные метрики, их определения и способы расчёта. Метрики пейволов доступны в списке пейволов — здесь вы сразу видите общую картину эффективности всех пейволов. Сводное представление показывает агрегированные метрики по каждому пейволу, что позволяет оценить их результативность и найти точки роста. Для детального анализа отдельного пейвола перейдите к метрикам конкретного пейвола. Здесь вы найдёте подробные метрики по выбранному пейволу и получите более глубокое понимание его работы. ### Фильтрация метрик по дате установки \{#filter-metrics-by-install-date\} Метрики пейволов, триалов и покупок можно группировать по двум разным датам: - **Дата события** — когда был просмотрен пейвол, начался триал или совершена покупка. - **Дата установки** — когда пользователь впервые открыл приложение. Два режима могут показывать очень разные цифры для одного и того же периода. Чекбокс **Filter metrics by install date** определяет, какой из них использует дашборд: - **Не отмечен (по умолчанию)**: метрики группируются по дате события. - **Отмечен**: метрики группируются по дате установки. **Пример.** Вы задаёте период с 1 по 30 апреля и смотрите на триалы. - **Не отмечен**: показывает триалы, которые *начались* в апреле, независимо от того, когда эти пользователи установили приложение. - **Отмечен**: показывает триалы от пользователей, которые *установили* приложение в апреле, независимо от того, когда у них начались триалы. Используйте режим по дате установки, чтобы оценить эффективность привлечения пользователей для конкретной когорты. Используйте режим по дате события, чтобы измерить активность пейвола или онбординга за конкретный период. ### Управление метриками \{#metrics-controls\} Система отображает метрики за выбранный период и группирует их по параметру левой колонки с тремя уровнями вложенности. Для активного пейвола метрики охватывают период с даты запуска до текущей даты. Для неактивных пейволов — весь период с даты запуска до конца выбранного временного диапазона. Черновики и архивные пейволы включены в таблицу метрик, но если данных по ним нет, они отображаются без метрик. #### Варианты отображения данных метрик \{#view-options-for-metrics-data\} На странице пейвола доступны два варианта отображения данных метрик: по плейсментам и по аудиториям. В режиме отображения по плейсментам метрики группируются по плейсментам, связанным с пейволом. Это позволяет анализировать метрики в разрезе разных плейсментов. В режиме просмотра по аудитории метрики сгруппированы по целевой аудитории пейвола. Пользователи могут оценить метрики, характерные для разных сегментов аудитории. Выбрать нужный режим можно через выпадающий список в верхней части страницы с детальной информацией о пейволе. #### Временные диапазоны \{#time-ranges\} Вы можете выбрать один из нескольких временных периодов для анализа данных метрик: конкретные дни, недели, месяцы или произвольный диапазон дат. #### Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: Adapty предоставляет мощные инструменты для фильтрации и настройки анализа метрик под ваши задачи. На странице метрик доступны различные временные диапазоны, параметры группировки и возможности фильтрации. - Фильтрация по: аудитории, стране, пейволу, состоянию пейвола, группе пейволов, плейсменту, стране, стору, продукту и стору продукта. - Группировка по: продукту и стору. #### График отдельной метрики \{#single-metrics-chart\} Один из ключевых компонентов страницы метрик пейвола — раздел с графиком, который наглядно отображает выбранные метрики и упрощает анализ. Раздел графика на странице метрик пейвола содержит горизонтальную столбчатую диаграмму, наглядно отображающую выбранные значения метрик. Каждый столбец соответствует значению метрики и пропорционален ему по размеру, что позволяет легко воспринимать данные с первого взгляда. Горизонтальная линия показывает анализируемый временной период, а вертикальный столбец отображает числовые значения метрик. Суммарное значение всех метрик выводится рядом с графиком. Кроме того, нажав на иконку стрелки в правом верхнем углу раздела с графиком, можно развернуть его на всю ширину и просмотреть выбранные метрики. #### Сводка общих метрик \{#total-metrics-summary\} Рядом с графиком отдельной метрики отображается раздел со сводкой общих метрик — он показывает накопленные значения выбранных метрик на конкретный момент времени. Отображаемую метрику можно изменить через выпадающее меню. ### Определения метрик \{#metrics-definitions\} :::note Adapty конвертирует другие валюты в USD по курсу [currencylayer.com](https://currencylayer.com/) (обновляется каждые 8 часов). Курс **фиксируется на момент транзакции** — последующие изменения не влияют на результат конвертации. ::: #### Выручка \{#revenue\} Эта метрика отражает общую сумму денег в USD, полученную от покупок и продлений. Обратите внимание, что при расчёте выручки комиссия App Store / Play Store не вычитается — сумма указывается до удержания каких-либо сборов. #### Поступления \{#proceeds\} Эта метрика отражает фактическую сумму в USD, которую получает владелец приложения от покупок и продлений после вычета применимой комиссии App Store / Play Store. :::important Уведомите Adapty, если ваше приложение участвует в программе сниженной комиссии. Для корректных расчётов укажите статус участия в программах [Small Business Program](app-store-small-business-program) и [Reduced Service Fee](google-reduced-service-fee) в [настройках приложения](general). ::: Отражает чистый доход, который напрямую формирует выручку приложения. Подробнее о расчёте proceeds читайте в [документации](analytics-cohorts#revenue-vs-proceeds) Adapty. #### ARPPU \{#arppu\} ARPPU — это средний доход на одного платящего пользователя. Рассчитывается как общий доход, делённый на количество уникальных платящих пользователей. $15 000 доход / 1000 платящих пользователей = $15 ARPPU. #### ARPAS \{#arpas\} Средний доход на активного подписчика (ARPAS) показывает, сколько в среднем приносит каждый активный подписчик. Рассчитывается как общий доход, делённый на количество подписчиков, которые активировали пробный период или подписку. Например, если общий доход составляет $5 000, а подписчиков 1 000, то ARPAS равен $5. Эта метрика помогает оценить средний потенциал монетизации на одного подписчика. #### Уникальный коэффициент конверсии (CR) в покупки \{#unique-conversion-rate-cr-to-purchases\} Уникальный коэффициент конверсии в покупки рассчитывается путём деления числа покупок на количество уникальных просмотров. Например, если было совершено 10 покупок при 100 уникальных просмотрах, уникальный CR в покупки составит 10%. Эта метрика отражает соотношение покупок к уникальному числу просмотров и показывает, насколько эффективно уникальные посетители конвертируются в платящих пользователей. #### CR в покупки \{#cr-to-purchases\} Конверсия в покупки рассчитывается как отношение числа покупок к общему числу просмотров. Например, если было 10 покупок и 100 просмотров, конверсия в покупки составит 10%. Эта метрика показывает, какой процент просмотров заканчивается покупкой, и даёт представление об эффективности вашего пейвола в превращении пользователей в платящих клиентов. #### Уникальная конверсия в триалы \{#unique-cr-to-trials\} Уникальная конверсия в триалы рассчитывается как отношение количества начатых триалов к числу уникальных просмотров. Например, если было начато 30 триалов при 100 уникальных просмотрах, уникальная конверсия в триалы составит 30%. Эта метрика показывает, какой процент уникальных просмотров приводит к активации триалов, и помогает оценить эффективность пейвола в плане привлечения уникальных посетителей к пробному периоду. #### Покупки \{#purchases\} Purchases — это совокупное количество различных транзакций, совершённых на пейволе. В эту метрику входят следующие транзакции (продления не учитываются): - Новые покупки, совершённые непосредственно на пейволе. - Конвертации триалов, изначально активированных на пейволе. - Даунгрейды, апгрейды и кросс-грейды подписок, выполненные на пейволе. - Восстановления подписок на пейволе — например, когда подписка возобновляется после истечения срока без автопродления. Принимая во внимание все типы транзакций, метрика покупок даёт исчерпывающее представление об общей активности по привлечению пользователей и монетизации через ваш пейвол. #### Пробные периоды \{#trials\} Метрика пробных периодов отражает общее количество активированных триалов. Она показывает, сколько пользователей запустили пробный период через ваш пейвол. Метрика помогает отслеживать эффективность предложения пробного периода и даёт представление о вовлечённости пользователей и их конверсии из триала в платную подписку. #### Отменённые пробные периоды \{#trials-canceled\} Метрика «отменённые триалы» отражает количество пробных периодов, в которых пользователь отключил автопродление. Это происходит, когда пользователь вручную отписывается от триала — то есть решает не продолжать подписку после его окончания. Отслеживание отменённых триалов даёт ценную информацию о поведении пользователей и позволяет понять, как часто они отказываются от пробного периода. #### Возвраты \{#refunds\} Метрика возвратов отражает количество возвращённых покупок и подписок. Сюда входят транзакции, отменённые или возвращённые по различным причинам: по запросу пользователя, из-за проблем с оплатой или в соответствии с применимой политикой возврата. #### Процент возвратов \{#refund-rate\} Процент возвратов рассчитывается как отношение числа возвратов к числу первичных покупок (продления не учитываются). Например, если было 5 возвратов и 1000 первичных покупок, процент возвратов составит 0,5%. #### Просмотры \{#views\} Метрика просмотров показывает общее количество раз, когда пользователи открывали пейвол. Каждое посещение пейвола засчитывается как отдельный просмотр. Например, если пользователь открыл пейвол дважды, это будет записано как два просмотра. Отслеживание просмотров помогает понять уровень вовлечённости пользователей и их взаимодействие с пейволом, а также оценить эффективность плейсмента и дизайна. #### Уникальные просмотры \{#unique-views\} Метрика уникальных просмотров отражает количество уникальных случаев, когда пользователи просматривали пейвол. В отличие от общего числа просмотров, где каждый визит считается отдельно, уникальные просмотры учитывают посещение пейвола каждым пользователем только один раз — сколько бы раз он ни заходил. Например, если пользователь посетил пейвол дважды, это будет засчитано как один уникальный просмотр. Отслеживание уникальных просмотров даёт более точное представление о вовлечённости пользователей и охвате пейвола, поскольку акцент делается на отдельных людях, а не на общем количестве визитов. :::warning Обязательно отправляйте данные о просмотрах пейвола в Adapty с помощью метода `.logShowFlow()` (iOS SDK v4+) / `.logShowPaywall()`. Иначе просмотры пейвола не будут учитываться в метриках, и конверсии окажутся недостоверными. ::: --- # File: migrate-paywalls --- --- title: "Миграция пейволов между приложениями" description: "Узнайте, как мигрировать пейволы из других приложений в Adapty." --- В Adapty не нужно создавать новый пейвол с нуля для каждого приложения. Если вы управляете несколькими приложениями, можно перенести конфигурацию Paywall Builder из одного приложения в другое — для любого пейвола, созданного с помощью билдера. При миграции копируются все визуальные настройки: - Настройки макета пейвола и всех его элементов - Медиафайлы - Локализация Миграция применяется только к конфигурации билдера и не копирует продукты или Remote Config. :::note Если вы мигрируете конфигурацию Paywall Builder с кастомными шрифтами, проверьте их отображение на устройстве — они могут отображаться некорректно. ::: ## Миграция пейвола \{#migrate-paywall\} :::important Мигрировать можно только пейволы, созданные в **новом** Paywall Builder Adapty. Чтобы мигрировать пейволы из **старого** билдера, сначала необходимо перенести их в новый Paywall Builder. ::: Чтобы мигрировать конфигурацию Paywall Builder: 1. **Для нового пейвола**: начните [создание пейвола](create-paywall) и добавьте продукты. Затем нажмите **Build no-code paywall**, чтобы открыть библиотеку шаблонов. **Для существующего пейвола**: перейдите в раздел **Layout settings** на вкладке **Builder & Generator** и нажмите **Change template**. 2. Нажмите **Choose paywall** в блоке **Copy a design from your apps** при редактировании шаблона пейвола. <img src="/assets/shared/img/migrate-paywall-builder.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Выберите приложение и пейвол, конфигурацию которого хотите скопировать. <img src="/assets/shared/img/migrate-app.png" style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Нажмите **Copy Selected Paywall**. После миграции вы можете вносить любые правки — они не затронут исходный пейвол. --- # File: duplicate-paywalls --- --- title: "Дублирование пейвола" description: "Узнайте, как управлять дублированными пейволами и оптимизировать их эффективность в Adapty." --- Если вам нужно внести небольшие изменения в существующий пейвол в Adapty — особенно когда он уже используется в вашем мобильном приложении и вы не хотите нарушить аналитику — просто продублируйте его. Дубликаты можно использовать для замены оригинальных пейволов в некоторых или во всех плейсментах по мере необходимости. При дублировании создаётся копия пейвола со всеми его настройками: названием, продуктами и акциями. К названию нового пейвола добавляется слово «Copy», чтобы его можно было легко отличить от оригинала. Чтобы продублировать пейвол в дашборде Adapty: 1. Откройте раздел [**Paywalls**](https://app.adapty.io/paywalls) в главном меню Adapty. На странице списка пейволов отображаются все пейволы вашего аккаунта. 2. Нажмите кнопку **3-dot** рядом с нужным пейволом и выберите **Duplicate**. <img src="/assets/shared/img/duplicate.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Настройте новый пейвол и нажмите кнопку **Save**. 4. Adapty предложит заменить оригинальные пейволы их дубликатами в плейсментах, если оригинальный пейвол уже используется в каком-либо плейсменте. Если вы выберете **Create and replace original**, новые пейволы сразу перейдут в статус **Live**. В противном случае они будут созданы как новые пейволы в статусе **Draft**, и вы сможете добавить их в плейсменты позже. --- # File: archive-paywalls --- --- title: "Архивирование пейвола" description: "Узнайте, как архивировать устаревшие пейволы в Adapty без потери данных." --- По мере работы с Adapty и настройки пейволов у вас может накопиться немало пейволов, которые больше не вписываются в текущую стратегию или кампании. Эти неиспользуемые пейволы со статусом `Inactive` загромождают рабочее пространство и затрудняют поиск нужных. Чтобы решить эту проблему, Adapty предлагает возможность архивировать ненужные пейволы. Архивирование позволяет безопасно хранить их без окончательного удаления — при необходимости к ним всегда можно вернуться. Кроме того, архивные пейволы можно скрыть из стандартного представления, освободив рабочее пространство и упростив интерфейс. В этом гайде мы покажем, как эффективно архивировать пейволы в Adapty, чтобы вы могли управлять ими удобнее. Важное уточнение: пейволы, которые в данный момент активны хотя бы в одном плейсменте, архивировать нельзя. Если вы хотите заархивировать такой пейвол, сначала удалите его из всех плейсментов. :::note Нельзя заархивировать пейвол, если он используется в неархивированном A/B-тесте. Это сделано для того, чтобы пользователь мог просматривать детальные метрики завершённого A/B-теста, а связанный пейвол оставался частью этих данных. ::: **Чтобы заархивировать пейвол:** 1. Откройте раздел [**Paywalls**](https://app.adapty.io/paywalls) в главном меню Adapty. 2. Нажмите кнопку **3-dot** рядом с нужным пейволом и выберите **Archive**. <img src="/assets/shared/img/archive-paywall.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. В открывшемся окне **Archive paywall** введите название пейвола, который хотите заархивировать, и нажмите кнопку **Archive**. --- # File: restore-paywall --- --- title: "Восстановление пейвола из архива" description: "Восстанавливайте пейволы в Adapty, чтобы обеспечить бесперебойный доступ пользователей к подпискам." --- Возможность архивировать пейволы значительно упрощает управление ими: вы можете скрыть ненужные пейволы и не захламлять рабочее пространство. А опция восстановления из архива даёт дополнительную гибкость — вы всегда можете вернуть пейвол в работу, если он снова окажется полезным. Архивные пейволы могут быть скрыты в стандартном отображении. Чтобы их увидеть, выберите **Archived** в фильтре **State**. **Чтобы вернуть пейвол из архива:** 1. Откройте раздел [**Paywalls**](https://app.adapty.io/paywalls) в главном меню Adapty. 2. Убедитесь, что архивные пейволы отображаются в списке. Если нет, обновите фильтр справа. <img src="/assets/shared/img/paywall-filter.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Нажмите кнопку **3-dot** рядом с архивным пейволом и выберите **Back to active**. <img src="/assets/shared/img/restore-paywall.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: profiles-crm --- --- title: "Профили/CRM" description: "Управляйте профилями пользователей и данными CRM в Adapty для улучшения сегментации аудитории." --- Профили — это CRM для ваших пользователей. С помощью профилей вы можете: 1. Находите конкретных пользователей по ID профиля, customer user ID, email или ID транзакции. 2. Просматривайте временную шкалу событий пользователя, включая проблемы с оплатой, льготные периоды и другие [события](events). 3. Анализируйте свойства пользователя: статус подписки, общий доход/выручку и многое другое. 4. Выдавайте пользователю подписку. :::note События из ленты событий поступают на дашборд с задержкой. Новые профили и изменения атрибутов могут отображаться не сразу. ::: <img src="/assets/shared/img/profiles.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::link Чтобы узнать, как Adapty создаёт и связывает профили пользователей, см. [Как работают профили](how-profiles-work). ::: ## Поиск пользователей \{#finding-users\} В списке профилей можно найти конкретного пользователя по: - **Profile ID**: внутренний идентификатор пользователя в Adapty (также называется Adapty ID). - **Customer user ID**: идентификатор пользователя в вашем приложении, если вы его задали. - **Email**: электронная почта пользователя, если передана как кастомный атрибут. - **Transaction ID**: идентификатор транзакции стора из покупки. Нажмите на любую строку, чтобы открыть полный профиль пользователя. ## Состояние подписки \{#subscription-state\} В списке профилей вы можете фильтровать и сортировать пользователей по состоянию подписки. Доступные значения состояния: | **Состояние** пользователя | Описание | | :------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Subscribed | У пользователя активная подписка с включённым автопродлением. | | Auto-renew off | Пользователь отключил автопродление, но сохраняет доступ к премиум-функциям до конца оплаченного периода. | | Subscription cancelled | Пользователь отменил подписку, и она полностью завершилась. | | Billing issue | Пользователя не удалось списать средства из-за проблем с оплатой — после истечения подписки или пробного периода. | | Grace period | Пользователь находится в льготном периоде из-за проблем с оплатой, возникших при попытке списать средства после истечения подписки или пробного периода. | | Active trial | У пользователя активная подписка, которая сейчас находится в пробном периоде. | | Trial cancelled | Пользователь отменил пробный период и не имеет активной подписки. | | Never subscribed | Пользователь никогда не оформлял подписку и не начинал пробный период — остаётся пользователем бесплатного тарифа. | ## Атрибуты пользователя \{#user-attributes\} <img src="/assets/shared/img/ce8df4d-CleanShot_2023-06-26_at_20.32.232x.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> Вы можете передавать дополнительные свойства пользователя в Adapty через SDK. По умолчанию Adapty устанавливает: | Свойство | Описание | | ---------------- | ------------------------------------------------------------ | | Customer user ID | Идентификатор вашего конечного пользователя в вашей системе. | | Adapty ID | Внутренний идентификатор Adapty для вашего конечного пользователя, называемый Profile ID. | | IDFA | Идентификатор для рекламодателей, присваиваемый Apple устройству пользователя. Требует разрешения App Tracking Transparency (ATT) на iOS 14+. Недоступен на Android. | | Country | Страна вашего конечного пользователя. | | OS | Операционная система конечного пользователя. | | Device | Название модели устройства, отображаемое пользователю. | | Install date | Дата первой записи пользователя в Adapty: <ul><li>Дата создания пользователя.</li><li>Если пользователь установил ваше приложение до интеграции Adapty, дата установки соответствует дате его первой транзакции.</li><li>Если применимо, дата, указанная при импорте исторических данных.</li></ul> | | Created at | Дата создания пользователя. | Отправляйте хотя бы внутренний идентификатор пользователя или его email. Это позволит находить пользователей по этим данным в списке Profiles. После установки SDK Adapty автоматически собирает пользовательские события из очереди платежей и отображает их в профиле пользователя. Атрибуты из таблицы выше собираются автоматически — отправлять их вручную не нужно. ### Пользовательские атрибуты \{#custom-attributes\} В разделе **Attributes** профиля можно просмотреть пользовательские атрибуты, заданные через SDK или API. Также можно назначать атрибуты вручную с помощью кнопки **Add attribute**. <img src="/assets/shared/img/378c1fb-add_attribute.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> ## Предоставление подписки \{#granting-a-subscription\} В профиле можно продлить активную подписку или предоставить пользователю пожизненный доступ к уровню доступа — без необходимости совершать покупку. <img src="/assets/shared/img/b1d74fd-edit_paid_access_level.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> Это особенно удобно в следующих случаях: - Компенсация пользователю после проблем с оплатой или обращения в поддержку. - Проведение ручных акций или бета-программ. - Тестирование флоу подписок без реальной покупки. Чтобы предоставить доступ, откройте профиль пользователя, перейдите в раздел **Access levels** и нажмите **Edit**. Установите дату истечения доступа и сохраните. Дата должна быть в будущем и не может быть уменьшена после установки. Изменение даты для активных подписок не влияет на текущие платежи. :::note Предоставление доступа не создаёт события покупки в App Store или Google Play. Лента событий пользователя и аналитика будут отличаться от реального флоу покупки. ::: Также можно предоставить доступ программно с помощью метода API [Grant access level](api-adapty/operations/grantAccessLevel). ## Совместное использование платного доступа между аккаунтами пользователей \{#sharing-paid-access-between-user-accounts\} :::link Основная статья: [Совместное использование платного доступа между аккаунтами пользователей](sharing-paid-access-between-user-accounts) ::: ### История совместного использования уровней доступа \{#access-sharing-history\} Когда уровни доступа передаются или используются совместно, в профиле пользователя отображается ссылка на связанный профиль — тот, который поделился доступом, или тот, который его получил. Чтобы открыть связанный профиль, в **Profile** пользователя нажмите на ссылку рядом с уровнем доступа. <img src="/assets/shared/img/profile-access-level-origin.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::note Балансы виртуальной валюты не распределяются и не передаются между профилями, как уровни доступа. Каждый баланс привязан к одному профилю — подробнее см. [Балансы, профили и устройства](virtual-currency-balance#balances-profiles-and-devices). ::: ## Следующие шаги \{#next-steps\} - Чтобы понять, как Adapty создаёт и связывает профили, см. [Как работают профили](how-profiles-work). - Чтобы настроить политику общего доступа, см. [Общий доступ к платным функциям между аккаунтами](sharing-paid-access-between-user-accounts). - Чтобы выдать доступ программно, см. метод API [Grant access level](api-adapty/operations/grantAccessLevel). --- # File: how-profiles-work --- --- title: "Как работают профили" description: "Узнайте, как Adapty создаёт, отслеживает и связывает профили пользователей — включая анонимные профили, идентифицированных пользователей и отношения родительский/наследующий профиль." --- Каждый пользователь вашего приложения получает профиль Adapty, в котором отслеживаются его покупки, события и состояние подписки. Понимание того, как профили создаются и связываются, поможет вам избежать ошибок интеграции, фрагментации данных и правильно интерпретировать данные в разделе [Профили](profiles-crm). ## Создание профиля \{#profile-creation\} Adapty автоматически создаёт профиль при первом запуске приложения пользователем. **Без Customer User ID** профиль анонимный. Новый анонимный профиль создаётся каждый раз, когда: - Пользователь переустанавливает приложение - Пользователь выходит из вашего приложения (когда приложение вызывает `Adapty.logout()`) Покупки привязываются к установке приложения, а не к постоянной идентификации пользователя. **С Customer User ID** профиль сохраняется между переустановками и на разных устройствах. Customer User ID позволяет: 1. Отслеживать пользователя при переустановке приложения и на нескольких устройствах. 2. Находить пользователей по их customer user ID в разделе [**Profiles**](profiles-crm). 3. Использовать customer user ID в [серверном API](getting-started-with-server-side-api). 4. Adapty передаёт customer user ID во все интеграции. Поведение профиля с customer user ID зависит от того, когда вы его устанавливаете: - **При активации SDK**: Adapty использует существующий профиль с этим customer user ID (для вернувшихся пользователей) или создаёт новый профиль (для новых пользователей). - **После активации SDK**: Adapty создаёт анонимный профиль при активации. Когда вы впоследствии идентифицируете пользователя, Adapty привязывает customer user ID к анонимному профилю (для новых пользователей) или переключается на существующий профиль с этим ID (для вернувшихся пользователей). **Какой подход выбрать:** - **Customer user ID доступен при запуске приложения** (например, сохранён из предыдущей сессии) — передайте его в `activate()` при инициализации SDK. - **Пользователи входят в систему после запуска приложения** — вызовите `identify()` после аутентификации. Adapty привяжет ID к текущему профилю (если ID новый) или переключится на существующий профиль (если ID уже есть). - **Пользователи могут совершать покупки до входа в систему** — вызовите `identify()` после входа. Если customer user ID уже существует в Adapty, получите профиль после этого, чтобы синхронизировать текущий уровень доступа. Подробнее о реализации — в гайде SDK по [идентификации пользователей](identifying-users). :::note Если вернувшийся пользователь ранее использовал приложение без customer user ID, анонимные профили не объединяются автоматически при идентификации во время активации SDK. Чтобы сохранить полную историю для таких пользователей, вызывайте `identify()` после входа в систему. ::: ## Родительские и дочерние профили \{#parent-and-inheritor-profiles\} Когда одна и та же подписка в сторе привязана к нескольким профилям Adapty, Adapty выстраивает из них цепочку: один **родительский** профиль и один или несколько **дочерних** профилей, которые получают доступ от той же покупки. Это происходит в следующих случаях: - [Совместный доступ к платным возможностям между аккаунтами](sharing-paid-access-between-user-accounts) включён, и пользователь входит на устройстве, где покупка была совершена из другого профиля. - Пользователь переустанавливает приложение без `customer_user_id`, и новый профиль подхватывает покупку из предыдущей установки. - Разные идентифицированные пользователи восстанавливают покупки на одном устройстве. - Приложение переходит между Apple Team ID, и новое приложение подхватывает покупки, сделанные в рамках старого Team ID. **Как выбирается родительский профиль.** Родительский профиль — это **первый профиль, зафиксировавший покупку**, который определяется порядком поступления чеков покупок в Adapty, а не порядком создания профилей. Например: вы установили приложение и ничего не купили, затем переустановили и оформили подписку. Второй профиль становится родительским, поскольку именно он совершил покупку. Первый профиль становится наследником и получает доступ через общий доступ. **Как распределяются события:** - **Транзакционные события** (покупки, продления, отмены, проблемы с оплатой, льготные периоды, возвраты): отображаются только в **родительском профиле**, совершившем покупку. Все продления и обновления подписки продолжают отображаться в этом профиле. - **События `access_level_updated`**: отображаются **в обоих профилях — родительском и профиле-наследнике** — при каждом изменении состояния уровня доступа. Это позволяет всем связанным профилям быть в курсе актуального статуса доступа. В родительском профиле отображается полная история транзакций. Дочерние профили показывают только обновления уровня доступа и ссылку на родительский профиль в разделе **Access level**. <img src="/assets/shared/img/98d0dad-non-original_profile.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> **Отслеживание одной и той же подписки в нескольких профилях.** У каждого унаследованного профиля есть свой `profile_id`, поэтому `profile_id` не является стабильным идентификатором в цепочке. Чтобы идентифицировать одну и ту же подписку в нескольких профилях — например, при сверке событий вебхука или сопоставлении профилей дашборда с одним базовым пользователем — используйте идентификатор на стороне стора. | Поле | Применение | | --- | --- | | `store_original_transaction_id` | Идентификация цепочки подписки в разных профилях. Уникально для каждой подписки Apple. | | `profiles_sharing_access_level` (поле вебхука) | Все профили, которым в данный момент предоставлен уровень доступа по подписке, если включено совместное использование. | | `profile_id` | **Не** подходит для отслеживания между профилями — у каждого наследника свой. | ## Транзакции без профилей \{#transactions-without-profiles\} Некоторые транзакции в Adapty не привязаны ни к одному профилю — они отображаются в аналитике и экспортах, но не попадают в список профилей. Это происходит, когда **серверные (S2S) уведомления стора** приходят от пользователей, чьи аккаунты никогда не подключались к вашему приложению через SDK Adapty. Известные источники: - S2S-уведомления App Store (включая события возврата средств) - S2S-уведомления Google Play - Вебхук-события Stripe и Paddle Эти транзакции: - **Отображаются в аналитических графиках** (учитываются в общих метриках) - **Отображаются в экспортах** (S3, GCS, BigQuery) с `profile_id`, равным `null` - **Не отображаются в списке профилей** — привязать их не к чему Если в аналитике или экспортах событий больше, чем вы можете найти в интерфейсе профилей, разница, скорее всего, объясняется именно такими транзакциями без профиля. Чтобы найти их в экспорте, отфильтруйте строки, где `profile_id IS NULL`. ## Общий доступ к платному контенту между аккаунтами пользователей \{#sharing-paid-access-between-user-accounts\} :::link Основная статья: [Общий доступ к платному контенту между аккаунтами пользователей](sharing-paid-access-between-user-accounts) ::: Чтобы задать политику общего доступа к уровням доступа, на странице настроек [**General**](general) выберите подходящий вариант. Для [среды песочницы](test-purchases-in-sandbox) можно задать отдельную политику. **Включено (по умолчанию)** Идентифицированные пользователи (те, у кого задан [Customer User ID](identifying-users#set-customer-user-id-on-configuration)) могут совместно использовать один и тот же [уровень доступа](access-level), предоставленный Adapty, если их устройство привязано к одному Apple/Google ID. Это удобно, когда пользователь переустанавливает приложение и входит с другим email — он всё равно сохранит доступ к своей предыдущей покупке. При этой опции несколько идентифицированных пользователей могут совместно использовать один уровень доступа. Несмотря на то что уровень доступа является общим, все прошлые и будущие транзакции фиксируются как события в исходном Customer User ID — для обеспечения корректной аналитики и сохранения полной истории транзакций: пробных периодов, покупок подписок, продлений и прочего, привязанных к одному профилю. **Передача доступа новому пользователю** Идентифицированные пользователи сохраняют доступ к [уровню доступа](access-level), предоставленному Adapty, даже если они входят с другим [Customer User ID](identifying-users#set-customer-user-id-on-configuration) или переустанавливают приложение — при условии, что устройство привязано к одному Apple/Google ID. В отличие от предыдущей опции, Adapty передаёт покупку между идентифицированными пользователями. Это гарантирует доступность купленного контента, однако одновременно доступ может быть только у одного пользователя. Например, если UserA оформляет подписку, а UserB входит на том же устройстве и восстанавливает транзакции, UserB получит доступ к подписке, а у UserA он будет отозван. Если один из пользователей (новый или прежний) не идентифицирован, уровень доступа всё равно будет общим между этими профилями в Adapty. Несмотря на передачу уровня доступа, все прошлые и будущие транзакции фиксируются как события в исходном Customer User ID — для обеспечения корректной аналитики и сохранения полной истории транзакций: пробных периодов, покупок подписок, продлений и прочего, привязанных к одному профилю. После переключения на **Transfer access to new user** уровни доступа не будут немедленно переданы между профилями. Процесс передачи для каждого конкретного уровня доступа запускается только при получении Adapty события от стора — например, при продлении подписки, восстановлении или при валидации транзакции. **Отключено** Первый идентифицированный профиль пользователя, получивший уровень доступа, сохранит его навсегда. Это оптимальный вариант, если бизнес-логика вашего приложения требует привязки покупок к единственному Customer User ID. Обратите внимание, что уровни доступа по-прежнему остаются общими между анонимными пользователями. Вы можете «отвязать» покупку, [удалив профиль пользователя-владельца](https://adapty.io/docs/ru/api-adapty/operations/deleteProfile). После удаления уровень доступа становится доступным первому профилю, который его запросит — анонимному или идентифицированному. Отключение совместного использования распространяется только на новых пользователей. Подписки, уже разделённые между пользователями, продолжат оставаться общими даже после отключения этой опции. :::warning Apple и Google требуют, чтобы встроенные покупки были доступны для совместного использования или передачи между пользователями, поскольку они привязывают покупку к Apple/Google ID. Без совместного использования восстановление покупок после повторной установки может не работать. Отключение совместного использования может лишить пользователей возможности восстановить доступ после входа в аккаунт. Рекомендуем отключать совместное использование только в том случае, если пользователи **обязаны войти в аккаунт** до совершения покупки. В противном случае идентифицированный пользователь может оформить подписку, войти в другой аккаунт и навсегда потерять к ней доступ. ::: ### Какой вариант выбрать? \{#which-setting-should-i-choose\} | Моё приложение... | Вариант | | ------------------------------------------------------------ | ------------------------------------------------------------ | | Не имеет системы входа и использует только анонимные идентификаторы профилей Adapty. | Используйте вариант по умолчанию — уровни доступа всегда являются общими между анонимными идентификаторами профилей для всех трёх вариантов. | | Имеет необязательную систему входа и позволяет совершать покупки до создания аккаунта. | Выберите **Transfer access to new user**, чтобы пользователи, совершившие покупку без аккаунта, могли впоследствии восстановить свои транзакции. | | Требует создания аккаунта перед покупкой, но допускает привязку покупок к нескольким Customer User ID. | Выберите **Transfer access to new user**, чтобы одновременно доступ был только у одного Customer User ID, при этом пользователи могли входить с другим Customer User ID без потери оплаченного доступа. | | Требует создания аккаунта перед покупкой и жёстко привязывает покупки к единственному Customer User ID. | Выберите **Disabled**, чтобы транзакции никогда не передавались между аккаунтами. | ## Временны́е метки событий с датами в будущем (Apple/iOS) \{#event-timestamps-with-future-dates-appleios\} Это поведение характерно только для Apple App Store. Система уведомлений Google Play не отправляет события заранее. Временны́е метки событий в профилях и интеграциях могут показывать даты в будущем, потому что Apple отправляет события о продлении подписки заранее. - **Почему это происходит**: Apple делает это, чтобы подписки автоматически обновлялись до истечения срока, не прерывая сервис для пользователей. Подробнее — на форуме разработчиков Apple: [Server Notifications for Subscriptions](https://developer.apple.com/forums/tags/app-store-server-notifications). - **Затронутые типы событий**: Как правило, это касается продлений подписок и конверсий из пробного периода в платный. У таких событий могут быть будущие временные метки, поскольку Apple уведомляет системы заранее. - **Другие типы событий**: Дополнительные встроенные покупки и изменения тарифного плана подписки записываются с фактическими временными метками, так как эти события невозможно предсказать заранее. - **Влияние на Analytics и Event Feed**: Такие события появятся в **Analytics** и **Event Feed** только после того, как наступит их временная метка. События с будущими временными метками не отображаются ни в одном из этих разделов. - **Влияние на интеграции**: Adapty отправляет события в интеграции сразу после их получения. Если у события будущая временная метка, Adapty передаёт его в вашу интеграцию с этой временной меткой без изменений. ## Дальнейшие шаги \{#next-steps\} - Чтобы использовать дашборд Profiles для поиска и управления пользователями, см. [Profiles](profiles-crm). - Чтобы настроить идентификацию пользователей в вашем приложении, см. гайд SDK по [идентификации пользователей](identifying-users). - Чтобы настроить политику общего доступа, см. [Общий доступ к платному контенту между аккаунтами пользователей](sharing-paid-access-between-user-accounts). --- # File: sharing-paid-access-between-user-accounts --- --- title: "Общий доступ к платным функциям между аккаунтами" description: "Общий доступ к платным функциям между разными аккаунтами пользователей для тех, кто использует несколько устройств или несколько профилей в приложении" --- Когда пользователь совершает покупку, Adapty присваивает новый [уровень доступа](access-level) его активному [профилю](identifying-users). Этот уровень доступа разрешает покупателю доступ к платному контенту. Профиль покупателя может непреднамеренно измениться, если он переустановит приложение или войдёт в новый аккаунт внутри приложения. Чтобы обеспечить бесперебойный доступ, Adapty автоматически передаёт уровень доступа пользователя между исходным профилем и последующими. Такой подход подходит для большинства приложений. Но если ваша бизнес-логика требует иного, вы можете выбрать более ограниченную политику совместного использования платного доступа. Откройте страницу [General Settings](https://app.adapty.io/settings/general), чтобы задать политику совместного использования уровней доступа. Для удобства тестирования можно изменить этот параметр [только для среды песочницы](#sharing-paid-access-on-sandbox). <Details> :::important Если в вашем приложении не предусмотрена аутентификация пользователей, этот параметр можно игнорировать. Анонимные профили, привязанные к одному аккаунту стора, *всегда* делятся уровнем доступа. ::: <summary>Какую политику совместного доступа выбрать? (нажмите, чтобы развернуть)</summary> | Мой случай... | Лучший вариант | | ------------------------------------------------------------ | ------------------------------------------------------------ | | Приложение не поддерживает аутентификацию и использует только анонимные идентификаторы профилей Adapty. | Используйте настройку **Enabled (default)**. | | Приложение поддерживает аутентификацию, но позволяет совершать покупки без аккаунта. | Включите настройку **Transfer access to new user**. Пользователи смогут зарегистрироваться и привязать анонимные покупки. | | Приложение требует создания аккаунта перед покупкой, но допускает привязку одного продукта к нескольким Customer User ID. | Включите настройку **Transfer access to new user**. Несколько аккаунтов смогут использовать продукт, но только последовательно. | | Приложение требует создания аккаунта перед покупкой, при этом покупки строго привязаны к одному Customer User ID. | **Отключите** совместный доступ к уровню доступа. | </Details> <img src="/assets/shared/img/sharing-paid-access.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Включено (по умолчанию) \{#enabled-default\} Эта настройка лучше всего подходит для приложений **без собственной аутентификации**. После покупки все профили, связанные с одним аккаунтом стора, автоматически *наследуют* уровень доступа. * Если пользователь входит в ваше приложение с новыми учётными данными, он сохраняет доступ к платному контенту. * Если пользователь переустанавливает приложение после сброса настроек до заводских, он сохраняет доступ к платному контенту. * Если пользователь устанавливает приложение на других устройствах с тем же аккаунтом стора, покупка становится доступна на всех устройствах — даже если у каждого экземпляра приложения есть свой профиль покупателя. ## Перенос доступа на нового пользователя \{#transfer-access-to-new-user\} Эта настройка лучше всего подходит для приложений, которые допускают покупки **с авторизацией и без**, или хотят ввести политику **«одно устройство на одного пользователя»**. Adapty ограничивает доступ к покупке одним customer ID одновременно. Владелец устройства может переустанавливать приложение, входить и выходить из аккаунта, но не может получить доступ к одному и тому же продукту более чем с одного customer ID одновременно. При включённом параметре анонимные профили (например, профиль, становящийся активным после выхода пользователя из системы) всегда наследуют уровень доступа последнего активного customer ID. Это необходимо, чтобы предотвратить потерю доступа в дальнейшем. :::warning Если вы отключите настройку по умолчанию и включите **Transfer access to new user**, Adapty не обновит немедленно уровни доступа существующих профилей пользователей. Переключение происходит, когда пользователь инициирует новое событие стора: например, продлевает подписку или восстанавливает покупки. ::: :::important Adapty отзывает старый профиль только тогда, когда у нового профиля есть [Customer User ID](identifying-users#set-customer-user-id-on-configuration) в момент, когда SDK распространяет транзакцию. Если `restorePurchases` выполняется на анонимном профиле, и старый Customer User ID, и новый анонимный профиль получают уровень доступа. Старый профиль отзывается позже, когда вы идентифицируете анонимный профиль. Чтобы избежать этого, вызывайте методы SDK по порядку: `activate` → `identify` → `restorePurchases`. ::: ## Отключение общего доступа к платным функциям \{#disable-paid-access-sharing\} Этот параметр **подходит только** для приложений с **обязательной аутентификацией** или собственной реализацией управления доступом. В остальных случаях пользователи могут потерять доступ к своим покупкам, и ваше приложение рискует **не пройти обязательную проверку стора**. Если вы отключите совместный доступ к оплаченному контенту, Adapty привяжет продукт к активному [идентификатору пользователя](identifying-users#set-customer-user-id-on-configuration) на момент покупки и не будет предоставлять уровень доступа другим профилям. Это обеспечивает строгое распределение продукта по принципу «один к одному». :::warning Если вы отключите совместный доступ к оплаченному контенту, идентификаторы пользователей перестанут наследовать оплаченный доступ. Если идентификатор ранее уже унаследовал оплаченный доступ, отозвать его автоматически невозможно. ::: :::important В экстренных случаях вам может потребоваться [удалить профиль пользователя](api-adapty/operations/deleteProfile), чтобы следующий доступный профиль (идентифицированный или анонимный) мог получить его уровень доступа. ::: ## Практический справочник \{#practical-reference\} После выбора режима контракты ниже описывают, чего ожидать: какие профили получают доступ, когда старый профиль его теряет и какие события вебхуков срабатывают. | Режим | Несколько профилей делят одну покупку? | Старый профиль отзывается при передаче? | Когда отзывается старый профиль | Webhook-события, когда второй профиль претендует на подписку | | --- | --- | --- | --- | --- | | **Включён (по умолчанию)** | Да — каждый профиль, восстановивший покупку или выполнивший вход, наследует доступ | Никогда | Н/Д | `access_level_updated` (`is_active=true`) для каждого нового профиля, унаследовавшего доступ | | **Передача доступа новому пользователю** | Нет — эксклюзивный, но переносимый между профилями | Да | Сразу после того, как новое идентифицированное устройство передаёт транзакцию (`restorePurchases`, идентификация или следующее событие на стороне стора) | Новый профиль: `access_level_updated` (`is_active=true`). Старый профиль: `access_level_updated` (`is_active=false`) | | **Отключён** | Нет — один Customer User ID на покупку, навсегда | Н/Д — доступ не передаётся никогда | Н/Д | Ни одного для второго профиля. SDK не показывает для него никакого доступа | ## Совместный доступ к оплаченному контенту в песочнице \{#sharing-paid-access-on-sandbox\} Вы можете задать политику совместного доступа к оплаченному контенту отдельно для среды песочницы. При тестировании покупок в песочнице ожидайте следующего поведения: * Apple хранит информацию о прошлых покупках в истории покупок аккаунта. SDK Adapty тоже может получить к ней доступ. * Если вы переустановите приложение, и Adapty обнаружит, что продукт уже был куплен, активный профиль унаследует уровень доступа. * Если Apple обнаруживает существующую покупку продукта, повторная покупка того же продукта будет невозможна, даже если у активного профиля нет нужного уровня доступа. Это поведение возникает **независимо от настройки общего доступа к платным функциям**. Если приложение не показывает пейвол, купить продукт невозможно. Единственное решение — **очистить историю покупок аккаунта**. Подробные инструкции см. в [гайде по тестированию в песочнице](test-purchases-in-sandbox). :::warning Подписки в песочнице Apple автоматически продлеваются каждые несколько минут. Такие частые продления могут менять профиль, который Adapty считает [родительским](how-profiles-work#parent-and-inheritor-profiles) — паттерн цепочки, который в продакшене воспроизводится крайне редко. Тестируйте в том же режиме, который используете в продакшене, и проверяйте поведение на реальном Apple ID, прежде чем делать выводы по данным из песочницы. ::: ## Совместный доступ в аналитике \{#paid-access-sharing-in-analytics\} * Adapty фиксирует транзакции по мере их поступления. Одна транзакция может быть связана с несколькими профилями, но учитывается только один раз. * Если два или более профиля используют один уровень доступа, покупка атрибутируется [родительскому профилю](how-profiles-work#parent-and-inheritor-profiles). * Наследование уровня доступа не влияет на статистику установок. Чтобы задать способ подсчёта установок в Adapty, выберите один из двух доступных [вариантов определения установок](installs#calculation) на странице настроек. --- # File: segments --- --- title: "Сегменты" description: "Создавайте сегменты пользователей и управляйте ими для более точного таргетинга в Adapty." --- **Сегмент** — это набор фильтров, который группирует пользователей с общими свойствами. Используйте сегменты для более точного таргетинга пейволов и A/B-тестов. :::note События из ленты событий поступают на дашборд с задержкой. Новые профили и изменения атрибутов могут отображаться не сразу. ::: После того как вы создадите сегмент, вы можете [использовать его как **аудиторию** в плейсментах и A/B-тестах](audience), чтобы управлять тем, какой пейвол видят пользователи (один или несколько). Примеры: - Показывать стандартный пейвол тем, кто ещё не подписался, и предлагать скидку пользователям, которые ранее отменили подписку или пробный период. - Показывать разные пейволы пользователям из разных стран. - Настраивать таргетинг на основе данных атрибуции Apple Search Ads. - Гарантировать, что пользователи на старых версиях приложения продолжают видеть прежний пейвол, а на новых — обновлённый. - [В Analytics](controls-filters-grouping-compare-proceeds#filter-and-group-data), фильтровать по сегментам, чтобы смотреть показатели для конкретных групп пользователей. Группировать по сегменту, чтобы сравнивать результаты или вклад в рамках **All users**. <img src="/assets/shared/img/3244407-Segments.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Создание \{#creation\} Чтобы создать сегмент, введите название и выберите атрибуты, которые задают его фильтры. Если выбрано несколько атрибутов, пользователь должен соответствовать всем условиям одновременно. Adapty применяет логику AND между атрибутами. <img src="/assets/shared/img/1af9744-new_cohort.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Доступные атрибуты \{#available-attributes\} :::note Многие атрибуты пользователя устанавливаются автоматически (например, **Country** или **Calculated total revenue USD**), однако **Age**, **App user ID**, данные **Attribution**, **Gender** и **Custom attributes** автоматически не определяются. Чтобы использовать их для сегментации, необходимо [задать атрибуты пользователя](setting-user-attributes) или [передать данные атрибуции](attribution-integration). ::: :::tip Для атрибутов на основе дат доступна фильтрация по: - **Фиксированная дата**: выберите конкретные даты в календаре (например, покажите специальное предложение пользователям, установившим приложение в период с Чёрной пятницы по Киберпонедельник) - **Относительный диапазон**: задайте динамические временные окна, например «Последние 7 дней» или «Последние 3 месяца» (например, верните пользователей, которых не было 30+ дней, или нацельтесь на недавние установки) Относительные диапазоны обновляются автоматически — это идеальный вариант для постоянных кампаний. Фиксированные даты лучше подходят для акций с ограниченным сроком. ::: | Атрибут | Фильтр по | |---------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Age** | Возраст пользователя. Обратите внимание: возраст рассчитывается в момент первого получения данных Adapty и в дальнейшем не обновляется. | | **App User ID** | Идентификатор пользователя в вашем приложении ([customer_user_id](profiles-crm#user-attributes)). Можно фильтровать по наличию или отсутствию значения — например, чтобы показывать пейвол только пользователям, которые не вошли в аккаунт. | | **App version (current)** | Текущая версия приложения, установленного на устройстве пользователя, с которого Adapty последний раз получал данные о событиях — **обновляется при каждом обновлении приложения**, поэтому всегда отражает актуальную версию. Используйте этот атрибут, когда нужно охватить всех, кто сейчас работает с конкретной версией, включая тех, кто обновился до неё с более ранней. При создании сегмента нажмите на значок карандаша рядом с **App version** и добавьте новую версию, чтобы сразу использовать её.<br/> Условие **version > X.X** позволяет измерить влияние на конверсию всех версий приложения старше или новее указанной — без необходимости перечислять каждую версию отдельно.<br/><br/> **Формат:** Строки версии должны соответствовать формату [SemVer](https://semver.org/). Ведущие нули в любой части недопустимы — `26.03.4` не будет совпадать, а `26.3.4` будет. Некорректные версии молча исключаются из сегмента. | | **App version (on install)** | Версия приложения, установленного на устройстве пользователя в момент первого получения данных о событиях Adapty — **фиксируется при установке и никогда не обновляется**, даже если пользователь впоследствии обновил приложение. Используйте этот атрибут для таргетинга по версии, с которой пользователь начал работу, а не по текущей. `App version (on install) = 1.5.7` совпадает только с пользователями, чья первая установка была версии 1.5.7, и молча исключает тех, кто обновился до 1.5.7 с более ранней версии — чтобы охватить и таких пользователей, используйте **App version (current)**.<br/><br/> **Формат:** Строки версии должны соответствовать формату [SemVer](https://semver.org/). Ведущие нули в любой части недопустимы — `26.3.04` не будет совпадать, а `26.3.4` будет. Некорректные версии молча исключаются из сегмента. | | **Attribution: Ad Group** | Группа объявлений атрибуции. | | **Attribution: Ad Set** | Набор объявлений атрибуции. | | **Attribution: Campaign** | Название маркетинговой кампании. | | **Attribution: Creative** | Ключевое слово креатива атрибуции. | | **Attribution: Channel** | Название маркетингового канала. | | **Attribution: Source** | Источник атрибуции. | | **Attribution: Status** | Статус атрибуции. Возможные значения: <ul><li> **Organic** — пользователь установил приложение без участия платного маркетинга (например, нашёл его напрямую в App Store/Google Play, по рекомендации или через органические публикации в социальных сетях).</li><li> **Non-organic** — пользователь был привлечён через платный маркетинговый канал (например, реклама, кампании с инфлюенсерами, реферальные программы).</li><li> **Unknown** — данные об атрибуции для этого пользователя недоступны.</li></ul> | | **Calculated subscription state** | [Текущий статус подписки](profiles-crm#subscription-state) пользователя: активна ли подписка, отменена или есть нерешённая проблема с оплатой. | | **Calculated total revenue USD** | Общая выручка, полученная от этого пользователя. | | **Country** | Страна пользователя, определяемая по последнему известному IP-адресу. Adapty обновляет IP-сигнал не чаще одного раза в неделю, поэтому данные могут устареть, если пользователь сменил местоположение или использует VPN. Чтобы таргетировать по стране учётной записи App Store / Play Store, используйте **Country from store account**. | | **Country from store account** | Страна, привязанная к учётной записи iOS или Android пользователя в сторе. Обратите внимание: Adapty собирает страну стора только для устройств iOS версии 13 и выше. | | **Creation date** | Дата создания профиля (когда приложение было впервые установлено на устройстве пользователя). | | **Device** | Тип устройства на основе метаданных. Например, «Samsung Galaxy» или «iPhone 13». | | **Gender** | Пол пользователя. Обратите внимание: значение задаёте вы сами. | | **Installation date** | Дата установки приложения пользователем. | | **Language** | Язык устройства пользователя. <Callout type="warning">Adapty хранит язык в виде двухбуквенного кода `ISO 639-1`. Не используйте расширенные локали вроде `zh-Hant-TW` или `pt-BR` — они могут отображаться в выпадающем списке, но не совпадут ни с одним пользователем.</Callout> <Callout type="tip">Для более точного языкового таргетинга сочетайте **Language** с **Country**. Например, **упрощённый китайский (`zh`)** + **Country = TW, HK, MO** позволяет таргетировать пользователей с традиционным китайским письмом.</Callout> | | **Last seen** | Последняя дата, когда пользователь открывал приложение. | | **OS** | Версия операционной системы устройства пользователя. | | **Paid access level** | Уровень доступа, предоставленный пользователю. | | **Platform** | Платформа устройства пользователя. Возможные значения: `iOS`, `macOS`, `iPadOS`, `visionOS`, `Android`. <br/> Если пользователи работают с вашим приложением на нескольких платформах (например, iOS и Android), членство в сегменте оценивается отдельно для каждой платформы на основе последних данных с конкретного устройства. Это позволяет применять платформенный таргетинг даже для одного и того же профиля пользователя. | | **Subscription expiration date** | Дата окончания подписки или наличие/отсутствие значения. Для покупок с пожизненным доступом отображается `none`; поле остаётся пустым, если у пользователя есть профиль, но никогда не было пробного периода, подписки или покупки с пожизненным доступом. | | **Subscription product** | Последний идентификатор продукта активной подписки пользователя. | | **[Custom attributes](profiles-crm#custom-attributes)** | Определяйте собственные атрибуты для создания узкоцелевых сегментов на основе свойств, уникальных для вашего приложения или бизнеса. | ## Пользовательские атрибуты \{#custom-attributes\} Задайте пользовательские атрибуты, чтобы формировать более точные сегменты на основе свойств, специфичных для вашего приложения или бизнеса. :::note - Настроить пользовательские атрибуты можно через SDK или дашборд Adapty. Инструкции по настройке через SDK — [здесь](setting-user-attributes#custom-user-attributes). - Если изменить пользовательский атрибут после того, как он уже используется в сегменте, пользователь может выйти из этого сегмента в [аналитике](controls-filters-grouping-compare-proceeds#filter-and-group-data). В данных отобразится предыдущее значение. ::: ### Как настроить пользовательский атрибут \{#how-to-configure-a-custom-attribute\} В дашборде Adapty выберите **Create custom attributes** из выпадающего меню атрибутов. <img src="/assets/shared/img/883d3b2-CleanShot_2023-03-16_at_17.20.452x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> | Поле | Описание | | ------ |--------------------------------------------------------------------------------------------------------------------------------------| | **Name** | Название пользовательского атрибута, отображаемое только в дашборде Adapty. | | **Key** | Уникальный идентификатор атрибута. Должен совпадать с ключом, используемым в SDK. | | **Type** | Выберите один из вариантов:<ul><li>String: требует заранее заданного списка возможных значений.</li><li>Number: принимает только числовые значения.</li></ul> | | **Values** | Если выбран тип `String`, укажите список возможных значений. Если выбран тип `Number`, атрибут будет принимать только числовой ввод. Числовые атрибуты поддерживают десятичные значения и могут использоваться с операторами сравнения. | После заполнения обязательных полей вы сможете использовать пользовательские атрибуты в своих сегментах, [A/B-тестах](ab-tests) и не только. Каждый профиль может иметь до 30 пользовательских атрибутов. ## Общее число пользователей и случайная выборка \{#total-number-and-random-sample\} После создания сегмента Adapty показывает общее количество пользователей, соответствующих критериям сегмента. Adapty также показывает случайную выборку из 40 пользователей, подходящих под заданные критерии. Используйте её, чтобы проверить сегмент и убедиться, что он настроен правильно. <img src="/assets/shared/img/segment-random-set.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Дублирование сегментов \{#duplicate-segments\} Если вам нужен сегмент, похожий на уже существующий, продублируйте его вместо того, чтобы создавать с нуля. Это экономит время командам, которые запускают несколько кампаний или A/B-тестов с пересекающимися группами пользователей. При дублировании сегмента создаётся его копия со всеми фильтрами и описанием. К названию нового сегмента добавляется «(copy)», чтобы вы могли отличить его от оригинала. Новый сегмент независим от исходного: изменения в одном не влияют на другой. Чтобы продублировать сегмент в дашборде Adapty: 1. Откройте раздел **Profiles & Segments** в главном меню Adapty и перейдите на вкладку [**Segments**](https://app.adapty.io/segments). 2. Нажмите кнопку **3-dot** рядом с нужным сегментом и выберите **Duplicate**. <img src="/assets/shared/img/duplicate-segment.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Откройте новый сегмент и настройте фильтры по необходимости. ## Удаление сегментов \{#delete-segments\} Если сегмент больше не нужен, его можно удалить без возможности восстановления. Adapty заблокирует удаление, если сегмент используется как аудитория в одном из следующих объектов: - **Плейсмент**: хотя бы один неудалённый плейсмент использует этот сегмент в качестве аудитории. - **A/B-тест (активный или завершённый)**: хотя бы один неудалённый A/B-тест использует этот сегмент в качестве аудитории. При удалении сегмента Adapty считает активными как **Live**, так и **Completed** A/B-тесты. Завершённый тест по-прежнему использует аудиторию для показа пейвола или онбординга после окончания теста подходящим пользователям, а исторические метрики теста привязаны к этому сегменту. Сегмент освобождается только при удалении самого A/B-теста. :::warning Удаление сегмента необратимо. Восстановить его не получится. ::: Чтобы удалить сегмент в дашборде Adapty: 1. Перейдите в раздел **Profiles & Segments** главного меню Adapty и откройте вкладку [**Segments**](https://app.adapty.io/segments). 2. Нажмите кнопку **3-dot** рядом с сегментом и выберите **Delete**. 3. Введите название сегмента в поле подтверждения, затем нажмите **Delete forever**. :::info Если сегмент используется, в диалоге отобразится список плейсментов и A/B-тестов, которые на него ссылаются. Чтобы разблокировать удаление, откройте каждый плейсмент или A/B-тест из списка и либо удалите сегмент из его аудитории, либо удалите сам плейсмент или A/B-тест. Как только ничто не будет ссылаться на сегмент, вы сможете его удалить. ::: --- # File: event-feed --- --- title: "Лента событий" description: "Отслеживайте и анализируйте активность пользователей с помощью ленты событий Adapty." --- Лента событий позволяет наглядно отслеживать [события](events), генерируемые Adapty, и проверять статус их экспорта в сторонние интеграции, включая вебхук. :::warning Лента событий не отображает: - **Транзакции server-side API v1**: созданные через [server-side API (версия 1)](server-side-api-specs-legacy#requests). Используйте [server-side API (версия 2)](api-adapty/operations/setTransaction), чтобы они отображались. - **События без профиля**: транзакции, поступившие до того, как SDK идентифицировал пользователя, — например, уведомления от серверов сторов. Чтобы включить их в экспорт, активируйте **Include events without profile** в настройках интеграции [S3](s3-exports) или [Google Cloud Storage](google-cloud-storage). ::: <img src="/assets/shared/img/event-status.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> :::note Статус отправки для AppsFlyer, Facebook Ads и Branch может отображаться некорректно, так как эти сервисы не всегда возвращают ошибки при их возникновении. ::: Чтобы открыть профиль пользователя, инициировавшего транзакцию, нажмите кнопку **View Profile** в деталях события. --- # File: ab-tests --- --- title: "A/B-тест" description: "Оптимизируйте ценообразование подписок с помощью A/B-тестов в Adapty для повышения конверсии." --- :::tip Вы можете получить готовый план A/B-теста без самостоятельного исследования. [AI Growth Advisor](autopilot) проверяет ваш пейвол, анализирует конкурентов и формирует рекомендации на основе обезличенных данных более чем 20 000 приложений с подписками, отслеживаемых Adapty. ::: Увеличьте доход приложения с помощью A/B-тестов в Adapty. Сравнивайте разные флоу, пейволы и онбординги, чтобы найти лучший вариант конверсии — без изменений в коде. Например, можно тестировать: - Цены на подписки - Дизайн, текст и макет пейвола - Пробные периоды и длительность подписок - Дизайн онбордингов ## Предварительные требования \{#prerequisites\} Перед настройкой A/B-теста убедитесь, что у вас есть: - **Плейсменты**: Один или несколько [плейсментов](placements), где отображается флоу, пейвол или онбординг. - **Для флоу**: Минимум два [флоу](adapty-flow-builder). - **Для пейволов**: Минимум два [пейвола](paywalls). - **Для онбордингов**: Минимум два [онбординга](onboardings). :::warning Если вы не используете [Adapty Flow builder](adapty-flow-builder) или [Adapty Paywall builder](adapty-paywall-builder), [отправляйте события просмотра пейвола в Adapty](present-remote-config-paywalls#track-paywall-view-events) с помощью `.logShowFlow()` (iOS SDK v4+) / `.logShowPaywall()`. Без этого метода Adapty не сможет подсчитать просмотры пейвола в тесте, и статистика конверсий будет неточной. ::: ## Типы A/B-тестов \{#ab-test-types\} Adapty поддерживает два основных типа A/B-тестов: - **Regular**: Запускается на одном плейсменте (флоу, пейвол или онбординг). - **Crossplacement**: Запускается сразу на нескольких плейсментах пейволов и показывает пользователю одинаковый вариант во всех из них. На данный момент доступен только для пейволов. Подробное сравнение типов, сценариев использования и правил приоритетов см. в разделе [Типы A/B-тестов](ab-test-types). ## Дальнейшие шаги \{#next-steps\} - [AI Growth Advisor](autopilot) — Проанализируйте свой пейвол, получите рыночные инсайты и сформируйте план A/B-теста - [Типы A/B-тестов](ab-test-types) — Узнайте о типах тестов и когда применять каждый из них - [Создание, запуск и остановка A/B-теста](run_stop_ab_tests) — Настройте и запустите свой первый тест - [Результаты и метрики A/B-теста](results-and-metrics) — Разберитесь в данных A/B-теста и выберите победителя --- # File: ab-test-types --- --- title: "Типы A/B-тестов" description: "Узнайте о типах A/B-тестов в Adapty." --- Adapty предлагает два типа A/B-тестов, каждый из которых подходит для разных сценариев тестирования: - **Обычный A/B-тест:** A/B-тест, созданный для одного [флоу](adapty-flow-builder)/[пейвола](paywalls)/[онбординга](onboardings) плейсмента. - **Кросс-плейсментный A/B-тест:** A/B-тест, созданный для нескольких плейсментов пейволов в вашем приложении. После того как A/B-тест назначает <InlineTooltip tooltip="вариант">Варианты A/B-теста — это альтернативные версии флоу, пейвола или онбординга для тестирования.</InlineTooltip>, он показывает этот вариант одинаково во всех выбранных разделах приложения. :::warning Кросс-плейсментные A/B-тесты доступны только начиная с Adapty SDK v3.5.0. Кросс-плейсментные A/B-тесты работают только с пейволами. A/B-тесты для флоу требуют Adapty SDK v4.0.0+. A/B-тесты для онбордингов требуют Adapty SDK v3.8.0+ (iOS, Android, React Native, Flutter), v3.14.0+ (Unity) или v3.15.0+ (Kotlin Multiplatform, Capacitor). Пользователи с более ранними версиями пропускают их. ::: Каждый флоу/пейвол/онбординг получает вес, который распределяет трафик в ходе теста. Например, при соотношении 70% и 30% первый пейвол увидят примерно 700 из 1000 пользователей, второй — около 300. В кросс-плейсментных тестах веса задаются для каждого варианта, а не для каждого пейвола. Такой подход позволяет сравнивать разные флоу и пейволы и принимать решения на основе данных для монетизации вашего приложения. ## Когда использовать каждый тип \{#when-to-use-each-type\} Каждый тип A/B-теста полезен в следующих случаях: - **Обычные A/B-тесты**: - У вас только один плейсмент в приложении. - Вы хотите запустить A/B-тест на одном плейсменте и отслеживать изменения экономики только для него, даже если в приложении несколько плейсментов. - Вы хотите провести A/B-тест на старых пользователях (тех, кто уже видел хотя бы один пейвол Adapty). - **Кросс-плейсментный A/B-тест**: - Вы хотите синхронизировать варианты сразу на нескольких плейсментах. Например, одновременно изменить цены в онбординге и в настройках приложения. - Вы хотите оценить общую экономику приложения. Запуск теста на всех плейсментах упрощает анализ статистики A/B-теста по сравнению с тестированием отдельных плейсментов. - Вы хотите проводить A/B-тест только на новых пользователях, то есть тех, кто ещё ни разу не видел ни одного пейвола Adapty. - Вы хотите использовать несколько пейволов в рамках одного варианта: <img src="/assets/shared/img/ab-test-variants.png" alt="Пример нескольких пейволов в рамках одного варианта кросс-плейсментного A/B-теста" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Ключевые различия \{#key-differences\} | Функция | Обычный A/B-тест | Кросс-плейсментный A/B-тест | | ------------------------------- |--------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------| | **Что тестируется** | Один флоу/пейвол/онбординг | Набор пейволов, принадлежащих одному варианту | | **Согласованность варианта** | Вариант определяется отдельно для каждого плейсмента | Один и тот же вариант используется во всех плейсментах пейволов | | **Таргетинг по аудитории** | Задаётся для каждого плейсмента флоу/пейвола/онбординга | Общий для всех плейсментов пейволов | | **Аналитика** | Анализируется один плейсмент флоу/пейвола/онбординга | Анализируется всё приложение по тем плейсментам, которые входят в тест | | **Распределение веса вариантов** | Для каждого флоу/пейвола/онбординга | Для набора пейволов | | **Пользователи** | Для всех пользователей | Только новые пользователи (те, кто ещё не видел пейвол Adapty) | | **Версия SDK Adapty** | Для флоу: v4.0.0+. Для пейволов: любая. Для онбордингов: v3.8.0+ (iOS, Android, React Native, Flutter), v3.14.0+ (Unity), v3.15.0+ (KMP, Capacitor) | 3.5.0+ | | **Лучше всего подходит для** | Тестирования независимых изменений в одном плейсменте флоу/пейвола/онбординга без учёта общей экономики приложения | Оценки общих стратегий монетизации в масштабах всего приложения | ## Логика выбора A/B-теста \{#ab-test-selection-logic\} **Межплейсментные A/B-тесты имеют приоритет над обычными A/B-тестами.** Однако межплейсментные тесты показываются только **новым пользователям** — тем, кто ещё не видел ни одного пейвола Adapty (метод SDK `getPaywall` для них ни разу не вызывался). Это обеспечивает согласованность результатов между плейсментами. На следующей схеме показана логика, которую Adapty использует для выбора A/B-теста для плейсмента: <img src="/assets/shared/img/ab-tests-scheme.webp" alt="Diagram showing the A/B test selection logic for a paywall placement" style={{ border: '1px solid #727272', /* border width and color */ width: '350px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> На странице **A/B Tests** тесты пейволов, онбордингов, флоу и кросс-плейсментные тесты отображаются на отдельных вкладках. <img src="ab-tests-tabs.webp" alt="Страница списка A/B-тестов с вкладками для типов тестов: обычные, онбординг и кросс-плейсмент" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Ограничения кросс-плейсментных A/B-тестов \{#crossplacement-ab-test-limitations\} :::warning Кросс-плейсментные A/B-тесты не могут включать плейсменты с флоу или онбордингом. ::: Кросс-плейсментные A/B-тесты гарантируют, что каждый пользователь видит один и тот же вариант во всех плейсментах теста. Это создаёт следующие ограничения: * Участвовать могут только новые пользователи. Новый пользователь — тот, кто ещё не видел ни одного пейвола Adapty и чьё приложение ни разу не вызывало `getPaywall`. Для остальных пользователей Adapty не может гарантировать согласованную цепочку пейволов. * Первый плейсмент, с которым сталкивается пользователь, определяет, какой пейвол покажет Adapty. Изменить назначение пользователя или записать одного и того же пользователя в более чем один кросс-плейсментный A/B-тест нельзя. :::warning Как только пользователь получает кросс-плейсментный пейвол, он видит его в течение 90 дней — даже после остановки теста. Чтобы изменить этот срок, в настройках **General** измените значение **[Cross-placement variation stickiness](general#9-cross-placement-variation-stickiness)**. ::: ## Приоритет Crossplacement A/B-тестов \{#crossplacement-ab-test-priority\} * Crossplacement A/B-тесты всегда имеют приоритет над обычными A/B-тестами и A/B-тестами онбордингов. Если новый пользователь подходит одновременно для Crossplacement-теста и обычного теста на одном плейсменте, будет показан Crossplacement-тест. * Когда несколько Crossplacement A/B-тестов с одной и той же аудиторией используют один плейсмент, Adapty автоматически расставляет приоритеты по порядку добавления тестов. Первый добавленный тест получает наивысший приоритет. Изменить его вручную нельзя. * Тесты, нацеленные на меньшие сегменты аудитории, автоматически получают приоритет над тестами, которые нацелены на сегмент «Все пользователи». :::note В Analytics кросс-плейсментный A/B-тест отображается как несколько дочерних тестов — по одному на каждый плейсмент. Дочерние тесты именуются по шаблону `<test-name> child-0`, `<test-name> child-1` и так далее. Нумерация соответствует порядку плейсментов на странице с деталями A/B-теста. Чтобы просмотреть результаты по конкретному плейсменту, используйте фильтр **Placement**. ::: ## Следующие шаги \{#next-steps\} - [Создание, запуск и остановка A/B-теста](run_stop_ab_tests) — настройте и запустите свой первый тест - [Результаты и метрики A/B-теста](results-and-metrics) — проанализируйте результаты и выберите победителя --- # File: run_stop_ab_tests --- --- title: "Создание, запуск и остановка A/B-теста" description: "Пошаговый гайд по созданию, запуску и остановке A/B-тестов в Adapty." --- В этой статье рассматривается полный жизненный цикл A/B-теста в Adapty: создание теста, его запуск и остановка по готовности к анализу результатов. ## Предварительные требования \{#prerequisites\} Перед настройкой A/B-теста у вас должно быть: - Как минимум два [флоу](adapty-flow-builder)/[пейвола](paywalls)/[онбординга](onboardings) - Настроенный [плейсмент](placements) в приложении :::warning Если вы не используете [Adapty Flow Builder](adapty-flow-builder) или [Adapty Paywall Builder](adapty-paywall-builder), [отправляйте просмотры пейвола в Adapty](present-remote-config-paywalls#track-paywall-view-events) с помощью `.logShowPaywall()`. Без этого метода Adapty не сможет подсчитать просмотры пейвола в тесте, и статистика конверсий будет неточной. ::: :::info A/B-тесты в Adapty работают в два этапа. Сначала вы создаёте тест и сохраняете его как черновик — он не запускается сразу. Когда будете готовы, запустите его отдельно. Это позволяет проверить настройки до того, как тест увидят пользователи. ::: ## Создание A/B-теста \{#create-an-ab-test\} При создании нового A/B-теста необходимо включить как минимум два [флоу](adapty-flow-builder)/[пейвола](paywalls)/[онбординга](onboardings). Чтобы создать новый A/B-тест: 1. Перейдите в раздел [A/B tests](ab-tests) из главного меню Adapty. <img src="/assets/shared/img/go-to-abtests.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. В правом верхнем углу нажмите **Create A/B test**. <img src="/assets/shared/img/create-abtest.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. В окне **Create the A/B test** введите **Test name**. Это обязательное поле. Выберите название, которое чётко описывает суть теста, чтобы легко найти его при просмотре результатов. 4. Заполните поле **Test goal** — опишите, чего хотите достичь (например, увеличить число подписок или снизить отток). 5. Нажмите **Select placement** и выберите плейсмент с флоу, пейволом или онбордингом. 6. Настройте содержимое теста в таблице **Variants**. Каждая строка — это вариант, каждый столбец — плейсмент. В каждой ячейке добавьте пейвол. По умолчанию таблица содержит 2 варианта и 1 плейсмент. Можно добавить до 20 вариантов. После добавления второго плейсмента тест становится кросс-плейсментным A/B-тестом. Обратите внимание: кросс-плейсментные A/B-тесты доступны только для пейволов. <img src="/assets/shared/img/abtest-variants.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> 7. Сохраните тест. У вас есть два варианта: 1. **Save as draft**: тест не запустится сразу. Вы сможете запустить его позже из списка плейсментов или A/B-тестов. Используйте этот вариант, чтобы проверить настройки перед запуском. 2. **Run A/B test**: запускает тест немедленно. Тест становится активным сразу после нажатия этой кнопки. После сохранения в черновик перейдите к разделу [Запуск A/B-теста](#run-an-ab-test). ## Редактирование A/B-теста \{#edit-an-ab-test\} Редактировать можно только те A/B-тесты, которые сохранены как черновики. После запуска теста изменить его нельзя. Чтобы обновить активный тест, воспользуйтесь опцией **Modify** — она создаёт дубликат с тем же именем, в котором можно внести правки. Adapty остановит исходный тест, и оба — исходный и изменённый — будут отображаться в аналитике отдельно. ## Запустите A/B-тест \{#run-an-ab-test\} Запустить A/B-тест в Adapty — значит назначить его на плейсмент, чтобы он начал показывать пейволы и онбординги пользователям. 1. Откройте раздел [A/B-тесты](ab-tests) из главного меню Adapty. 2. Убедитесь, что просматриваете нужный список — A/B-тесты **Paywall**, **Flow**, **Onboardings** и **Crossplacement** отображаются на отдельных вкладках, между которыми можно переключаться. 3. Перейдите на вкладку **Drafts**. Запустить можно только черновые тесты. 4. Рядом с нужным тестом нажмите **Run A/B test**. 5. Откроется окно **Edit A/B test**. Проверьте настройки и при необходимости внесите изменения. Если плейсмент или аудитория не указаны, добавьте их сейчас. 6. После проверки настроек нажмите **Run A/B test**, чтобы запустить тест. После запуска теста вы можете отслеживать его ход и просматривать данные о результатах на странице [Результаты и метрики A/B-теста](results-and-metrics). ## Остановить A/B-тест \{#stop-an-ab-test\} Когда вы останавливаете A/B-тест, он завершается и становятся доступны его результаты. Также нужно решить, что показывать пользователям в затронутых плейсментах после окончания теста. <img src="/assets/shared/img/stop-ab-test.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. Откройте раздел [A/B tests](https://app.adapty.io/ab-tests) и перейдите на вкладку **Live**. 2. Рядом с нужным тестом нажмите меню с тремя точками и выберите **Stop A/B test**. 3. В окне **Stop the A/B test** выберите, что должно произойти после завершения теста. Доступны три варианта: | Опция | Описание | |----------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Показать один из тестируемых пейволов/онбордингов | Выберите победивший пейвол или онбординг на основе результатов теста: выручка, вероятность быть лучшим (**P2BB**) и выручка на 1K пользователей. Этот пейвол или онбординг будет показываться для выбранного плейсмента и аудитории. | | Выбрать пейволы/онбординги, не участвующие в A/B-тесте | Выберите любой пейвол или онбординг, который не является частью текущего A/B-теста. Используйте этот вариант, если ни один из тестируемых вариантов не достиг ваших целей. | | Не показывать конкретный пейвол/онбординг | Для выбранного плейсмента и аудитории после завершения A/B-теста конкретный пейвол или онбординг выбран не будет. Вместо этого будет показан следующий доступный пейвол или онбординг согласно приоритету аудитории. Это хороший выбор, если вы предпочитаете, чтобы текущая настройка сама определяла, какой пейвол или онбординг отображать, без ручного выбора. | :::note Остановка A/B-теста необратима — его нельзя перезапустить. Убедитесь, что собрали достаточно данных, прежде чем принять решение об остановке. ::: 4. Нажмите кнопку **Stop and complete this A/B test**. После завершения A/B-тест перестаёт быть активным, и пейволы или онбординги из него больше не показываются новым пользователям. Вы по-прежнему можете просматривать результаты и метрики A/B-теста на [странице метрик A/B-теста](results-and-metrics#metrics-controls), чтобы оценить эффективность для пользователей, участвовавших в тесте. Метрики могут продолжать обновляться по мере того, как новые события покупок или дохода атрибутируются этим пользователям. --- # File: ab-test-no-paywall-variants --- --- title: "Добавление вариантов A/B-теста без флоу или пейволов" description: "Запустите A/B-тест, в котором один вариант пропускает флоу или пейвол, используя флаг в Remote Config для управления его отображением." --- Вы можете измерить влияние флоу или пейвола, запустив A/B-тест с пустым вариантом. Один вариант показывает флоу или пейвол, другой не показывает ничего. Приложение читает флаг из Remote Config и решает, нужно ли что-то отображать. ## Как это работает \{#how-it-works\} Настройка использует два флоу/пейвола в одном плейсменте: - **Флоу/Пейвол A**: флоу или пейвол, который вы хотите протестировать, с `show_paywall` установленным в `true` в его Remote Config. - **Флоу/Пейвол B**: пустой флоу или пейвол с `show_paywall` установленным в `false` в его Remote Config. Когда SDK возвращает флоу или пейвол, ваше приложение считывает флаг `show_paywall`. Если флаг равен `true`, приложение отображает его. Если флаг равен `false`, приложение пропускает отображение, и пользователь продолжает работу, ничего не видя. ## 1. Добавьте флаг show_paywall в Remote Config \{#1-add-the-show_paywall-flag-in-remote-config\} Вам понадобятся два флоу или два пейвола в одном плейсменте: Flow/Paywall A (тот, который хотите протестировать) и Flow/Paywall B (пустой). Добавьте поле `show_paywall` в каждый из них, чтобы приложение могло принять решение по одному и тому же ключу для обоих вариантов. Чтобы добавить флаг в Flow/Paywall A: 1. Откройте раздел [**Flows**](https://app.adapty.io/flows)/[**Paywalls**](https://app.adapty.io/paywalls) в главном меню Adapty и выберите Flow/Paywall A. 2. Откройте раздел **Remote config**. 3. Создайте поле с именем `show_paywall` и значением `true`. В режиме **JSON** запись выглядит так: ```json showLineNumbers { "show_paywall": true } ``` 4. Сохраните изменения. Повторите те же шаги для Flow/Paywall B, но задайте для `show_paywall` значение `false`. Подробнее о Remote Config читайте в разделах [Настройка флоу с помощью Remote Config](customize-flow-with-remote-config) и [Дизайн пейвола с помощью Remote Config](customize-paywall-with-remote-config). :::tip Установка `show_paywall` в обоих вариантах делает путь выполнения кода одинаковым для обеих групп и упрощает добавление новых вариантов в будущем. ::: ## 2. Настройте A/B-тест \{#2-set-up-the-ab-test\} 1. [Создайте A/B-тест](run_stop_ab_tests) на плейсменте и добавьте оба флоу/пейвола как варианты. 2. Задайте веса вариантов, чтобы распределить трафик между пользователями, которые видят флоу/пейвол, и теми, кто не видит. ## 3. Проверьте флаг в приложении \{#check-the-flag-in-your-app\} Прочитайте `show_paywall` из Remote Config, который возвращает SDK. Если флаг равен `false`, пропустите рендеринг и дайте пользователю продолжить. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> ```swift showLineNumbers do { let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID") let config = flow.remoteConfigs.first(where: { $0.locale == "en" }) ?? flow.remoteConfigs.first let showPaywall = config?.dictionary?["show_paywall"] as? Bool ?? true if showPaywall { // render the flow or paywall } } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android"> ```kotlin showLineNumbers Adapty.getPaywall("YOUR_PLACEMENT_ID") { result -> when (result) { is AdaptyResult.Success -> { val paywall = result.value val showPaywall = paywall.remoteConfig?.dataMap?.get("show_paywall") as? Boolean ?: true if (showPaywall) { // Render the paywall } } is AdaptyResult.Error -> { // handle the error } } } ``` </TabItem> <TabItem value="react-native" label="React Native"> ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: "YOUR_PLACEMENT_ID" }); const showPaywall = paywall.remoteConfig?.data?.["show_paywall"] ?? true; if (showPaywall) { // Render the paywall } } catch (error) { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter"> ```dart showLineNumbers try { final paywall = await Adapty().getPaywall(id: "YOUR_PLACEMENT_ID"); final bool showPaywall = paywall.remoteConfig?.dictionary?['show_paywall'] as bool? ?? true; if (showPaywall) { // Render the paywall } } on AdaptyError catch (adaptyError) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity"> ```csharp showLineNumbers Adapty.GetPaywall("YOUR_PLACEMENT_ID", (paywall, error) => { if (error != null) { // handle the error return; } var showPaywall = paywall.RemoteConfig?.Dictionary?["show_paywall"] as bool? ?? true; if (showPaywall) { // Render the paywall } }); ``` </TabItem> <TabItem value="kmp" label="Kotlin Multiplatform"> ```kotlin showLineNumbers Adapty.getPaywall( placementId = "YOUR_PLACEMENT_ID" ).onSuccess { paywall -> val showPaywall = paywall.remoteConfig?.dataMap?.get("show_paywall") as? Boolean ?: true if (showPaywall) { // Render the paywall } }.onError { error -> // handle the error } ``` </TabItem> <TabItem value="capacitor" label="Capacitor"> ```typescript showLineNumbers try { const paywall = await adapty.getPaywall({ placementId: 'YOUR_PLACEMENT_ID' }); const showPaywall = paywall.remoteConfig?.data?.['show_paywall'] ?? true; if (showPaywall) { // Render the paywall } } catch (error) { // handle the error } ``` </TabItem> </Tabs> Значение по умолчанию `true` сохраняет флоу/пейвол видимым, если флаг отсутствует, — так что существующие флоу/пейволы без этого флага не будут затронуты. :::important Если вы рендерите пейвол самостоятельно (без [Flow Builder](adapty-flow-builder) или [Paywall Builder](adapty-paywall-builder)), вызовите [`logShowFlow` (iOS SDK v4+) / `logShowPaywall`](present-remote-config-paywalls#track-paywall-view-events) при отображении флоу/пейвола A. Без этого Adapty не сможет учитывать просмотры в тесте. Не логируйте просмотр для флоу/пейвола B, поскольку он никогда не показывается. ::: ## Следующие шаги \{#next-steps\} - [Создание, запуск и остановка A/B-теста](run_stop_ab_tests) — настройте тест, включающий оба варианта - [Результаты и метрики A/B-теста](results-and-metrics) — сравните пустой вариант с вашим флоу/пейволом --- # File: results-and-metrics --- --- title: "Результаты и метрики A/B-теста" description: "Анализируйте результаты и ключевые метрики в Adapty для улучшения показателей подписок и вовлечённости пользователей." --- Изучайте важные данные и выводы из [A/B-тестов](ab-tests): сравнивайте разные пейволы и онбординги, чтобы понять, как они влияют на поведение пользователей, вовлечённость и конверсию. Анализируйте метрики и результаты, чтобы принимать взвешенные решения и повышать эффективность приложения. Погружайтесь в данные — находите полезные инсайты и добивайтесь успеха. ## Результаты A/B-теста \{#ab-test-results\} Adapty предоставляет три метрики для результатов A/B-теста: <img src="/assets/shared/img/ab-test-results.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::note Adapty конвертирует другие валюты в USD по курсу [currencylayer.com](https://currencylayer.com/) (обновляется каждые 8 часов). Курс **фиксируется на момент транзакции** — последующие изменения не влияют на результат конвертации. ::: **Revenue**: Эта метрика показывает общую сумму денег в USD, полученных от покупок и продлений подписок, за вычетом возвратов средств пользователям. Она учитывает как первоначальную покупку, так и последующие продления подписки. Revenue помогает понять, как каждый вариант A/B-теста работает с финансовой точки зрения, и определить, какой из них приносит больше всего денег. Узнайте больше о метриках [пейвола](paywall-metrics). **Вероятность быть лучшим**: Adapty использует надёжную математическую методику для анализа результатов A/B-тестов и предоставляет метрику под названием «Вероятность быть лучшим». Эта метрика оценивает вероятность того, что конкретный вариант является наилучшим по долгосрочной выручке среди всех тестируемых вариантов. Метрика выражается в процентах от 1% до 100%. Подробнее о том, как Adapty рассчитывает эту метрику, читайте в [документации.](maths-behind-it) Наилучший вариант, определяемый по выручке на 1000 пользователей, выделяется зелёным цветом и автоматически выбирается по умолчанию. **Доход на 1K пользователей**: метрика «Доход на 1K пользователей» вычисляет средний доход, генерируемый на 1 000 пользователей для каждого варианта A/B-теста. Она помогает оценить эффективность вариантов с точки зрения дохода вне зависимости от общего числа пользователей — сравнивать варианты на стандартизированной шкале и принимать взвешенные решения на основе показателей эффективности генерации дохода. **Доверительные интервалы прогноза для дохода на 1K пользователей**: Метрика дохода на 1K пользователей включает доверительные интервалы прогноза. Они показывают диапазон, в который, по оценке статистического анализа на основе имеющихся данных, попадёт истинное значение дохода на 1 000 пользователей для данного варианта. В контексте A/B-тестирования при анализе выручки, генерируемой разными вариантами, мы рассчитываем среднюю выручку на 1000 пользователей для каждого варианта. Поскольку выручка может варьироваться от пользователя к пользователю, интервалы прогноза наглядно показывают правдоподобный диапазон значений выручки на 1000 пользователей с учётом вариативности и неопределённости, присущих процессу прогнозирования. Включая интервалы прогнозов в метрику дохода на 1000 пользователей, Adapty позволяет оценивать эффективность вариантов A/B-теста с учётом диапазона возможных результатов по выручке. Эта информация помогает принимать решения на основе данных и эффективно оптимизировать стратегию подписок — с учётом неопределённости в процессе прогнозирования и возможных значений дохода на 1000 пользователей. Анализируя эти метрики в Adapty, вы получаете представление о финансовых показателях, статистической значимости и эффективности дохода вариантов A/B-теста — и можете принимать решения на основе данных, оптимизируя стратегию подписок. ## Метрики A/B-теста \{#ab-test-metrics\} Adapty предоставляет полный набор метрик для эффективного измерения результатов A/B-теста на вариантах пейвола или онбординга. Метрики обновляются в реальном времени, за исключением просмотров — они обновляются периодически. Понимание этих метрик поможет вам оценить эффективность разных вариантов и принимать решения на основе данных для оптимизации стратегии пейвола или онбординга. Метрики A/B-тестов доступны в списке A/B-тестов, где можно получить общее представление о результатах всех ваших A/B-тестов. В этом сводном представлении отображаются агрегированные метрики для каждого варианта теста, что позволяет сравнивать их эффективность и выявлять значимые различия. Для более детального анализа конкретного A/B-теста можно перейти к детальным метрикам A/B-теста. В этом разделе представлены подробные метрики, относящиеся к выбранному A/B-тесту, которые позволяют глубоко изучить результаты отдельных вариантов. Все метрики, за исключением просмотров, относятся к продукту в рамках пейвола или онбординга. ## Элементы управления метриками \{#metrics-controls\} Система отображает метрики на основе выбранного периода времени и организует их в соответствии с параметром левого столбца с тремя уровнями отступов. ### Фильтрация профилей по дате установки \{#profile-install-date-filtration\} <img src="/assets/shared/img/2bf4d9f-Area.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Флажок **Filter metrics by install date** позволяет фильтровать метрики по дате установки профиля вместо стандартных фильтров, которые используют дату пробного периода/покупки для транзакций или дату просмотра для пейволов и онбордингов. Выбрав этот флажок, вы можете сосредоточиться на оценке эффективности привлечения пользователей за конкретный период, привязав метрики к дате установки профиля. Эта опция полезна, когда нужно адаптировать анализ метрик под конкретные задачи. ### Временные диапазоны \{#time-ranges\} Вы можете выбирать из ряда временных периодов для анализа данных метрик, что позволяет сосредоточиться на конкретных промежутках, таких как дни, недели, месяцы или произвольные диапазоны дат. <img src="/assets/shared/img/ab-test-time-ranges.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Инструменты управления аналитикой](controls-filters-grouping-compare-proceeds) ::: Adapty предоставляет мощные инструменты для фильтрации и настройки анализа метрик под ваши задачи. На странице метрик доступны различные временные диапазоны, варианты группировки и возможности фильтрации. - ✅ Фильтрация по: аудитории, атрибуции, стране, пейволу, состоянию пейвола, группе пейволов, онбордингу, плейсменту, стране, стору, продукту и стору продукта. - ✅ Группировка по: продукту и стору. :::note Когда вы фильтруете по A/B-тесту, кросс-плейсментные A/B-тесты отображаются как отдельные дочерние тесты (например, `My test child-0`, `My test child-1`) — по одному на каждый плейсмент. Подробнее см. в разделе [Ограничения кросс-плейсментных A/B-тестов](ab-test-types#crossplacement-ab-test-limitations). ::: ## График отдельной метрики \{#single-metrics-chart\} Один из ключевых элементов страницы метрик пейвола или онбординга — раздел с графиком, который наглядно отображает выбранные метрики и упрощает их анализ. <img src="/assets/shared/img/e6b0674-Area.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> График на странице метрик A/B-теста содержит горизонтальную столбчатую диаграмму, которая визуально отображает значения выбранной метрики. Каждый столбец соответствует значению метрики и пропорционален ему по размеру — это позволяет мгновенно считывать данные. Горизонтальная линия показывает анализируемый временной промежуток, вертикальный столбец отображает числовые значения метрик. Суммарное значение всех метрик отображается рядом с графиком. Кроме того, нажатие на значок стрелки в правом верхнем углу раздела графика разворачивает область просмотра, отображая выбранные метрики на полной линии графика. ## Сводка A/B-теста \{#ab-test-summary\} Рядом с графиком отдельной метрики отображается раздел сводки деталей A/B-теста, который содержит информацию о состоянии, продолжительности, плейсментах и других связанных деталях A/B-теста. <img src="/assets/shared/img/90fa3f5-Area.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Определения метрик \{#metrics-definitions\} Вот ключевые метрики, доступные для A/B-тестов: <img src="/assets/shared/img/30c7b68-Area.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Доход \{#revenue\} Доход — это общая сумма в USD, полученная от покупок и продлений подписок в рамках A/B-теста. Включает первоначальную покупку и последующие продления подписки. Метрика рассчитывается до вычета комиссии App Store или Play Store. Подробнее о метриках [дохода пейвола](paywall-metrics#revenue). ### CR to purchases \{#cr-to-purchases\} Коэффициент конверсии в покупки измеряет эффективность A/B-теста в плане конвертации просмотров в реальные покупки. Рассчитывается путём деления числа покупок на число просмотров. Например, если было 10 покупок и 100 просмотров, коэффициент конверсии в покупки составит 10%. ### CR trials \{#cr-trials\} Коэффициент конверсии (CR) в пробные периоды — это число пробных периодов, запущенных из A/B-теста, делённое на число просмотров. CR в пробные периоды измеряет эффективность A/B-теста в конвертации просмотров в активации пробного периода. Рассчитывается путём деления числа запущенных пробных периодов на число просмотров. ### Purchases \{#purchases\} Метрика Purchases представляет общее число транзакций, совершённых в пейволе или онбординге в результате A/B-теста. Она включает следующие типы покупок: - Новые совершённые покупки. - Конверсии из пробных периодов, которые были активированы. - Даунгрейды, апгрейды и кросс-грейды подписок. - Восстановления подписок (например, когда подписка истекла без автопродления и впоследствии была восстановлена). Обратите внимание, что продления не включаются в метрику Purchases. ### Trials \{#trials\} Метрика Trials указывает общее число активированных пробных периодов в результате A/B-теста. ### Trials cancelled \{#trials-cancelled\} Метрика Trials cancelled представляет число пробных периодов, в которых автопродление было отключено. Это происходит, когда пользователи вручную отменяют подписку на пробный период. ### Refunds \{#refunds\} Refunds для A/B-теста представляют число возвратов покупок и подписок, непосредственно связанных с тестируемыми вариациями. ### Views \{#views\} Views — это число просмотров пейволов или онбордингов, входящих в A/B-тест. Если пользователь посещает дважды, это считается двумя посещениями. ### Unique views \{#unique-views\} Unique views — это число уникальных просмотров пейвола или онбординга. Если пользователь посещает его дважды, это считается одним уникальным просмотром. ### Probability to be the best \{#probability-to-be-the-best\} Метрика Probability to be the best количественно оценивает вероятность того, что конкретный вариант в A/B-тесте является наилучшим среди всех протестированных пейволов или онбордингов. Она предоставляет числовую вероятность, указывающую на относительную производительность каждого пейвола или онбординга. Метрика выражается в процентах от 1% до 100%. ### ARPU (Average revenue per user) \{#arpu-average-revenue-per-user\} Только для A/B-тестов онбординга. Измеряет средний доход, генерируемый от каждого пользователя за определённый период. Рассчитывается путём деления общего дохода на число уникальных пользователей. ### ARPPU (Average revenue per paying user) \{#arppu-average-revenue-per-paying-user\} ARPPU расшифровывается как Average Revenue Per Paying User (средний доход на платящего пользователя) в результате A/B-теста. Рассчитывается как общий доход, делённый на число уникальных платящих пользователей. Например, если вы получили $15 000 дохода от 1 000 платящих пользователей, ARPPU составит $15. ### ARPAS (Average revenue per active subscriber) \{#arpas-average-revenue-per-active-subscriber\} ARPAS — метрика, позволяющая измерить средний доход, генерируемый на активного подписчика в результате A/B-теста. Рассчитывается путём деления общего дохода на число подписчиков, активировавших пробный период или подписку. Например, если общий доход составляет $5 000 и у вас 1 000 подписчиков, ARPAS составит $5. Эта метрика помогает оценить средний потенциал монетизации на одного подписчика. ### Proceeds \{#proceeds\} Метрика Proceeds для A/B-теста представляет фактическую сумму денег, полученных владельцем приложения в USD от покупок и продлений после вычета применимой комиссии App Store / Play Store. Она отражает чистый доход, непосредственно связанный с вариациями, протестированными в A/B-тесте, и напрямую влияет на заработок приложения. Дополнительную информацию о том, как рассчитываются Proceeds, см. в [документации](analytics-cohorts#revenue-vs-proceeds) Adapty. ### Unique subscribers \{#unique-subscribers\} Метрика Unique subscribers представляет количество уникальных пользователей, оформивших подписку или активировавших пробный период через вариации в A/B-тесте. Каждый подписчик учитывается только один раз, независимо от числа инициированных подписок или пробных периодов. ### Unique paid subscribers \{#unique-paid-subscribers\} Метрика Unique paid subscribers представляет число уникальных пользователей, успешно совершивших покупку и ставших платными подписчиками через вариации в A/B-тесте. ### Refund rate \{#refund-rate\} Refund rate для A/B-теста рассчитывается путём деления числа возвратов, непосредственно связанных с вариациями теста, на число первичных покупок (продления исключаются). Например, если было 5 возвратов и 1 000 первичных покупок, refund rate составит 0,5%. ### Unique CR purchases \{#unique-cr-purchases\} Уникальный коэффициент конверсии в покупки для A/B-теста рассчитывается путём деления числа покупок, непосредственно связанных с вариациями теста, на число уникальных просмотров. Например, если было 10 покупок и 100 уникальных просмотров, уникальный коэффициент конверсии в покупки составит 10%. ### Unique CR trials \{#unique-cr-trials\} Уникальный коэффициент конверсии в пробные периоды для A/B-теста рассчитывается путём деления числа запущенных пробных периодов, непосредственно связанных с вариациями теста, на число уникальных просмотров. Например, если было запущено 30 пробных периодов и 100 уникальных просмотров, уникальный коэффициент конверсии в пробные периоды составит 30%. ### Completions & unique completions \{#completions--unique-completions\} Только для A/B-тестов онбординга. Completions подсчитывают количество раз, когда пользователи завершают онбординг через вариации в A/B-тесте, то есть проходят путь от первого до последнего экрана. Если кто-то завершил его дважды, это два **completions**, но одно **unique completion**. ### Unique completions rate \{#unique-completions-rate\} Только для A/B-тестов онбординга. Число уникальных завершений, делённое на число уникальных просмотров. Эта метрика помогает понять, как пользователи взаимодействуют с онбордингом через вариации в A/B-тесте, и вносить изменения, если вы замечаете, что пользователи его игнорируют. --- # File: maths-behind-it --- --- title: "Математика за A/B-тестами" description: "Разберитесь в математике за аналитикой подписок для более глубокого понимания доходов." --- A/B-тестирование — мощный инструмент для сравнения эффективности двух версий флоу, пейвола или онбординга. Конечная цель — определить, какая версия приносит больше дохода на пользователя за 12-месячный период. Однако ждать целый год ради сбора данных непрактично. Поэтому в качестве прокси-метрики используется доход на пользователя за 2 недели — этот показатель выбран на основе анализа исторических данных как аппроксимация целевой метрики. Чтобы получить точные и надёжные результаты, необходим статистический метод, способный работать с разнородными данными. Байесовская статистика — популярный подход в современном анализе данных — предоставляет гибкий и интуитивно понятный фреймворк для A/B-тестирования. Байесовские методы учитывают предварительные знания и обновляют их по мере поступления новых данных, что позволяет принимать взвешенные решения в условиях неопределённости. Этот документ содержит подробное описание математического анализа, который Adapty применяет при оценке результатов A/B-тестов и формировании выводов для принятия решений на основе данных. ## Подход Adapty к статистическому анализу \{#adaptys-approach-to-statistical-analysis\} Adapty использует комплексный подход к статистическому анализу, чтобы оценивать результаты A/B-тестов и давать точные и надёжные выводы. Наша методология включает следующие ключевые шаги: 1. **Определение метрики:** Чтобы успешно провести A/B-тест, нужно определить ключевую метрику, которая соответствует целям и задачам анализа. Adapty проанализировал огромный массив исторических данных приложений с подписками, чтобы выяснить, какой показатель лучше всего служит прокси-метрикой для долгосрочной цели — средней выручки через 1 год. Этой метрикой оказался ARPU за 14 дней. 2. **Формулировка гипотез:** Для A/B-теста формулируются две гипотезы. Нулевая гипотеза (H0) предполагает, что значимой разницы между контрольной группой (A) и тестовой группой (B) нет. Альтернативная гипотеза (H1) предполагает, что значимая разница между двумя или более группами существует. 3. **Выбор распределения:** Мы выбираем наиболее подходящее семейство распределений, исходя из характеристик данных и наблюдаемой метрики. Чаще всего используется логнормальное распределение (с учётом нулевых значений). 4. **Расчёт вероятности быть лучшим:** Используя байесовский подход к A/B-тестированию, мы рассчитываем вероятность быть наилучшим вариантом для каждого пейвола или онбординга, участвующего в тесте. Это значение связано с p-value, которые применялись ранее, но по сути представляет собой другой подход — более надёжный и понятный. 5. **Интерпретация результатов:** Вероятность быть лучшим означает именно то, о чём говорит название. Чем выше вероятность, тем больше шансов, что конкретный вариант окажется оптимальным для решаемой задачи. Порог для принятия решений нужно определять самостоятельно с учётом множества факторов конкретной ситуации; типичное пороговое значение вероятности — 95%. 6. **Интервалы прогнозирования:** Adapty рассчитывает интервалы прогнозирования для метрик производительности каждой группы, задавая диапазон значений, в который с высокой вероятностью попадает истинный параметр генеральной совокупности. Это помогает количественно оценить неопределённость, связанную с оцениваемыми метриками. ## Определение размера выборки \{#sample-size-determination\} Правильный выбор размера выборки — ключевое условие надёжных и однозначных результатов A/B-теста. Adapty учитывает такие факторы, как статистическая мощность и ожидаемый размер эффекта — они остаются важными и в рамках байесовского подхода, — чтобы обеспечить достаточный объём выборки. Методы оценки необходимого размера выборки, характерные для используемого байесовского подхода, гарантируют надёжность анализа. Чтобы узнать больше о функциональности A/B-тестов, рекомендуем ознакомиться с нашей документацией по [созданию](ab-tests) и [запуску A/B-тестов](run_stop_ab_tests), а также разобраться с различными [метриками и результатами A/B-тестов](results-and-metrics). Аналитическая система Adapty для A/B-тестов теперь использует байесовский подход, однако основные принципы остались прежними: определение метрик, формулировка гипотез и выбор распределений. Вместо p-значений мы теперь вычисляем апостериорные распределения и рассчитываем вероятность того, что каждый вариант является лучшим. Также мы определяем интервалы предсказания. Этот обновлённый подход — не менее полный, но более надёжный — даёт результаты, которые проще интерпретировать и понимать. Цель остаётся прежней: помочь бизнесу оптимизировать стратегии, улучшать показатели и расти на основе статистически обоснованного анализа A/B-тестов. --- # File: autopilot-how-it-works --- --- title: "AI Growth Advisor: принцип работы" description: "Разберитесь в логике AI Growth Advisor и доверьтесь нам для роста вашего дохода." --- [AI Growth Advisor](autopilot) помогает понять, какие эксперименты стоит запустить, опираясь на реальные данные о вашей эффективности и на то, как ведут себя похожие приложения в вашей нише. Вместо того чтобы гадать, что может сработать, вы получаете конкретные рекомендации по тестам, которые с большей вероятностью улучшат результаты. В этой статье подробно разобрано, как работает AI Growth Advisor: какие данные он использует, как оценивает возможности и почему появляются те или иные рекомендации. Цель — чтобы вы могли уверенно использовать его в рабочем процессе роста. ## Что на самом деле делает AI Growth Advisor \{#what-ai-growth-advisor-actually-does\} AI Growth Advisor анализирует метрики вашего приложения и пейволов, чтобы найти эксперименты, которые с наибольшей вероятностью увеличат доход. Он изучает: - **Вашу текущую настройку**: цены, пробные периоды, продукты и их конверсию - **Рыночные паттерны**: как похожие приложения структурируют свои предложения и что они берут - **Историю тестов**: какие эксперименты вы уже проводили и что они показали - **Потенциал роста**: какие изменения с наибольшей вероятностью принесут результат Growth Advisor использует ИИ, чтобы оценить все эти факторы вместе и превратить их в A/B-тесты, которые можно запустить прямо сейчас. Вы получаете готовый план без необходимости исследовать конкурентов или гадать, что тестировать следующим. ## Данные, на которых работает AI Growth Advisor \{#the-data-behind-ai-growth-advisor\} Каждая рекомендация строится на трёх основных источниках данных, которые работают в связке. #### Собственные данные вашего приложения \{#your-apps-own-data\} AI Growth Advisor анализирует, как ваше приложение работает сегодня: - Метрики конверсии по вашим пейволам - Структура цен и продуктов Это даёт AI Growth Advisor отправную точку, прежде чем предлагать какие-либо изменения. :::note Мы не используем данные о производительности вашего приложения для обучения рекомендаций для других приложений. Ваши данные остаются конфиденциальными. ::: #### Анализ пейвола \{#paywall-analysis\} AI Growth Advisor анализирует скриншот вашего пейвола и сравнивает его дизайн с устоявшимися паттернами, которые используют лучшие приложения в вашей категории. Он оценивает компоновку, тексты, описание подписок и элементы, ориентированные на конверсию, — например, бейджи с выгодой или блоки с отзывами. По результатам анализа формируются два типа рекомендаций: - **Рекомендации на основе бенчмарков** — что делают по-другому лучшие приложения, каждая подкреплена конкретной статистикой (например, «Используется в 72% лучших приложений категории Education»). - **Рекомендации визуального анализа**, сгенерированные ИИ на основе вашего скриншота: улучшения текста, изменения макета и другие корректировки дизайна. Эти рекомендации напрямую попадают в ваш [план роста](autopilot-growth-plan#view-the-growth-plan) как гипотезы, которые можно [запустить в виде A/B-тестов](autopilot-execute-plan). #### Данные о конкурентах \{#competitor-data\} AI Growth Advisor сравнивает вашу настройку с похожими приложениями в вашей категории, используя публичные данные: ценообразование, структуру подписок и распространённые паттерны в вашей нише. Эти сравнения выполняются в разрезе стран, поскольку цены конкурентов и их структуры различаются по рынкам. Данные о ценах конкурентов поступают из сторонних и публичных источников, например из App Store, — и не связаны с анонимизированными данными сети Adapty, которые используются для анализа метрик. Таким образом, вы тестируете стратегии, которые уже работают у похожих приложений, а не случайные идеи. Когда вы видите аналитику, вы можете сравнить свои показатели и цены конкурентов бок о бок. Если у похожих приложений дела идут лучше с другим ценообразованием или структурой — это хороший сигнал, что тот же подход может сработать и у вас. :::tip AI Growth Advisor автоматически подбирает релевантных конкурентов — тех, с которыми вы реально можете конкурировать. Как правило, мы рекомендуем придерживаться этих предложений, а не добавлять приложения, которые намного опережают вас или намного уступают. Если ваше приложение относится к нескольким категориям, возможно, стоит скорректировать список, чтобы сосредоточиться на наиболее релевантном сегменте рынка. ::: #### Отраслевые бенчмарки \{#industry-benchmarks\} AI Growth Advisor использует анонимизированные данные 20 000 приложений с подписками, отслеживаемых Adapty, чтобы показать, как вы выглядите на фоне среднего значения по категории в конкретной стране. Данные агрегируются по всей сети и никогда не привязываются к конкретному приложению. Например, ваша воронка конверсий и доход с установки сравниваются со средними показателями приложений в вашей категории и стране. Это позволяет понять, отстаёте ли вы, держитесь на уровне среднего или уже опережаете конкурентов. #### Данные по географическим рынкам \{#geographic-market-data\} AI Growth Advisor анализирует отдельные географические рынки — опираясь на паттерны сети из 20 000 приложений Adapty — чтобы выявить, где региональные корректировки цен могут принести больше дохода. Для каждой страны оцениваются: - **Конверсия**: как соотношение установок к платным пользователям соотносится с общемировым средним. Высокий показатель может говорить о возможности повысить цены; низкий — о чувствительности к ценам. - **Ценовой индекс**: позиция страны в [Adapty Pricing Index](https://uploads.adapty.io/adapty_pricing_index.pdf), отражающая покупательную способность её жителей. Вы можете действовать по этим рекомендациям, создавая A/B-тесты на основе [предложений по гео-ценообразованию](autopilot-growth-plan#geo-pricing-hypotheses) в вашем плане роста. ## Как AI Growth Advisor принимает решения о рекомендациях \{#how-ai-growth-advisor-decides-what-to-recommend\} AI Growth Advisor формирует набор предложений по улучшению конверсии вашего пейвола. Эти предложения рассчитаны на поочерёдное тестирование — чтобы можно было надёжно измерить эффект каждого изменения. Вот как AI Growth Advisor приходит к своим предложениям: 1. **Находит наиболее перспективные точки роста** AI Growth Advisor анализирует ваши цены, продукты и эффективность воронки, а затем сравнивает их с отраслевыми паттернами и похожими приложениями. Анализ ведётся в валюте вашего основного рынка, а не только в USD, — поэтому рекомендации по ценам соответствуют тому, что реально платят ваши подписчики. Советник определяет, где у вас больше всего пространства для роста: скорректировать цену, добавить пробный период или изменить структуру оффера. 2. **Выберите следующий эксперимент** Каждая гипотеза генерируется на основе истории ваших тестов. AI Growth Advisor знает, какие эксперименты вы уже проводили, какие из них победили и в каких направлениях ещё есть смысл работать. Следующее предложение опирается на результаты предыдущего, а не следует фиксированной последовательности. 3. **Запускайте тесты «победитель против претендента»** После каждого эксперимента победитель становится новым базовым вариантом. Этот результат определяет следующую рекомендацию в вашем плане роста — AI Growth Advisor сохраняет то, что сработало, отсеивает то, что не дало результата, и выбирает следующий тест. 4. **Оставайтесь практичными** AI Growth Advisor предлагает только те тесты, которые можно запустить с уже имеющимися продуктами и настройками — или с небольшими изменениями вроде создания нового продукта или корректировки цены. Цель — сделать тестирование быстрым и управляемым. 5. **Объясняет логику рекомендаций** Для каждой рекомендации AI Growth Advisor формулирует чёткую гипотезу: почему этот тест стоит запустить. Вы увидите, как ваши текущие метрики соотносятся с показателями конкурентов и отраслевыми средними, в чём заключается возможность для роста и какие ключевые метрики должны улучшиться. Это превращает эксперименты в повторяемый процесс, где каждый тест чему-то учит и приближает вас к более эффективному пейволу. ## Что происходит после каждого эксперимента \{#what-happens-after-each-experiment\} Рекомендации не заканчиваются. Каждый завершённый тест становится основой для новых экспериментов. Пока вы продолжаете тестировать, AI Growth Advisor продолжает предлагать, что попробовать дальше. Чтобы обновить рыночные данные, запустите анализ для того же плейсмента повторно. Каждый повторный запуск подтягивает актуальные цены конкурентов, эталонные показатели конверсии и тренды категории, а также добавляет новые гипотезы в ваш план роста, не затрагивая уже существующие. Все созданные ИИ гипотезы, пользовательские гипотезы и текущие A/B-тесты сохраняются при повторных запусках. После оптимизации базового варианта вы можете перейти к конкуренции с более продвинутыми альтернативами. Такой итеративный подход помогает постоянно наращивать доход по мере роста приложения и эволюции рынка. :::tip Готовы попробовать? Запустите [AI Growth Advisor](autopilot-analysis), чтобы проанализировать пейволы и сформировать план роста с A/B-тестами. Воспользуйтесь [встроенным мастером](autopilot-execute-plan), чтобы легко запускать сложные тесты: он проведёт вас через создание продуктов, дублирование пейволов и настройку сегментов. ::: --- # File: autopilot-analysis --- --- title: "Анализ пейвола и рынка" description: "Получите план роста на основе данных, адаптированный под ваше приложение." --- Следуйте инструкциям в статье, чтобы запустить анализ AI Growth Advisor и сгенерировать план роста. Если вы уже создавали план роста для целевого плейсмента, этот анализ предложит новые гипотезы на выбор. :::tip Убедитесь, что вы выполнили [требования для анализа](autopilot#prerequisites) перед началом работы. ::: ## Анализ пейвола \{#paywall-analysis\} ### Выберите пейвол для анализа \{#select-a-paywall-for-analysis\} 1. Откройте страницу **AI Growth Advisor** и нажмите кнопку [Get Growth plan](https://app.adapty.io/ab-tests/analysis/start). 2. На странице **Paywall Diagnostic** выберите **Placement** и **Paywall** из выпадающих списков. Adapty автоматически предлагает плейсмент с наибольшей выручкой и его лучший пейвол. Чтобы проанализировать другой пейвол, сначала смените плейсмент. 3. Загрузите скриншот. AI Growth Advisor нужен скриншот для анализа дизайна и содержимого вашего пейвола. 4. Проверьте активные продукты пейвола. Карточки продуктов справа показывают длительность подписки, цену и пробный период для каждого продукта. 5. Нажмите **Confirm & Analyze**, чтобы продолжить. Adapty проанализирует ваш пейвол и отобразит диагностический отчёт. ### Отчёт об анализе пейвола \{#paywall-analysis-report\} После того как вы выберете пейвол и загрузите скриншот, Adapty проанализирует его на соответствие устоявшимся паттернам дизайна и укажет как на удачные решения, так и на точки роста. #### Что работает хорошо \{#whats-working-well\} В этом разделе отображаются применённые вами паттерны, которые максимизируют конверсию. Например: заметный бейдж с экономией, выделенный блок с отзывами пользователей или понятная разбивка подписок. #### Что исправить на пейволе \{#what-to-fix-on-your-paywall\} Adapty разбивает рекомендации на две категории: - **Рекомендации на основе бенчмарков**: предложения на основе данных о лучших приложениях в вашей категории. Каждая рекомендация содержит статистику бенчмарка (например, «Используется в 72% лучших приложений категории Education») и описание того, что нужно изменить. - **Рекомендации на основе визуального анализа**: предложения, сгенерированные ИИ на основе скриншота вашего пейвола. Они включают: улучшения текста, изменения макета и многое другое. :::tip Ваш [план роста](autopilot-growth-plan#view-the-growth-plan) будет включать гипотезы на основе бенчмарковых рекомендаций. Визуальные предложения по анализу можно добавить в план вручную. ::: Нажмите **Get Market Insights**, чтобы продолжить. ## Анализ рынка и конкурентов \{#market-and-competitor-analysis\} :::note Анализ рынка и конкурентов требует предварительного завершения [анализа пейвола](#paywall-analysis). ::: Анализ Market Insights сравнивает цены и метрики конверсии вашего приложения с конкурентами и средними показателями по отрасли. Сравнения выполняются в разрезе стран. Для формирования эталонных показателей Adapty агрегирует и анализирует данные приложений App Store в вашей подкатегории и стране — эти данные не публикуются в открытом доступе. ### Выбор конкурентов \{#select-competitors\} Выберите до 5 конкурентов для сравнения. Adapty автоматически подберёт 5 приложений и предложит ещё 5. Вы также можете добавить приложения вручную по ссылке из App Store. Для лучших результатов выбирайте приложения с MRR выше вашего. Нажмите **Generate report**, чтобы подтвердить список, и дождитесь завершения анализа. ### Выберите страну \{#select-a-country\} Используйте выпадающий список **Country**, чтобы выбрать одну из ваших ключевых стран для детального анализа. ### Распределение выручки \{#revenue-distribution\} Этот график распределения выручки показывает, из каких стран поступает ваш доход, с разбивкой по процентам. На нём выделены топ-5 стран, которые и будут в центре внимания дальнейшего анализа. ### Таблица цен конкурентов \{#competitor-pricing\} Таблица цен конкурентов сравнивает цены на подписки вашего пейвола с ценами конкурентов в [выбранной стране](#select-a-country). Для каждой длительности подписки предусмотрен отдельный столбец. ### Конверсионная воронка \{#conversion-funnel\} График показывает коэффициенты конверсии — Views-to-Trial, Trial-to-Paid и Views-to-Paid — в сравнении со средними значениями по похожим приложениям. ### Распределение выручки по длительности подписки \{#revenue-distribution-by-duration\} Этот график показывает, какие длительности подписок вносят наибольший вклад в вашу выручку, в сравнении со средними показателями по индустрии. Если выручка сильно сконцентрирована в одной длительности, это может указывать на возможность оптимизировать ценовую стратегию. ### Activation ARPU График **Activation ARPU: your app vs. category** сравнивает средний доход с одной новой установки вашего приложения со средним показателем по категории. Используйте его вместе с [воронкой конверсии](#conversion-funnel): - Конверсия показывает, сколько пользователей платят. - Activation ARPU показывает средний доход на одного пользователя. Высокая конверсия при низком Activation ARPU может свидетельствовать о том, что предложения недооценены. Метрика основана на **когортах**. Adapty берёт пользователей, установивших приложение за последние 90 дней, и делит полученный от них доход на их количество. #### Сравнение с другими метриками \{#comparison-to-other-metrics\} ARPU при активации не совпадёт со значениями ARPU, которые вы видите в других разделах дашборда — каждая метрика измеряет разные вещи. - **[График ARPU](arpu)**: учитывает продления от более старых когорт, поэтому значение в несколько раз выше, чем ARPU при активации. - **[График Revenue](revenue), фильтр Period установлен на «Activation»**: учитывает только первый платёж каждого пользователя. Продления когорты в рамках 90-дневного окна не учитываются. - **[Доход когорты](analytics-cohorts) (90 дней)**: наиболее близкий эквивалент — используйте эту метрику как ориентир. ## Дальнейшие шаги \{#next-steps\} Прочитайте статью [Управление и выполнение плана роста](autopilot-growth-plan), чтобы узнать, как запускать A/B-тесты на основе результатов анализа. Результаты анализа всегда можно посмотреть на странице плана роста — просто перейдите на вкладку **Analysis Results**. --- # File: autopilot-growth-plan --- --- title: "Управление планом роста" description: "Добавляйте пользовательские гипотезы, архивируйте их и обновляйте план роста." --- После завершения [анализа](autopilot-analysis) Adapty формирует план роста — список **конкретных гипотез по улучшению**. Каждый пункт предлагает новую ценовую точку или изменение дизайна. Откройте гипотезу, чтобы [проверить её с помощью A/B-теста](autopilot-execute-plan). У каждого плейсмента есть свой план роста. По мере изменения рыночных условий вы можете повторно запустить анализ, чтобы обновить предложения. Предыдущие запуски сохраняются в истории версий. ## Гипотезы \{#hypotheses\} Переключайтесь между вкладками вверху раздела growth plan, чтобы фильтровать гипотезы по типу: - **Top priority** включает гипотезы с наибольшим потенциалом, заслуживающие вашего внимания. Если таких гипотез нет, вкладка скрыта. - **All** показывает все гипотезы из активного плана. - **Pricing** — гипотезы, исследующие новые ценовые точки или конфигурации пробного периода. Каждая основана на конкретной рекомендации из диагностики пейвола или отчёта о рыночных инсайтах. - **Visual** — предложения по улучшению дизайна. Могут включать изменения текста, макета или других визуальных элементов. - Гипотезы [**Geo-pricing**](#geo-pricing-hypotheses) тестируют корректировку цен для отдельных стран. - Гипотезы [**Archived**](#archive-a-hypothesis) — предложения, удалённые из активного плана. Их можно восстановить в любой момент. Вы можете [добавить собственную гипотезу](#add-your-own-hypothesis) или [архивировать](#archive-a-hypothesis) те, которые не хотите тестировать. Проверяйте гипотезы по одной, в любом порядке. Исключение — тесты гео-ценообразования: их аудитории не пересекаются, поэтому они могут выполняться параллельно. ### Гипотезы гео-прайсинга \{#geo-pricing-hypotheses\} :::important Разовые покупки не поддерживают региональную оптимизацию цен. ::: Откройте вкладку **Geo-pricing**, чтобы увидеть список рекомендаций по гео-прайсингу. Каждая рекомендация направлена на одну страну с одним изменением цены и запускается как отдельный A/B-тест. Adapty определяет страны, в которых требуется корректировка цен, и предоставляет рекомендации на основе данных, проверенных с помощью [Adapty Pricing Index](https://uploads.adapty.io/adapty_pricing_index.pdf). <br /> #### Как Adapty формирует рекомендации по гео-ценообразованию \{#how-adapty-makes-geo-pricing-suggestions\} - Рекомендации по ценам основаны на данных App Store. Итоговый A/B-тест можно запустить как в App Store, так и в Google Play. - Процент изменения цены одинаков для всех длительностей подписки. - Все цены округляются до ближайшего ценового уровня App Store. - Цены отображаются в местной валюте (например, EUR или GBP), если у Adapty есть данные о транзакциях для этой страны. Если локальных данных нет, цены показываются в USD. ### Статусы гипотез \{#hypothesis-status-badges\} 1. **Молния** — обозначает предложения с наивысшим приоритетом. 2. **Статус синхронизации продукта** — появляется, когда для запуска A/B-теста требуется действие с продуктом. - **Draft** — настройка продукта не завершена (на стороне Adapty) - **Action required** — настройка продукта не завершена (на стороне стора) - **Pending...** — Adapty ожидает завершения проверки или первичной синхронизации со стороны стора. - **Approved** — продукт одобрен стором и готов к тестированию. - **Rejected** — стор отклонил продукт. - **Not connected** — продукт ещё не привязан к стору. 3. **Статус A/B-теста** — появляется после запуска A/B-теста: - **Draft test** — тест создан как черновик и ещё не запущен. - **Running** — тест активен. - **Completed** — тест завершён. - **Archived test** — тест был заархивирован без подведения итогов. ## Получайте новые гипотезы, сгенерированные ИИ \{#get-new-ai-generated-hypotheses\} После каждого теста Adapty автоматически обновляет ваши гипотезы на основе его результатов. Чтобы подтянуть актуальные данные о ценах конкурентов, эталонные показатели конверсии и тренды категории, нажмите **Update** Refresh в заголовке плана роста — или **Update Analysis** на главной странице AI Growth Advisor. Затем нажмите **Get New Ideas**. Adapty открывает мастер анализа с уже выбранным плейсментом. После повторного анализа пейвола и исследования конкурентов Adapty выявляет новые гипотезы. Выберите нужные или нажмите **Add All To Plan** Plus, чтобы принять все сразу. Новые гипотезы появятся в начале списка. Существующие гипотезы, сгенерированные ИИ, пользовательские гипотезы и запущенные A/B-тесты останутся без изменений. Если вы прервали обновление, не завершив его, можно **возобновить его** или **отменить** — чтобы начать заново. ## Добавьте собственную гипотезу \{#add-your-own-hypothesis\} Нажмите **Add Hypothesis** Plus, чтобы создать собственное предложение по ценообразованию или визуальному оформлению. Заполните форму: название, описание и тип гипотезы (**Monetization** или **Visual**). - Выберите метрики, которые хотите улучшить, из выпадающего меню. - Для монетизационных гипотез также нужно выбрать тестируемые продукты. ## Архивирование гипотезы \{#archive-a-hypothesis\} Чтобы архивировать гипотезу, нажмите кнопку Close, затем подтвердите действие, нажав Skip. При желании можно указать причину — это поможет Adapty улучшить будущие предложения. Гипотеза переместится на вкладку **Archived**. Чтобы восстановить архивную гипотезу в активный план, нажмите **Restore** на карточке. ## Просмотр и повторное использование старых гипотез \{#revisit-and-reuse-old-hypotheses\} Чтобы просмотреть предложения, сгенерированные в прошлых анализах, нажмите **Clock** Clock в заголовке плана роста. В модальном окне **Version history** отображается список всех прошлых запусков для данного плейсмента — дата, пейвол и количество принятых тогда гипотез. Нажмите на прошлый запуск, чтобы увидеть гипотезы, которые он сгенерировал. Любую из них можно добавить в активный план с помощью **Add to Plan** Plus — удобно, если хотите вернуться к предложениям, которые пропустили в первый раз. --- # File: autopilot-execute-plan --- --- title: "Выполнение плана роста" description: "Запускайте A/B-тесты на основе гипотез из вашего плана роста." --- Тесты можно запускать в любом порядке, но только по одному за раз. Поскольку аудитории гео-прайсинговых тестов не пересекаются, они могут работать параллельно. После завершения теста переносите победившую стратегию в следующий раунд. Каждый раунд приближает вас к оптимальной настройке для вашего приложения. По нашим оценкам, прохождение полного набора рекомендованных тестов может увеличить выручку **до 80%**. :::important Каждое предложение включает минимальную продолжительность A/B-теста. Следуйте этим рекомендациям, чтобы получить наиболее точные данные, прежде чем переходить к следующему этапу. Остановить A/B-тест потребуется вручную. ::: Откройте гипотезу и нажмите **Set Up & Run Test**, чтобы запустить мастер создания A/B-теста. ## Шаг 1: Просмотр гипотезы \{#step-1-view-the-hypothesis\} На первом шаге вы видите обзор гипотезы: предлагаемые изменения и обоснование за ними. Нажмите **Set up & Run Test**, чтобы перейти к следующему шагу. {/* TODO: REPLACE SCREENSHOT */} ## Шаг 2: Создайте новые продукты \{#step-2-create-new-products\} Если тест предполагает изменение цены, на этом шаге вы создаёте новые продукты для варианта теста. Визуальные гипотезы пропускают этот шаг. * Нажмите **Create a new product and push to stores**, чтобы создать новый продукт с нуля. * Нажмите **Connect an existing product**, если нужный продукт уже существует в настройках вашего стора. ## Шаг 3: Настройте сегмент и пейвол \{#step-3-set-up-segment-and-paywall\} На третьем шаге вы настраиваете тестовый вариант пейвола. Adapty предложит вам дублировать пейвол и внести нужные изменения. Для **гипотез о гео-ценообразовании** мастер предложит выбрать существующий сегмент с гео-фильтрацией или создать новый. Нажмите **Next**, когда новый пейвол будет готов и сегмент настроен. ## Шаг 4: Проверка и запуск \{#step-4-review--launch\} Последний шаг — сводка предстоящего теста. Она включает: - Ключевые метрики для обоих вариантов **Variant A vs Variant B** — название пейвола, выбор продуктов, длительность триала и цену. - **Duration**, **Traffic** (распределение) и **Subscribers** (минимальный размер выборки) для теста. - Раздел **How to interpret results**, описывающий, какие сигналы свидетельствуют об успехе теста. Проверьте конфигурацию и нажмите **Launch Test**, чтобы запустить A/B-тест. --- # File: how-adapty-analytics-works --- --- title: "Как работает аналитика Adapty" description: "Узнайте, как работает аналитика Adapty для эффективного отслеживания показателей подписок." --- В этой статье описано, как работает аналитика Adapty: какие данные она отображает, откуда берутся данные и как они обрабатываются. Здесь также объясняются архитектурные решения, которые отличают аналитику Adapty от других инструментов, и чем они полезны для вас. ## Аналитика Adapty vs аналитика сторов \{#adapty-analytics-vs-store-analytics\} - **Разнообразие данных**: Сторы могут отображать только собственные данные и не имеют доступа к поведению пользователей внутри приложения. Adapty объединяет данные из нескольких сторов, а также из дополнительных источников — маркетинговых платформ и рекламных сетей. SDK Adapty отслеживает взаимодействия пользователей с пейволами и онбордингами. - **Частота обновлений**: Сторы, как правило, обновляют данные раз в сутки, что ограничивает возможность принимать решения в режиме реального времени. Adapty предлагает аналитику [близкую к реальному времени](#data-processing). - **Расширенные метрики**: сторы показывают базовую статистику — загрузки, выручку, показатели удержания. Adapty дополнительно рассчитывает продвинутые метрики: регулярную выручку, средний доход на пользователя и другие. Отдельные разделы посвящены проблемам с подписками: оттоку пользователей, сбоям при оплате и т.д. Полный список метрик — в статье [Таблица сравнения метрик](metric-comparison-table). - **Прогнозы**: Adapty использует алгоритмы машинного обучения для [прогнозирования LTV и выручки](predicted-ltv-and-revenue). ## Данные и их источники \{#data-and-its-sources\} Adapty Analytics обрабатывает следующие данные и отображает их в виде [графиков и диаграмм](analytics): - [События подписки](events), возникающие на протяжении всего жизненного цикла пользователя, — запуск триала, покупки, продления, отмены, сбои оплаты, возвраты. Adapty агрегирует их в [аналитические графики](analytics) и в режиме реального времени передаёт в [вебхуки](webhook), [ленту событий](event-feed) и [интеграции на основе событий](analytics-integration). - [Данные о транзакциях](revenue) — выручка, возвраты, страна покупателя и т.д. - **Данные приложения**: количество установок и [взаимодействия с пейволами](paywalls). - [Данные атрибуции для транзакций](attribution-integration): источники трафика и рекламные кампании. I notice the input appears to be just "This data comes from the following sources:" without any actual MDX content to translate. However, following my instructions to never refuse and to translate what is given, here is the translation: Эти данные получены из следующих источников: - <InlineTooltip tooltip="Adapty SDK">[iOS](ios-sdk-overview), [Android](android-sdk-overview), [React Native](react-native-sdk-overview), [Flutter](flutter-sdk-overview), [Unity](unity-sdk-overview), [Kotlin Multiplatform](kmp-sdk-overview), [Capacitor](capacitor-sdk-overview) </InlineTooltip> передаёт данные о поведении пользователей из приложения. Если Adapty управляет процессом покупок, SDK предоставляет информацию о событиях покупок из первых рук. Если вы используете [режим наблюдателя](observer-vs-full-mode), SDK получает [отчёты о событиях](report-transactions-observer-mode), которые вы настраиваете вручную. - Сторы используют серверное взаимодействие (server-to-server), чтобы уведомлять Adapty о транзакциях (триалах, продлениях подписок, отменах и т. д.). - Сторонние [сервисы атрибуции](attribution-integration) (Appsflyer, Adjust, Branch и др.) передают данные об источниках трафика и рекламных кампаниях. Если вы настроили [Adapty Attribution](adapty-user-acquisition), Adapty может самостоятельно обрабатывать данные рекламных кампаний, минуя этот шаг. - Пользователи могут [вручную импортировать исторические данные о транзакциях](importing-historical-data-to-adapty) для анализа и отображения в Adapty. Проблема с одним из источников может повлиять на общее качество аналитических данных. Подробнее см. в разделе [Устранение неполадок](#troubleshooting). ## Сторонние интеграции \{#third-party-integrations\} Вы можете подключить [Adapty User Acquisition](adapty-user-acquisition), чтобы расширить возможности аналитики Adapty данными рекламных кампаний. Это поможет выявить корреляции между расходами на рекламу и поведением пользователей. Также можно [экспортировать](analytics-integration) аналитические данные на сторонние платформы или [собственный сервер](webhook) и анализировать данные Adapty в другом инструменте. ## Обработка данных \{#data-processing\} Adapty предлагает аналитику, близкую к реальному времени, что позволяет быстро реагировать на изменения ключевых метрик. - **Графики аналитики**: данные появляются с **задержкой 15–30 минут** после транзакции. Adapty нужно это время, чтобы подтвердить транзакцию, применить комиссии и налоги и агрегировать данные. - **[Лента событий](event-feed)**: обновляется в реальном времени, как только сторы доставляют событие. - **[Вебхуки](webhook) и интеграции на основе событий** (AppsFlyer, Branch и др.): Adapty пересылает события по мере их поступления — задержка 15–30 минут, характерная для аналитики, здесь не применяется. Задержку может вносить сама принимающая служба. Каждый источник данных работает по своему расписанию. Одно и то же событие может появляться в разное время на графиках, в ленте событий и в интеграциях. Небольшие расхождения между ними — это нормально. ## Комиссии и налоги \{#commissions-and-taxes\} При просмотре графиков, связанных с выручкой, можно выбрать один из вариантов: **Gross revenue**, **Revenue after commissions** или **Revenue after commissions and taxes**. ### Комиссии \{#commissions\} Сторы удерживают комиссию с каждой транзакции. Если ваша организация участвует в программе сниженной комиссии, измените настройки Adapty, чтобы скорректировать расчёт ставок комиссий: * [App Store Small Business Program](app-store-small-business-program) * [Программа сниженной комиссии](google-reduced-service-fee) Google Сторы автоматически сообщают, снижают ли другие факторы размер вашей комиссии: * [Продление подписок App Store сроком от 1 года](https://developer.apple.com/app-store/subscriptions/) — комиссия 15% * Страновые ставки (например, [21% для приложений App Store, распространяемых в Японии](https://developer.apple.com/support/app-distribution-in-japan/#business-terms)) ### Налоги \{#taxes\} **Adapty не рассчитывает налоги.** Ставку налога для каждой транзакции определяют Apple и Google — они передают это значение в Adapty, который отображает его без изменений. Ставка налога для конкретной транзакции зависит от: - **Страна выставления счёта покупателя** и действующая там ставка местного налога. - **Правила стора по работе с налогами**. В одних юрисдикциях стор самостоятельно удерживает и перечисляет налог от имени разработчика, в других — эта ответственность лежит на разработчике. - Для транзакций App Store — **налоговая категория**, присвоенная приложению или встроенной покупке (книги, новости, видео и т. д.) — в зависимости от местных правил категории могут облагаться налогом по разным ставкам. Налоговые ставки могут существенно различаться между приложениями — и даже между транзакциями в рамках одного приложения — из-за совокупности факторов: страна покупателя, правила обработки сторов и (для App Store) присвоенная налоговая категория. Актуальные правила смотрите в официальной документации сторов: - [App Store: Understanding taxes](https://developer.apple.com/help/app-store-connect/making-payments-to-apple/understanding-taxes/) - [Google Play: Tax rates and VAT](https://support.google.com/googleplay/android-developer/answer/138000) ## Решение проблем \{#troubleshooting\} :::link Основная статья: [Расхождения и решение проблем](discrepancies-and-troubleshooting) ::: * Неправильно настроенный или отсутствующий источник данных может негативно повлиять на всю систему аналитики. Если вы столкнулись с проблемами с данными, убедитесь, что интеграции со сторами и сторонними платформами настроены и активны. * Если вы сравниваете графики Adapty с другими аналитическими платформами, вы можете заметить расхождения. Это ожидаемое поведение, которое может быть вызвано различиями в обработке данных. Читайте статью [о расхождениях](discrepancies-and-troubleshooting), чтобы узнать о распространённых причинах расхождений в данных. --- # File: metric-comparison-table --- --- title: "Сравнение метрик" description: "Справочные таблицы метрик аналитики Adapty, сгруппированные по категориям." --- Это обзор метрик, доступных в аналитике Adapty. Используйте его, чтобы понять, что измеряет каждая метрика и чем она отличается от похожих. Подробнее о том, как Adapty обрабатывает данные аналитики, см. в разделе [Как работает аналитика Adapty](how-adapty-analytics-works). :::note Эта статья не охватывает метрики [Атрибуции Adapty](adapty-user-acquisition). Прочитайте [аналитику Атрибуции Adapty](ua-analytics), чтобы узнать больше о метриках рекламных кампаний (Spend, CPI, ROAS, CTR и другие). ::: ## Глобальные метрики \{#global-metrics\} Глобальные метрики отслеживают эффективность всего приложения — по всем плейсментам и пейволам. ### Доход \{#revenue\} Эти метрики показывают, сколько денег приносит приложение и из каких источников. | Метрика | Описание | Ключевое отличие | |---------|----------|-----------------| | [Revenue](revenue) | Общая выручка от подписок и разовых покупок за вычетом возвратов | Фактически полученная выручка. Можно отобразить как валовую выручку, выручку после комиссии стора или выручку после налогов и комиссии — в зависимости от [настроек графика](controls-filters-grouping-compare-proceeds) | | [MRR](mrr) | Ежемесячная регулярная выручка от активных подписок | Предсказуемый ежемесячный доход приложения. Не включает разовые покупки и неповторяющиеся подписки | | [ARR](arr) | Годовая регулярная выручка от активных подписок | Рассчитывается как MRR, но в годовом масштабе. Удобен для прогнозирования годового дохода | | [ARPU](arpu) | Средняя выручка на пользователя | Делит выручку на общее число пользователей — платящих и неплатящих. Показывает, сколько в среднем приносит каждый пользователь | | [ARPPU](arppu) | Средняя выручка на платящего пользователя | Учитывает только пользователей, совершивших покупку за выбранный период, включая возвращённые транзакции. Всегда выше ARPU | | [LTV (lifetime value)](ltv) | Выручка от платящих клиентов, делённая на их количество в когорте | Реализованная ценность одного платящего клиента за всё время. В отличие от ARPPU (один период), LTV отражает суммарную выручку за всё время взаимодействия с клиентом. Можно смотреть по продлениям или по календарным дням | | [Predicted LTV](predicted-ltv-and-revenue) | Прогнозируемая lifetime value на пользователя в когорте | Прогноз на будущее. В отличие от реализованного LTV, оценивает будущую ценность на основе исторических паттернов удержания когорты. Доступен на 3, 6, 9, 12, 18 и 24 месяца | | [Predicted revenue](predicted-ltv-and-revenue) | Прогнозируемая суммарная выручка, которую сгенерирует когорта | Прогноз на будущее. В отличие от реализованного Revenue, предсказывает итоговую сумму, которую когорта принесёт за выбранный период. Обновляется ежедневно | | [Non-subscriptions](non-subscriptions) | Количество встроенных покупок: расходуемых, нерасходуемых и невозобновляемых подписок | Не включает автовозобновляемые подписки | | [Refund events](refund-events) | Количество возвращённых покупок или подписок | Относится к дате возврата, а не к дате исходной покупки | | [Refund money](refund-money) | Общая сумма возвратов за выбранный период | Финансовое влияние возвратов. Рассчитывается до удержания комиссии стора. В отличие от Refund events (количество), показывает денежную сумму | ### Подписчики и конверсия \{#subscribers-and-conversion\} Эти метрики отслеживают, как пользователи попадают в приложение и продвигаются по воронке. | Метрика | Описание | Ключевое отличие | |---------|----------|-----------------| | [Installs](installs) | Количество установок приложения за период | Считает одно из следующего в зависимости от [определения установки](general#4-installs-definition-for-analytics): <br /> • Установки на устройство (пользователь, переустановивший приложение, считается снова) <br /> • Уникальные пользователи (учитываются только пользователи, у которых задан `customer_user_id`. Анонимные пользователи исключаются полностью — если ни один пользователь не идентифицирован, счётчик равен 0) | | [New trials](new-trials) | Триалы, активированные за период | Считает каждый старт триала, даже если к моменту просмотра графика триал уже истёк или конвертировался в платную подписку | | [Active trials](active-trials) | Количество триалов, которые ещё не истекли | Учитывает только триалы, активные на конец периода | | [New subscriptions](reactivated-subscriptions) | Подписки, активированные впервые за период, включая первые покупки без триала и конвертации из триала в платную подписку | Исключает продления и реактивации. Не то же самое, что интеграционное событие `subscription_started`, которое считает только первые покупки без триала — конвертации из триала вместо этого генерируют событие `trial_converted` | | [Active subscriptions](active-subscriptions) | Количество платных подписок, которые ещё не истекли | Исключает триалы и подписки с отменённым продлением | | [Install to trial](analytics-conversion#install---trial) | Процент пользователей, установивших приложение и запустивших триал | В знаменателе — все установки, а не только просмотры пейвола, поэтому показатель может быть ниже, чем Paywall view to trial. Эти две метрики также могут расходиться, если приложение не логирует просмотры пейвола. Это возможно при использовании кастомного пейвола, который не вызывает `logShowFlow` (iOS SDK v4+) / `logShowPaywall`, или когда пользователь запускает триал через [promoted in-app purchase](https://developer.apple.com/documentation/storekit/supporting-promoted-in-app-purchases-in-your-app). | | [Paywall view to trial](analytics-conversion#paywall-view---trial) | Процент пользователей, просмотревших пейвол и запустивших триал | Учитывает только пользователей, которые видели пейвол, поэтому показатель может быть выше, чем Install to trial | | [Trial to paid](analytics-conversion#trial---paid) | Процент пользователей с триалом, оформивших платную подписку | Измеряет качество триала и эффективность конвертации. В отличие от Install to paid, фокусируется только на пользователях, завершивших триал | | [Install to paid](analytics-conversion#install---paid) | Процент пользователей, установивших приложение и оформивших первую подписку | Учитывает все установки, а не только просмотры пейвола. Показатель может быть ниже, чем Paywall view to paid. Включает как прямые покупки, так и конвертации из триала | | [Paywall view to paid](analytics-conversion#paywall-view---paid) | Процент пользователей, просмотревших пейвол и в итоге оформивших подписку | Учитывает только пользователей, которые видели пейвол, поэтому показатель может быть выше, чем Install to paid. Включает пользователей, сначала завершивших триал | ### Удержание и продление подписки \{#retention-and-subscription-renewal\} Эти метрики показывают, насколько хорошо приложение удерживает платящих подписчиков с течением времени. | Метрика | Описание | Ключевое отличие | |---------|----------|------------------| | [Retention](analytics-retention) | Доля исходных подписчиков, остающихся после каждого расчётного периода — 1-го продления, 2-го продления и т. д. | Отслеживает подписчиков начиная с первого платежа. В отличие от метрик «период к периоду» ниже, всегда сравнивает с исходной группой, поэтому общая картина видна сразу | | [Paid to 2nd period](analytics-conversion#paid---2nd-period) | Процент новых подписчиков, продливших подписку на второй период | Измеряет переход между двумя конкретными соседними периодами. В отличие от Retention, фокусируется на единственном самом критичном продлении — первом | | [2nd to 3rd period](analytics-conversion#2nd-period---3rd-period) | Процент продлений со 2-го по 3-й период | Показывает стабильность удержания после первоначального продления | | [3rd to 4th period](analytics-conversion#3rd-period---4th-period) | Процент продлений с 3-го по 4-й период | Индикатор удержания на среднем сроке | | [4th to 5th period](analytics-conversion#4th-period---5th-period) | Процент продлений с 4-го по 5-й период | Индикатор долгосрочной лояльности | | [6 Months+](analytics-conversion#6-months-) | Процент новых подписчиков, остающихся в подписке более 6 месяцев | Измеряет календарное время, а не количество продлений. Годовой подписчик считается удержанным по истечении 6 месяцев даже без продления | | [1 Year+](analytics-conversion#1-year-) | Процент новых подписчиков, остающихся в подписке более 12 месяцев | Годовой рубеж удержания | | [2 Years+](analytics-conversion#2-years-) | Процент новых подписчиков, остающихся в подписке более 24 месяцев | Долгосрочный рубеж удержания | ### Отток \{#churn\} Эти метрики показывают, сколько подписчиков и пользователей пробного периода приложение теряет. | Метрика | Описание | Ключевое отличие | |---------|----------|-----------------| | [Отменённое продление пробных периодов](trials-renewal-cancelled) | Пробные периоды, для которых пользователь отключил автопродление | Пользователь сохраняет доступ до конца пробного периода, но не перейдёт на платную подписку автоматически. В отличие от отменённого продления подписок, относится к пользователям на пробном периоде, которые ещё ничего не платили | | [Истёкшие (ушедшие) пробные периоды](expired-churned-trials) | Пробные периоды, которые истекли — пользователь потерял доступ к премиум-функциям | Пользователь уже потерял доступ. Привязывается к дате истечения, даже если пользователь отменил продление в предыдущем периоде. Можно группировать по причине (добровольно или из-за ошибки оплаты) | | [Отменённое продление подписок](cancelled-subscriptions) | Подписки, для которых пользователь отключил автопродление | Пользователь сохраняет доступ до конца периода. Сигнализирует о риске оттока, но не о самом оттоке — пользователь может снова включить автопродление до окончания периода | | [Ушедшие (истёкшие) подписки](churned-expired-subscriptions) | Подписки, которые истекли — пользователь потерял доступ к премиум-функциям | Фактический отток. Пользователь уже потерял доступ. Привязывается к дате истечения, даже если пользователь отменил продление в предыдущем периоде. Можно группировать по причине (добровольно или из-за ошибки оплаты) | ### Проблемы с оплатой и восстановление дохода \{#billing-issues-and-revenue-recovery\} Эти метрики показывают, насколько эффективно приложение восстанавливает доход, потерянный из-за проблем с оплатой. | Метрика | Описание | Ключевое отличие | |---------|----------|------------------| | [Льготный период](grace-period) | Подписки, перешедшие в льготный период из-за проблем с оплатой | Включает пользователей, у которых льготный период истёк и доступ был потерян | | [Льготный период → оплачено](analytics-conversion#grace-period---paid) | Доля пользователей в льготном периоде, которые продлили подписку до его окончания | Показатель в %. Отвечает на вопрос: «какая доля пользователей в льготном периоде восстановила доступ?» | | [Конвертировано из льготного периода](grace-period-converted) | Абсолютное число подписок в льготном периоде, которые успешно продлились | Те же события, что и «Льготный период → оплачено», но в виде количества, а не процента | | [Выручка с конвертированных из льготного периода](grace-period-converted-revenue) | Выручка от восстановлений в льготном периоде | Финансовый эффект от льготного периода | | [Проблема с оплатой](billing-issue) | Подписки, перешедшие в состояние проблемы с оплатой | Начинается после истечения льготного периода. В отличие от льготного периода, учитывает только пользователей, уже потерявших доступ к премиуму | | [Проблема с оплатой → оплачено](analytics-conversion#billing-issue---paid) | Доля пользователей с проблемой оплаты, которые продлили подписку до окончания расчётного цикла | Показатель в %. Отвечает на вопрос: «какая доля пользователей с проблемой оплаты восстановила доступ?» | | [Конвертировано из проблемы с оплатой](billing-issue-converted) | Абсолютное число подписок с проблемой оплаты, которые успешно продлились | Количество подписок с проблемой оплаты, успешно продлившихся. Те же события, что и «Проблема с оплатой → оплачено», но в виде количества, а не процента | | [Выручка с конвертированных из проблемы с оплатой](billing-issue-converted-revenue) | Выручка от восстановлений при проблеме с оплатой | Финансовый эффект от восстановления при проблеме с оплатой | ## Метрики пейвола, плейсмента и онбординга \{#paywall-placement-and-onboarding-metrics\} Эти метрики рассчитываются для отдельных [пейволов](paywall-metrics), [плейсментов](placement-metrics) и онбордингов. Они измеряют эффективность конкретного пейвола или плейсмента, а не приложения в целом. В столбце **Associated global metric** указана соответствующая метрика из раздела глобальной аналитики. | Метрика | Описание | Ключевое отличие | Связанная глобальная метрика | |--------|-------------|----------------|---------------| | [Proceeds](paywall-metrics#proceeds) | Выручка за вычетом налогов и комиссии для отдельного плейсмента | Аналог [Revenue](revenue) после вычета налогов и комиссии | [Revenue](revenue) | | [ARPPU](paywall-metrics#arppu) | Средняя выручка на платящего пользователя для данного пейвола или плейсмента | Рассчитывается так же, как глобальный ARPPU, но ограничен одним пейволом или плейсментом | [ARPPU](arppu) | | [ARPAS](paywall-metrics#arpas) | Выручка, делённая на количество активных подписчиков (триальных и платных) | Учитывает триальных пользователей. В отличие от ARPPU, отражает потенциал выручки по всей базе подписчиков | — | | [Views](paywall-metrics#views) | Общее количество показов пейвола или плейсмента | Считает каждый показ. Если один пользователь просмотрел пейвол дважды, это считается как 2 просмотра | — | | [Unique views](paywall-metrics#unique-views) | Количество уникальных пользователей, которые видели пейвол или плейсмент | Каждый пользователь считается один раз, независимо от числа просмотров. В отличие от Views, измеряет охват, а не частоту взаимодействия | — | | [CR to purchases](paywall-metrics#cr-to-purchases) | Покупки, делённые на общее число просмотров | В знаменателе используются все просмотры (включая повторные одним пользователем) | [Paywall view to paid](analytics-conversion#paywall-view---paid) | | [Unique CR to purchases](paywall-metrics#unique-conversion-rate-cr-to-purchases) | Покупки, делённые на уникальные просмотры | В знаменателе используются уникальные просмотры. Показатель выше, чем неуникальный CR, так как повторные просмотры считаются один раз | [Paywall view to paid](analytics-conversion#paywall-view---paid) | | [CR to trials](paywall-metrics#unique-cr-to-trials) | Запущенные триалы, делённые на общее число просмотров | Измеряет, насколько эффективно пейвол конвертирует просмотры в триалы | [Paywall view to trial](analytics-conversion#paywall-view---trial) | | [Unique CR to trials](paywall-metrics#unique-cr-to-trials) | Запущенные триалы, делённые на уникальные просмотры | Рассчитывается как CR to trials, но в знаменателе — уникальные зрители | [Paywall view to trial](analytics-conversion#paywall-view---trial) | | [Purchases](paywall-metrics#purchases) | Общее количество транзакций для данного пейвола: новые покупки, конверсии из триала, апгрейды, даунгрейды и возобновлённые подписки | Не включает продления. | [Revenue](revenue) | | [Trials](paywall-metrics#trials) | Общее число активированных триалов через данный пейвол | Ограничено только этим пейволом | [New trials](new-trials) | | [Trials canceled](paywall-metrics#trials-canceled) | Количество триалов, при которых пользователь отключил автопродление | Ограничено триалами только этого пейвола | [Trials renewal cancelled](trials-renewal-cancelled) | | [Refund rate](paywall-metrics#refund-rate) | Возвраты, делённые на первичные покупки (продления исключены) | Показатель в %, не количество. Нормализует возвраты относительно числа покупок | [Refund events](refund-events) (количество, не процент) | | Completions | Количество раз, когда пользователи прошли онбординг от первого до последнего экрана | Только для плейсмента и онбординга. Считает каждое прохождение, включая повторные | — | | Unique completions | Количество уникальных пользователей, завершивших онбординг | Только для плейсмента и онбординга. Каждый пользователь считается один раз. В отличие от Completions, показывает, сколько отдельных людей дошли до конца | — | | Unique completions rate | Уникальные завершения, делённые на уникальные просмотры | Только для плейсмента и онбординга. Измеряет эффективность онбординга: какая доля пользователей, начавших его, действительно прошла до конца | — | --- # File: overview --- --- title: "Страница обзора аналитики" description: "Просматривайте несколько графиков аналитики Adapty на одной странице для общего обзора производительности вашего приложения" --- Страница [Overview](https://app.adapty.io/overview) отображает объединённые метрики для всех ваших приложений в одном месте. Это главная страница дашборда, также доступная из левого меню. Чтобы просмотреть данные по отдельному приложению, откройте соответствующий [график](charts). ## Графики \{#charts\} Overview отображает настраиваемый набор [аналитических графиков](charts) Adapty. Описания и сравнительную таблицу всех доступных графиков можно найти в разделе [Сравнение метрик](metric-comparison-table). Чтобы настроить, какие графики отображаются и в каком порядке, нажмите **Edit** в правом верхнем углу. Там можно удалять, добавлять и менять порядок графиков: Доступны следующие графики: - [Выручка](revenue) - [MRR](mrr) - [ARR](arr) - [ARPU](arpu) - [ARPPU](arppu) - [ARPAS](placement-metrics#arpas) - [Установки](installs) - [Новые триалы](new-trials) - [Новые подписки](reactivated-subscriptions) - [Активные триалы](active-trials) - [Активные подписки](active-subscriptions) - [Новые разовые покупки](non-subscriptions) - [События возвратов](refund-events) - [Сумма возвратов](refund-money) - [Отменённые продления подписок](cancelled-subscriptions) - [Конверсия из установки в триал, из установки в платёж и из триала в платёж](analytics-conversion) ## Управление \{#controls\} Страница Overview поддерживает большинство [инструментов аналитики](controls-filters-grouping-compare-proceeds), включая фильтрацию, группировку и сравнение временных периодов. Единственная возможность, уникальная для Overview, — это группировка и фильтрация по приложениям. Поскольку страница объединяет данные всех ваших приложений, просмотр по отдельным приложениям показывает, как каждое из них влияет на бизнес-метрики: ## Количество установок и часовой пояс \{#install-count-and-timezone\} В Overview данные объединяются по всем вашим приложениям с использованием **собственных настроек часового пояса и подсчёта установок** — настройки отдельных приложений здесь не применяются. - **Installs**: выберите способ подсчёта установок. **By device installations** считает каждую установку на устройстве — включая повторные — как отдельную. **By unique users** считает только первую установку на каждого идентифицированного пользователя. Чтобы изменить настройку, нажмите **Edit Metrics** и выберите [другой вариант](general#4-installs-definition-for-analytics) из выпадающего списка. - **Timezone**: чтобы изменить часовой пояс раздела Overview, нажмите **Edit Metrics** и выберите часовой пояс из выпадающего списка. Это особенно удобно, если разные приложения в вашем аккаунте используют разные часовые пояса. --- # File: controls-filters-grouping-compare-proceeds --- --- title: "Управление аналитикой" description: "Фильтруйте, группируйте и сравнивайте данные аналитики в Adapty." --- В каждой вкладке аналитики Adapty доступны инструменты для уточнения данных: временной диапазон, сравнение периодов, фильтрация, группировка и визуализация на графике. Набор доступных инструментов зависит от вкладки. **Доступные инструменты по вкладкам аналитики:** | Управление | Графики | Когорты | Воронки | Удержание | Конверсия | LTV | | :--- | :---: | :---: | :---: | :---: | :---: | :---: | | Диапазон дат | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Сравнение периодов | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | Фильтр | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Группировка | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | | Визуализация графика | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | | Табличное представление | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Экспорт в CSV | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | Комиссии и налоги | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ### Задайте диапазон дат \{#set-the-date-range\} Используйте календарь **Date range** над каждым графиком, чтобы выбрать временной период. Аналитика Adapty работает в **часовом поясе UTC**; на [странице Overview](overview) есть собственный настраиваемый часовой пояс. #### Предустановленные диапазоны \{#preset-ranges\} Используйте опцию **Custom**, чтобы указать произвольные даты начала и окончания. Доступные предустановки: | Пресет | Начало | Конец | | --- | --- | --- | | Последние 7 дней | 6 дней назад | Сегодня | | Последние 28 дней | 27 дней назад | Сегодня | | Последний месяц | Та же дата в предыдущем месяце | Сегодня | | Последние 3 месяца | 3 месяца назад | Сегодня | | Последние 6 месяцев | 6 месяцев назад | Сегодня | | Последний год | 1 год назад | Сегодня | | Предыдущий месяц | Первый день предыдущего месяца | Последний день предыдущего месяца | | Текущий месяц | 1-е число текущего месяца | Сегодня | | Текущий квартал | 1-е число текущего квартала | Сегодня | | Текущий год | 1 января текущего года | Сегодня | :::tip Используйте **Last 28 days** для отслеживания продуктов с еженедельной подпиской — диапазон охватывает четыре полных недельных цикла, поэтому неполная неделя не искажает сравнение. ::: #### Временной масштаб \{#time-scale\} Каждая точка на графике соответствует одному временному блоку — выберите день, неделю, месяц, квартал или год в выпадающем списке. День и неделя показывают краткосрочные колебания; месяц, квартал и год — долгосрочные тренды. В анализах [когорт](analytics-cohorts) и [LTV](ltv) аналогичная настройка называется **cohort length** — подробности см. в соответствующих статьях. ### Сравнение двух периодов \{#compare-two-time-periods\} Нажмите на опцию сравнения рядом с календарём, чтобы наложить текущий период на более ранний. По умолчанию Adapty сравнивает с предшествующим периодом той же длины. Чтобы изменить диапазон сравнения, нажмите на опцию ещё раз и выберите произвольный диапазон. Сравнение отображается: - **На графике** — наложенные линии, области или столбцы, при выбранной нулевой или одной группировке. - **Как числовое значение** — разница между двумя периодами, выделенная зелёным (выше) или красным (ниже). - **В подсказке** — наведите курсор на любую точку данных, чтобы увидеть числовую разницу для этой точки. ### Фильтрация и группировка данных \{#filter-and-group-data\} **Фильтр** ограничивает график данными, соответствующими одному или нескольким атрибутам (например, отдельной стране или продукту). **Группировка** разбивает общий итог на отдельные серии — по одной на каждое значение атрибута. Например, сгруппируйте Revenue по стране, чтобы получить отдельную линию выручки для каждой страны вместо одного общего итога. **Доступные атрибуты для фильтрации и группировки:** | Атрибут | Фильтр | Группа | Описание | | --- | :---: | :---: | --- | | Attribution | ✅ | ✅ | Источник, статус, канал, кампания, группа объявлений, набор объявлений и креатив (ключевое слово). Требует [интеграции атрибуции](attribution-integration). | | Аудитория | ✅ | ✅ | [Аудитория](audience), к которой принадлежит пользователь. | | Статус продления | ❌ | ✅ | Будет ли подписка продлена в следующем периоде. | | Период | ✅ | ✅ | Этап жизненного цикла подписки: **Trial**, **Activation** (первый платёж) или **Renewal 1**–**Renewal 5**, **Renewals 6+** (последующие продления). | | Страна | ✅ | ✅ | Страна стора пользователя. Если данные недоступны, Adapty определяет страну по коду валюты или IP-адресу устройства. | | Тип оффера | ✅ | ✅ | Оффер, применённый к транзакции: <ul><li>**Introductory** — introductory offer на начальный период подписки. Используйте **Offer Discount Type**, чтобы различить платный вводный период и бесплатный пробный.</li><li>**Promotional** — promotional offer App Store и аналогичные.</li><li>**Offer Code** — промокоды, которые покупатель вводит в сторе.</li><li>**No offer** — оффер не применялся.</li></ul> | | ID оффера | ✅ | ✅ | Конкретный ID оффера. | | Тип скидки оффера | ✅ | ✅ | Модель ценообразования introductory или promotional offer: **Free Trial**, **Pay As You Go** или **Pay Up Front**. Комбинируйте с **Offer Type**, чтобы различать, например, бесплатный пробный период и платный вводный. | | Пейвол | ✅ | ✅ | [Пейвол](paywalls), использованный для покупки. | | A/B-тесты | ✅ | ❌ | [A/B-тест](ab-tests), активный во время покупки. | | Плейсмент | ✅ | ✅ | [Плейсмент](placements), в котором была совершена покупка. | | Стор | ✅ | ✅ | Стор, обработавший транзакцию: App Store, Google Play, Stripe и т.д. | | Продукт | ✅ | ✅ | [Продукт](product) — подписки и разовые покупки. | | Длительность | ✅ | ✅ | Длительность продукта. | | Сегмент | ✅ | ✅ | [Сегмент](segments) пользователей. Группируйте по сегменту, чтобы сравнивать эффективность сегмента с **All users**. <ul><li>Воронки не поддерживают группировку по сегменту.</li><li>Если изменить пользовательский атрибут после того, как сегмент начал его использовать, Adapty может исключить пользователя из сегмента в аналитике. При этом данные продолжат отображать предыдущее значение.</li></ul> | | Причина возврата | ✅ | ✅ | Причина возврата транзакции (например, **Refund** или **Upgraded**). Доступно на графиках возвратов и устранения проблем с оплатой. | | Причина истечения | ❌ | ✅ | Причина истечения подписки или пробного периода: **Cancelled by customer**, **Billing issue**, **Customer hasn't agreed to price increase**, **Unknown** или **Refund**. Доступно для истёкших (Churned) подписок и истёкших (Churned) пробных периодов. | | Когорта (только LTV) | ❌ | ✅ | На графике LTV группируйте по длине когорты: **Day**, **Week**, **Month** или **Year**. Заменяет группировку по Attribution на этом графике. | Не каждый аналитический раздел поддерживает все перечисленные выше фильтры и атрибуты группировки. ARPU и Installs на вкладке Charts ограничены Attribution, Country, Segment, Store и (только как фильтр) A/B-тестами. Вкладки LTV, Cohorts, Funnels, Retention и Conversion поддерживают разные подмножества. Подробнее — в статье для соответствующего графика или вкладки. ### Как определяется страна \{#how-country-is-determined\} Каждой транзакции присваивается страна в момент её создания. Источник определения страны, в порядке приоритета: 1. **IP-страна устройства** пользователя на момент транзакции. 2. **Страна стора** пользователя — страна аккаунта в App Store или Google Play. 3. Последняя известная **IP-страна** пользователя. Страна стора недоступна для веб-платежей (Stripe, Paddle), вручную выданного доступа, а также транзакций, по которым стор не предоставил эти данные. В таких случаях Adapty использует страну по IP. Поскольку страна фиксируется на уровне транзакции, у пользователя, сменившего страну App Store после установки, в транзакциях до и после смены будут разные значения страны. Прошлые транзакции сохраняют исходное значение страны. **GB и United Kingdom.** Данные о стране хранятся в формате кодов ISO 3166-1 alpha-2 (то есть «GB», а не «United Kingdom»). Слой отображения дашборда сопоставляет коды с полными названиями через таблицу соответствий, которая содержит устаревший псевдоним `'UK' → 'United Kingdom'` — именно поэтому оба варианта могут отображаться как опции при создании сегмента. ### Изменение типа визуализации графика \{#change-the-chart-visualization\} Выберите нужный тип отображения графика в выпадающем списке визуализации: - **Stacked column** — каждый столбец показывает итоговое значение, разбитое на цветные сегменты по группам. - **Stacked area** — то же, что stacked column, но с закрашенными областями, соединяющими точки данных. - **Line** — по одной линии на группу, без заливки. - **100% stacked column** — каждый столбец занимает всю высоту графика; сегменты показывают относительную долю (в процентах) каждой группы, а не абсолютные значения. Удобно для отображения пропорций во времени. - **100% stacked area** — то же, что 100% stacked column, но с закрашенными областями вместо столбцов. ### Табличное представление \{#table-view\} Помимо графика, Adapty также предоставляет табличное представление для каждого графика. В нём отображаются исходные данные, на основе которых построен график, — в структурированном виде, что позволяет анализировать информацию более детально. Табличное представление удобно для тех, кто предпочитает работать с данными в структурированном формате или хочет экспортировать их для дальнейшего анализа за пределами Adapty. ### Экспорт данных в CSV \{#csv-data-export\} Чтобы проанализировать исходные данные за графиками, когортными анализами, воронками, удержанием или конверсионной аналитикой, вы можете экспортировать их в формате CSV, нажав кнопку **Export**. <img src="/assets/shared/img/03eee2c-CleanShot_2023-07-10_at_20.49.152x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Также можно [получить те же данные через API](export-analytics-api). Независимо от способа, файл с данными будет одинаковым. Эта функция открывает доступ к исходным данным, которые можно дополнительно анализировать в табличных редакторах или других инструментах для более глубокого изучения. ### Отображение валовой или чистой выручки \{#display-gross-or-net-revenue\} Для графиков, связанных с выручкой ([Revenue](revenue), [MRR](mrr), [ARR](arr), [ARPU](arpu), [ARPPU](arppu)), Adapty предлагает выпадающий список с тремя режимами отображения: - **Gross revenue** — общая выручка без каких-либо вычетов. - **Proceeds after store commission** — выручка за вычетом комиссии стора, без учёта налогов. - **Proceeds after store commission and taxes** — выручка за вычетом как комиссии, так и налогов. Подробнее о расчёте комиссий и налогов см. в разделе [Комиссии и налоги](how-adapty-analytics-works#commissions-and-taxes) статьи *Как работает аналитика Adapty*. --- # File: revenue --- --- title: "Выручка" description: "Отслеживайте и анализируйте выручку вашего приложения с помощью аналитики подписок Adapty." --- График Revenue отображает суммарный доход от подписок и разовых покупок за вычетом возвратов. Это основная метрика для отслеживания финансовых результатов приложения. Переключитесь на месячное разрешение, чтобы оценить общие тренды за последние 12 месяцев. Сгруппируйте график по продукту, сегменту пользователей или источнику атрибуции, чтобы понять, откуда приходит доход, и следите за соотношением новых и повторных платежей — это поможет разобраться, что именно движет ростом. ## Расчёт \{#calculation\} :::warning Калькулятор ниже **не учитывает** [комиссию стора и налоги](how-adapty-analytics-works#commissions-and-taxes). Сравнивайте результат с расчётами **валовой выручки**. ::: Выручка — это сумма всех платных транзакций за период (новые подписки, продления, конвертации из пробного периода, разовые покупки) за вычетом возвратов, обработанных в этом же периоде: **Выручка = все транзакции − возвраты**. Полная сумма каждой транзакции учитывается в день покупки, а не распределяется по сроку действия подписки. График по умолчанию отображает валовую выручку. Используйте [настройки графика](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue), чтобы переключиться между валовой выручкой, выручкой за вычетом комиссии или выручкой за вычетом комиссии и налогов. <CompoundCalculator client:load heading="Выручка" formuLatex="\sum P_i \times Q_i - D" variables={[ { nameInTheFormula: "P", variableName: "price", variableDescription: "Цена за единицу", variableValue: 10 }, { nameInTheFormula: "Q", variableName: "qty", variableDescription: "Количество", variableValue: 1, isInteger: true }, { nameInTheFormula: "D", variableName: "refunds", variableDescription: "Сумма возвратов", variableValue: 35, global: true } ]} rowFormula="price * qty" resultFormula="_sum - refunds" defaultRows={[ { price: 10, qty: 5 }, { price: 50, qty: 10 }, { price: 100, qty: 1 } ]} /> ## Обработка возвратов \{#refund-handling\} Выручка уменьшается на сумму каждого возврата в день его обработки — не в день исходной покупки. График может показывать отрицательное значение для конкретной группы или дня, если возвраты в этом диапазоне превышают новую выручку. Полное сравнение по всем метрикам см. в разделе [Как метрики обрабатывают возвраты](refund-events#how-metrics-handle-refunds). ## Валюта \{#currency\} Adapty отображает все денежные графики в **долларах США**, независимо от исходной валюты транзакции. Это касается Revenue, MRR, ARR, ARPU, ARPPU, LTV, прогнозируемого дохода, возвратов средств, а также показателей дохода внутри когорт и отчётов по A/B-тестам. Настройки для отображения в другой валюте нет. Adapty конвертирует каждую транзакцию в USD по курсу с [currencylayer.com](https://currencylayer.com/), который обновляется каждые 8 часов и **фиксируется на момент транзакции**. Исторические значения в USD не пересчитываются при изменении курса валют. Значения в локальной валюте доступны для каждой транзакции в: - Полях `price_local` и `currency` в вебхуках - Колонках с суффиксом `_local` (например, `revenue_local` и `proceeds_local`) и поле `currency` в экспортах S3, GCS и BigQuery - На странице профиля (в представлении по отдельным транзакциям) Для финансовой отчётности в локальной валюте выгружайте значения по каждой транзакции из экспорта и агрегируйте их самостоятельно. ## Цены при продлении \{#renewal-pricing\} Adapty рассчитывает выручку от продлений по текущей цене продукта — даже для тех пользователей, которые оформили подписку по старой цене. После изменения цены в App Store Connect или Google Play показатели Revenue, MRR и ARR в дашборде для существующих подписчиков могут расходиться с фактически собранной выручкой: Adapty применяет новую цену, даже если стор оставил этих пользователей на прежней. Чтобы проверить это, сравните поле `price` на уровне транзакций в экспорте S3, GCS или BigQuery с данными дашборда для тех же транзакций. Поле в экспорте отражает то, что сообщил стор (цену, которую фактически заплатил покупатель); дашборд отражает текущую цену продукта. ## Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтрация по: атрибуции, аудитории, стране, типу предложения, ID предложения, типу скидки предложения, пейволу, A/B-тестам, плейсменту, периоду, сегменту, стору, продукту и длительности. - ✅ Группировка по: периоду, статусу продления, продукту, стране, стору, пейволу, аудитории, плейсменту, длительности, типу предложения, типу скидки предложения, ID предложения, сегменту и атрибуции. ## Похожие метрики \{#similar-metrics\} Для сравнения этих метрик рядом см. [таблицу сравнения метрик](metric-comparison-table#revenue). - [MRR](mrr) - [ARR](arr) - [ARPU](arpu) - [ARPPU](arppu) --- # File: mrr --- --- title: "MRR" description: "Поймите и оптимизируйте Monthly Recurring Revenue (MRR) в Adapty." --- График MRR (Monthly Recurring Revenue) отображает выручку от активных платных подписок, нормализованную к месячному значению. Он показывает стабильную выручку, которую генерирует ваш подписочный бизнес, независимо от длительности подписки. Чтобы увидеть, как каждая когорта подписчиков вносит вклад в регулярную выручку со временем, сгруппируйте график по первому месяцу покупки и переключитесь на месячное разрешение. В режиме наложенных областей наглядно виден вклад каждой когорты месяц за месяцем. ## Расчёт \{#calculation\} :::warning Калькулятор ниже **не учитывает** [комиссию стора и налоги](how-adapty-analytics-works#commissions-and-taxes). Сравнивайте результат с вашими расчётами **валовой выручки**. ::: MRR нормализует выручку каждой подписки до месячного эквивалента — годовая подписка за $240 добавляет $20 в месяц, а не $240 сразу. Это позволяет MRR оставаться стабильным вне зависимости от распределения платёжных периодов подписок. MRR — это сумма значений (цена × активные подписчики ÷ период выставления счёта в месяцах) по всем типам подписок. Для еженедельных подписок используется период выставления счёта ≈ 0,23 месяца. <SimpleCalculator client:load heading="MRR" formuLatex="\sum_{subscriptions}^{}\frac{P_s\times N_s}{D_m}" variables={[ { nameInTheFormula: "P_s", variableName: "subscriptionPrice", variableDescription: "Цена", variableValue: 10 }, { nameInTheFormula: "N_s", variableName: "activeSubs", variableDescription: "Подписчики", variableValue: 1, isInteger: true }, { nameInTheFormula: "D_m", variableName: "duration", variableDescription: "Период подписки", variableValue: 1, options: [ { label: "Еженедельно", value: 0.23 }, { label: "Ежемесячно", value: 1 }, { label: "2 месяца", value: 2 }, { label: "3 месяца", value: 3 }, { label: "6 месяцев", value: 6 }, { label: "Ежегодно", value: 12 } ] } ]} formulaCalculation="(subscriptionPrice * activeSubs) / duration" isSum={true} defaultRows={[ { subscriptionPrice: 240, activeSubs: 2, duration: 12}, { subscriptionPrice: 30, activeSubs: 10, duration: 1}, { subscriptionPrice: 10, activeSubs: 20, duration: 0.23}, ]} /> MRR не учитывает продукты, которые не генерируют регулярный доход: - разовые покупки - расходуемые покупки - неавтоматически возобновляемые подписки Ваша аудитория может стабильно приносить доход через разовые продукты. Но этот доход не учитывается в MRR, поскольку сами покупки не являются регулярными. ## Обработка возвратов \{#refund-handling\} Когда подписка возвращается, MRR убирает её вклад с каждой даты на графике, где он ранее учитывался. Прошлые значения MRR могут снизиться после поступления возврата. Полное сравнение по метрикам см. в разделе [Как метрики обрабатывают возвраты](refund-events#how-metrics-handle-refunds). ## Валюта \{#currency\} Adapty отображает все денежные графики в **долларах США**, независимо от исходной валюты транзакции. Это касается Revenue, MRR, ARR, ARPU, ARPPU, LTV, прогнозируемого дохода, возвратов средств, а также показателей дохода внутри когорт и отчётов по A/B-тестам. Настройки для отображения в другой валюте нет. Adapty конвертирует каждую транзакцию в USD по курсу с [currencylayer.com](https://currencylayer.com/), который обновляется каждые 8 часов и **фиксируется на момент транзакции**. Исторические значения в USD не пересчитываются при изменении курса валют. Значения в локальной валюте доступны для каждой транзакции в: - Полях `price_local` и `currency` в вебхуках - Колонках с суффиксом `_local` (например, `revenue_local` и `proceeds_local`) и поле `currency` в экспортах S3, GCS и BigQuery - На странице профиля (в представлении по отдельным транзакциям) Для финансовой отчётности в локальной валюте выгружайте значения по каждой транзакции из экспорта и агрегируйте их самостоятельно. ## Ценообразование при продлении \{#renewal-pricing\} Adapty рассчитывает выручку от продлений по текущей цене продукта — даже для тех пользователей, которые оформили подписку по старой цене. После изменения цены в App Store Connect или Google Play показатели Revenue, MRR и ARR в дашборде для существующих подписчиков могут расходиться с фактически собранной выручкой: Adapty применяет новую цену, даже если стор оставил этих пользователей на прежней. Чтобы проверить это, сравните поле `price` на уровне транзакций в экспорте S3, GCS или BigQuery с данными дашборда для тех же транзакций. Поле в экспорте отражает то, что сообщил стор (цену, которую фактически заплатил покупатель); дашборд отражает текущую цену продукта. ## Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтровать по: атрибуции, аудитории, стране, типу предложения, ID предложения, типу скидки предложения, пейволу, A/B-тестам, плейсменту, периоду, сегменту, стору, продукту и длительности. - ✅ Группировать по: периоду, статусу продления, продукту, стране, стору, пейволу, аудитории, плейсменту, длительности, типу предложения, типу скидки предложения, ID предложения, сегменту и атрибуции. ## Похожие метрики \{#similar-metrics\} Для сравнения этих метрик рядом друг с другом см. [таблицу сравнения метрик](metric-comparison-table#revenue). - [Revenue](revenue) - [ARR](arr) - [ARPU](arpu) - [ARPPU](arppu) --- # File: arr --- --- title: "ARR" description: "Отслеживайте Annual Recurring Revenue (ARR) и оптимизируйте стратегию подписок." --- График Annual recurring revenue отображает выручку от всех активных авто-возобновляемых подписок, приведённую к году. График считает активной любую оплаченную и не истёкшую подписку. ARR — ключевая метрика для отслеживания роста subscription-бизнеса и прогнозирования будущей выручки. ## Расчёт \{#calculation\} :::warning Калькулятор ниже **не учитывает** [комиссию стора и налоги](how-adapty-analytics-works#commissions-and-taxes). Сравнивайте результат с расчётами **валовой выручки**. ::: ARR — это годовая версия вашей регулярной выручки от подписок. Наиболее полезен, когда ваш основной продукт — годовые подписки. Для бизнесов, где преобладают месячные или еженедельные подписки, более информативен [MRR](mrr). ARR — это сумма (цена × количество активных подписчиков ÷ расчётный период в годах) по всем типам подписок. Используйте 1/12 для ежемесячных и 1/52 для еженедельных. <SimpleCalculator client:load heading="ARR" formuLatex="\sum \frac{P_s \times U_s}{D_y}" variables={[ { nameInTheFormula: "P_s", variableName: "price", variableDescription: "Цена подписки", variableValue: 240 }, { nameInTheFormula: "U_s", variableName: "subs", variableDescription: "Активные платные подписки", variableValue: 2, isInteger: true }, { nameInTheFormula: "D_y", variableName: "periods", variableDescription: "Период подписки", variableValue: 1, options: [ { label: "Еженедельно", value: "1/52" }, { label: "Ежемесячно", value: "1/12" }, { label: "2 месяца", value: "2/12" }, { label: "3 месяца", value: "3/12" }, { label: "6 месяцев", value: "6/12" }, { label: "Ежегодно", value: 1 } ] } ]} formulaCalculation="(price * subs ) / periods" isSum={true} defaultRows={[ { price: 240, subs: 2, periods: "1" }, { price: 30, subs: 10, periods: "1/12" }, { price: 10, subs: 20, periods: "1/52" } ]} /> ## Обработка возвратов \{#refund-handling\} Когда подписка возвращается, ARR убирает её вклад со всех дат на графике, где она ранее учитывалась. Прошлые значения ARR могут снизиться после того, как возврат будет обработан. Полное сравнение по всем метрикам — в разделе [Как метрики обрабатывают возвраты](refund-events#how-metrics-handle-refunds). ## Валюта \{#currency\} Adapty отображает все денежные графики в **долларах США**, независимо от исходной валюты транзакции. Это касается Revenue, MRR, ARR, ARPU, ARPPU, LTV, прогнозируемого дохода, возвратов средств, а также показателей дохода внутри когорт и отчётов по A/B-тестам. Настройки для отображения в другой валюте нет. Adapty конвертирует каждую транзакцию в USD по курсу с [currencylayer.com](https://currencylayer.com/), который обновляется каждые 8 часов и **фиксируется на момент транзакции**. Исторические значения в USD не пересчитываются при изменении курса валют. Значения в локальной валюте доступны для каждой транзакции в: - Полях `price_local` и `currency` в вебхуках - Колонках с суффиксом `_local` (например, `revenue_local` и `proceeds_local`) и поле `currency` в экспортах S3, GCS и BigQuery - На странице профиля (в представлении по отдельным транзакциям) Для финансовой отчётности в локальной валюте выгружайте значения по каждой транзакции из экспорта и агрегируйте их самостоятельно. ## Цены при продлении \{#renewal-pricing\} Adapty рассчитывает выручку от продлений по текущей цене продукта — даже для тех пользователей, которые оформили подписку по старой цене. После изменения цены в App Store Connect или Google Play показатели Revenue, MRR и ARR в дашборде для существующих подписчиков могут расходиться с фактически собранной выручкой: Adapty применяет новую цену, даже если стор оставил этих пользователей на прежней. Чтобы проверить это, сравните поле `price` на уровне транзакций в экспорте S3, GCS или BigQuery с данными дашборда для тех же транзакций. Поле в экспорте отражает то, что сообщил стор (цену, которую фактически заплатил покупатель); дашборд отражает текущую цену продукта. ## Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтровать по: атрибуции, аудитории, стране, типу оффера, ID оффера, типу скидки оффера, пейволу, A/B-тестам, плейсменту, периоду, сегменту, стору, продукту и длительности. - ✅ Группировать по: периоду, статусу продления, продукту, стране, стору, пейволу, аудитории, плейсменту, длительности, типу оффера, типу скидки оффера, ID оффера, сегменту и атрибуции. ## Похожие метрики \{#similar-metrics\} Для сравнения этих метрик см. [таблицу сравнения метрик](metric-comparison-table#revenue). - [Revenue](revenue) - [MRR](mrr) - [ARPU](arpu) - [ARPPU](arppu) --- # File: arpu --- --- title: "ARPU" description: "Анализируйте средний доход на пользователя (ARPU) для оптимизации монетизации." --- График ARPU (average revenue per user) показывает среднюю выручку на пользователя за выбранный период. Метрика рассчитывается как отношение общей выручки когорты к числу пользователей в ней. Используйте ARPU, чтобы сравнивать доходность разных пользовательских сегментов — по источнику атрибуции, стране или продукту. ## Расчёт \{#calculation\} :::warning Калькулятор ниже **не учитывает** [комиссию стора и налогообложение](how-adapty-analytics-works#commissions-and-taxes). Сравнивайте результат с расчётами **валовой выручки**. ::: ARPU показывает среднюю выручку приложения на одного пользователя — распространённый показатель эффективности монетизации. ARPU — это выручка за период (за вычетом возвратов), делённая на общее количество пользователей приложения за этот период. <CompoundCalculator client:load heading="ARPU" formuLatex="\frac{\sum P_i \times Q_i - D}{U_p}" variables={[ { nameInTheFormula: "P", variableName: "price", variableDescription: "Цена продукта", variableValue: 10 }, { nameInTheFormula: "Q", variableName: "qty", variableDescription: "Куплено продуктов", variableValue: 1, isInteger: true }, { nameInTheFormula: "D", variableName: "refunds", variableDescription: "Сумма возвратов", variableValue: 35, global: true }, { nameInTheFormula: "Up", variableName: "users", variableDescription: "Всего пользователей", variableValue: 160, global: true, isInteger: true } ]} rowFormula="price * qty" resultFormula="(_sum - refunds) / users" defaultRows={[ { price: 10, qty: 5 }, { price: 50, qty: 10 }, { price: 100, qty: 1 } ]} /> ## Обработка возвратов \{#refund-handling\} Возвраты вычитаются из числителя выручки на дату обработки возврата. Полное сравнение по метрикам см. в разделе [Как метрики обрабатывают возвраты](refund-events#how-metrics-handle-refunds). ### Доступные фильтры и группировка \{#available-filters-and-grouping\} - ✅ Фильтрация по: атрибуции, стране и стору. - ✅ Группировка по: стране, стору, статусу атрибуции, каналу атрибуции, кампании атрибуции, группе объявлений атрибуции, набору объявлений атрибуции и креативу атрибуции. Подробнее о доступных элементах управления, фильтрах, вариантах группировки, настройках налогов и комиссий и способах их применения читайте в [этой документации.](controls-filters-grouping-compare-proceeds) ## Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтровать по: атрибуции, стране, A/B-тестам, сегменту и стору. - ✅ Группировать по: стране, стору, сегменту и атрибуции. ### Похожие метрики \{#similar-metrics\} Помимо графика ARPU, Adapty предоставляет метрики и для других событий, связанных с доходами: Revenue, MRR, ARR и ARPPU. Подробнее об этих метриках читайте в следующих гайдах: - [Revenue](revenue) - [MRR](mrr) - [ARPPU](arppu) - [ARR](arr) --- # File: arppu --- --- title: "ARPPU" description: "Понимание ARPPU (средней выручки на платящего пользователя) и его влияния на монетизацию приложения." --- График Average revenue per paying user (ARPPU) показывает средний доход на одного платящего пользователя. Он отображает фактический доход, полученный от платящих клиентов, делённый на их количество, за вычетом возвратов. Группируйте ARPPU по атрибуции, чтобы увидеть, из каких каналов привлечения приходят пользователи с более высокой ценностью. ## Расчёт \{#calculation\} :::warning Калькулятор ниже **не учитывает** [комиссию стора и налоги](how-adapty-analytics-works#commissions-and-taxes). Сравнивайте результат с расчётами **валовой выручки**. ::: ARPPU показывает среднюю выручку на одного платящего пользователя — как правило, она значительно выше [ARPU](arpu), поскольку неплатящие пользователи исключены из знаменателя. ARPPU — это выручка за период (за вычетом возвратов), делённая на количество платящих пользователей за этот период. <CompoundCalculator client:load heading="ARPPU" formuLatex="\frac{\sum P_i \times Q_i - D}{U_p}" variables={[ { nameInTheFormula: "P", variableName: "price", variableDescription: "Цена продукта", variableValue: 10 }, { nameInTheFormula: "Q", variableName: "qty", variableDescription: "Куплено продуктов", variableValue: 1, isInteger: true }, { nameInTheFormula: "D", variableName: "refunds", variableDescription: "Сумма возвратов", variableValue: 35, global: true }, { nameInTheFormula: "Up", variableName: "users", variableDescription: "Платящие пользователи", variableValue: 16, global: true, isInteger: true } ]} rowFormula="price * qty" resultFormula="(_sum - refunds) / users" defaultRows={[ { price: 10, qty: 5 }, { price: 50, qty: 10 }, { price: 100, qty: 1 } ]} /> ## Обработка возвратов \{#refund-handling\} Возвраты вычитаются из числителя выручки на дату обработки возврата. Пользователь, чья покупка была впоследствии возвращена, по-прежнему учитывается в знаменателе платящих пользователей, поэтому большое количество возвратов снижает ARPPU быстрее, чем можно ожидать. Полное сравнение по всем метрикам см. в разделе [Как метрики обрабатывают возвраты](refund-events#how-metrics-handle-refunds). ## Валюта \{#currency\} Adapty отображает все денежные графики в **долларах США**, независимо от исходной валюты транзакции. Это касается Revenue, MRR, ARR, ARPU, ARPPU, LTV, прогнозируемого дохода, возвратов средств, а также показателей дохода внутри когорт и отчётов по A/B-тестам. Настройки для отображения в другой валюте нет. Adapty конвертирует каждую транзакцию в USD по курсу с [currencylayer.com](https://currencylayer.com/), который обновляется каждые 8 часов и **фиксируется на момент транзакции**. Исторические значения в USD не пересчитываются при изменении курса валют. Значения в локальной валюте доступны для каждой транзакции в: - Полях `price_local` и `currency` в вебхуках - Колонках с суффиксом `_local` (например, `revenue_local` и `proceeds_local`) и поле `currency` в экспортах S3, GCS и BigQuery - На странице профиля (в представлении по отдельным транзакциям) Для финансовой отчётности в локальной валюте выгружайте значения по каждой транзакции из экспорта и агрегируйте их самостоятельно. ## Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтрация по: атрибуции, аудитории, стране, пейволу, A/B-тестам, плейсменту, периоду, сегменту, стору, продукту и длительности. - ✅ Группировка по: периоду, статусу продления, продукту, стране, стору, пейволу, аудитории, плейсменту, длительности, сегменту и атрибуции. ## Похожие метрики \{#similar-metrics\} Для сравнения этих метрик рядом см. [таблицу сравнения метрик](metric-comparison-table#revenue). - [Revenue](revenue) - [MRR](mrr) - [ARPU](arpu) - [ARR](arr) --- # File: installs --- --- title: "Установки" description: "Отслеживайте установки приложения и анализируйте их влияние на подписки с помощью Adapty." --- График установок показывает, сколько пользователей установили ваше приложение за выбранный период. Что считается установкой и как каждая установка группируется — зависит от настройки подсчёта установок. В этой статье объясняется, как выбрать подходящий режим подсчёта и как [устранить возможные расхождения](#troubleshooting) между различными источниками аналитики. ## Что считается установкой \{#what-counts-as-an-install\} SDK Adapty регистрирует «установку» и отправляет её в Adapty при первом запуске приложения пользователем. Из этого вытекают два следствия: - Установка появляется в Adapty, когда пользователь открывает приложение впервые, — это может произойти спустя часы или дни после загрузки. - Если пользователь скачал приложение, но так и не открыл его, Adapty эту установку не учитывает. Ваш **часовой пояс отчётности** в настройках приложения определяет, к какому дню относится каждая установка. Например, установка в 23:30 UTC 1 июня попадёт на 2 июня, если ваш часовой пояс отчётности — +02:00, тогда как App Store Connect или Google Play могут показывать её датой 1 июня. ### Режимы подсчёта \{#counting-modes\} Настройка **Installs definition for analytics** определяет, что считается новой установкой. Чтобы изменить её, откройте [App Settings → General → Installs definition for analytics](general#4-installs-definition-for-analytics). | Режим | Что считается | Пример | Метрика сторонних сервисов | Возможные расхождения | | --- | --- | --- | --- | --- | | **New device_ids** (рекомендуется) | **Каждая установка приложения** — включая повторные. Авторизация, создание профиля и обновление версии в счёт не идут. | Один пользователь на 5 устройствах = 5 установок. <br /> <br /> Повторная установка на том же устройстве = 2 установки. | App Store: <br /> **Total Active Devices** <br /> <br /> Google Play: **Devices** | **Больше числа загрузок**, если повторные установки распространены. <br /> <br /> **Меньше числа загрузок**, если многие пользователи скачивают приложение, но не открывают его. | | **New customer_user_ids** | Только **первая установка** для каждого [идентифицированного пользователя](identifying-users). Дополнительные устройства и анонимные пользователи не учитываются. | Один пользователь на 5 устройствах = 1 установка. <br /> <br /> Повторная установка с повторным входом = новой установки нет. <br /> <br /> Использование приложения без аккаунта = новой установки нет. | Статистика регистраций из системы аутентификации вашего приложения | **Остаётся пустым**, если вы вообще не идентифицируете пользователей. | | **New profiles in Adapty** (устаревший) | Считает каждую установку и переустановку, **а также анонимные профили, созданные при выходе из аккаунта**. | Один пользователь, одно устройство, 3 выхода из аккаунта = 4 установки. | Нет | **Выше всех внешних метрик**. Каждый анонимный профиль, созданный при выходе из аккаунта, засчитывается как установка. | Используйте **New device_ids**, если нет веской причины переключаться. ## Устранение неполадок \{#troubleshooting\} ### Количество у Adapty выше, чем в App Store Connect или Google Play \{#adaptys-count-is-higher-than-app-store-connect-or-google-play\} Вероятных причин две: - **Переустановки.** Если выбранный [режим подсчёта](#counting-modes) — **New device_ids**, Adapty учитывает как первые запуски, так и повторные установки. «Total Downloads» в App Store Connect считает только первоначальную загрузку. - **Дата первого запуска ≠ дата загрузки.** Сторы используют дату загрузки. Пользователи, открывшие приложение позже, попадают в другой день. Для более точного сравнения откройте **App Store Connect → Total Active Devices** или **Google Play → Devices**. Эти метрики привязаны к устройствам и ближе всего к режиму **New device_ids** в Adapty. ### Счётчик Adapty равен нулю \{#adaptys-count-is-zero\} Если в режиме подсчёта выбрано **New customer_user_ids**, но вы не <InlineTooltip tooltip="идентифицируете пользователей">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), [Unity](unity-identifying-users), [Kotlin Multiplatform](kmp-quickstart-identify), [Capacitor](capacitor-quickstart-identify)</InlineTooltip>, Adapty не зарегистрирует ни одной установки — в этом режиме анонимные установки не учитываются. Переключитесь на **New device_ids** или реализуйте идентификацию пользователей. ### Данные Adapty расходятся с данными AppsFlyer или Adjust \{#adaptys-count-differs-from-appsflyer-or-adjust\} MMP-сервисы атрибутируют установки по собственному событию инициализации SDK или по первому касанию. Это событие происходит в другой момент, чем первый запуск SDK Adapty, — небольшое расхождение в данных считается нормой. ## Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтровать по: атрибуция, страна, A/B-тест, сегмент и стор. - ✅ Группировать по: страна, стор, сегмент и атрибуция. ## Похожие метрики \{#similar-metrics\} Для сравнения этих метрик в едином формате см. [таблицу сравнения метрик](metric-comparison-table#subscribers-and-conversion). - [Новые подписки](reactivated-subscriptions) - [Активные подписки](active-subscriptions) - [Новые триалы](new-trials) --- # File: active-subscriptions --- --- title: "Активные подписки" description: "Отслеживайте активные подписки и управляйте ими с помощью аналитики Adapty." --- График **Active subscriptions** показывает количество уникальных платных подписок, которые не истекли к концу каждого выбранного периода. Учитываются активные встроенные подписки, которые начались и действуют на текущий момент; бесплатные пробные периоды и подписки с отменённым продлением в расчёт не берутся. График отражает размер и рост вашей базы подписчиков. ## Расчёт \{#calculation\} Метрика активных подписок считает платные, не истёкшие подписки на конец каждого периода. Для подписок без льготного периода истечение происходит, когда дата следующего продления проходит без успешного списания. Например: 500 активных подписок на конец прошлого месяца + 50 новых в этом месяце − 25 истёкших в этом месяце = 525 активных подписок на конец этого месяца. ## Обработка возвратов \{#refund-handling\} Когда подписка возвращается, Adapty убирает её из числа активных — как для текущей даты, так и ретроактивно для прошедших. Подробное сравнение по всем метрикам — в разделе [Как метрики обрабатывают возвраты](refund-events#how-metrics-handle-refunds). ## Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтровать по: атрибуция, аудитория, страна, тип оффера, ID оффера, тип скидки оффера, пейвол, A/B-тесты, плейсмент, период, сегмент, стор, продукт и продолжительность. - ✅ Группировать по: период, статус продления, продукт, страна, стор, пейвол, аудитория, плейсмент, продолжительность, тип оффера, тип скидки оффера, ID оффера, сегмент и атрибуция. ## Похожие метрики \{#similar-metrics\} Для сравнения этих метрик рядом см. [таблицу сравнения метрик](metric-comparison-table#subscribers-and-conversion). - [Истёкшие подписки](churned-expired-subscriptions) - [Отменённые подписки](cancelled-subscriptions) - [Разовые покупки](non-subscriptions) --- # File: reactivated-subscriptions --- --- title: "Новые подписки" description: "Отслеживайте новые подписки в Adapty, чтобы мониторить первичные конверсии и конверсии из бесплатного пробного периода в платную подписку." --- График **New subscriptions** показывает количество новых (впервые активированных) подписок в вашем приложении. Метрика отражает число новых подписок, начавшихся за выбранный период, включая как подписки, оформленные с нуля, так и бесплатные пробные периоды, которые перешли в платные. Продления подписок и возобновлённые подписки в метрику не включаются. ## Расчёт \{#calculation\} Метрика новых подписок считает первые активации подписки за период — как подписки, начатые с нуля, так и бесплатные пробные периоды, перешедшие в платные подписки. ## Обработка возвратов \{#refund-handling\} Новые подписки **не** учитывают возвраты — в счётчик входят подписки, по которым впоследствии был оформлен возврат. Чтобы оценить реальный эффект, сравните с [событиями возврата](refund-events). Полное сравнение метрик по обработке возвратов см. в разделе [Как метрики обрабатывают возвраты](refund-events#how-metrics-handle-refunds). ## Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтровать по: атрибуции, аудитории, стране, типу офера, ID офера, типу скидки офера, пейволу, A/B-тестам, плейсменту, периоду, сегменту, стору, продукту и длительности. - ✅ Группировать по: статусу продления, продукту, стране, стору, пейволу, аудитории, плейсменту, длительности, типу офера, типу скидки офера, ID офера, сегменту и атрибуции. ## Похожие метрики \{#similar-metrics\} Для сравнения этих метрик см. [Таблицу сравнения метрик](metric-comparison-table#subscribers-and-conversion). - [Активные подписки](active-subscriptions) - [Отменённые (истёкшие) подписки](churned-expired-subscriptions) - [Отменённые подписки](cancelled-subscriptions) - [Разовые покупки](non-subscriptions) --- # File: non-subscriptions --- --- title: "Разовые покупки" description: "Узнайте, как управлять продуктами без подписки в Adapty и эффективно отслеживать покупки пользователей." --- График «Разовые покупки» учитывает встроенные покупки, не являющиеся автовозобновляемыми подписками: расходуемые покупки, нерасходуемые покупки и невозобновляемые подписки. Продления в него не включаются. :::note «Разовые покупки» — понятие более узкое, чем «не-подписки»: расходуемые покупки и невозобновляемые подписки можно приобретать несколько раз. ::: ## Расчёт \{#calculation\} Каждая встроенная покупка, не являющаяся подпиской, относится к одному из трёх типов: - **Расходуемые покупки**: товары, которые пользователь может покупать несколько раз, — например, корм для рыб в приложении для рыбалки или внутриигровая валюта. - **Нерасходуемые покупки**: товары, которые покупаются один раз и остаются навсегда, — например, гоночная трасса в игре или отключение рекламы. - **Невозобновляемые подписки**: подписки с фиксированным сроком действия, которые не продлеваются автоматически, — например, годовой доступ к каталогу контента. Содержимое может быть статичным, но по истечении срока подписка не возобновляется. :::note Этот график учитывает только события покупок и не вычитает возвраты. Если у вас есть продукты, не являющиеся подписками, которые часто возвращают, отображаемое количество будет выше фактического числа покупок, принёсших доход. ::: ## Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтровать по: атрибуции, аудитории, стране, пейволу, A/B-тестам, плейсменту, сегменту, стору и продукту. - ✅ Группировать по: продукту, стране, стору, пейволу, аудитории, плейсменту, сегменту и атрибуции. ## Похожие метрики \{#similar-metrics\} Для сравнения этих метрик в одной таблице см. [Таблицу сравнения метрик](metric-comparison-table#revenue). - [Активные подписки](active-subscriptions) - [Новые подписки](reactivated-subscriptions) - [Ушедшие (истёкшие) подписки](churned-expired-subscriptions) - [Отменённые подписки](cancelled-subscriptions) --- # File: cancelled-subscriptions --- --- title: "Отмена продления подписок" description: "Эффективно управляйте отменёнными подписками с помощью инструментов Adapty." --- График **Subscriptions renewal canceled** отображает количество подписок, у которых был отключён авторенью (отменён пользователем). Когда авторенью подписки отключается, это означает, что подписка не продлится автоматически на следующий период. При этом пользователь сохраняет доступ к премиум-функциям приложения до конца текущего периода. ## Расчёт \{#calculation\} Метрика отменённых продлений подписки считает подписки, у которых автопродление было отключено в течение периода. Пользователь сохраняет доступ к premium-функциям до конца текущего расчётного периода, но после этого подписка автоматически не продлится. ## Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтровать по: атрибуции, аудитории, стране, пейволу, A/B-тестам, плейсменту, периоду, сегменту, стору, продукту и длительности. - ✅ Группировать по: продукту, стране, стору, пейволу, аудитории, плейсменту, длительности, сегменту и атрибуции. ## Похожие метрики \{#similar-metrics\} Для сравнения этих метрик рядом см. [Таблицу сравнения метрик](metric-comparison-table#churn). - [Активные подписки](active-subscriptions) - [Отписавшиеся (истёкшие) подписки](churned-expired-subscriptions) - [Новые подписки](reactivated-subscriptions) - [Разовые покупки](non-subscriptions) --- # File: churned-expired-subscriptions --- --- title: "Истёкшие (отменённые) подписки" description: "Управляйте истёкшими и отменёнными подписками для улучшения удержания пользователей." --- График отозванных (истёкших) подписок показывает количество подписок, срок действия которых истёк, — то есть пользователь больше не имеет доступа к премиальным функциям приложения. Как правило, это происходит когда пользователь решает не продлевать подписку по окончании расчётного периода или сталкивается с проблемами оплаты. Используйте группировку по причине истечения, чтобы разделить добровольный отток от оттока, вызванного проблемами с оплатой. ## Расчёт \{#calculation\} Метрика истёкших (churned) подписок считает подписки, срок которых истёк в течение периода — то есть пользователь потерял доступ к премиум-функциям. Сюда входят как те, кто сам отказался от продления, так и те, кто потерял подписку из-за проблем с оплатой. ## Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтр по: атрибуции, аудитории, стране, пейволу, A/B-тестам, плейсменту, сегменту, стору, продукту и длительности. - ✅ Группировка по: причине истечения, продукту, стране, стору, пейволу, аудитории, плейсменту, длительности, сегменту и атрибуции. ## Похожие метрики \{#similar-metrics\} Для сравнения этих метрик рядом см. [таблицу сравнения метрик](metric-comparison-table#churn). - [Активные подписки](active-subscriptions) - [Новые подписки](reactivated-subscriptions) - [Отменённые подписки](cancelled-subscriptions) - [Разовые покупки](non-subscriptions) --- # File: active-trials --- --- title: "Активные триалы" description: "Отслеживайте активные триалы подписок и управляйте ими с помощью аналитики Adapty." --- График активных триалов в Adapty показывает количество неистёкших бесплатных триалов, которые активны на конец заданного периода. «Активные» — значит подписки, срок действия которых ещё не истёк; то есть пользователи по-прежнему имеют доступ к платным функциям приложения. ## Расчёт \{#calculation\} Метрика активных пробных периодов считает неистёкшие бесплатные пробные периоды на конец каждого расчётного периода. Отмена автопродления не убирает пробный период из счётчика — только истечение. Например: 100 активных пробных периодов вчера + 10 новых сегодня − 5 истёкших сегодня = 105 активных пробных периодов сегодня. ## Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Настройки аналитики](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтровать по: атрибуция, аудитория, страна, тип предложения, ID предложения, тип скидки предложения, пейвол, A/B-тесты, плейсмент, сегмент, стор, продукт и длительность. - ✅ Группировать по: период, статус продления, продукт, страна, стор, пейвол, аудитория, плейсмент, длительность, тип предложения, тип скидки предложения, ID предложения, сегмент и атрибуция. ## Похожие метрики \{#similar-metrics\} Чтобы сравнить эти метрики рядом, см. [таблицу сравнения метрик](metric-comparison-table#subscribers-and-conversion). - [Новые триалы](new-trials) - [Отменённые продления триала](trials-renewal-cancelled) - [Истёкшие триалы](expired-churned-trials) --- # File: new-trials --- --- title: "Новые триалы" description: "Управляйте новыми триалами подписки и оптимизируйте конверсию из триала в платную подписку." --- График новых триалов показывает количество активированных триалов за выбранный период. Используйте его для отслеживания объёма триалов из рекламных кампаний и других каналов привлечения. ## Расчёт \{#calculation\} Метрика новых триалов учитывает триалы, запущенные в течение периода, независимо от того, остаются ли они активными к его концу. Например, если 50 пользователей начали триал в мае, на графике за май будет показано 50 — даже если к моменту просмотра часть из них уже истекла или конвертировалась в платную подписку. ## Доступные фильтры и группировки \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтры: Attribution, аудитория, страна, тип оффера, ID оффера, тип скидки оффера, пейвол, A/B-тесты, плейсмент, сегмент, стор, продукт и длительность. - ✅ Группировка: продукт, страна, стор, пейвол, аудитория, плейсмент, длительность, тип оффера, тип скидки оффера, ID оффера, сегмент и атрибуция. ## Похожие метрики \{#similar-metrics\} Для сравнения этих метрик рядом см. [таблицу сравнения метрик](metric-comparison-table#subscribers-and-conversion). - [Активные триалы](active-trials) - [Отменённые продления триала](trials-renewal-cancelled) - [Истёкшие триалы](expired-churned-trials) --- # File: trials-renewal-cancelled --- --- title: "Отменённые продления пробных периодов" description: "Узнайте о продлениях и отменах пробных периодов, а также о подписках с помощью инструментов Adapty." --- График **Trials renewal cancelled** отображает количество пробных периодов с отменённым продлением (отменённых пользователем). Когда продление пробного периода отключено, это означает, что пробный период не будет автоматически конвертирован в платную подписку, однако пользователь сохраняет доступ к премиум-функциям приложения до конца текущего периода. ## Расчёт \{#calculation\} Метрика «Отменённые продления триалов» учитывает триалы, для которых пользователь отключил автопродление в течение выбранного периода. Пользователь сохраняет доступ по триалу до его окончания, но триал автоматически не конвертируется в платную подписку. ## Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтровать по: Атрибуция, Аудитория, Страна, Пейвол, A/B-тесты, Плейсмент, Сегмент, Стор, Продукт и Период. - ✅ Группировать по: Продукт, Страна, Стор, Пейвол, Аудитория, Плейсмент, Период, Сегмент и Атрибуция. ## Похожие метрики \{#similar-metrics\} Для сравнения этих метрик рядом см. [таблицу сравнения метрик](metric-comparison-table#churn). - [Новые триалы](new-trials) - [Активные триалы](active-trials) - [Истёкшие триалы](expired-churned-trials) --- # File: expired-churned-trials --- --- title: "Истёкшие (ушедшие) триалы" description: "Управляйте истёкшими и ушедшими триалами с помощью аналитики Adapty." --- График Expired (churned) trials показывает количество триалов, которые истекли и оставили пользователей без доступа к премиум-функциям приложения. В большинстве случаев это происходит, когда пользователи решают не платить за приложение или сталкиваются с проблемами при оплате. ## Расчёт \{#calculation\} Метрика истёкших триалов учитывает триалы, завершившиеся в течение периода, — пользователь потерял доступ к премиум-функциям. Сюда входят как те, кто сам решил не конвертироваться, так и те, у кого конвертация не прошла из-за проблем с оплатой. Используйте группировку по **Expiration reason**, чтобы разделить добровольный отток от оттока по причине ошибок оплаты. ## Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтрация по: атрибуции, аудитории, стране, пейволу, A/B-тестам, плейсменту, сегменту, стору, продукту и периоду. - ✅ Группировка по: причине истечения, продукту, стране, стору, пейволу, аудитории, плейсменту, периоду, сегменту и атрибуции. ## Похожие метрики \{#similar-metrics\} Чтобы сравнить эти метрики рядом, смотрите [таблицу сравнения метрик](metric-comparison-table#churn). - [Новые триалы](new-trials) - [Активные триалы](active-trials) - [Отменённые триалы](trials-renewal-cancelled) --- # File: refund-events --- --- title: "Refund events" description: "Управляйте событиями возврата средств в Adapty, чтобы сократить отток и оптимизировать доход." --- График Refund events показывает, сколько покупок и подписок было возвращено. Adapty привязывает каждое событие возврата к дате его оформления, а не к дате начала подписки. ## Расчёт \{#calculation\} Adapty учитывает каждую покупку или подписку, по которой был оформлен возврат в выбранный период. Каждый возврат относится к дате его оформления, а не к дате начала подписки. Возвраты по триалам не учитываются, так как триалы не генерируют выручку. ## Как метрики обрабатывают возвраты \{#how-metrics-handle-refunds\} Разные метрики обрабатывают возвраты по-разному. Одно и то же событие возврата может сразу уменьшить один график, ретроактивно изменить значения другого (затронув данные за прошлые периоды) или вообще не повлиять на третий. В таблице ниже описаны правила для каждой метрики. | Метрика | Учитываются возвраты? | Дата атрибуции | Может быть отрицательной? | Примечания | | --- | --- | --- | --- | --- | | [Revenue](revenue) | Да | Дата возврата — не дата исходной покупки | Да — в дни, когда возвраты превышают новую выручку | Revenue = все транзакции − возвраты. | | [MRR](mrr) | Да, ретроактивно | Подписка удаляется из всех периодов, в которых она была активна | Нет | Значения за прошлые периоды могут уменьшиться после возврата. | | [ARR](arr) | Да, ретроактивно | Так же, как MRR | Нет | Значения за прошлые периоды могут уменьшиться после возврата. | | [ARPU](arpu) | Да | Дата возврата | Да (в периоды с большим числом возвратов) | Возвраты вычитаются из числителя выручки. | | [ARPPU](arppu) | Да, только числитель | Дата возврата | Да (в периоды с большим числом возвратов) | Возвраты вычитаются из числителя выручки. Пользователь с возвратом по-прежнему учитывается в знаменателе (платящие пользователи), поэтому при большом числе возвратов ARPPU падает быстрее, чем ожидается. | | [Active subscriptions](active-subscriptions) | Да, ретроактивно | Подписка удаляется из счётчика | Нет | | | [New subscriptions](reactivated-subscriptions) | **Нет** | — | Нет | В счётчик включаются подписки, по которым впоследствии был оформлен возврат. Для оценки чистого эффекта сравните с [Refund events](refund-events). | | [Refund money](refund-money) / [Refund events](refund-events) | Возвраты **и есть** данные | Дата возврата | Нет (всегда ≥ 0) | | | [Retention](analytics-retention) | **Нет** | — | Нет | Пользователи с возвратами остаются на кривой удержания. Из-за этого Retention может выглядеть выше, чем [Active subscriptions](active-subscriptions) или [Revenue](revenue) для той же когорты. | | [Cohort revenue](analytics-cohorts) | Да, накопительно | Дата возврата | Нет (накопленные вычеты не опускают доход когорты ниже нуля) | Возвраты вычитаются из дохода когорты по мере поступления. Об обработке возвратов для остальных метрик когорт см. [Когорты > Обработка возвратов](analytics-cohorts#refund-handling). | | [Paywall metrics](paywall-metrics) / [A/B test metrics](results-and-metrics) (счётчики) | **Нет** | — | Нет | Показатели Subscribers, Paying Subscribers и ARPPU на этих страницах не учитывают возвраты. | | GCS / S3 экспорты | Возврат как отдельная строка события | `event_datetime` = временна́я метка возврата | Суммарные столбцы могут быть отрицательными | Строка возврата содержит `is_refund = true` (S3/GCS) или тип события `subscription_refunded` (вебхуки). | ### Отрицательные значения \{#negative-values\} В агрегированных представлениях (график Revenue, кастомная аналитика при экспорте) метрика может принимать отрицательное значение за определённый период или в определённой группировке, если сумма возвратов в этом сегменте превышает новую выручку за тот же период. Это не баг — арифметика работает именно так, как задумано. Например: в какой-то стране во вторник не было новых покупок, но в этот день был обработан возврат на $100 за более раннюю покупку. Тогда выручка этой страны за вторник отобразится как −$100. ## Доступные фильтры и группировки \{#available-filters-and-grouping\} :::link Основная статья: [Инструменты аналитики](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтровать по: атрибуции, аудитории, причине возврата, стране, типу предложения, ID предложения, типу скидки предложения, пейволу, A/B-тестам, плейсменту, периоду, сегменту, стору, продукту и длительности. - ✅ Группировать по: причине возврата, продукту, стране, стору, пейволу, аудитории, плейсменту, длительности, типу предложения, типу скидки предложения, ID предложения, сегменту и атрибуции. ## Похожие метрики \{#similar-metrics\} Для сравнения этих метрик в одной таблице смотрите [Таблицу сравнения метрик](metric-comparison-table#revenue). - [Возврат средств](refund-money) - [Проблема с оплатой](billing-issue) - [Льготный период](grace-period) --- # File: refund-money --- --- title: "Возвраты средств" description: "Узнайте, как обрабатывать возвраты по подпискам в Adapty без потери выручки." --- График «Возвраты средств» показывает сумму, возвращённую пользователям за выбранный период. Adapty привязывает каждое событие возврата к дате его оформления, поэтому выручка уменьшается именно в этом периоде. ## Расчёт \{#calculation\} Adapty учитывает только транзакции, приносящие доход: новые платные подписки, продления и разовые покупки. Бесплатные пробные периоды исключаются — они не генерируют выручку и не могут быть возвращены. Сумма каждого возврата привязана к дате его обработки, поэтому уменьшение выручки отображается в том же периоде. :::info Сумма возврата рассчитывается до вычета комиссии стора. ::: ## Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтровать по: атрибуции, аудитории, причине возврата, стране, типу оффера, ID оффера, типу скидки оффера, пейволу, A/B-тестам, плейсменту, периоду, сегменту, стору, продукту и длительности. - ✅ Группировать по: причине возврата, продукту, стране, стору, пейволу, аудитории, плейсменту, длительности, типу оффера, типу скидки оффера, ID оффера, сегменту и атрибуции. ## Управление запросами на возврат средств \{#refund-request-management\} Refund saver помогает пользователям Adapty эффективнее обрабатывать запросы на возврат средств из App Store благодаря автоматизации. Он экономит время и снижает потери выручки, упрощая весь процесс. Уведомления в реальном времени и полезная аналитика делают работу с запросами на возврат удобнее — при полном соответствии требованиям Apple. Подробнее о [Refund saver](refund-saver). ## Похожие метрики \{#similar-metrics\} Для сравнения этих метрик рядом см. [таблицу сравнения метрик](metric-comparison-table#revenue). - [События возврата средств](refund-events) - [Проблемы с оплатой](billing-issue) - [Льготный период](grace-period) --- # File: grace-period --- --- title: "Льготный период" description: "Узнайте, как работают льготные периоды подписки и как улучшить удержание пользователей." --- График «Grace period» отображает количество подписок, перешедших в состояние льготного периода из-за [проблемы с оплатой](billing-issue). В течение этого времени подписка остаётся активной, пока стор пытается получить платёж от пользователя. Если оплата так и не поступает до окончания льготного периода, подписка переходит в состояние проблемы с оплатой. ### Расчёт \{#calculation\} Adapty рассчитывает график льготного периода, отслеживая количество подписок, перешедших в состояние льготного периода за заданный промежуток времени. Льготный период начинается, когда подписка переходит в состояние проблемы с оплатой из-за неуспешного платежа, и заканчивается по истечении установленного времени (6 дней для еженедельных подписок, 16 дней для всех остальных) либо при успешном получении оплаты. График помогает оценить эффективность функции льготного периода и выявить возможные проблемы с обработкой платежей или управлением подписками. ## Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтровать по: атрибуция, аудитория, страна, пейвол, A/B-тесты, плейсмент, период, сегмент, стор, продукт и длительность. - ✅ Группировать по: продукт, страна, стор, пейвол, аудитория, плейсмент, длительность, сегмент и атрибуция. ### Похожие метрики \{#similar-metrics\} Помимо графика льготного периода, Adapty также предоставляет метрики для других событий, связанных с проблемами: возврат средств (события и суммы), а также проблема с оплатой. Подробнее об этих метриках читайте в следующих разделах документации: - [Возврат средств](refund-money) - [События возврата](refund-events) - [Проблема с оплатой](billing-issue) --- # File: grace-period-converted --- --- title: "Grace period converted" description: "Отслеживайте количество подписок, которые вошли в льготный период и были возобновлены до его окончания." --- График **Grace period converted** отображает количество подписок, которые перешли в состояние [льготного периода](grace-period) и были успешно возобновлены до его истечения. ### Расчёт \{#calculation\} График Grace period converted отображает ежедневное количество продлений подписок для пользователей в льготном периоде. Льготный период начинается, когда подписка переходит в состояние проблемы с оплатой из-за неуспешного платежа, и заканчивается по истечении заданного времени (6 дней для еженедельных подписок, 16 дней для всех остальных) или после успешного получения платежа. График показывает эффективность функции льготного периода и помогает выявлять возможные проблемы с обработкой платежей или управлением подписками. ### Доступные фильтры \{#available-filters\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтровать по: атрибуции, аудитории, причине возврата, стране, типу предложения, ID предложения, типу скидки предложения, пейволу, A/B-тестам, плейсменту, периоду, сегменту, стору, продукту и длительности. - ✅ Группировать по: продукту, стране, стору, пейволу, аудитории, плейсменту, длительности, сегменту и атрибуции. ### Использование графика «Grace period converted» \{#grace-period-converted-chart-usage\} Используйте этот график, чтобы отслеживать, насколько эффективно льготный период помогает восстанавливать подписки с проблемами оплаты. Наблюдая за динамикой конверсии, вы сможете выявлять закономерности в разрешении платёжных проблем и оценивать влияние изменений в процессах обновления платежей или коммуникационных стратегиях в течение льготного периода. ### Похожие метрики \{#similar-metrics\} - [Проблемы с оплатой](billing-issue) - [Конвертация из проблем с оплатой](billing-issue-converted) - [Выручка от конвертации из проблем с оплатой](billing-issue-converted-revenue) - [Льготный период](grace-period) - [Выручка от конвертации в льготный период](grace-period-converted-revenue) - [Возвраты (сумма)](refund-money) - [Возвраты (события)](refund-events) --- # File: grace-period-converted-revenue --- --- title: "Выручка от конвертаций в льготном периоде" description: "Отслеживайте общую выручку от конвертаций в льготном периоде." --- График **Grace period converted revenue** отображает выручку, полученную от [конвертаций в льготном периоде](grace-period-converted): подписок, которые перешли в состояние [льготного периода](grace-period) и были успешно продлены до его окончания. ### Расчёт \{#calculation\} График «Grace period converted revenue» отображает ежедневную выручку от продления подписок пользователей, находящихся в льготном периоде. Льготный период начинается, когда подписка переходит в состояние проблемы с оплатой из-за сбоя платежа, и заканчивается по истечении заданного времени (6 дней для еженедельных подписок, 16 дней для всех остальных) или после успешного получения платежа. График помогает оценить эффективность льготного периода и выявить возможные проблемы с обработкой платежей или управлением подписками. ### Доступные фильтры \{#available-filters\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтровать по: атрибуции, аудитории, причине возврата, стране, типу офера, ID офера, типу скидки офера, пейволу, A/B-тестам, плейсменту, периоду, сегменту, стору, продукту и длительности. - ✅ Группировать по: продукту, стране, стору, пейволу, аудитории, плейсменту, длительности, сегменту и атрибуции. ### Использование графика доходов от льготного периода \{#grace-period-converted-revenue-chart-usage\} Используйте этот график, чтобы оценить финансовый эффект льготного периода — отследить доход, восстановленный по подпискам с проблемами оплаты. Это позволяет измерить эффективность вашей стратегии льготного периода и понять, насколько оправданы вложения в реализацию связанных функций или коммуникаций. ### Похожие метрики \{#similar-metrics\} - [Billing issue](billing-issue) - [Billing issue converted](billing-issue-converted) - [Billing issue converted revenue](billing-issue-converted-revenue) - [Льготный период](grace-period) - [Grace period converted](grace-period-converted) - [Refund money](refund-money) - [Refund events](refund-events) --- # File: billing-issue --- --- title: "Проблема с оплатой" description: "Решайте проблемы с оплатой подписок с помощью инструментов поддержки Adapty." --- График «Проблема с оплатой» показывает количество подписок, перешедших в состояние «Проблема с оплатой». Это состояние возникает, когда стор — App Store или Google Play — по какой-либо причине не может списать оплату с подписчика. Причиной может быть, например, истёкший срок действия карты или недостаточный баланс. ## Расчёт \{#calculation\} Метрика billing issue считает подписки, которые перешли в состояние billing issue за выбранный период. Подписка переходит в это состояние, когда стор (Apple или Google) не может провести платёж при продлении — как правило, из-за истёкшей карты или недостатка средств. В состоянии billing issue подписка неактивна. Если включён [льготный период](grace-period), подписка переходит в состояние billing issue только после того, как льготный период истечёт без успешного платежа. ## Доступные фильтры и группировка \{#available-filters-and-grouping\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтровать по: атрибуция, аудитория, страна, пейвол, A/B-тест, плейсмент, период, сегмент, стор, продукт и длительность. - ✅ Группировать по: продукт, страна, стор, пейвол, аудитория, плейсмент, длительность, сегмент и атрибуция. ## Похожие метрики \{#similar-metrics\} Для сравнения этих метрик см. [таблицу сравнения метрик](metric-comparison-table#billing-issues-and-revenue-recovery). - [Billing issue converted](billing-issue-converted) - [Billing issue converted revenue](billing-issue-converted-revenue) - [Refund money](refund-money) - [Refund events](refund-events) - [Grace period](grace-period) - [Grace period converted](grace-period-converted) - [Grace period converted revenue](grace-period-converted-revenue) --- # File: billing-issue-converted --- --- title: "Billing issue converted" description: "Отслеживайте количество проблем с оплатой, решённых до конца расчётного периода." --- График Billing issue converted отображает ежедневное количество подписок, перешедших в состояние [Billing Issue](billing-issue) и возобновлённых до окончания расчётного периода. ### Расчёт \{#calculation\} График «Billing issue converted» показывает количество подписок, которые перешли в состояние [Billing Issue](billing-issue) в текущем расчётном периоде и были возобновлены в этот день. Подписка переходит в состояние Billing Issue, когда стор (например, Apple или Google) по какой-либо причине не может провести платёж — например, из-за истёкшей карты или недостатка средств. В состоянии Billing Issue подписка не считается активной. Если в настройках стора включён льготный период, подписка перейдёт в состояние Billing Issue только после его окончания. ### Доступные фильтры \{#available-filters\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтровать по: атрибуции, аудитории, причине возврата, стране, типу предложения, ID предложения, типу скидки предложения, пейволу, A/B-тестам, плейсменту, периоду, сегменту, стору, продукту и длительности. - ✅ Группировать по: продукту, стране, стору, пейволу, аудитории, плейсменту, длительности, сегменту и атрибуции. ### Использование графика Billing issue converted \{#billing-issue-converted-chart-usage\} Используйте этот график, чтобы отслеживать, насколько эффективно проблемы с оплатой решаются в течение расчётного периода после окончания льготного периода. Анализ динамики решения проблем позволяет выявлять закономерности в восстановлении платежей и оценивать влияние изменений в логике повторных попыток оплаты или стратегиях коммуникации при возникновении проблем с оплатой. ### Похожие метрики \{#similar-metrics\} - [Billing issue](billing-issue) - [Billing issue converted revenue](billing-issue-converted-revenue) - [Refund money](refund-money) - [Refund events](refund-events) - [Grace period](grace-period) - [Grace period converted](grace-period-converted) - [Grace period converted revenue](grace-period-converted-revenue) --- # File: billing-issue-converted-revenue --- --- title: "Доход от восстановленных подписок после проблем с оплатой" description: "Решайте проблемы с оплатой подписок с помощью инструментов поддержки Adapty." --- График **Billing issue converted revenue** отображает доход от [восстановленных подписок после проблем с оплатой](billing-issue-converted): подписок, перешедших в состояние [Billing Issue](billing-issue) и возобновлённых до конца расчётного периода. ### Расчёт \{#calculation\} График **Billing issue converted revenue** отображает ежедневную выручку от подписок, которые перешли в состояние [Billing Issue](billing-issue) в текущем расчётном периоде и были успешно обновлены в этот день. Подписка переходит в состояние Billing Issue, когда стор (например, Apple или Google) не может обработать платёж по какой-либо причине — например, из-за истёкшей карты или нехватки средств. В состоянии Billing Issue подписка считается неактивной. Если в настройках стора включён льготный период, подписка перейдёт в состояние Billing Issue только после его истечения. ### Доступные фильтры \{#available-filters\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: - ✅ Фильтровать по: атрибуции, аудитории, причине возврата, стране, типу офера, ID офера, типу скидки офера, пейволу, A/B-тестам, плейсменту, периоду, сегменту, стору, продукту и длительности. - ✅ Группировать по: продукту, стране, стору, пейволу, аудитории, плейсменту, длительности, сегменту и атрибуции. ### Как использовать график Billing issue converted revenue \{#billing-issue-converted-revenue-chart-usage\} Используйте этот график, чтобы оценить финансовый эффект от решения проблем с оплатой — он показывает доход, восстановленный по подпискам после истечения льготного периода. Это помогает измерить эффективность стратегии возврата подписчиков с проблемами оплаты и оценить отдачу от внедрения механизмов повторных попыток списания или целевых коммуникаций с пользователями. ### Похожие метрики \{#similar-metrics\} - [Billing issue](billing-issue) - [Billing issue converted](billing-issue-converted) - [Возврат средств](refund-money) - [События возврата](refund-events) - [Grace period](grace-period) - [Grace period converted](grace-period-converted) - [Grace period converted revenue](grace-period-converted-revenue) --- # File: ltv --- --- title: "Lifetime Value (LTV)" description: "Learn how to calculate and optimize Lifetime Value (LTV) in Adapty." --- Realized LTV (Lifetime Value) на одного платящего пользователя показывает выручку, которую когорта платящих пользователей фактически принесла после вычета возвратов, делённую на количество платящих пользователей в этой когорте. Таким образом, этот график отвечает на вопрос: сколько в среднем приносит каждый платящий пользователь. Adapty проектирует LTV-график так, чтобы он отвечал на несколько важных вопросов о выручке приложения и поведении пользователей, например: 1. Сколько денег приносит каждая когорта за всё время использования приложения? 2. В какой момент когорта окупается? 3. Как оптимизировать маркетинг и расходы на привлечение пользователей, чтобы привлекать ценных клиентов с высоким LTV? 4. Сколько времени нужно, чтобы окупить вложения в привлечение новых клиентов? График LTV работает с данными приложения, которые мы собираем через SDK и события встроенных покупок. С помощью этой информации вы сможете отслеживать эффективность подписок и понимать, сколько выручки приносят подписчики за заданный период. Это поможет принимать обоснованные решения о продуктовых предложениях, рекламных бюджетах и стратегиях привлечения пользователей. Фильтры позволяют сегментировать данные по стране, атрибуции и другим параметрам — так вы получаете более детальное представление о своей аудитории. ### LTV по продлениям \{#ltv-by-renewals\} Представление **LTV by renewals** отображает данные по периоду подписки (P), фиксируя первый момент, когда клиент совершает платёж. Для еженедельной подписки это соответствует следующему еженедельному периоду. ### LTV по дням \{#ltv-by-days\} Представление **LTV by days** организует и фильтрует данные по дням, неделям или месяцам. Оно показывает общую выручку от всех пользователей, установивших приложение в конкретный день, неделю или месяц, делённую на количество платящих пользователей за тот же период. Это представление даёт ценное понимание динамики выручки и позволяет комплексно отслеживать поведение пользователей во времени. ### Расчёт \{#calculation\} Реализованный LTV рассчитывается на основе общей выручки каждой когорты клиентов за вычетом возвратов. _LTV за день/неделю/месяц = Выручка от всех платящих пользователей, установивших приложение в этот день/неделю/месяц / количество платящих пользователей, установивших приложение в этот день/неделю/месяц_ Расчёт LTV включает апгрейды, даунгрейды и реактивации — например, когда пользователь меняет тарифный план или ценообразование. Учитывается выручка от первоначальной подписки и последующих продлений по обновлённому плану. ### Использование графика LTV \{#ltv-chart-usage\} График LTV — ценный инструмент в Adapty, который помогает понять долгосрочную ценность клиентов. Анализируя поведение клиентов и паттерны покупок во времени, график LTV позволяет оценить выручку, генерируемую разными сегментами или когортами клиентов. График LTV особенно полезен для выявления высокоценных клиентских сегментов, отслеживания эффективности маркетинговых кампаний и оценки общей финансовой результативности бизнеса. Понимая пожизненную ценность клиентов, вы можете принимать обоснованные решения о распределении ресурсов, стратегиях привлечения и удержания клиентов. Кроме того, график LTV можно использовать для сравнения различных клиентских сегментов, оценки влияния изменений продукта или корректировок цен, а также выявления возможностей для апселлинга или кросс-продаж. ### Доступные группировки и фильтры \{#available-grouping-and-filtering\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: Фильтры и группировки можно применять как к представлению по продлениям, так и к представлению по дням на графике LTV — это позволяет детально изучить конкретные когорты и понять их поведение с течением времени - ✅ Фильтровать по: Attribution, Audience, Country, Paywall, A/B tests, Placement, Segment, Store, Product и Duration. - ✅ Группировать по: Product, Country, Store, Duration, Segment и Cohort (Day, Week, Month или Year). График Realized LTV в Adapty помогает получить ценные сведения о поведении пользователей, оптимизировать маркетинговые стратегии, отслеживать динамику выручки и принимать решения на основе данных, чтобы максимально увеличить долгосрочную ценность клиентов. --- # File: analytics-cohorts --- --- title: "Когортный анализ" description: "Используйте когорты в аналитике Adapty для отслеживания вовлечённости пользователей и тенденций подписок." --- Когорты в Adapty помогают ответить на несколько важных вопросов: 1. В какой день когорта окупается? 2. Сколько денег приносит приложение для конкретной когорты? 3. Сколько можно потратить на привлечение платящего пользователя? 4. Сколько времени нужно, чтобы окупить рекламные расходы? Когорты работают с данными приложения, которые мы собираем через SDK и уведомления стора, и не требуют никакой дополнительной настройки с вашей стороны. ## Когорты по продлениям или по дням \{#cohorts-by-renewals-or-by-days\} Вы можете анализировать когорты по продлениям или по дням. Переключатель меняет заголовки столбцов и, соответственно, подход к анализу. Отслеживание **по дням** помогает планировать бюджет и понимать сроки платежей. Это особенно удобно для расходуемых покупок и разовых покупок. В этом режиме синий цвет в ячейках таблицы, как правило, концентрируется в середине строк по двум причинам. Во-первых, просмотр когорт по дням позволяет раньше увидеть платежи по краткосрочным продуктам, тогда как в режиме продлений они объединяются с ежемесячными и ежегодными продлениями. Во-вторых, на характер распределения влияют отложенные платежи: часть пользователей платит позже ожидаемого срока. Тогда как отслеживание **по продлениям** показывает удержание и отток когорт от одного платежа к другому без привязки к дате. Запоздавшие пользователи, оплатившие с любой задержкой (иногда в несколько месяцев), добавляются к числу соответствующего расчётного периода подписки. Этот подход не отражает реальную картину доходов по календарю, зато гораздо удобнее для анализа удержания и оттока когорт и извлечения инсайтов из их поведения. Выбирайте удобный режим или используйте оба — для более полных выводов и идей. ## Как Adapty формирует когорты \{#how-adapty-builds-cohorts\} Рассмотрим на примере когорт по продлениям, как формируется таблица. Для построения когорт используются два показателя: установки приложения и транзакции (покупки). Каждая строка когорты представляет отдельный временной интервал — от одного дня до года. Строка начинается с количества пользователей, которые установили приложение в этом интервале и оформили подписку или совершили разовую/нерегулярную покупку. Каждый следующий столбец в строке показывает количество пользователей, продливших подписку к этому периоду. M3 означает 3-й месяц, то есть подписчики к этому моменту оформили 3 последовательных продления; W7 — 7-я неделя, Y2 — 2-й год. Иногда в когортах можно встретить обозначение P2. P означает период подписки — Adapty использует его вместо W/M/Y, когда в одной когорте присутствует несколько продуктов с разными периодами продления. Для наглядного выделения различий в значениях когорт используется градиентная заливка: чем больше число, тем насыщеннее цвет. На изображении ниже вы можете видеть типичную когорту. 1. В этой когорте отображаются данные только для еженедельных продуктов (отметка #1). 2. Комиссии сторов не вычитаются, выручка отображается в абсолютных значениях (отметка #2). 3. Рассматриваемый период — последние 6 месяцев, длина когорты — 1 месяц (отметка #3). 4. Строка **Total** (отметка #4) показывает накопленное значение за каждый период. $442K в первой ячейке строки **Total** — это суммарная выручка первого периода (активации подписки) по всем месяцам (ноябрь, декабрь и далее) вплоть до конца временного интервала. Ячейка Total показывает количество пользователей, установивших приложение за весь период. 5. Первый столбец строки Nov 2023 (отметка #5) показывает выручку первого периода (активации подписки) в размере $37,7K от пользователей, установивших приложение в ноябре 2023 года. Количество таких пользователей — 95 129 — отображается в столбце заголовка. Второй столбец строки Nov 2023 показывает выручку 2-й недели (подписки, продлившиеся до 2-й недели) в размере $8,77K от пользователей, установивших приложение в ноябре 2023 года. 6. В таблице можно увидеть Total revenue, ARPU, ARPPU и ARPAS (отметка #6). Подробнее о них — чуть ниже в этой статье. 7. Столбцы в правой части таблицы настраиваются с помощью выпадающего поля **Columns** (отметка #7). 8. Над таблицей справа (отметка #8) также есть выпадающее поле для расчёта комиссий сторов и налогов применительно к конкретному когортному анализу. О том, как Adapty рассчитывает комиссии сторов и налоги, можно узнать в [этой статье](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue). После выбора соответствующего варианта данные о выручке будут пересчитаны с его учётом. 9. В правой части таблицы отображаются прогнозируемая выручка (Predicted Revenue) и прогнозируемый LTV (Predicted LTV) (отметка #9). Поле **Predicted Revenue** показывает оценку общей выручки, которую принесёт когорта подписчиков за определённый период, а поле **Predicted LTV** — ожидаемую ценность каждого пользователя в когорте. Наведите курсор на любую ячейку когорты, чтобы увидеть подробные метрики за этот период. Ячейки с косой штриховкой на фоне — это периоды, которые ещё не завершились, поэтому значения в них могут вырасти. ## Длина когорты и временной диапазон \{#cohort-length-and-time-frame\} Два параметра времени определяют, что отображается в таблице: - **Time frame** — диапазон дат. Устанавливается в календаре над таблицей. - **Cohort length** — размер каждой строки: день, неделя, месяц, квартал или год. При месячной длине каждая строка охватывает один месяц установок. Эти параметры работают независимо друг от друга. Например: временной диапазон в 6 месяцев и месячная длина когорты дают таблицу из 6 строк. Временной диапазон в 1 год и недельная длина когорты дают 52 строки. ## Фильтры, метрики, когортные сегменты и экспорт в CSV \{#filters-metrics-cohort-segments-and-export-in-csv\} :::link Основная статья: [Управление аналитикой](controls-filters-grouping-compare-proceeds) ::: По умолчанию Adapty строит когорты на основе данных по всем покупкам. Вы можете фильтровать по длительности продукта, конкретным продуктам, стране, стору, пейволу, сегменту и данным атрибуции. В правой части панели управления находится кнопка экспорта данных когорт в CSV. Файл можно открыть в Excel или Google Sheets либо импортировать в собственную аналитическую систему. Есть 6 метрик, которые можно отображать в когортах: Subscriptions, Payers, Revenue, ARPU, ARPPU и ARPAS. Их можно показывать как абсолютные значения или как относительное изменение с начала когорты. ## Подписки, платящие пользователи, общая выручка, ARPU, ARPPU и ARPAS \{#subscriptions-payers-total-revenue-arpu-arppu-and-arpas\} **Подписки** — это общее количество активных подписок, покупок с пожизненным доступом и разовых покупок, совершённых когортой за выбранный период. Отслеживание этой метрики помогает понять поведение пользователей и эффективность ваших предложений, а значит — точнее выстраивать продуктовую стратегию, корректировать маркетинг и оптимизировать источники дохода. **Payers** — это общее количество пользователей, совершивших покупку в рамках когорты. Эта метрика помогает понять, сколько уникальных пользователей вносят вклад в вашу выручку. Для приложений со значительным количеством разовых покупок она наглядно показывает реальный охват ваших продуктов: покупки делает широкая база пользователей или выручку формирует небольшая группа постоянных покупателей. Понимание числа плательщиков помогает оценивать вовлечённость клиентов, планировать таргетированный маркетинг и оптимизировать стратегии монетизации. **Total revenue** накапливается для когорты в рамках выбранного периода (25 ноя 2022 — 24 мая 2023). Это помогает понять, сколько денег принесли пользователи из конкретной когорты, и рассчитать ROAS. Например, если расходы на рекламу за сентябрь 2022 года составили $10 000, а суммарная выручка когорты за сентябрь 2022 года — $30 000, то ROAS = 3:1. **ARPU** — это средняя выручка на одного пользователя. Рассчитывается как общая выручка / количество уникальных пользователей. Например: $60 000 выручки / 5000 пользователей = $12 ARPU. Удобно сравнивать это значение со стоимостью установки (CPI), чтобы оценить эффективность маркетинговых кампаний. **ARPPU** — это средняя выручка на одного платящего пользователя. Рассчитывается как общая выручка / количество уникальных платящих пользователей. Например: $60 000 выручки / 1000 платящих пользователей = $60 ARPPU. Помогает понять, сколько в среднем приносит один платящий клиент. **ARPAS** — это средний доход на одного активного подписчика. Рассчитывается как общий доход / количество активных подписчиков. Под подписчиками понимаются те, кто активировал пробный период или подписку. $60 000 дохода / 1500 подписчиков = $40 ARPAS. ## Комиссии и налоги \{#commission-fees-and-taxes\} Важный аспект расчёта выручки в когортах — учёт комиссий стора и налогов (которые зависят от страны аккаунта пользователя в сторе). Adapty поддерживает расчёт комиссий и налогов как для App Store, так и для Play Store в когортной аналитике. Подробнее о том, как Adapty рассчитывает налоги и комиссии в аналитике, читайте в нашей [документации](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue). ## Revenue и Proceeds \{#revenue-vs-proceeds\} Revenue и Proceeds — оба показателя измеряют деньги. Revenue можно считать валовой выручкой, а Proceeds — чистой. Revenue не учитывает комиссию App Store / Play Store, а Proceeds учитывает. Поэтому Proceeds всегда меньше Revenue. Фактический размер комиссии зависит от нескольких факторов: участия в программах вроде [Small Business Program](app-store-small-business-program) (15%), сниженных ставок для долгосрочных подписок (15% после года непрерывных продлений), страновых ставок и стандартной ставки (до 30%). Adapty автоматически определяет применимую ставку комиссии для каждой транзакции ваших клиентов и рассчитывает выручку на её основе. Подробнее о том, как определяются ставки комиссии, см. в документации [Комиссия стора и налоги](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue). ## Обработка возвратов \{#refund-handling\} Два правила действуют для всех метрик когорт: - Возврат датируется днём его оформления, а не датой исходной покупки. Возврат влияет на когорту только в том случае, если дата возврата попадает в выбранный временной диапазон. - Возврат никогда не удаляет пользователя из его когорты и не изменяет количество установок. Принадлежность к когорте фиксируется в момент установки приложения. Помимо этих правил, влияние возврата зависит от метрики и [режима просмотра](#cohorts-by-renewals-or-by-days). Изучите столбец для того режима, который вы используете. | Метрика | По renewals | По дням | | --- | --- | --- | | Installs (размер когорты) | Не затрагивается. Пользователь остаётся в своей когорте. | То же, что «По renewals». | | Подписки | Возвращённые подписки по-прежнему учитываются. | Возврат исключает подписку из подсчёта. | | Payers | Возвращённые платящие пользователи по-прежнему учитываются. | Возврат исключает пользователя из подсчёта, даже если у него были другие успешные платежи. | | Revenue | Возвращённая сумма вычитается из столбца периода renewals, в котором изначально был учтён платёж. | Возвращённая сумма вычитается начиная с дня возврата. | | ARPU | Revenue / installs. Возврат уменьшает revenue; количество installs никогда не меняется. | То же, что «По renewals». | | ARPPU | Revenue / платящие пользователи. Возврат может одновременно уменьшить revenue и количество платящих пользователей, поэтому ARPPU может изменяться резче, чем revenue в отдельности. | То же, что «По renewals». | | ARPAS | Revenue / активные подписчики. Возврат уменьшает revenue; количество подписчиков не меняется. | То же, что «По renewals». | | Retention | Не затрагивается. Учитываются события триала и покупки, а не возвраты. | То же, что «По renewals». | Возврат средств вычитается из выручки независимо от выбранного режима учёта выручки. ### Коэффициент конверсии и ARPPU \{#conversion-rate-and-arppu\} Возвраты не влияют на коэффициент конверсии, основанный на установках, поскольку их количество никогда не меняет. Коэффициент конверсии, основанный на платящих пользователях, — другое дело. Возвраты снижают его в представлении **by days**, но не в представлении **by renewals**. В представлении **by renewals** столбцы Revenue и Payers показывают данные за один период. Столбец ARPPU — нет. Каждая ячейка ARPPU суммирует всё с первого периода когорты до периода столбца включительно. То есть всегда охватывает несколько периодов сразу и не учитывает пользователей с возвратами. Поэтому поделить выручку за один период на количество платящих пользователей и получить то же значение ARPPU не выйдет. **Пример.** Пользователь устанавливает приложение в феврале, оформляет подписку, а в апреле получает полный возврат. Когорта февраля в представлении **by days**: - Период февраль–март, до того как возврат попадает в окно: пользователь считается 1 платящим пользователем, а его выручка учитывается в полном объёме. - Период февраль–июнь, после того как возврат попадает в окно: пользователь считается 0 платящих пользователей, а его выручка обнуляется. В обоих периодах количество установок за февраль и Retention остаются неизменными. [Как метрики обрабатывают возвраты](refund-events#how-metrics-handle-refunds) — сравнение тех же правил для MRR, графиков выручки и экспорта данных. ## Прогноз: выручка и LTV \{#prediction-revenue-and-ltv\} **Прогнозируемая выручка** — это расчётная совокупная выручка, которую когорта платящих подписчиков ожидаемо принесёт за выбранный период с момента создания когорты. Она рассчитывается путём умножения прогнозируемого LTV когорты на прогнозируемое количество платящих пользователей в ней. Например, если прогнозируемый LTV составляет $50, а в когорте 100 платящих пользователей, прогнозируемая выручка составит $5 000. **Predicted LTV** — это прогнозируемая пожизненная ценность одного платящего подписчика: средняя выручка, которую каждый платящий подписчик, как ожидается, принесёт за выбранный период после создания когорты. Прогнозы строятся на основе исторических паттернов удержания когорт: когда собственных данных приложения достаточно, используются они; в противном случае — средние показатели по другим приложениям. Подробнее о модели прогнозирования Adapty читайте в нашей [документации по прогнозам](predicted-ltv-and-revenue). Когорты Adapty дают подробное представление о поведении пользователей и финансовых показателях вашего приложения. Анализируя когорты по продлениям или дням, вы можете определить, когда они становятся прибыльными, отслеживать выручку, рассчитывать средний доход на пользователя и понимать, сколько времени уходит на окупаемость рекламных расходов. Гибкие фильтры, метрики и возможности экспорта помогают принимать решения на основе данных и оптимизировать стратегии привлечения пользователей и монетизации. --- # File: analytics-funnels --- --- title: "Воронка продаж" description: "Разберитесь в воронках аналитики Adapty для отслеживания поведения пользователей и улучшения конверсии." --- Воронки в Adapty помогают ответить на такие вопросы: 1. Какой процент установок конвертируется в платящих клиентов? 2. Какая часть тех, кто попробовал продукт, стала лояльными пользователями? 3. На каких шагах наблюдается наибольший отток и требуется дополнительное внимание? 4. Почему клиенты перестают платить? С помощью воронки можно получить дополнительные сведения о поведении пользователей, настроив фильтры и группировки. Воронки работают с данными, которые Adapty собирает через SDK и уведомления от стора, и не требуют дополнительной настройки с вашей стороны. :::note Воронки отражают данные об установках в соответствии с настройкой определения установки в [App Settings](general#4-installs-definition-for-analytics). ::: <img src="/assets/shared/img/funnels-tab.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Воронка шаг за шагом \{#funnel-chart-step-by-step\} Разберём элементы воронки, чтобы понять, как читать пользовательский путь на графике. <img src="/assets/shared/img/ed5bf5d-CleanShot_2022-06-23_at_09.36.49.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Установки \{#installs\} 1-я колонка (1) — количество установок. Отображается как абсолютное значение (2) общего числа установок (не уникальных пользователей), а также как 100% — максимальное исходное число для расчёта относительных конверсий на последующих шагах. Если пользователь удаляет приложение и устанавливает его заново, это считается двумя отдельными установками. Серая область рядом отражает параметры перехода между шагами. Процент конверсии на следующий шаг (Отображённый пейвол) показан на флажке (3). Ниже (4) указаны процент отсева и абсолютное значение оттока. <img src="/assets/shared/img/00416f9-CleanShot_2022-06-23_at_14.02.06.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Пейвол показан Во 2-м столбце (5) отображается количество пользователей приложения, которые увидели пейвол хотя бы один раз (6). Учитываются только те установки, которые произошли в выбранный период. Если пользователь увидел пейвол в выбранный период, но дата его установки выходит за пределы диапазона, этот просмотр не засчитывается. Здесь также указывается процент таких просмотров от 1-го шага (7). Можно заметить, что этот процент совпадает с серым флагом (3) 1-го шага. Такое совпадение характерно только для этих первых шагов. Мы собираем данные для этого шага со всех ваших пейволов, которые используют метод `logShowFlow()` (iOS SDK v4+) / `logShowPaywall()`. Поэтому обязательно отправляйте каждый просмотр пейвола в Adapty с помощью этого метода, как описано в [документации](present-remote-config-paywalls#track-paywall-view-events). Серая область рядом со 2-м столбцом обозначает переход. Процент конверсии на следующий шаг (Trial) отображается на флажке (8). Процент оттока и абсолютное значение ушедших пользователей после пейвола показаны ниже (9). <img src="/assets/shared/img/fb11650-CleanShot_2022-06-23_at_15.54.32.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Пробные периоды \{#trials\} 3-й столбец (10) показывает количество пробных периодов, активированных на пейволах пользователями, установившими приложение в выбранный период (11). Если в фильтре указан продукт без пробного периода, это значение равно нулю и столбец остаётся пустым. Также обратите внимание на процент пробных периодов от первого шага, отражающий конверсию из установок в триалы (12). Заметьте, что это значение не совпадает с серым флажком (8) конверсии предыдущего шага. Это объясняется тем, что мы сравниваем текущее значение с первым шагом вверху графика, а с предыдущим шагом — на серых флажках. Серая область рядом с третьим столбцом показывает процент конверсии на следующий шаг (Платный), который отображается на флажке (13). Ниже указаны процент оттока и абсолютное количество пользователей, покинувших воронку в течение пробного периода (14). <img src="/assets/shared/img/7b88909-CleanShot_2022-06-23_at_15.54.32_-_2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Подписки и продления \{#subscriptions-and-renewals\} В 4-м столбце отображается количество активированных подписок (15). Для продуктов без пробного периода это число включает прямые подписки с пейвола. Для продуктов с пробным периодом — количество триалов, конвертированных в платные подписки. Если у вас есть продукты обоих типов — с триалом и без — отображается их суммарное значение. Процент в верхней части показывает конверсию с установок (16). Процент на сером флаге показывает конверсию на следующий шаг (продление на 2-й период) (17). Отток до продления на 2-й период — процент и абсолютное значение — отображаются под конверсией (18). <img src="/assets/shared/img/d13bf9b-CleanShot_2022-06-23_at_15.54.32-3.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Этот шаг начинает последовательность шагов с аналогичной структурой. После 2-го продления идёт 3-е, затем 4-е и так далее. Если в истории вашего приложения достаточно данных, с помощью горизонтальной прокрутки можно увидеть десятки периодов. Логика этих шагов одинакова: - процент от установок — вверху, - процент от предыдущего шага — внизу, - абсолютное количество продлений — вверху, - абсолютное количество оттока — внизу, - всплывающее окно с причинами оттока при наведении курсора. ### Причины оттока \{#churn-reasons\} Adapty детализирует статистику *оттока* начиная со стадии Trial. Каждый пользователь, который прошёл одну стадию, но не перешёл к следующей, засчитывается как случай оттока. * Если конкретное событие (например, истечение триала или проблема с оплатой) стало причиной отсутствия конверсии, Adapty отображает эту причину. * Статус **unknown** — временный. Он означает, что пользователь ещё не столкнулся с событием, которое позволило бы ему перейти к следующей стадии. На этапе Trial это обычно означает, что пробный период ещё не завершился. Чаще всего это происходит при просмотре воронок за короткие периоды или за один день, поскольку пробные периоды требуют времени для завершения. Adapty обновит информацию, как только пользователь конвертируется или отменит пробный период. <img src="/assets/shared/img/churn-reasons.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Таблица, фильтры и экспорт в CSV \{#table-view-filters-and-csv-export\} График воронки дополнен таблицей с данными — удобно работать с числами. <img src="/assets/shared/img/4787aff-CleanShot_2022-06-23_at_21.01.44.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Эта таблица повторяет подход воронки с некоторыми изменениями. В ней есть столбцы с данными по всем шагам, кроме шага первой платной подписки. Вместо него — два отдельных: Install -> Paid и Trial -> Paid. Они отображают ключевой момент конверсии, когда бесплатный пользователь становится платящим. Может показаться, что деление по типам продуктов такое: столбец Install → Paid содержит только продукты без триала, а Trial → Paid — только продукты с триалом. Но на самом деле всё немного иначе. Мы также учитываем пользователей, у которых триал истёк и которые затем купили продукт с триалом так, как будто триала у него нет вовсе. <img src="/assets/shared/img/a9bcbc7-CleanShot_2022-06-23_at_21.29.12.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Погружаясь глубже в цифры, вы найдёте мощные инструменты фильтрации для проверки новых гипотез. Задавайте условия по разным параметрам. Собирайте настоящие инсайты на основе данных. Варьируйте: 1. Тип продукта — экономика, длительность и т. д. 2. Временной диапазон. 3. Сегментация по стране. 4. Атрибуция трафика. 5. Стор. Выберите абсолютные значения (#), относительные (%) или оба варианта, чтобы отображать только нужные данные. <img src="/assets/shared/img/1475e42-CleanShot_2022-06-23_at_21.50.33_-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Наконец, справа на панели управления есть кнопка для экспорта данных воронки в CSV. Затем вы можете открыть файл в Excel, Google Sheets или импортировать его в собственную аналитическую систему. :::important Уведомите Adapty, если ваше приложение участвует в программе сниженной комиссии. Для корректных расчётов укажите статус участия в [программе Small Business Program](app-store-small-business-program) и [программе Reduced Service Fee](google-reduced-service-fee) в [настройках приложения](general). ::: <img src="/assets/shared/img/ff23846-CleanShot_2022-06-23_at_22.15.49.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: analytics-retention --- --- title: "Анализ удержания" description: "Изучите аналитику удержания пользователей и оптимизируйте стратегию подписок." --- Графики удержания помогают ответить на следующие вопросы: 1. Как приложение удерживает клиентов от периода к периоду? 2. Какие продукты привлекательнее и удерживают лучше? 3. Какие группы пользователей более лояльны? 4. Какой уровень удержания можно использовать как ориентир для роста? 5. И, конечно, как сэкономить, инвестируя в привлечённую аудиторию, а не в захват новой. Настраивая фильтры и группы, вы найдёте ценные сведения о поведении пользователей. Данные для анализа удержания мы собираем через SDK и уведомления стора — никакой дополнительной настройки с вашей стороны не требуется. ### Как мы рассчитываем удержание? \{#how-do-we-calculate-retention\} Наблюдая график удержания, вы видите, как количество пользователей зависит от шага, который они совершают: триал (если установлен флажок «показывать триалы»), 1-й платёж, 2-й платёж и так далее. Уточним, какие пользователи учитываются при выборе диапазона дат для графика удержания. Например, вы выбрали последние 3 месяца в календаре, а флажок «показывать триалы» снят. Это означает, что учитываются только те, у кого первая подписка была оформлена в течение последних 3 месяцев. Если флажок «показывать триалы» установлен и в календаре выбраны последние 3 месяца, учитываются все, у кого триал начался в течение этих 3 месяцев. Для таких подписчиков абсолютное удержание на N-м шаге отображается как количество тех, кто совершил N-й платёж. Относительное значение удержания на N-м шаге рассчитывается как отношение абсолютного числа N-х платежей к общему числу подписок (или триалов) за выбранный период. :::info Retention изменяется ретроспективно Независимо от того, когда вы смотрите на график, базовое значение (100%) остаётся неизменным для выбранного периода. При этом retention к следующему периоду может расти со временем. Например, для ежемесячной подписки: если с 1 по 31 декабря было совершено 20 первых покупок, ожидается, что retention ко второму периоду будет расти на протяжении января (а возможно и дольше) — пока пользователи будут переходить в следующий период подписки вовремя или с задержкой по тем или иным причинам (например, из-за льготного периода). ::: ### Обработка возвратов \{#refund-handling\} Возвраты **не** исключаются из удержания. Пользователь, оформивший возврат, остаётся на кривой удержания, из-за чего Retention может выглядеть выше, чем [Активные подписки](active-subscriptions) или [Выручка](revenue) для той же когорты. Подробное сравнение по метрикам см. в разделе [Как метрики обрабатывают возвраты](refund-events#how-metrics-handle-refunds). ### Возможности удержания \{#retention-opportunities\} Разберёмся, как выжать максимум из функции удержания в Adapty. Чтобы видеть не просто цифры, а реальную бизнес-ценность от аналитических данных, стоит сначала подумать о целях. Углубившись в возможности графиков, важно понять, какое влияние эти данные могут оказать. Итак, рассмотрим вместе — ЗАЧЕМ и КАК. 1 - работа с аудиторией. Прежде всего, удержание — это про целевую аудиторию, её предпочтения и то, насколько ваш продукт оправдывает её ожидания на протяжении всего жизненного цикла. Если вы хотите измерить ключевые отношения с теми, кто приносит деньги вашему бизнесу, — удержание именно для этого. Такой подход выгоден, потому что продавать существующему клиенту обычно дешевле, чем новому. И дешевле по двум причинам: меньше усилий на продажу и более высокий средний чек. Поэтому, когда удержание падает, имеет смысл инвестировать в лояльность подписчиков. 2 — работа с продуктом. Вторая причина «ЗАЧЕМ» — графики удержания показывают реальное время жизни вашего продукта и позволяют строить долгосрочные прогнозы. Если хотите улучшить показатели, скорректируйте работу, которая обеспечивает продукт, чтобы изменить его время жизни, а затем снова постройте прогноз — и так постепенно приближайтесь к бизнес-целям. Такие обновления могут быть частью стратегического видения, которое работает в связке с регулярным прогнозированием. И да, этот процесс никогда не заканчивается — мы все бежим всё быстрее, чтобы оставаться на месте в постоянно меняющейся среде. 3 — работа с рынком. Опережать основных конкурентов — хорошо, но иногда выход за рамки привычной гонки приносит больше пользы. Анализируя поведение пользователей в разных странах и сторах, можно заметить местные особенности, которые открывают неожиданные инсайты и новые возможности для бизнеса. Культурный и рыночный контекст можно изучать через призму удержания, а затем использовать для сегментации и дальнейшего развития. Например, в отдельных регионах можно обнаружить «голубой океан» и расти там значительно быстрее. Конечно, использование данных об удержании не ограничивается этой базовой интерпретацией, но это хорошая отправная точка, если вы хотите быстро получить реальную пользу. ### Кривые, табличное представление, фильтры и экспорт в CSV \{#curves-table-view-filters-and-csv-export\} Теперь, когда у нас есть общее понимание целей удержания и базовых способов интерпретации, давайте разберём инструменты, которые делают работу с этим удобной. Основа функции удержания в Adapty — это график. Он показывает, как уровень удержания зависит от шагов в жизненном цикле пользователя. Шаги отображаются на горизонтальной оси: Trial, Paid (1-я подписка), P2 (2-я подписка), P3, P4 и т. д. Обратите внимание: ось начинается с шага Trial только тогда, когда установлен флажок «Show trials». С точки зрения расчёта данных этот флажок работает следующим образом. Когда «Show trials» включён и ось начинается с шага Trial, вы видите только сценарии, содержащие триалы — транзакции напрямую с установок не отображаются, а шаг Paid включает только транзакции, пришедшие из триалов. Когда «Show trials» выключен и ось начинается с шага Paid, этот первый шаг содержит все первые транзакции — как из триалов, так и напрямую с установок. При наведении курсора на график появляется всплывающее окно с сводкой данных. А при наведении на столбец в таблице ниже отображается аналогичное всплывающее окно с соответствующими данными на графике. Таблица содержит те же группировки и фильтры, которые выбраны для графика. Используйте комбинации фильтров и группировок для углублённого анализа и получения реальных инсайтов на основе данных. Варьируйте: 1. Тип продукта. 2. Длительность. 3. Временной диапазон. 4. Страна. 5. Атрибуция трафика. 6. Стор. Используйте переключатель #Абсолютные / %Относительные значения для отображения нужных данных. Наконец, справа на панели управления есть кнопка экспорта данных воронки в CSV. Вы можете открыть файл в Excel или Google Sheets, либо импортировать его в собственную аналитическую систему для дальнейшего анализа и прогнозирования в удобной среде. :::warning Обязательно укажите, что ваше приложение входит в Small Business Program, в [основных настройках Adapty](https://app.adapty.io/settings/general). ::: --- # File: analytics-conversion --- --- title: "Анализ конверсий" description: "Измеряйте коэффициенты конверсии подписок с помощью аналитических инструментов Adapty." --- Воронки дают общее представление, удержание отражает лояльность пользователей, а анализ конверсий позволяет оценить эффективность на каждом ключевом шаге пути пользователя — в динамике. Конверсии помогают ответить на следующие вопросы: 1. Как конверсия приложения меняется со временем? Есть ли сезонные тренды? 2. Как конверсия изменяется в моменты маркетинговых активностей или других новых обстоятельств? 3. Как пользователи из разных регионов реагируют на обновления вашего приложения? 4. Какие типы продуктов лучше конвертируют со временем? Конверсия рассчитывается на основе данных, которые мы собираем через Adapty SDK и уведомления стора, и не требует никакой дополнительной настройки с вашей стороны. ## Основные элементы управления и графики \{#main-controls-and-charts\} Выручка — привычный способ измерить успех, но это лишь часть общей картины. Не менее важно понимать, как ведёт себя ваш бизнес с течением времени — в разрезе разных пользовательских сценариев и этапов жизненного цикла. Именно здесь на помощь приходит аналитика конверсий. Фильтры и группировки помогут глубже изучить поведение пользователей. Чтобы выявлять и отслеживать тенденции, следите за динамикой конверсий в разбивке по дням, месяцам или годам. С левой стороны графика расположен блок управления шагами конверсии. Он позволяет выбрать конкретные конверсии для отслеживания — например, Install → Trial, Trial → Paid или Paid → Renewal. Каждая метрика конверсии рассчитывается по следующей логике: - Пусть **X** — количество пользователей, которые вошли в начальное состояние в выбранную дату (например, установки). - Пусть **Y** — количество из них, кто в итоге достиг целевого состояния (например, начал пробный период). - Конверсия рассчитывается как: **Конверсия = (Y / X) × 100%** :::note Дата на графике соответствует моменту, когда пользователи перешли в начальное состояние (X) — то есть стали потенциальными покупателями. ::: Ниже приведено описание каждой конверсии с примерами. ### Install -> Paid Эта метрика показывает, какой процент пользователей, установивших приложение в определённый день, в итоге совершили первую покупку подписки. <details> <summary>Как это работает</summary> **Обозначения**: - **X** = количество установок за выбранный день (одинаково для всех продуктов, так как в момент установки продукт ещё не выбран). - **Y** = количество тех пользователей, которые в итоге купили первую подписку (пробную или обычную). **Формула**: Конверсия = (Y / X) × 100% **Пример**: - 1 января было 100 установок. - К 8 января 20 из этих пользователей оформили подписку. - 8 января конверсия для 1 января = (20 / 100) × 100% = 20% - К 1 февраля ещё 30 пользователей из группы установки 1 января оформили подписку. - 1 февраля конверсия для 1 января = ((20 + 30) / 100) × 100% = 50% Это означает, что 50% пользователей, установивших приложение 1 января, в итоге перешли на платную подписку — на текущий момент. </details> ### Установка → Пробный период \{#install---trial\} Эта метрика показывает процент пользователей, которые установили приложение в определённую дату и в итоге начали пробный период. <details> <summary>Как это работает</summary> **Пусть**: - **X** = количество установок за выбранную дату (одинаково для всех продуктов, так как продукт не выбирается в момент установки). - **Y** = количество тех пользователей, которые в итоге активировали пробный период — в любое время. **Формула**: Конверсия = (Y / X) × 100% **Пример**: - 1 января было 100 установок. - К 8 января 20 из этих пользователей запустили пробный период. - 8 января конверсия для 1 января = (20 / 100) × 100% = 20% - К 1 февраля ещё 30 пользователей из группы установки 1 января начали пробный период. - 1 февраля конверсия для 1 января = ((20 + 30) / 100) × 100% = 50% Это означает, что 50% пользователей, установивших приложение 1 января, в итоге начали пробный период — на текущий момент. </details> ### Пейвол → Пробный период \{#paywall-view---trial\} Эта метрика показывает, сколько пользователей запустили пробный период после просмотра пейвола. <details> <summary>Как это работает</summary> **Пусть**: - **X** = количество пользователей, которые увидели пейвол в выбранную дату. - **Y** = количество пользователей, которые запустили пробный период в любой момент после этого. **Формула**: Конверсия = (Y / X) × 100% **Пример**: - 1 января пейвол просмотрели 100 пользователей. - К 8 января 20 из них запустили пробный период. - 8 января конверсия за 1 января = (20 / 100) × 100% = 20% </details> - К 1 февраля ещё 30 пользователей начали пробный период. - 1 февраля конверсия для 1 января = ((20 + 30) / 100) × 100% = 50% Это означает, что 50% пользователей, увидевших пейвол 1 января, начали пробный период к текущему моменту. ### Пейвол просмотрен → Оплачено \{#paywall-view---paid\} Эта метрика показывает, сколько пользователей совершили покупку после того, как увидели пейвол. <details> <summary>Как это работает</summary> **Пусть**: - **X** = количество пользователей, которые увидели пейвол в выбранную дату. - **Y** = количество пользователей, которые совершили покупку в любой момент после этого. **Формула**: Конверсия = (Y / X) × 100% **Пример**: - 1 января пейвол просмотрели 100 пользователей. - К 8 января 20 из них совершили покупку. - 8 января конверсия для 1 января = (20 / 100) × 100% = 20% </details> - К 1 февраля ещё 30 пользователей совершили покупку. - 1 февраля конверсия для 1 января = ((20 + 30) / 100) × 100% = 50% Это означает, что 50% пользователей, которые увидели пейвол 1 января, совершили покупку к текущему моменту. ### Trial -> Paid \{#trial---paid\} Эта метрика показывает процент пользователей, которые начали пробный период в конкретную дату и впоследствии оформили первую подписку. <details> <summary>Как это работает</summary> **Обозначения**: - **X** = количество пробных периодов, начатых в выбранную дату. - **Y** = количество тех пользователей, которые в итоге оформили подписку после окончания пробного периода. **Формула**: Конверсия = (Y / X) × 100% **Пример**: - 1 января было начато 100 пробных периодов. - К 8 января 20 из этих пользователей оформили подписку. - 8 января конверсия за 1 января = (20 / 100) × 100% = 20% </details> - К 1 февраля ещё 30 пользователей из группы, начавшей триал 1 января, оформили подписку. - 1 февраля конверсия для 1 января = ((20 + 30) / 100) × 100% = 50% Это означает, что 50% пользователей, начавших триал 1 января, в итоге перешли на платную подписку — на текущий момент. ### Платная подписка → 2-й период \{#paid---2nd-period\} Эта метрика показывает долю пользователей, продливших подписку после первого платежа. <details> <summary>Как это работает</summary> **Пусть**: - **X** = количество первичных подписок за выбранную дату. - **Y** = количество пользователей, которые продлили подписку на второй период в любое более позднее время (обычно после одного цикла подписки; включает продления в льготный период). - **Формула**: конверсия = (Y / X) × 100% **Пример**: - 1 января было оформлено 100 первичных подписок. - К 8 января 20 из них продлили подписку. - 8 января конверсия для 1 января = (20 / 100) × 100% = 20% - К 1 февраля ещё 30 пользователей из этой группы продлили подписку. - 1 февраля конверсия для 1 января = ((20 + 30) / 100) × 100% = 50% Это означает, что 50% пользователей, совершивших первый платёж по подписке 1 января, продлили её на второй период — на текущий момент. </details> ### 2nd Period -> 3rd Period \{#2nd-period---3rd-period\} Эта метрика показывает, сколько пользователей продлили подписку снова после второго расчётного периода. <details> <summary>Как это работает</summary> **Обозначения**: - **X** = количество подписок второго периода на выбранную дату. - **Y** = количество пользователей, продливших подписку на третий период в любое время после этого (как правило, после ещё одного расчётного цикла; учитываются продления в льготный период). **Формула**: Конверсия = (Y / X) × 100% **Пример**: - 1 января было 100 подписок второго периода. - К 8 января 20 из этих пользователей продлили подписку. - 8 января конверсия для 1 января = (20 / 100) × 100% = 20% - К 1 февраля ещё 30 пользователей продлили подписку. - 1 февраля конверсия для 1 января = ((20 + 30) / 100) × 100% = 50% Это означает, что 50% пользователей, вошедших во второй период подписки 1 января, продлили её на третий — на текущий момент. </details> ### 3-й период → 4-й период \{#3rd-period---4th-period\} Эта метрика показывает процент пользователей, продливших подписку после третьего периода. <details> <summary>Как это работает</summary> **Пусть**: - **X** = количество подписок третьего периода на выбранную дату. - **Y** = количество пользователей, продливших подписку на четвёртый период в любое время после этого (как правило, по истечении одного расчётного цикла; включает продления в льготный период). **Формула**: Конверсия = (Y / X) × 100% **Пример**: - 1 января было 100 подписок третьего периода. - К 8 января 20 пользователей продлили подписку. - 8 января: конверсия для 1 января = (20 / 100) × 100% = 20% - К 1 февраля ещё 30 пользователей продлили подписку. - 1 февраля: конверсия для 1 января = ((20 + 30) / 100) × 100% = 50% Это означает, что 50% пользователей, вошедших в третий период подписки 1 января, продлили её до четвёртого — на текущий момент. </details> ### 4-й период → 5-й период \{#4th-period---5th-period\} Эта метрика показывает процент пользователей, которые продлили подписку после четвёртого периода. <details> <summary>Как это работает</summary> **Пусть**: - **X** = количество подписок четвёртого периода на выбранную дату. - **Y** = количество пользователей, продливших подписку на пятый период в любой последующий момент (обычно после одного расчётного цикла; учитываются продления в льготный период). **Формула**: Конверсия = (Y / X) × 100% **Пример**: - 1 января было 100 подписок четвёртого периода. - К 8 января 20 пользователей продлили подписку. - 8 января конверсия для 1 января = (20 / 100) × 100% = 20% - К 1 февраля ещё 30 пользователей продлили подписку. - 1 февраля конверсия для 1 января = ((20 + 30) / 100) × 100% = 50% Это означает, что 50% пользователей, вошедших в четвёртый период подписки 1 января, продлили её до пятого — на текущий момент. </details> ### 6 Месяцев + \{#6-months-\} Эта метрика показывает процент пользователей, остававшихся подписанными более 6 месяцев с момента первой подписки. <details> <summary>Как это работает</summary> **Пусть**: - **X** = количество первых подписок за выбранную дату. - **Y** = количество тех пользователей, которые продлили подписку хотя бы один раз спустя 6 месяцев с даты оформления первоначальной подписки. **Формула**: Конверсия = (Y / X) × 100% **Пример**: - 1 января было оформлено 100 первых подписок. - К первой неделе июля 20 из них продлили подписку (например, на 25-й неделе еженедельной подписки). - 8 июля конверсия для 1 января = (20 / 100) × 100% = 20% - К 1 августа ещё 30 пользователей продлили подписку спустя 6 месяцев. - 1 августа конверсия для 1 января = ((20 + 30) / 100) × 100% = 50% Это означает, что 50% пользователей, оформивших подписку 1 января, продолжали оставаться подписчиками более 6 месяцев по состоянию на 1 августа. </details> ### 1 год и более \{#1-year-\} Эта метрика показывает процент пользователей, которые оставались подписаны более 12 месяцев с момента первой подписки. <details> <summary>Как это работает</summary> **Обозначения**: - **X** = количество первых подписок за выбранную дату. - **Y** = количество тех пользователей, которые продлили подписку хотя бы раз спустя 12 месяцев с даты первоначальной подписки. **Формула**: Конверсия = (Y / X) × 100% **Пример**: - 1 января 2021 года было оформлено 100 первых подписок. - К первой неделе января 2022 года 20 из них продлились. - 8 января 2022 года конверсия = (20 / 100) × 100% = 20% - К 1 февраля 2022 года ещё 30 человек продлили подписку спустя 12 месяцев. - 1 февраля 2022 года конверсия = ((20 + 30) / 100) × 100% = 50% Это означает, что 50% пользователей, оформивших подписку 1 января 2021 года, оставались активными более одного года. </details> ### 2 года и более \{#2-years-\} Эта метрика показывает, какой процент пользователей оставался подписчиком более 24 месяцев с даты первого платежа. <details> <summary>Как это работает</summary> **Обозначения**: - X = количество первых подписок за выбранную дату. - Y = количество пользователей из этой группы, которые продлили подписку хотя бы один раз спустя 24 месяца с даты оформления. **Формула**: Конверсия = (Y / X) × 100% **Пример**: - 1 января 2020 года было оформлено 100 первых подписок. - К первой неделе января 2022 года 20 из них продлили подписку. - 8 января 2022 года конверсия = (20 / 100) × 100% = 20% - К 1 февраля 2022 года ещё 30 пользователей продлили подписку через 2 года. - 1 февраля 2022 года конверсия = ((20 + 30) / 100) × 100% = 50% Это означает, что 50% пользователей, оформивших подписку 1 января 2020 года, оставались активными спустя 2 года — по состоянию на 1 февраля 2022 года. </details> ### Льготный период → Оплата \{#grace-period---paid\} Эта метрика показывает процент пользователей, которые вошли в [льготный период подписки](grace-period) и решили проблему *до* его окончания. <details> <summary>Как это работает</summary> **Пусть**: - X = количество подписчиков, которые вошли в льготный период. - Y = количество из них, кто продлил подписку до окончания льготного периода. **Формула**: Конверсия = (Y / X) × 100% **Пример**: - 1 января 2025 года у 100 пользователей не удалось автоматически продлить подписку. Они вошли в 16-дневный льготный период, который должен был завершиться 17 января. - 50 человек обновили платёжные данные в период с 1 по 17 января, и их подписка была успешно продлена. - 17 января 2025 года конверсия = (50 / 100) × 100% = 50% </details> ### Billing issue -> Paid \{#billing-issue---paid\} Эта метрика показывает процент пользователей, у которых возникла [проблема с оплатой](/billing-issue) и которые возобновили оплату до окончания расчётного периода. <details> <summary>Как это работает</summary> **Допустим**: - X = количество подписчиков, у которых возникла проблема с оплатой. - Y = количество из них, кто продлил подписку в период между возникновением проблемы и окончанием расчётного периода. **Формула**: Конверсия = (Y / X) × 100% **Пример**: - 1 января 100 подписчиков столкнулись с проблемой списания — их подписка не смогла автоматически продлиться. - Примечание: если льготный период включён, статус проблемы со списанием начинается только после его окончания. В этом примере предполагается, что льготный период завершился 1 января. - К 8 января 10 из этих пользователей устранили проблему с оплатой и продлили подписку. - 8 января конверсия за 1 января = (10 / 100) × 100% = 10% - К 31 января (конец платёжного цикла) ещё 10 пользователей продлили подписку. - 31 января конверсия за 1 января = ((10 + 10) / 100) × 100% = 20% Это означает, что 20% пользователей, у которых возникла проблема с оплатой 1 января, решили её и продлили подписку до конца расчётного периода. </details> ## Группировка и временные диапазоны \{#grouping-and-time-ranges\} График — основной объект анализа при выборе конверсии. На нём отображается, как процент конверсии меняется со временем. Используйте датапикер для выбора быстрых вариантов временного периода. Обычно график содержит несколько кривых. По умолчанию в списке группировки выбрано до пяти из них; изменить выбор можно с помощью чекбоксов в области справа от графика. При первом открытии страницы по умолчанию выбрана группировка по длительности продукта. Ваши настройки сохраняются в кэше, и при следующем посещении вы увидите последнюю выбранную группировку. Доступны следующие группировки: - Продукт - Страна - Стор - Пейвол - Длительность - Маркетинговая атрибуция Если выбранный диапазон дат слишком мал, чтобы показать результаты, появится уведомление с предложением подходящего диапазона и возможностью применить его одним кликом. ## Таблица, фильтры и экспорт в CSV \{#table-view-filters-and-csv-export\} Сравнение кривых даёт наглядную картину, а таблица под графиком позволяет изучить данные подробнее. Таблица синхронизирована с графиком: при наведении на столбец над кривыми появляется соответствующее всплывающее окно. Группировка, описанная выше, влияет одновременно на графики и таблицу. Вы можете быстро отфильтровать данные по продукту или воспользоваться расширенными фильтрами: Product, Country, Store, Duration, Attribution. Мы понимаем, что важно работать с данными так, как вам удобно. Поэтому в правой части панели управления есть кнопка экспорта данных воронки в CSV. Вы можете открыть файл в Excel или Google Sheets, либо импортировать его в собственную аналитическую систему для дальнейшего анализа и прогнозирования в привычной среде. :::important Уведомите Adapty, если ваше приложение участвует в программе сниженной комиссии. Для корректных расчётов укажите статус участия в [программе Small Business Program](app-store-small-business-program) и [программе Reduced Service Fee](google-reduced-service-fee) в [настройках приложения](general). ::: --- # File: reports --- --- title: "Отчёты" description: "Создавайте подробные отчёты о подписках в Adapty для анализа доходов приложения и поведения пользователей." --- Получайте актуальную информацию прямо на почту: доходы, уровень оттока, активные подписчики, активные триалы и другие метрики — те же, что доступны в разделе [Графики](charts). Отчёты приходят ежедневно, еженедельно или ежемесячно и показывают динамику в сравнении с предыдущим периодом. Данные в отчётах формируются на основе настроек вашей страницы [**Overview**](https://app.adapty.io/overview) — метрик, их порядка, часового пояса и типа выручки. У вас есть возможность выбрать уровень детализации отчётов: сводный или по отдельному приложению. Сводный отчёт — это одно письмо с агрегированными данными по всем вашим приложениям (или выбранному их подмножеству). Отчёт по приложению, напротив, содержит данные только по одному выбранному приложению. Рекомендуем включать сводные отчёты для всех приложений, а отчёты по отдельным приложениям — для недавно выпущенных или приоритетных, а также тех, за которые вы лично отвечаете. Независимо от выбранного уровня детализации, email-отчёты приходят в ваш почтовый ящик в 9:00 по местному времени: ежедневные — каждый день, еженедельные — по понедельникам, ежемесячные — первого числа каждого месяца. Каждый отчёт содержит актуальные данные и их сравнение с предыдущим периодом (например, в ежедневном отчёте сравниваются данные за вчера и позавчера; в еженедельном — за прошлую неделю и позапрошлую и т. д.). Какие бы отчёты вы ни выбрали, вы будете получать самую актуальную и точную информацию прямо на свою электронную почту. ## Включение отчётов \{#enable-reports\} 1. Откройте раздел [**Account**](https://app.adapty.io/account) в верхнем меню Adapty. 2. В разделе **Email reports** выберите типы отчётов, которые хотите получать: ежедневные, еженедельные и/или ежемесячные. 2. Настройте каждый тип отчёта, выбрав нужные приложения. Для этого нажмите кнопку **Edit**. 3. В окне отчёта выберите приложения, которые хотите включить. 4. Нажмите кнопку **Save changes**, чтобы применить изменения. ## Установка часового пояса \{#set-your-time-zone\} 1. Откройте раздел [**Overview**](https://app.adapty.io/overview) в главном меню Adapty. 2. Нажмите кнопку **Edit** и выберите часовой пояс. <img src="/assets/shared/img/59ad3d8-time_zone.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Нажмите кнопку **Done**, чтобы сохранить изменения. --- # File: discrepancies-and-troubleshooting --- --- title: "Устранение расхождений в данных" description: "Найдите причину расхождений в данных" --- Пользователи Adapty могут замечать **расхождения** при сравнении похожих наборов данных из разных источников. В частности, это может происходить при сравнении: * графиков Adapty с отчётами сторов * графиков Adapty с графиками сторонних сервисов * разных графиков внутри Adapty ## Алгоритм устранения расхождений \{#troubleshooting-algorithm\} Большинство расхождений между Adapty и другими платформами — ожидаемы и нормальны. Они возникают потому, что **разные источники обрабатывают одни и те же данные по-разному**. В других случаях они указывают на **проблему в конфигурации Adapty**. Если вы подозреваете, что данные различаются от платформы к платформе, лучший способ разобраться — [экспортировать необработанные данные](export-analytics-api-requests) и **сравнить файлы**. * Даже в сторах случаются проблемы с обработкой и отображением данных. Для наиболее точного сравнения используйте **необработанные данные о транзакциях** из сторов. * При сравнении Adapty с другой аналитической платформой опирайтесь на отчёты о транзакциях из сторов как на источник истины. * Расхождения проще выявлять на небольших объёмах данных. Сравнивайте ограниченные выборки — возьмите конкретный продукт и один день. * Определите, в чём причина расхождения: в **ценах** или в **количестве событий**. Проблемы с ценами решаются через [обновление продукта](#product-pricing). Проблемы с событиями могут указывать на [неполадки на стороне сервера](#issues-with-server-notifications-and-rtdn). * Следите за входящими событиями в [ленте событий](event-feed) — там можно заметить неожиданное поведение. После того как вы определите, где данные расходятся, изучите следующие распространённые причины: ## Проблемы с серверными уведомлениями и RTDN \{#issues-with-server-notifications-and-rtdn\} Adapty не получает необходимые данные о событиях, если вы неправильно настроили подключение к стору. Это особенно важно для событий, которые происходят без прямого участия пользователя, — продление подписок, проблемы с оплатой и т. д. Настройте серверную интеграцию как можно скорее ([App Store](enable-app-store-server-notifications) | [Play Store](enable-real-time-developer-notifications-rtdn)) и [подождите](#data-delays), пока сторы установят соединение. Вы можете [вручную загрузить](importing-historical-data-to-adapty) недостающие данные из App Store Connect в Adapty. ## Отсутствующие данные \{#missing-data\} ### Пользователи с устаревшими версиями приложения \{#users-with-out-of-date-app-versions\} Если часть пользователей использует старую версию приложения без Adapty SDK, Adapty не получает их данные. По этой причине показатели Adapty и других источников будут расходиться. ### Проблемы с интеграциями \{#integration-issues\} Некоторые интеграции Adapty (например, Adjust или AppsFlyer) требуют дополнительного кода в приложении. Если вы настроили дашборд Adapty, но не обновили приложение, необходимые данные не появятся в Adapty. ### Отсутствие исторических данных \{#missing-historical-data\} Adapty не имеет доступа к историческим данным вашего приложения, если вы не [импортировали](importing-historical-data-to-adapty) их вручную. Если [временной диапазон](controls-filters-grouping-compare-proceeds#time-ranges) графика начинается раньше момента интеграции Adapty, а исторические данные не были импортированы, его значения будут отличаться от других источников. ## Задержки данных \{#data-delays\} Adapty стремится обеспечить анализ экономики вашего приложения практически в режиме реального времени. При этом действуют следующие ограничения и исключения: * При первой интеграции Adapty данные могут появиться не сразу. * При подключении интеграции со сторонней платформой синхронизация данных может занять некоторое время. * После того как Adapty получает данные из стора, их обработка и отображение на странице Analytics занимает ещё **15–30 минут**. * Обмен данными между Adapty и сторонними сервисами **не всегда происходит мгновенно** из-за множества переменных. * Расчёт некоторых продвинутых метрик (например, [прогнозов для когорт](predicted-ltv-and-revenue)) требует достаточного объёма данных. Adapty выполняет эти расчёты только после накопления необходимого количества данных. ## Время и календарь \{#time-and-calendar\} #### Даты и часовые пояса \{#dates-and-timezones\} Одна из самых распространённых причин расхождений в данных — разные настройки часового пояса. Adapty считает дни по часовому поясу `UTC`. Если другая платформа использует иной часовой пояс, результаты будут отличаться. По мере увеличения масштаба разница сглаживается. Вы можете [изменить настройку часового пояса](general#3-reporting-timezone) для каждого приложения. #### Фискальный календарь Apple \{#the-apple-fiscal-calendar\} Apple использует собственный [финансовый календарь](https://adapty.io/apple-fiscal-calendar/) для определения периодов продаж и дат выплат. Каждый «месяц» в этом календаре состоит из **4 или 5 недель** и **может включать дни из соседних календарных месяцев**. Выплаты, как правило, производятся через 30–45 дней после окончания периода продаж. Например, период продаж «январь 2026» начинается 28 декабря 2025 года — за 4 дня до начала календарного месяца. Ориентировочная дата выплаты за этот период — 5 марта. Не сравнивайте данные из отчётов о выплатах Apple с календарными месяцами. Вместо этого выберите [произвольный диапазон дат](controls-filters-grouping-compare-proceeds#set-the-date-range), соответствующий нужному периоду продаж. #### Даты транзакций \{#transaction-dates\} Некоторые сервисы (например, AppsFlyer) могут применять правила [когорт](analytics-cohorts) при отображении транзакций и относить их к дате установки приложения, а не к дате самой транзакции. ## Расчёт выручки \{#revenue-calculation\} ### Комиссии и налоги \{#fees-and-taxes\} В зависимости от [настройки](controls-filters-grouping-compare-proceeds#store-commission-and-taxes) графики Adapty могут отображать **валовую выручку**, **выручку после комиссии стора** или **выручку после комиссии стора и налогов**. Некоторые сторы и сторонние платформы могут не поддерживать отображение валовой выручки или автоматически вычитать налоги. Если вы видите расхождение между двумя графиками выручки, убедитесь, что сравнение корректно. ### Отмены и возвраты \{#cancellations-and-refunds\} Разные платформы по-разному отображают данные о возвратах. Adapty учитывает возвраты как отрицательную выручку. Если пользователь оформил подписку, а на следующий день запросил возврат, оба события отразятся в графиках Adapty — каждое в свой день. Другие платформы могут вычитать сумму возврата из исходной транзакции. ## Покупки в песочнице \{#sandbox-purchases\} [Лента событий](event-feed) отображает покупки, сделанные sandbox-аккаунтами. Графики аналитики — нет. Однако если импортированные исторические данные содержат покупки из песочницы, Adapty не сможет их распознать, и графики будут отражать исторические покупки из песочницы. ## Установки и загрузки \{#installs-and-downloads\} Сторы (особенно Apple App Store) могут отслеживать загрузки напрямую. Их статистика может включать случаи, когда приложение было установлено, но так и не запущено. Adapty может зарегистрировать установку только тогда, когда пользователь запускает приложение, независимо от вашего [определения установки](general#4-installs-definition-for-analytics). ## Страна и стор \{#country-and-store\} Для точной отчётности Adapty [может определять](controls-filters-grouping-compare-proceeds#filtering-and-grouping) страну пользователя по IP-адресу. Сторы всегда привязывают загрузки и покупки к конкретному магазину приложений. Если вам нужно чётко разграничить эти два подхода, вы можете [создать новый сегмент пользователей](segments) с атрибутом `Country by store account` и [фильтровать аналитику по сегменту](controls-filters-grouping-compare-proceeds#filtering-and-grouping). ## Ценообразование продукта \{#product-pricing\} Если из-за неверного ценообразования возникло расхождение в выручке, изменение цены не исправит его ретроактивно. Чтобы изменить цены в существующих транзакциях, необходимо принудительно перезаписать их путём импорта корректных данных. Когда пользователь восстанавливает старую покупку после изменения цены, Apple может неверно указать её стоимость. Для корректного отображения в Adapty необходимо импортировать исторические данные. ## Конфликты атрибуции \{#attribution-conflicts\} Adapty может использовать [только один источник атрибуции](attribution-integration#prevent-data-issues) для каждой транзакции. Впоследствии эти данные нельзя переопределить. Если в вашей конфигурации несколько провайдеров атрибуции расходятся друг с другом, одна и та же транзакция на двух разных платформах может отображаться с двумя разными источниками трафика. ## Различия в терминологии \{#differences-in-terminology\} На разных платформах одни и те же понятия могут называться по-разному. Метрики, связанные с [выручкой](#fees-and-taxes), на разных платформах имеют разные названия: | Adapty | App Store Connect | Google Play Console | |--------|-------------------|----------------------| | **Gross revenue** | Sales | Gross Revenue | | **Proceeds after store commission** | N/A | N/A | | **Proceeds after store commission and taxes** | Proceeds | Earnings | | **ARPPU** | Proceeds per paying user | ARPPU | Определения других метрик также могут различаться: - **Подписки**: - Adapty не учитывает новые триалы как подписки. [Новая подписка](reactivated-subscriptions) всегда начинается с финансовой транзакции. - Другие платформы, например Google Play Console, могут считать **каждый триал новой подпиской** — даже до совершения первого платежа. - **Удержание**: - Adapty измеряет удержание на основе количества продлений подписки. - App Store Connect считает пользователя удержанным, если он открыл приложение в указанный день. Пользователь без подписки засчитывается, а подписчик, не открывший приложение в этот день, — нет. - Метрика «Retained Installers» в Google Play Console измеряет удержание по количеству дней, в течение которых приложение остаётся установленным на устройстве пользователя. Пользователи, не открывающие приложение, учитываются в этой метрике. ## Метрика «Новые подписки» и событие `subscription_started` \{#new-subscriptions-metric-vs-the-subscription_started-event\} Метрика [Новые подписки](reactivated-subscriptions) и событие интеграции `subscription_started` ([события интеграции](events)) считают разные вещи, поэтому их итоговые значения не совпадают. Метрика учитывает как первые покупки без пробного периода, так и конвертации из пробного периода в платную подписку. Событие `subscription_started` срабатывает только при первой покупке без пробного периода — когда пробный период конвертируется в платный, Adapty отправляет `trial_converted`. В результате количество новых подписок всегда выше числа событий `subscription_started`, если в вашем приложении есть конвертации из пробного периода. --- # File: predicted-ltv-and-revenue --- --- title: "Прогнозы в когортах" description: "Используйте предиктивную аналитику Adapty для прогнозирования LTV и выручки." --- Прогнозы Adapty помогают ответить на следующие вопросы: 1. Каков прогнозируемый LTV ваших когорт пользователей? 2. Какие когорты, скорее всего, принесут наибольшую выручку в будущем? 3. Сколько можно вложить с учётом ожидаемой отдачи? С помощью Adapty Predictions вы можете принимать решения об увеличении доходов и росте приложения на основе данных. Модель прогнозирования Adapty оценивает долгосрочный потенциал выручки когорт пользователей вашего приложения. Для каждой когорты она строит прогноз того, как будут развиваться выручка, количество платящих подписчиков и средний LTV. Это помогает принимать взвешенные решения в области привлечения пользователей, маркетинговых стратегий и развития продукта. Adapty предлагает прогноз пожизненной ценности (LTV) и прогноз выручки для когорт платящих подписчиков. Прогнозы отображаются на странице когортного анализа через 3, 6, 9, 12, 18 и 24 месяца после создания когорты. Для приложений с очень небольшой историей модель использует средние значения по всем приложениям, поэтому прогнозы для новых приложений могут не в полной мере отражать поведение их конкретных пользователей. ## Как работает модель \{#how-the-model-works\} Модель прогнозирования Adapty использует паттерны удержания из исторических данных когорт для проецирования будущей выручки и LTV. Для каждой комбинации приложения и типа подписки модель отслеживает изменение числа платящих пользователей и общей выручки от одного периода продления к следующему. Она рассчитывает два коэффициента удержания — один для подписчиков, другой для выручки — на основе прошлых когорт приложения. Затем эти коэффициенты применяются к новым когортам для прогноза их роста через 3, 6, 9, 12, 18 и 24 месяца после создания когорты. Используемые данные полностью анонимизированы. Модель рассчитывает два значения для каждой когорты: - **Predicted revenue**: суммарная выручка, которую когорта, по прогнозу, принесёт за выбранный горизонт. - **Predicted LTV**: predicted revenue, делённый на прогнозируемое количество платящих подписчиков в когорте. ### Веса для конкретного приложения и кросс-приложенческие веса \{#app-specific-and-cross-app-weights\} По умолчанию прогнозы для когорты используют веса удержания, полученные на основе исторических когорт самого приложения, — это отражает поведение именно его пользователей. Когда у приложения недостаточно истории для конкретного горизонта прогноза, Adapty использует запасные веса удержания, усреднённые по всем приложениям с аналогичным типом подписки. Например, прогноз на 12 месяцев для приложения, которому всего шесть месяцев, строится на основе кросс-приложенческих запасных данных. Запасной вариант применяется независимо для каждого горизонта, поэтому одна и та же когорта может использовать собственные веса приложения для прогноза на 3 месяца и кросс-приложенческие веса — для прогноза на 12 месяцев. ### Доступность и обновления \{#availability-and-updates\} Прогнозы становятся доступны после того, как когорта завершает первый период продления — как правило, через неделю после создания для еженедельных подписок и примерно через четыре недели для месячных. После этого прогнозы обновляются ежедневно на основе актуальных транзакционных данных, отражая текущее поведение когорты. ### Ограничения \{#limitations\} - **Качество данных**: нетипичное поведение когорты или когорты с очень небольшим числом платящих пользователей снижают точность. Когорты с менее чем 100 платящими подписчиками исключаются из обучающих данных модели. - **Новые приложения**: приложения без достаточной истории используют общие веса на основе данных других приложений, которые могут не отражать специфику поведения пользователей конкретного приложения. - **Возраст когорты**: прогнозы для заданного горизонта скрываются, как только когорта превышает этот горизонт. Например, трёхмесячные прогнозы перестают отображаться по истечении трёх месяцев, а для когорт старше 24 месяцев прогнозы не показываются вовсе. ## В дашборде \{#in-the-dashboard\} Чтобы посмотреть прогнозы, перейдите на страницу когортного анализа в дашборде Adapty. Подробнее о когортах — в разделе [Когортный анализ](analytics-cohorts). <img src="/assets/shared/img/4d808b4-Export-1691486610612.gif" alt="Страница когортного анализа с колонками Predicted Revenue и Predicted LTV" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Столбец **Predicted revenue** показывает предполагаемую совокупную выручку, которую когорта подписчиков должна принести за выбранный период после её создания. Значение рассчитывается с помощью модели прогнозирования Adapty на основе исторических паттернов удержания когорт приложения. Столбец **Predicted LTV** показывает предполагаемую пожизненную ценность каждого пользователя в выбранной когорте. Значение рассчитывается путём деления прогнозируемой выручки на прогнозируемое количество платящих пользователей в когорте. ### Выберите горизонт прогноза \{#select-the-horizon\} Чтобы изменить горизонт прогноза, выберите нужное значение в выпадающем списке **Predictions**. Доступные варианты: 3, 6, 9, 12, 18 и 24 месяца с момента создания когорты. ### Фильтрация по продукту \{#filter-by-product\} Вы можете фильтровать прогнозируемый доход и LTV по продукту. По умолчанию прогнозы строятся на основе всех данных о покупках — фильтрация по продукту показывает вклад каждого продукта. <img src="/assets/shared/img/66a9c61-Export-1691486288948.gif" alt="Когортный анализ с фильтрацией по продукту" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Когда прогнозы недоступны \{#when-predictions-are-unavailable\} Если для когорты невозможно построить прогноз, в столбцах Predicted Revenue и Predicted LTV отображаются длинные тире (—) вместо значений. Это может происходить по нескольким причинам: - **Недостаточно времени с момента создания когорты**: Прогнозы становятся доступны только после того, как когорта завершит первый период продления — примерно через неделю для недельных подписок и около четырёх недель для месячных. - **Маленький размер когорты**: Слишком мало платящих подписчиков для надёжного прогноза. - **Нестандартное поведение когорты**: Когорта значительно отклоняется от паттернов, ожидаемых моделью. Подождите несколько недель — по мере накопления данных ситуация может исправиться. - **Горизонт превышен**: Когорта старше выбранного горизонта прогнозирования. Например, 3-месячный прогноз скрывается через три месяца, 12-месячный — через двенадцать, а для когорт старше 24 месяцев прогнозы не отображаются вовсе. :::warning При включении прогнозов учтите, что данные прогнозов по Revenue и LTV могут появиться на дашборде Adapty с задержкой до 24 часов. ::: --- # File: predictions-in-ab-tests --- --- title: "Прогнозы в A/B-тестах" description: "Узнайте, как прогнозы в A/B-тестах помогают уточнять стратегии ценообразования подписок." --- Добро пожаловать в документацию по предиктивной аналитике Adapty для функции A/B-тестирования. Этот инструмент даёт представление о будущих результатах ваших текущих A/B-тестов и помогает принимать решения на основе данных быстрее 🚀 — с помощью прогнозов Adapty на базе машинного обучения. ### Что такое прогнозы A/B-тестов? \{#what-are-ab-test-predictions\} Прогнозы A/B-тестов в Adapty используют продвинутые методы машинного обучения (а именно модели градиентного бустинга), чтобы предсказать долгосрочный потенциальный доход от пейволов, сравниваемых в A/B-тесте. Прогнозная модель позволяет выбрать наиболее эффективный пейвол на основе прогнозируемой выручки за год, а не опираться только на метрики, которые вы наблюдаете в ходе теста. Это помогает быстрее и надёжнее определить победителя — без необходимости ждать неделями, пока накопится достаточно данных. ### Как работает модель? \{#how-does-the-model-work\} Модель обучена на обширных исторических данных A/B-тестов из множества приложений разных категорий. Она использует широкий набор признаков для прогнозирования выручки, которую пейвол, вероятно, сгенерирует в течение года после начала эксперимента. В числе этих признаков: - Транзакции пользователей и конверсии за разные периоды - Географическое распределение пользователей - Используемая платформа (iOS или Android) - Показатели отказов и возвратов - Продукты с подписками и их периоды (ежедневные, ежемесячные, ежегодные и т. д.) - Другие данные, связанные с транзакциями Модель также учитывает пробные периоды в пейволах, используя исторические коэффициенты конверсии для прогнозирования дохода так, как если бы пользователи уже конвертировались. Это обеспечивает справедливое сравнение пейволов с пробными периодами и без них, поскольку мы также учитываем, что активные пробные периоды потенциально принесут доход в будущем. ### Чем Predicted P2BB отличается от обычного P2BB? \{#how-is-predicted-p2bb-different-from-just-the-p2bb\} В наших A/B-тестах используется байесовский подход: мы моделируем распределение дохода на пользователя (точнее, «Доход на 1000 пользователей») и затем вычисляем вероятность того, что одно распределение «действительно» лучше другого, а не случайно — это и называется вероятностью быть лучшим, или P2BB (подробнее о нашем подходе можно узнать [здесь](maths-behind-it)). Важно учитывать, что при этом мы опираемся только на выручку, накопленную за время проведения теста. Поэтому если вы сравниваете годовую подписку с еженедельной, вам придётся ждать очень долго, чтобы действительно понять, что работает лучше. Похожая ситуация возникает при сравнении триальных подписок с нетриальными в A/B-тесте — активные триалы, которые потенциально могут изменить расстановку сил, всегда остаются за рамками учитываемой выручки. Именно здесь в дело вступает наша прогностическая модель. Имея текущее распределение выручки в A/B-тесте и будучи обученной на большом наборе данных, она способна предсказать будущий вид этого распределения (а именно — спустя 1 год). После этого модель вычисляет прогнозируемый P2BB — тот, который вы получили бы, если бы запускали тест в течение целого года. Обратите внимание, что иногда прогнозируемый P2BB может противоречить текущему P2BB. В таких случаях мы выделяем строки с вариантами жёлтым цветом, вот так: <img src="/assets/shared/img/74577c6-CleanShot_2024-02-15_at_13.08.452x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Мы считаем это сигналом к тому, чтобы накопить больше данных и подтвердить победителя или глубже изучить A/B-тест, чтобы выяснить причину. Как правило, мы рекомендуем доверять прогнозируемому P2BB, а не текущему, поскольку он учитывает больше данных — но окончательное решение, конечно, остаётся за вами. ### Точность и достоверность модели \{#model-accuracy-and-certainty\} Модель обеспечивает высокий уровень точности: средняя абсолютная процентная ошибка (MAPE) составляет чуть менее 10%. Такая точность позволяет уверенно опираться на прогнозы модели при принятии решений на основе данных. Для дополнительной стабильности модель использует критерий «достоверности», основанный на трёх факторах: - Узкий доверительный интервал прогноза — модель уверена в результате - Достаточное количество подписок и выручки в тесте - С момента начала теста прошло не менее 2 недель Прогноз считается надёжным, если выполнены хотя бы два из трёх критериев. Когда запускается новый A/B-тест, модель формирует прогноз годового дохода на 1000 пользователей (основная метрика A/B-теста) для каждого пейвола. Прогнозы отображаются только при соответствии критериям достоверности. Если данных недостаточно, модель выведет сообщение «insufficient data for prediction». ### Ограничения и особенности \{#limitations-and-considerations\} Несмотря на то что наша прогностическая модель — мощный инструмент, важно учитывать её ограничения. Качество работы модели зависит от полноты и репрезентативности доступных данных. Нетипичное поведение когорты или новые приложения, не вошедшие в обучающую выборку, могут снизить точность прогнозов. Тем не менее прогнозы обновляются ежедневно, отражая актуальные данные и поведение пользователей. Это гарантирует, что получаемые вами сведения всегда основаны на самой свежей информации. :::warning 🚧 Примечание: Этот инструмент дополняет, но не заменяет ваши профессиональные суждения и понимание уникальной динамики вашего приложения. Используйте эти прогнозы как ориентир наряду с другими метриками и знанием рынка для принятия взвешенных решений. ::: --- # File: adapty-ads-manager --- --- title: "Adapty Ads Manager" description: "Получайте аналитику в реальном времени из Apple Ads и управляйте своими кампаниями и оптимизируйте их" --- **Adapty Ads Manager** — это комплексная платформа для управления, оптимизации и масштабирования ваших кампаний Apple Ads. Она связывает данные о эффективности Apple Search Ads с ключевыми метриками дохода — установками, триалами, подписками и пожизненной ценностью пользователя — без необходимости подключать MMP. Благодаря аналитике в реальном времени, AI-прогнозам и умной автоматизации Adapty Ads Manager избавляет вас от утомительной ручной корректировки ставок, таблиц и догадок, заменяя их понятными инсайтами и инструментами для быстрых решений. С Adapty Ads Manager вы получаете: - **[Обзор](ads-manager-overview)**: Все ключевые метрики на одном экране — расходы, выручка, ROAS, CPA и другие — каждая с графиком динамики по дням - **[AI-агент](ads-manager-ai-agent)**: Задавайте вопросы на естественном языке и получайте ответы и рекомендации по всей воронке - **Данные о производительности в реальном времени**: По кампаниям, группам объявлений и ключевым словам - **Сквозное отслеживание выручки**: От поиска → установки → пробного периода → подписки → LTV - **Прогнозы и рекомендации AI**: Для прибыльного масштабирования - **Массовое управление**: Ставками, бюджетами, статусами и структурой - **[Автоматизации на основе правил](ads-manager-automations)**: Управление полным жизненным циклом ключевых слов - **[Рыночная аналитика](ads-manager-market-intelligence)**: Стратегии конкурентов по ключевым словам в более чем 50 странах - **[CPP A/B-тесты](ads-manager-cpp-ab-tests)**: Сравнивайте кастомные страницы продукта друг с другом и находите лучшую <CustomDocCardList ids={['adapty-ads-manager-get-started', 'ads-manager-overview', 'ads-manager-ai-agent', 'adapty-ads-manager-analytics', 'ads-manager-create-campaign', 'ads-manager-create-ad-group', 'ads-manager-manage-keywords', 'ads-manager-automations', 'ads-manager-market-intelligence', 'ads-manager-cpp-ab-tests']} /> ## Почему стоит выбрать Adapty Ads Manager? \{#why-choose-adapty-ads-manager\} Потому что мы предоставляем **наиболее точные данные на рынке.** В отличие от нативной консоли Apple Ads или MMP, наши данные **в реальном времени, без потерь и полностью связаны** с триалами, подписками и LTV — без задержек и пробелов в атрибуции. Благодаря простой реализации и удобному пользовательскому опыту вы можете управлять всем в одном месте, не переключаясь между разными инструментами. ## Начало работы \{#get-started\} Чтобы начать работу с Adapty Ads Manager, следуйте [гайду](adapty-ads-manager-get-started) — и можно приступать к изучению --- # File: adapty-ads-manager-get-started --- --- title: "Начало работы с Adapty Ads Manager" description: "Импортируйте исторические данные из Apple Ads и получайте обновления в реальном времени на дашборде" --- [Adapty Ads Manager](adapty-ads-manager) — это платформа для оптимизации и аналитики Apple Ads. В этом гайде вы узнаете, как начать работу с Adapty Ads Manager за два шага: 1. Установите SDK и дайте ему начать отслеживать данные о покупках. 2. Подключите Adapty Ads Manager к вашему аккаунту Apple Ads, чтобы импортировать исторические данные и начать отслеживать обновления в реальном времени. :::note Adapty Ads Manager не использует [интеграцию Apple Ads](apple-search-ads) из **App settings**. Чтобы использовать Adapty Ads Manager, достаточно выполнить настройку, описанную в этом гайде. ::: ## 1. Установите SDK \{#1-install-the-adapty-sdk\} :::important Adapty Ads Manager — это **самостоятельный продукт**. Вы можете использовать его, даже если пейволы, подписки или аналитика не управляются через Adapty — переносить весь стек на Adapty не обязательно. Для получения точных данных о доходах минимальная настройка — установить SDK в режиме наблюдателя и включить серверные уведомления App Store в Adapty. ::: Чтобы связать данные о доходах с эффективностью кампаний, позвольте Adapty отслеживать ваши покупки: 1. Первый шаг зависит от того, есть ли у вас уже реализованные встроенные покупки: - Если вы **уже реализовали встроенные покупки с помощью Adapty**, на этом этапе ничего делать не нужно. - Если вы **уже реализовали встроенные покупки без Adapty** и не планируете переходить на Adapty, установите SDK Adapty для вашей платформы в режиме наблюдателя. На этом этапе нужно лишь добавить SDK в проект, активировать его с включённым режимом наблюдателя и передавать транзакции: - [iOS](implement-observer-mode) - [Android](implement-observer-mode-android) - [React Native](implement-observer-mode-react-native) - [Flutter](implement-observer-mode-flutter) - [Unity](implement-observer-mode-unity) - [Kotlin Multiplatform](implement-observer-mode-kmp) - [Capacitor](implement-observer-mode-capacitor) - Если вы **ещё не реализовали встроенные покупки и хотите использовать Adapty**, выполните шаги из [краткого руководства по началу работы](quickstart), чтобы делегировать обработку покупок Adapty. 2. Чтобы получать уведомления об изменениях, связанных с доходами, напрямую из App Store, [включите серверные уведомления App Store в Adapty](enable-app-store-server-notifications). ## 2. Подключите Apple Ads \{#2-connect-apple-ads\} :::important Для подключения Apple Ads к Adapty вам нужна роль **Account Admin** в Apple Ads. ::: Теперь нужно подключить ваш аккаунт Adapty Ads Manager к аккаунту Apple Ads: 1. Нажмите на логотип Adapty в шапке и выберите **Search Ads**. 2. Нажмите **Continue with Apple**. 3. Войдите в свой Apple-аккаунт. 4. Выберите уровень доступа для Adapty Ads Manager: - **Read and Write**: предоставляет доступ ко всем группам кампаний. - **Limited access**: выберите конкретные группы кампаний и назначьте роль **Read & Write**, чтобы ограничить доступ только ими. 5. Нажмите **Grant access**. После этого Adapty начнёт синхронизировать исторические данные из Apple Ads. Вы уже можете начать работу с Adapty Ads Manager, но полный импорт исторических данных займёт некоторое время. ## Что дальше \{#whats-next\} После успешной синхронизации данных о транзакциях изучите, как: - [Управлять кампаниями, группами объявлений и ключевыми словами](ads-manager) - [Настраивать правила автоматизации для корректировки ставок на основе эффективности кампаний](ads-manager-automations) --- # File: ads-manager-overview --- --- title: "Обзор в Adapty Ads Manager" description: "Все ключевые метрики Apple Ads в одном месте, каждая с графиком тренда." --- Страница **Overview** отображает все ключевые метрики Apple Ads в одном месте, каждая — с графиком тренда. По умолчанию показываются данные по всем подключённым приложениям. Чтобы посмотреть данные по одному приложению, выберите его в выпадающем списке приложений в шапке. Чтобы открыть её, перейдите в раздел **Overview** на левой боковой панели Adapty Ads Manager. :::tip Чтобы получить краткую сводку того, что требует внимания, вместо того чтобы просматривать метрики вручную, обратитесь к [AI Agent](ads-manager-ai-agent). ::: ## Метрики \{#metrics\} Каждая метрика отображается как карточка с трендовым графиком за выбранный период. Определения метрик и формулы расчёта см. в [Метрики в Apple Ads Manager](adapty-ads-manager-metrics). ## Настройка отображаемых метрик \{#configure-displayed-metrics\} Чтобы изменить набор метрик на странице **Overview**, нажмите **Edit metrics**. Здесь можно: - **Добавить метрику**: нажмите **Add metric** и установите флажки для нужных метрик. - **Удалить метрику**: снимите флажок в панели **Add metric** или нажмите **×** рядом с ней. ## Элементы управления \{#controls\} Используйте элементы управления вверху страницы, чтобы настроить отображение на странице **Overview**: - **Date range**: Выберите предустановленный период (**Last 7 days**, **Last 30 days**, **Last 90 days**) или задайте произвольный диапазон. Все графики и сводные значения обновятся в соответствии с выбранным периодом. - **Chart type**: Переключитесь между видами: столбчатая диаграмма с накоплением, линейный график и круговая диаграмма. - **Revenue display**: Выберите способ расчёта метрик дохода: - **Gross revenue**: Общий доход без каких-либо вычетов. - **Proceeds after store commission**: Доход после вычета комиссии Apple. - **Proceeds after store commissions and taxes**: Чистый доход после вычета комиссии Apple и применимых налогов. --- # File: ads-manager-ai-agent --- --- title: "ИИ-агент в Adapty Ads Manager" description: "Задавайте вопросы об аккаунте Apple Ads на обычном языке и получайте ответы и рекомендации по всей воронке." --- ИИ-агент — это чат-ассистент в Adapty Ads Manager, который отвечает на вопросы об аккаунте Apple Ads на обычном языке. Он охватывает всю воронку — показы, установки, триалы, подписки и выручку — и анализирует доход и ROAS, а не только клики. Встроенная атрибуция Adapty передаёт данные воронки агенту практически в реальном времени. Агент выполняет консультационную функцию: он анализирует ваш аккаунт и даёт рекомендации, но не вносит изменения в кампании, ставки или бюджеты самостоятельно. ## Что умеет агент \{#what-you-can-ask\} Спрашивайте агента о любой части вашего аккаунта Apple Ads. Агент умеет: - **Обзор аккаунта**: краткая сводка о том, что происходит в аккаунте и на что обратить внимание в первую очередь. - **Неэффективные объекты**: поиск кампаний, которые не окупаются, и ключевых слов, не приносящих конверсий. - **Бюджет**: определение кампаний, достигших дневного лимита, и рекомендации — стоит ли его повышать. - **Ставки и отключение**: рекомендации по повышению ставки или сохранению текущей, а также по отключению кампании или её продолжению. - **Эффективность по гео**: анализ того, какие страны показывают высокие и низкие результаты, и советы по перераспределению бюджета. ## Примеры вопросов \{#example-questions\} Агент даёт наиболее полезные ответы, когда вы указываете метрику, временной диапазон и решение, которое рассматриваете. Например: - Какие ключевые слова дают наибольший ROAS и как перераспределить бюджет в их пользу? - Какие ключевые слова тратят бюджет, но не приносят триалов или подписок за последние 30 дней, и какие стоит приостановить? - Какие кампании достигают дневного лимита бюджета, оставаясь при этом прибыльными, и насколько стоит поднять бюджет для каждой? - Сравни страны по ROAS и стоимости подписки — куда стоит перераспределить бюджет? - Стоит ли снизить ставку по этому ключевому слову, приостановить его или дать ему больше времени? Покажи данные воронки, лежащие в основе ответа. - Какие ключевые слова привлекают дешёвые установки, которые редко конвертируются в платные подписки? ## Открытие AI-агента \{#open-the-ai-agent\} Чтобы открыть агент, нажмите **Ask AI Agent** в шапке аккаунта. Прежде чем задавать вопросы, выберите приложение, чтобы задать область работы агента. Затем введите вопрос и отправьте его. Агент выполняет задачи в фоновом режиме, так что вы можете продолжать работу в Adapty Ads Manager, пока он готовит ответ. Чтобы изменить модель, которая отвечает на ваши вопросы, воспользуйтесь переключателем рядом с полем ввода сообщения. ## Доступ к предыдущим чатам \{#access-previous-chats\} Компактная панель показывает только текущий разговор. Чтобы вернуться к более ранним чатам, нажмите кнопку разворачивания в верхней части панели и перейдите в полноэкранный режим. Слева откроется список **Chats**, в котором можно найти прошлые беседы или начать новую с помощью **New chat**. ## Ограничения \{#limitations\} AI-агент носит консультативный характер. Он рекомендует действия, но не применяет их за вас. Ознакомьтесь с рекомендациями, а затем самостоятельно внесите изменения [при управлении кампаниями](ads-manager-create-campaign) и [ключевыми словами](ads-manager-manage-keywords). --- # File: adapty-ads-manager-metrics --- --- title: "Метрики в Apple Ads Manager" description: "Просматривайте аналитику приложения в Apple Ads Manager." --- Apple Ads Manager предоставляет подробные метрики для оценки эффективности кампаний и поведения пользователей. ## Производительность \{#performance\} | Метрика | Описание | |--------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Spend | Суммарная стоимость каждого нажатия пользователей на ваше объявление. | | Impressions | Количество показов вашей рекламы в результатах поиска App Store за отчётный период. | | CPM | Средняя сумма, которую вы платите за тысячу показов объявления. Avg CPM = Spend / (Impressions / 1000) Примечание: для кампаний App Store Search Results с моделью ценообразования CPT отображается эффективный CPM. | | Taps | Количество нажатий пользователей на объявление за отчётный период. | | CPT | Средняя сумма, которую вы платите за одно нажатие на объявление. Avg CPT = Spend / Taps | | TTR | Количество нажатий на объявление, делённое на общее число его показов. TTR = Taps / Impressions * 100% | | Downloads (Total) | Общее количество новых загрузок и повторных загрузок по нажатию или просмотру объявления за отчётный период. | | Downloads (View-Through) | Количество загрузок и повторных загрузок от пользователей, которые просмотрели ваше объявление в течение 24 часов, но не нажали на него. | | Downloads (Tap-Through) | Общее количество новых загрузок и повторных загрузок от пользователей, нажавших на объявление в течение 30 дней. | | Avg CPA (Total) | Общая средняя стоимость привлечения (CPA) — общие расходы на кампанию, делённые на общее количество загрузок по просмотру или нажатию на объявление за отчётный период. | | Avg CPA (Tap-Through) | Средняя стоимость привлечения по нажатию (CPA) — общие расходы на кампанию, делённые на количество загрузок по нажатию за отчётный период. | | Download Rate (Total) | Общее количество загрузок по просмотру или нажатию на объявление, делённое на общее количество нажатий за отчётный период. Формула: (Total Downloads / Taps) × 100%, если Taps > 0, иначе 0%. | | Download Rate (Tap-Through) | Общее количество загрузок по нажатию на объявление, делённое на общее количество нажатий за отчётный период. Формула: (Tap-Through Downloads / Taps) × 100%, если Taps > 0, иначе 0%. | | DPM (Total) | Downloads per Mille (DPM) — количество загрузок на тысячу показов. Формула: (Total Downloads / Impressions) × 1000, если Impressions > 0, иначе 0. | ## Конверсии \{#conversions\} :::note Revenue, ARPU, ARPPU, ARPAS, ROAS и ROI также доступны как метрики когорт для анализа групп пользователей в разрезе времени. ::: | Метрика | Описание | |--------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Conversions | Conversions — общее количество событий конверсии за отчётный период. Формула: Trials started + Subscriptions started + Non-subscriptions | | Conversion CR | Conversion CR (Conversion Rate) — процент от общего числа загрузок, завершившихся конверсией. Формула: (Conversions / Total Downloads) × 100%, если Total Downloads > 0, иначе 0% | | Cost per Conversion | Cost per Conversion — общие расходы, делённые на количество конверсий. Формула: Spend / Conversions, если Conversions > 0, иначе 0 | | Revenue | Revenue — общая сумма дохода от покупок, продлений и других монетизированных конверсий в приложении за выбранный период (до вычета комиссии стора). | | ROAS | ROAS (Return on Ad Spend) — доход от рекламы, делённый на рекламные расходы, выраженный в процентах. Формула: (Revenue / Spend) × 100%, если Spend > 0, иначе 0% | | ROI | ROI (Return on Investment) — чистая прибыль относительно расходов. Формула: ((Revenue − Spend) / Spend) × 100%, если Spend > 0, иначе 0% | | ARPU | ARPU (Average Revenue per User) — средний доход на пользователя. Рассчитывается как общий доход, делённый на количество уникальных пользователей. Пример: $60 000 дохода / 5 000 пользователей = $12 ARPU. Полезно сравнивать это значение со стоимостью установки (CPI), чтобы оценить эффективность маркетинговых кампаний. | | ARPPU | ARPPU (Average Revenue per Paying User) — средний доход на платящего пользователя. Рассчитывается как общий доход, делённый на количество уникальных платящих пользователей. Пример: $60 000 дохода / 1 000 платящих пользователей = $60 ARPPU. Помогает понять, сколько денег в среднем приносит один платящий клиент. | | ARPAS | ARPAS — средний доход на активного подписчика. Рассчитывается как общий доход / количество активных подписчиков. Подписчиками считаются те, кто активировал пробный период или подписку. Пример: $60 000 дохода / 1 500 подписчиков = $40 ARPAS. | | Installs | Installs — общее количество пользователей, впервые установивших приложение, а также повторных установок существующими пользователями. Учитываются множественные установки одного пользователя на разных устройствах. Незавершённые загрузки и установки, отменённые до завершения, в счётчик не включаются. | | Installs CR | Installs CR (Conversion Rate) — процент пользователей от общего числа загрузок, установивших приложение. Формула: (Installs / Total Downloads) × 100%, если Total Downloads > 0, иначе 0% | | CPI | CPI (Cost per Install) — стоимость установки, зафиксированной Adapty. Формула: Spend / Installs, если Installs > 0, иначе 0 | | Trials | Trials — количество новых пробных подписок, запущенных за отчётный период. | | Trial CR | Trial CR (Conversion Rate) — процент от общего числа загрузок, завершившихся запуском пробного периода. Формула: (Trials / Total Downloads) × 100%, если Total Downloads > 0, иначе 0% | | Cost per Trial | Cost per Trial — общие расходы, делённые на количество новых запусков пробного периода. Формула: Spend / Trials, если Trials > 0, иначе 0 | | Trials converted | Trials converted — количество пробных подписок, успешно конвертировавшихся в платные подписки за отчётный период. | | Trial converted CR | Trial converted CR (Conversion Rate) — процент пробных подписок, конвертировавшихся в платные. Формула: (Trials converted / Trials) × 100%, если Trials > 0, иначе 0% | | Cost per Trial converted | Cost per Trial converted — общие расходы, делённые на количество конвертированных пробных периодов за тот же период. Формула: Spend / Trials converted, если Trials converted > 0, иначе 0 | | Subscriptions | Subscriptions — общее количество новых оформлений подписки (без пробного периода) за отчётный период. | | Subscription CR | Subscription CR (Conversion Rate) — процент от общего числа загрузок, завершившихся оформлением платной подписки (без бесплатного пробного периода). Формула: (Subscriptions / Total Downloads) × 100%, если Total Downloads > 0, иначе 0% | | Cost per Subscription | Cost per Subscription — общие расходы, делённые на количество новых оформленных подписок. Формула: Spend / Subscriptions, если Subscriptions > 0, иначе 0 | | Non-subscriptions started | Non-subscriptions started — общее количество разовых встроенных покупок, не являющихся подпиской. | | Non-subscription CR | Non-subscription CR (Conversion Rate) — процент от общего числа загрузок, завершившихся покупкой без подписки. Формула: (Non-subscriptions started / Total Downloads) × 100%, если Total Downloads > 0, иначе 0% | | Cost per Non-subscription | Cost per Non-subscription — общие расходы, делённые на количество покупок без подписки. Формула: Spend / Non-subscriptions started, если Non-subscriptions started > 0, иначе 0 | ## Расширенные возможности загрузки \{#advanced-downloads\} | Метрика | Описание | |--------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | New Downloads (Total) | Общее количество новых загрузок с переходом по рекламе и без него за отчётный период. | | New Downloads (View-Through) | Новые загрузки от пользователей, которые видели вашу рекламу, но не нажимали на неё и ранее не загружали приложение. Учитываются в окне атрибуции 24 часа. | | New Downloads (Tap-Through) | Новые загрузки от пользователей, которые нажали на вашу рекламу и ранее не загружали приложение. Учитываются в окне атрибуции 30 дней. | | New Download Share (Tap-Through) | Показывает, какой процент от общего числа загрузок по нажатиям составляют новые загрузки (от пользователей, нажавших на рекламу в течение 30-дневного окна атрибуции). | | Redownloads (Total) | Общее количество повторных загрузок с переходом по рекламе и без него за отчётный период. | | Redownloads (View-Through) | Повторные загрузки от пользователей, которые видели вашу рекламу, но не нажимали на неё, в течение 24 часов. Учитываются, когда пользователь скачал приложение, удалил его и снова скачал на том же или другом устройстве после просмотра рекламы. | | Redownloads (Tap-Through) | Повторные загрузки от пользователей, которые нажали на вашу рекламу, в течение 30-дневного окна атрибуции. Учитываются, когда пользователь скачал приложение, удалил его и снова скачал на том же или другом устройстве после нажатия на рекламу. | | Redownloads Share (Tap-Through) | Показывает, какой процент от общего числа загрузок по нажатиям составляют повторные загрузки. | ## Аналитика \{#insights\} | Метрика | Описание | |--------|------------------------------------------------------------------------------------------------------------------------------------------| | Impression Share | Impression Share — процент показов вашего объявления относительно общего числа показов по тому же поисковому запросу. | | Rank | Rank (текущий) — текущая позиция вашего приложения по доле показов для выбранного поискового запроса в конкретной стране или регионе. | | Search Popularity | Search Popularity (текущая) — популярность поисковых запросов по стране или региону. Шкала от 1 до 5, где 5 соответствует наибольшему объёму поиска. | --- # File: ads-manager-create-campaign --- --- title: "Управление кампаниями в Adapty Ads Manager" description: "Создание и редактирование кампаний Apple Ads в Adapty Ads Manager." --- Adapty Ads Manager работает в двустороннем режиме с Apple Ads: вы получаете данные о производительности почти в реальном времени и можете создавать и редактировать кампании прямо из дашборда Adapty — это намного удобнее, чем в нативном интерфейсе. Если вы создаёте кампанию в нативном дашборде Apple Ads, она автоматически появится в Adapty Ads Manager в течение 24 часов. Помимо [просмотра подробных метрик кампании](adapty-ads-manager-analytics), вы можете управлять всеми настройками кампании: - Создавать кампании - Редактировать существующие кампании - Запускать и приостанавливать кампании :::tip Чтобы найти кампании, требующие внимания, — например, кампании, достигшие дневного бюджетного лимита или не окупающиеся, — обратитесь к [AI-агенту](ads-manager-ai-agent). ::: ## Что такое кампания \{#what-is-a-campaign\} Кампания сосредоточена на одном приложении и показывает рекламу в одном плейсменте в App Store. Каждая кампания включает дневной бюджет и [группы объявлений](ads-manager-create-ad-group), ориентированные на конкретную стратегию продвижения приложения. Кампания продолжает расходовать средства в соответствии с настройками бюджета. :::important Обратите внимание, что кампания не может работать сама по себе: на уровне группы объявлений задаются ставка по умолчанию, аудитория и ключевые слова. Без группы объявлений у кампании нет таргетинга и ставок, и она не будет показываться. Создайте кампанию, а затем [добавьте хотя бы одну группу объявлений](ads-manager-create-ad-group), чтобы активировать её. ::: ## Создание кампаний \{#create-campaigns\} Чтобы создать кампанию: 1. Откройте страницу **Ads Manager** и нажмите **+**. Выберите **Create campaign**, чтобы запустить мастер создания кампании. 2. Выберите **placement** для вашей рекламы, затем нажмите **Start**: | Плейсмент | Где показывается реклама | | --- | --- | | **Search Results** | В верхней части результатов поиска в App Store. | | **Search Tab** | В списке рекомендуемых приложений на вкладке поиска, до того как пользователь начал вводить запрос. | | **Today Tab** | На странице «Сегодня» в App Store. | | **Product Pages** | В списке **You Might Also Like** на страницах других продуктов. Apple самостоятельно подбирает подходящие страницы. | | **Duplicate a Campaign** | Повторно использовать плейсмент, группы объявлений и ключевые слова из существующей кампании. | 3. Для **Search Result campaigns** выберите **тип кампании**. Группировка кампаний по типу упорядочивает данные отчётов и позволяет Adapty предложить подходящую стратегию ключевых слов. :::note Выберите **Max Conversions**, чтобы пропустить ручной выбор ключевых слов и позволить автоматическому алгоритму Apple оптимизировать конверсии. ::: | Тип кампании | Описание | | --- | --- | | **Generic** | Небрендированные запросы, описывающие, что делает ваше приложение. | | **Competitor** | Брендированные ключевые слова ваших конкурентов. | | **Discovery** | Search Match автоматически подбирает новые ключевые слова — так вы находите те, что приносят результат. | | **Max Conversions** | Автоматическое назначение ставок Apple, оптимизированное для конверсий. Укажите **Target CPA** на шаге Settings. | | **Brand** | Ключевые слова, связанные с брендом вашего приложения. | | **Custom** | Без предустановленной стратегии. Кампания строится с нуля. | 4. Выберите **app** (что именно вы хотите продвигать) и **campaign group** (аккаунт Apple Ads, с которого будет запускаться и оплачиваться кампания). 5. Выберите **countries or regions** для таргетинга. Для удобной оптимизации используйте одну кампанию на страну. 6. Настройте **basic settings** кампании. Таргетинг аудитории и ключевые слова задаются в [группах объявлений](ads-manager-create-ad-group) кампании, которые вы добавляете после её создания. | Настройка | Описание | | --- | --- | | **Campaign name** | Заполняется автоматически на основе приложения, плейсмента и страны. Можно изменить в любой момент. | | **Daily budget** | Сумма, которую кампания может тратить в день. | | **Target CPA** | Целевая стоимость привлечения для автоматического управления ставками. Отображается только для кампаний Max Conversions. | | **Ad scheduling** | Необязательно. Кампания запускается сразу, если не указана более поздняя дата начала; добавьте дату окончания, чтобы остановить её в нужный момент. | Аккаунты с кредитной линией также заполняют реквизиты для выставления счёта на этом шаге. 7. Проверьте сводку, подтвердите данные и нажмите **Create campaign**. 8. Перейдите к [настройке группы объявлений](ads-manager-create-ad-group). Без групп объявлений кампании не могут запускаться — именно они определяют аудиторию и/или ключевые слова. ## Редактирование кампаний \{#edit-campaigns\} Чтобы изменить созданную кампанию: 1. Откройте настройки кампании одним из способов: - Нажмите на название кампании в **Ads Manager > Campaigns**. Затем нажмите **Edit campaign** в правом верхнем углу. - Или установите флажок рядом с названием кампании и нажмите **Actions > Edit campaign settings**. 2. Измените настройки кампании. Вы можете редактировать название кампании, тип кампании (для кампаний Search Results), страны и дневной бюджет. Чтобы изменить размещение объявления, стратегию ставок или расписание, создайте новую кампанию. 3. Нажмите **Save changes**. Вы также можете изменить тип кампании прямо в столбце **Campaign type** в таблице кампаний. :::note Правки, внесённые непосредственно в Apple Ads, автоматически синхронизируются с Adapty Ads Manager, но могут отображаться с задержкой. ::: ## Экспорт кампаний \{#export-campaigns\} Чтобы экспортировать таблицу кампаний в CSV, нажмите на значок загрузки над таблицей и выберите **Export current page** или **Export all pages**. **Export all pages** скачивает все кампании со всех страниц в один файл. Прогресс загрузки отображается в модальном окне — его можно отменить в любой момент. Доступны два дополнительных фильтра: - **Enabled only**: включать только активные кампании. - **With spend ≥**: включать только кампании с расходами выше указанного порога. - **Group by country**: разбивать каждую строку кампании по странам. Таблица экспортируется в том виде, в котором она отображается на вашем дашборде, с теми столбцами, которые вы выбрали для показа. ## Запуск и приостановка кампаний \{#launch--pause-campaigns\} Чтобы запустить или приостановить кампанию в Adapty Ads Manager: 1. Перейдите в **Ads Manager > Campaigns**. 2. Переключите тогл рядом с названием кампании в колонке **Status**. ## Удаление кампаний \{#delete-campaigns\} Adapty Ads Manager не позволяет удалять кампании. Вместо этого вы можете [приостановить](#launch-pause-campaigns) кампанию — это остановит расходы, но сохранит её настройки и аналитику. --- # File: ads-manager-create-ad-group --- --- title: "Управление группами объявлений в Adapty Ads Manager" description: "Создание и редактирование групп объявлений Apple Ads в Adapty Ads Manager." --- Adapty Ads Manager имеет двустороннюю интеграцию с Apple Ads: вы получаете данные о производительности практически в реальном времени и можете создавать и редактировать кампании прямо из дашборда Adapty — значительно удобнее, чем в нативном интерфейсе. Если вы создадите группу объявлений в нативном дашборде Apple Ads, она автоматически появится в Adapty Ads Manager в течение 24 часов. Помимо [просмотра подробных метрик кампаний](adapty-ads-manager-analytics), вы можете управлять всеми настройками групп объявлений: - Создавать группы объявлений - Редактировать существующие группы объявлений - Запускать и приостанавливать группы объявлений ## Что такое группа объявлений \{#what-is-ad-group\} Группа объявлений входит в состав [кампании](ads-manager-create-campaign) и определяет настройки таргетинга и стратегию ставок для ваших объявлений. Каждая группа объявлений включает настройки ставок, параметры аудитории и [ключевые слова](ads-manager-manage-keywords), которые определяют, когда и кому показываются ваши объявления. Группы объявлений помогают организовать рекламную стратегию внутри кампании и тестировать разные подходы к таргетингу. :::important Обратите внимание: кампания не может работать без групп объявлений. Именно на уровне групп объявлений задаются ставка по умолчанию, аудитория и ключевые слова — без хотя бы одной группы у кампании нет таргетинга и ставок, и она не будет показываться. Сначала создайте кампанию, затем [добавьте хотя бы одну группу объявлений](ads-manager-create-ad-group), чтобы активировать её. ::: ## Создание групп объявлений \{#create-ad-groups\} Чтобы создать новую группу объявлений Apple Ads: 1. Перейдите в **Ads Manager** через боковое меню. На любой вкладке нажмите **+** над таблицей, затем выберите **Create ad group**. 2. Выберите приложение, в которое хотите добавить группу объявлений. 3. Выберите кампанию, в которую хотите добавить группу объявлений. 4. Настройте параметры группы объявлений: - **Ad group name**: Метка для идентификации и поиска группы объявлений в дашборде. - **Default max CPT bid**: Максимальная сумма, которую вы готовы платить за нажатие на объявление. Эта ставка применяется ко всем ключевым словам в группе, если вы не задали отдельные ставки для ключевых слов. - **CPA cap (limits impressions)** (Необязательно): Максимальная сумма, которую вы готовы тратить за конверсию по нажатию (например, загрузку или другое целевое действие). Устанавливает потолок ставки для всех ключевых слов в группе объявлений. Максимальная ставка рассчитывается как произведение заданного CPA cap на коэффициент конверсии по кликам: `CPA Cap × CR (Tap-Through)`. Если максимальная ставка CPT для ключевого слова ниже этого значения, применяется именно она. Например, если ваш CPA cap равен $5, а коэффициент конверсии по кликам составляет 65%, максимальная ставка для всех ключевых слов в группе объявлений будет $3,25. Если максимальная ставка CPT установлена на уровне $4, применяемая максимальная ставка всё равно составит $3,25. - **Search Match**: Переключите, чтобы автоматически сопоставлять объявление с релевантными поисками без указания ключевых слов. При включении Apple Ads может показывать ваше объявление по запросам, связанным с метаданными и категорией вашего приложения. - **Audience**: Критерии таргетинга, определяющие, каким пользователям показываются ваши объявления. - **All eligible users**: Показывает объявления всем пользователям, которые подходят для вашей кампании. - **Specific audiences**: Настройте таргетинг на конкретные сегменты пользователей: - **Devices**: Таргетинг на iPad, iPhone или оба устройства. - **Customer type**: Таргетинг на всех пользователей, новых или вернувшихся. - **Gender**: Таргетинг по полу или на всех пользователей. - **Age range**: Таргетинг на определённые возрастные диапазоны или на всех пользователей. - **Ad scheduling** (Необязательно, доступно при выборе **Specific audiences**): Задайте время показа объявлений: - **Start date and time**: Когда группа объявлений должна начать показывать рекламу. - **End date** (Необязательно): Когда группа объявлений должна прекратить показывать рекламу. 5. Нажмите **Create**. 6. Если тип размещения вашей кампании — **Search results**, вы можете [добавить ключевые слова](ads-manager-manage-keywords), чтобы начать показывать объявления. Для остальных типов размещения всё готово. :::note В кампании **Max Conversions** у группы объявлений есть переключатель **Bidding strategy**: **Standard** или **Automated**. Поле **Default max CPT bid** отсутствует. При выборе **Automated** вы задаёте целевой CPA, а Apple автоматически оптимизирует ставки. ::: ## Редактирование групп объявлений \{#edit-ad-groups\} Чтобы изменить любую созданную группу объявлений: 1. Откройте настройки кампании одним из способов: - Нажмите на название кампании в **Ads Manager > Ad groups**. Затем нажмите **Edit ad group** в правом верхнем углу. - Или установите флажок рядом с именем группы объявлений и нажмите **Actions > Edit ad group settings**. 2. Измените настройки группы объявлений. Приложение, кампания, тип аудитории, дата и время начала не редактируются. В автоматической группе объявлений можно изменить только название. 3. Нажмите **Save changes**. Чтобы продублировать группу объявлений, выберите её в **Ads Manager > Ad groups** и нажмите **Actions > Duplicate ad group**. :::note Правки, внесённые напрямую в группу объявлений в Apple Ads, автоматически синхронизируются с Adapty Ads Manager, но могут отображаться в Adapty Ads Manager с задержкой. ::: ## Экспорт групп объявлений \{#export-ad-groups\} Чтобы экспортировать таблицу групп объявлений в CSV, нажмите на значок скачивания над таблицей и выберите **Export current page** или **Export all pages**. **Export all pages** скачивает все группы объявлений со всех страниц в один файл. Прогресс загрузки отображается в модальном окне — его можно отменить в любой момент. Доступны два дополнительных фильтра: - **Enabled only**: включать только активные группы объявлений. - **With spend ≥**: включать только группы объявлений с расходами выше указанного порога. - **Group by country**: разбивать каждую строку группы объявлений по странам. Таблица экспортируется в том виде, в котором она отображается на вашем дашборде, с выбранными вами столбцами. ## Запуск и приостановка групп объявлений \{#launch--pause-ad-groups\} Чтобы запустить или приостановить любую группу объявлений из Apple Ads Manager: 1. Перейдите в **Ads Manager > Ad groups** или откройте страницу кампании, чтобы увидеть её группы объявлений. 2. Переключите тумблер рядом с названием группы объявлений в столбце **Status** в нужное положение. --- # File: ads-manager-manage-keywords --- --- title: "Управление ключевыми словами в Adapty Ads Manager" description: "Добавляйте и управляйте ключевыми словами Apple Ads, минус-словами и SKAG-ключевыми словами в Adapty Ads Manager." --- Adapty Ads Manager имеет двустороннюю интеграцию с Apple Ads: вы получаете данные о производительности практически в реальном времени и можете создавать и редактировать ключевые слова прямо из дашборда Adapty — гораздо удобнее, чем в нативном интерфейсе. Если вы создадите ключевое слово в нативном дашборде Apple Ads, оно автоматически появится в Adapty Ads Manager в течение 24 часов. Помимо [подробной аналитики](adapty-ads-manager-analytics), вы можете управлять всеми настройками ключевых слов: - Добавлять ключевые слова в группы объявлений - Добавлять минус-слова - Добавлять ключевые слова в формате SKAG (Single Keyword Ad Group) - Редактировать ключевые слова прямо в таблице - Выполнять массовые действия над несколькими ключевыми словами - Запускать и приостанавливать ключевые слова :::tip Чтобы найти неэффективные ключевые слова по всему аккаунту — например, ключевые слова с расходами, но без конверсий — воспользуйтесь [AI Agent](ads-manager-ai-agent). ::: ## Что такое ключевые слова \{#what-are-keywords\} Ключевые слова — это поисковые запросы, по которым ваши объявления показываются в результатах поиска App Store. Они организованы в [группы объявлений](ads-manager-create-ad-group), которые входят в [кампании](ads-manager-create-campaign). Такая иерархическая структура позволяет эффективно организовывать рекламную стратегию и управлять ею. :::important Ключевые слова применимы только для кампаний с типом размещения **Search results**. Для кампаний с другими типами размещения (Search tab или Product pages) ключевые слова не используются. ::: ### Стандартные ключевые слова \{#standard-keywords\} Стандартные ключевые слова — это основные запросы, по которым вы делаете ставки для показа объявлений. Когда пользователи ищут эти запросы в App Store, ваше объявление может появиться в результатах поиска. ### Минус-слова \{#negative-keywords\} Минус-слова не позволяют вашему объявлению показываться по нерелевантным для вашего приложения запросам. Добавляя минус-слова, вы сокращаете расходы на нецелевые показы. Минус-слова можно добавлять на уровне группы объявлений или в виде кросс-групповых минус-слов, применяющихся сразу к нескольким кампаниям. ### Ключевые слова в формате SKAG (Single Keyword Ad Group) \{#keywords-as-skag-single-keyword-ad-group\} SKAG (Single Keyword Ad Group) — это стратегия, при которой для каждого ключевого слова создаётся отдельная группа объявлений. Такой подход позволяет: - Точно управлять ставками для высокоценных ключевых слов - Лучше анализировать результативность на уровне отдельного ключевого слова SKAG особенно полезен для выявления наиболее эффективных ключевых слов и максимального раскрытия их потенциала через отдельные группы объявлений. ## Добавление ключевых слов \{#add-keywords\} Чтобы добавить ключевые слова в группу объявлений: :::note Ключевые слова в кампаниях **Maximize Conversions** не используют ставки — управление ставками осуществляется автоматически на основе целевого CPA кампании. Поле **CPT bid** к ним не применяется. ::: 1. Перейдите в **Ads Manager** через боковое меню. На любой вкладке нажмите **+** над таблицей, затем выберите **Add keywords** из выпадающего списка. 2. В модальном окне выберите кампании и группы объявлений, к которым хотите добавить ключевые слова. После выбора групп объявлений в одной кампании можно выбрать другую кампанию и добавить ещё группы объявлений в список. 3. Нажмите **Select**, чтобы продолжить. 4. В диалоге **Add keywords** введите ключевые слова в поле **Keywords list**. Если у вас есть файл с ключевыми словами, разделёнными запятыми, вы можете вставить его содержимое — тогда Adapty Ads Manager загрузит все ключевые слова сразу. 5. Для каждого ключевого слова в таблице настройте: - **Match type**: выберите тип соответствия **Exact** (точное) или **Broad** (широкое) - **CPT bid**: укажите максимальную ставку за клик для этого ключевого слова или оставьте поле пустым, чтобы использовать ставку по умолчанию для группы объявлений 6. Проверьте ключевые слова и нажмите **Add X keywords** (где X — количество добавляемых ключевых слов). :::important После сохранения ключевого слова изменить тип совпадения невозможно. Если нужно изменить тип совпадения, удалите ключевое слово и добавьте его заново с нужным типом. ::: ## Добавление стоп-слов \{#add-negative-keywords\} Чтобы добавить стоп-слова: 1. Перейдите в **Ads Manager** через боковое меню. На любой вкладке нажмите **+** над таблицей, затем выберите **Add negative keywords** из выпадающего списка. 2. В модальном окне **Add negative keywords to** выберите уровень, на который хотите добавить минус-слова: - **Selected campaigns**: добавить минус-слова на уровне кампании. - **Selected ad groups**: добавить минус-слова на уровне группы объявлений. - **All ad groups in selected campaigns**: добавить минус-слова на уровне группы объявлений ко всем группам в выбранных кампаниях. :::note Обратите внимание: - Минус-слова на уровне группы объявлений имеют более высокий приоритет, чем минус-слова на уровне кампании. - Если вы добавляете минус-слова ко всем группам объявлений в выбранных кампаниях, при создании новых групп объявлений их придётся добавлять вручную. ::: 3. Введите минус-слова в поле **Keywords list**. Если у вас есть файл с ключевыми словами, разделёнными запятыми, можно вставить его содержимое — Adapty Ads Manager загрузит все слова сразу. 4. Для каждого ключевого слова в таблице выберите **Match type**: - **Exact**: исключает только точное ключевое слово или его близкие вариации. - **Broad**: исключает ключевое слово и похожие поисковые запросы. Или выберите несколько слов с помощью чекбоксов и измените тип соответствия оптом. 5. Проверьте минус-слова и нажмите **Add X keywords** (где X — количество добавляемых слов). :::note Кросс-групповые минус-слова особенно полезны, когда нужно исключить определённые поисковые запросы сразу в нескольких кампаниях — это экономит время и обеспечивает единообразие рекламной стратегии. ::: ## Добавление ключевых слов как SKAG \{#add-keywords-as-skag\} Чтобы добавить ключевые слова как SKAG (Single Keyword Ad Group): 1. Перейдите в **Ads Manager** через боковое меню. На любой вкладке нажмите **+** над таблицей и выберите **Add keywords as SKAG** из выпадающего списка. 2. Выберите кампании, в которых нужно создать SKAG-группы объявлений. Можно выбрать несколько кампаний. 3. По умолчанию новые группы объявлений создаются с настройками по умолчанию, нацеленными на всех пользователей. Если хотите изменить это, выберите **Copy settings from ad group** и укажите существующую группу объявлений, настройки которой нужно скопировать. 4. Настройте параметры новых групп объявлений: - **Ad group name prefix**: необязательный префикс, добавляемый к имени каждой группы (например, «SKAG_» создаст «SKAG_keyword1», «SKAG_keyword2» и т. д.). Нажмите **Tag**, чтобы динамически добавить ключевое слово, название кампании и страну в имена групп. - **CPT bid** и **CPA cap**: задайте ставку для всех ключевых слов сразу или выберите **Set CPT bid and CPA cap for each word manually**, чтобы задать их для каждого ключевого слова отдельно. 5. Введите ключевые слова в поле **Keywords list**. Если у вас есть файл с ключевыми словами, разделёнными запятыми, можно вставить его содержимое — Adapty Ads Manager загрузит все ключевые слова скопом. 6. Для каждого ключевого слова в таблице выберите **Match type**: - **Exact**: соответствует только точному ключевому слову или очень близким вариациям. - **Broad**: соответствует ключевому слову и связанным поисковым запросам. Или установите флажки рядом с ними и измените тип соответствия для нескольких сразу. 7. Выберите **Check for duplicates in target campaign**, чтобы убедиться в отсутствии одинаковых ключевых слов в целевых кампаниях. 8. Нажмите **Create**, чтобы создать SKAG-группы объявлений. Каждое ключевое слово будет размещено в отдельной группе объявлений в рамках каждой выбранной кампании — это позволит управлять ими и оптимизировать их независимо друг от друга. ## Редактирование ключевых слов \{#edit-keywords\} Чтобы отредактировать существующие ключевые слова: 1. Перейдите в **Ads Manager > Keywords** или **Ads Manager > Negative keywords**, найдите нужное ключевое слово в таблице, либо откройте страницу кампании, затем группы объявлений, и найдите ключевое слово там. 2. Отредактируйте значения прямо в таблице: - **CPT bid**: нажмите на значение ставки и введите новую максимальную стоимость за клик - **Status**: используйте переключатель, чтобы приостановить или активировать ключевое слово :::note Изменения ключевых слов, внесённые напрямую в Apple Ads, автоматически синхронизируются с Adapty Ads Manager, но могут отображаться в Adapty Ads Manager с задержкой. ::: ## Массовые действия \{#bulk-actions\} Вы можете выполнять массовые действия над несколькими ключевыми словами сразу, чтобы сэкономить время и эффективнее управлять ими. Чтобы выполнить массовые действия: 1. Перейдите на вкладку **Ads Manager > Keywords** или **Ads Manager > Negative keywords**. 2. Выберите несколько ключевых слов, поставив галочки напротив нужных. 3. Нажмите на выпадающий список **Actions** и выберите один из следующих вариантов: - **Add as keywords**: добавить выбранные ключевые слова как стандартные - **Add as negative keywords**: добавить выбранные ключевые слова как минус-слова - **Add as SKAG**: создать группы объявлений с одним ключевым словом (SKAG) для выбранных ключевых слов - **Activate**: активировать выбранные ключевые слова - **Pause**: приостановить выбранные ключевые слова - **Create segment from keywords**: создать сегмент аудитории из выбранных ключевых слов - **Copy keywords**: скопировать названия выбранных ключевых слов в буфер обмена - **Edit CPT bids**: изменить ставки CPT для выбранных ключевых слов. Доступны следующие способы редактирования: - **Set to**: установить несколько ставок на конкретную сумму. - **Increase by/decrease by**: увеличить или уменьшить ставки на определённую сумму в USD или на процент от ставки. При необходимости задайте верхний предел ставки, чтобы случайно не потратить лишнее. - **Set to average CPT**: использовать метрику CPT (стоимость за нажатие) для выравнивания ставки по ней. Задайте коэффициент-множитель. Например, установите множитель 0,9, если результаты ниже ожиданий, или 1,1, если они превышают их. - **Set to average CPA**: использовать метрику CPA (стоимость за привлечение) для выравнивания ставки по ней. Задайте коэффициент-множитель. Вкладка **Negative keywords** включает ограниченный набор действий: - **Add as keywords** - **Add as negative keywords** - **Add as SKAG** - **Delete keywords** :::tip Массовые действия особенно удобны для: - Конвертации ключевых слов между разными типами (стандартные, минус-слова, SKAG) - Быстрого добавления ключевых слов с другими типами соответствия сразу для нескольких ключевых слов - Фильтрации наиболее эффективных ключевых слов и корректировки их ставок - Выявления низкоэффективных ключевых слов и их приостановки ::: ## Экспорт ключевых слов \{#export-keywords\} Чтобы экспортировать таблицу ключевых слов в CSV, нажмите на иконку загрузки над таблицей и выберите **Export current page** или **Export all pages**. **Export all pages** скачивает все ключевые слова со всех страниц в одном файле. Прогресс загрузки отображается в модальном окне — его можно отменить в любой момент. Доступны два опциональных фильтра: - **Enabled only**: включать только активные ключевые слова. - **With spend ≥**: включать только ключевые слова с расходами выше указанного порога. - **Group by country**: разбивать каждую строку ключевого слова по странам. Таблица экспортируется в том виде, в котором она отображается на вашем дашборде, с выбранными вами столбцами. ## Изучение графиков на уровне ключевых слов \{#explore-keyword-level-charts\} Вы можете открыть график для любого ключевого слова прямо из таблицы **Ads Manager > Keywords**. Это позволяет анализировать эффективность каждого отдельного ключевого слова с точностью до дня. Чтобы отобразить график, нажмите на иконку графика рядом с ключевым словом в таблице. По умолчанию график показывает метрику **Spend** для выбранного ключевого слова. Вы можете отображать несколько метрик одновременно, чтобы отслеживать корреляции и изменения со временем. Нажмите **+**, чтобы добавить новую метрику. Нажмите **Reset**, чтобы начать заново, или просто снимите флажки с метрик, чтобы скрыть их. ## История ставок \{#bid-history\} Чтобы просмотреть историю ставок для ключевого слова, нажмите **значок графика** рядом с ним в таблице. Откроется панель с двумя вкладками: **Metrics** и **Bid History**. - Вкладка **Metrics** показывает график с метриками во времени. Добавлять и убирать метрики можно так же, как в графиках на уровне ключевых слов: нажмите **+**, чтобы добавить, или снимите флажки, чтобы скрыть. В каждой точке, где изменилась ставка, появляется маркер — наведите на него курсор, чтобы увидеть точную сумму ставки на эту дату. Используйте это, чтобы сопоставить изменения ставок со сдвигами в показателях: если метрика упала или резко выросла после изменения, маркер покажет точный момент. - Вкладка **Bid History** перечисляет каждое изменение ставки: дату, тип, предыдущее и новое значение, а также причину — ручное изменение или правило автоматизации (отображается с ID правила). --- # File: ads-manager-manage-ads --- --- title: "Управление объявлениями в Adapty Ads Manager" description: "Создание и редактирование объявлений Apple Ads в Adapty Ads Manager." --- Adapty Ads Manager имеет двустороннюю интеграцию с Apple Ads: вы получаете данные о производительности практически в реальном времени и можете создавать и редактировать объявления прямо из дашборда Adapty — намного удобнее, чем в нативном интерфейсе. Если вы создадите объявление в нативном дашборде Apple Ads, оно автоматически появится в Adapty Ads Manager в течение 24 часов. ## Что такое объявления \{#what-are-ads\} Объявление — это рекламный креатив, назначенный [группе объявлений](ads-manager-create-ad-group) внутри [кампании](ads-manager-create-campaign). На каждую группу объявлений можно назначить одно активное объявление. ## Создание объявлений \{#create-ads\} Перед началом убедитесь, что вы создали: - **Ad group**. Вы можете [создать его прямо в дашборде Adapty Ads Manager](ads-manager-create-ad-group). - **Custom product page**. Вам нужно [настроить её непосредственно в Apple Ads](https://developer.apple.com/help/app-store-connect/create-custom-product-pages/configure-multiple-product-page-versions/). Она должна быть одобрена App Store, прежде чем вы сможете использовать её в объявлении. :::note Если внутри выбранной группы объявлений уже есть активное объявление, оно будет приостановлено для запуска нового. ::: Чтобы создать новое объявление Apple Ads: 1. Перейдите в **Ads Manager** через боковое меню. На любой вкладке нажмите **+** над таблицей и выберите **Create ad**. 2. Выберите приложение, для которого хотите запустить рекламу. 3. Выберите одну или несколько групп объявлений. Adapty создаст объявление в каждой выбранной группе. 4. Введите название объявления. 5. Задайте статус объявления. Отключите переключатель **Status**, чтобы запустить объявление позже. 6. Нажмите **Select CPP**. Вы увидите все кастомные страницы продукта вашего приложения, одобренные App Store. Можно выбрать только одну кастомную страницу продукта. 7. Нажмите **Create ad**. ## Редактирование объявлений \{#edit-ads\} :::note После создания объявления можно изменить его название и статус. Нельзя изменить CPP или переместить его в другую группу объявлений. ::: Чтобы изменить название объявления, воспользуйтесь одним из следующих способов: - Нажмите на название объявления в **Ads Manager > Ads**. Измените название и нажмите на галочку рядом с ним. - Установите флажок рядом с названием объявления и нажмите **Actions > Edit ad**. Измените название или статус и нажмите **Save changes**. :::note Изменения, внесённые непосредственно в рекламу в Apple Ads, автоматически синхронизируются с Adapty Ads Manager, но могут некоторое время не отображаться в Adapty Ads Manager. ::: ## Экспорт объявлений \{#export-ads\} Чтобы экспортировать таблицу объявлений в формате CSV, нажмите на иконку загрузки над таблицей и выберите **Export current page** или **Export all pages**. **Export all pages** скачивает все объявления со всех страниц в один файл. Модальное окно отображает прогресс загрузки; вы можете отменить её в любой момент. Доступны два дополнительных фильтра: - **Enabled only**: включать только активные объявления. - **With spend ≥**: включать только объявления с расходами выше указанного порога. - **Group by country**: разбивать каждую строку объявления по странам. Таблица экспортируется в том виде, в котором она отображается на вашем дашборде, с выбранными вами столбцами. ## Запуск и остановка рекламных объявлений \{#launch--pause-ads\} Чтобы запустить или поставить на паузу любое объявление в Adapty Ads Manager, воспользуйтесь одним из следующих способов: - Переключите тумблер **Status** в положение «вкл.» или «выкл.» в разделе **Ads Manager > Ads**. - Установите флажок рядом с названием объявления, нажмите **Actions > Edit ad**, переключите тумблер **Status** и нажмите **Save changes**. --- # File: ads-manager-create-segments --- --- title: "Создание сегментов на основе атрибуции Apple Ads в Adapty Ads Manager" description: "Создавайте сегменты из кампаний, групп объявлений и ключевых слов в два клика в Adapty Ads Manager." --- Вы можете создавать [сегменты](segments) пользователей прямо из [Adapty Ads Manager](adapty-ads-manager), выбирая кампании, группы объявлений или ключевые слова и превращая их в сегменты буквально в несколько кликов. Это позволяет легко персонализировать пейволы и офферы на основе источника привлечения, не настраивая условия сегмента вручную. После создания сегмента вы можете использовать его для назначения разных продуктов и цен, запуска A/B-тестов и настройки внешнего вида пейвола. ## Примеры использования \{#use-cases\} Вот несколько примеров того, как сегменты, созданные на основе данных Apple Ads, можно применять на практике: - **Пейволы на основе ключевых слов**. Показывайте пейвол с акцентом на функции пользователям, пришедшим по высокоинтентным ключевым словам, и общий пейвол — пользователям из более широких поисковых запросов. - **Офферы на уровне кампаний**. Предлагайте более длинные пробные периоды или специальные цены пользователям из выбранных кампаний Apple Ads, сохраняя стандартное предложение для остальных. - **Согласованность рекламы и пейвола**. Направляйте пользователей из групп объявлений, продвигающих конкретные функции, на пейволы, которые первым делом выделяют именно эти функции. - **Оптимизация кампаний с высоким ROI**. Показывайте пейвол с премиум-продуктами по полной цене пользователям из кампаний, которые стабильно обеспечивают высокий LTV. ## Создание сегментов \{#create-segments\} Чтобы создать сегмент из Adapty Ads Manager: 1. Перейдите в **Ads Manager** и откройте вкладку **Campaigns**, **Ad groups** или **Keywords**. Установите флажки рядом с нужными элементами. Обратите внимание: если вы выберете несколько элементов, на их основе будет создан один общий сегмент, а не отдельный сегмент для каждого элемента. 2. Нажмите **Actions > Create segment from campaigns/ad groups/keywords**. 3. При необходимости обновите данные сегмента в окне **Create segment**: - **Adapty project**: приложение в Adapty, в котором вы хотите создать этот сегмент. - **Build segment from campaign/ad group**: при создании сегмента из кампаний или групп объявлений на этом шаге можно скорректировать выбранные кампании или группы объявлений. - **Segment name** - **Segment description** 4. Нажмите **Create**. 5. После создания сегмента можно подготовиться к его использованию: - Добавьте его в [плейсмент](placements), чтобы использовать с существующим пейволом или онбордингом - Создайте новый [пейвол](adapty-paywall-builder) или [онбординг](onboardings), который будет отображаться пользователям из этого сегмента - Запустите [A/B-тест](ab-tests) --- # File: ads-manager-automations-keyword-rules --- --- title: "Правила для ключевых слов в Adapty Ads Manager" description: "Автоматически управляйте жизненным циклом ключевых слов — корректируйте ставки, включайте или приостанавливайте ключевые слова и перемещайте их между группами объявлений — на основе эффективности кампании." --- Правила по ключевым словам автоматически работают с вашими ключевыми словами на основе полноворонковой эффективности — от установок до триалов, подписок и выручки. Задайте условия с помощью таких метрик, как расходы, CPA, ROAS и данные когорт, а затем выберите действие, которое правило выполнит при их срабатывании. Правила запускаются по заданному вами расписанию и реагируют на изменения эффективности без ручного вмешательства. ## Доступные действия \{#available-actions\} Каждое правило для ключевых слов выполняет одно действие при выполнении условий: | Действие | Что делает | |--------|-------------| | **Change bid** | Увеличивает, уменьшает или задаёт ставку CPT | | **Enable keyword** | Включает приостановленное ключевое слово | | **Pause keyword** | Приостанавливает активное ключевое слово | | **Add as keyword to…** | Копирует ключевое слово в другую группу объявлений с указанной ставкой и типом соответствия | | **Add as negative keyword to…** | Добавляет ключевое слово как минус-слово в указанные группы объявлений или кампании | ## Создание правила для ключевых слов \{#create-a-keyword-rule\} Правила для ключевых слов можно создавать из шаблонов или вручную с нуля. ### На основе шаблона \{#from-a-template\} Adapty предоставляет готовые шаблоны для типовых сценариев оптимизации. Среди популярных шаблонов: - **Сократить расходы на неконвертирующие ключевые слова**: снизить ставки там, где Spend > X, а Installs или Trials = 0. - **Масштабировать успешные ключевые слова**: повысить ставки там, где ROAS > цели или CPA < цели. Чтобы создать правило на основе шаблона: 1. На левой боковой панели перейдите в **Automations** и нажмите **Templates**. 2. Выберите шаблон и нажмите **Next**. 3. Проверьте и при необходимости скорректируйте предзаполненные настройки: - **Rule name**: автоматически задаётся как название шаблона и текущая дата (например, «Scale Winning Keywords - [2025-11-12]»). - **Apply to**: выберите группы кампаний, приложения, кампании или группы объявлений, к которым должно применяться правило. - **Conditions**: при необходимости измените предустановленные условия. - **Action**: при необходимости измените предустановленное действие. - **Schedule**: задайте, как часто должно выполняться правило. 4. Нажмите **Save**, чтобы активировать правило. ### Вручную \{#manually\} Чтобы создать пользовательское правило по ключевым словам с нуля: 1. В левом сайдбаре перейдите в **Automations**, нажмите **Create rule** и выберите **Keywords** в качестве типа правила. 2. Введите понятное **Rule name**. 3. В разделе **Apply to** выберите группы кампаний, приложения, кампании или группы объявлений, к которым должно применяться правило. 4. Нажмите **Add condition** и выберите [метрику](adapty-ads-manager-metrics) из списка. Метрики рассчитываются за выбранный период времени в валюте вашего аккаунта. Данные обновляются практически в реальном времени, поэтому правила всегда используют актуальные данные о производительности. 5. Задайте временной период (например, Previous 3 days или Previous 7 days), выберите оператор сравнения и укажите пороговое значение. 6. Чтобы добавить дополнительные условия, нажмите **Add condition** и выберите оператор **And** или **Or** слева. 7. В разделе **Action** выберите, что должно произойти при выполнении условий: **Изменить ставку** - **Тип действия**: Выберите **Increase by**, **Decrease by** или **Set to**. - **Тип значения**: Переключайтесь между **$** (абсолютное значение) и **%** (относительно текущей ставки на момент выполнения правила). - **Верхний лимит ставки** (опционально): Максимальный порог ставки — защита от превышения бюджета, если правило многократно срабатывает на сильных сигналах. **Включить ключевое слово** - Без дополнительной настройки. Правило повторно активирует приостановленные ключевые слова, соответствующие условиям. **Приостановить ключевое слово** - Без дополнительной настройки. Правило приостанавливает активные ключевые слова, соответствующие условиям. **Добавить как ключевое слово в…** - **Целевые рекламные группы**: выберите рекламные группы, в которые будут скопированы ключевые слова. - **Ставка CPT**: укажите начальную ставку для скопированных ключевых слов. - **Тип соответствия**: выберите **Exact** или **Broad**. - **Пропустить, если ключевое слово уже существует**: если включено, пропускает термины, которые уже есть в целевой рекламной группе. **Добавить как минус-слово в…** - **Область**: выберите рекламные группы или кампании, в которые добавляется минус-слово. - **Тип соответствия**: выберите **Exact** или **Broad**. 8. В разделе **Schedule**: - Выберите частоту запуска: **Every day**, **Every 2 days**, **Every week** и т.д. - Укажите время запуска (все значения в UTC). Правила запускаются в указанное время по UTC. Выполнение обычно занимает несколько минут, после чего изменения отображаются в Logs и на главном дашборде. 9. Нажмите **Save**, чтобы создать правило. ## Лучшие практики \{#best-practices\} - **Начните с узкого охвата**: Сначала применяйте новые правила к нескольким кампаниям или группам объявлений, чтобы проверить их работу, и только потом расширяйте масштаб. - **Используйте короткие окна ретроспективы для активных кампаний**: Для быстро меняющихся кампаний 3–7 предыдущих дней обычно работают лучше, чем 30 дней. - **Комбинируйте расходы и конверсии**: Избегайте правил на основе одной метрики. Используйте Spend вместе с Installs, Trials или ROAS для более надёжных сигналов. - **Устанавливайте ограничения ставок в правилах Change bid**: Верхний предел ставки предотвращает неконтролируемый рост, когда сильный сигнал срабатывает несколько раз. - **Используйте Enable keyword с данными когорт**: Ключевое слово, приостановленное на раннем этапе из-за плохого начального CPA, может показать высокий D31 или D61 ROAS после накопления данных по когортам. Задайте условие по ROAS когорты и автоматически включайте ключевое слово, когда оно достигает целевого значения. - **Используйте Add as keyword to… для пайплайнов «тестирование → масштабирование»**: Когда ключевое слово в тестовой кампании достигает целевого CPA, автоматически копируйте его в масштабирующую кампанию. - **Используйте Add as negative keyword to…, чтобы поддерживать порядок в Discovery-кампаниях**: Когда ключевое слово подтверждено как точное совпадение, добавляйте его как минус-слово в Discovery- или Search Match-кампании, чтобы избежать конкуренции за один и тот же запрос. - **Не спешите с правилами ставок для недавно добавленных ключевых слов**: Если вы используете [автоматизации поисковых запросов](ads-manager-automations-search-terms) для переноса запросов в кампании с ключевыми словами, дайте этим ключевым словам день-два на накопление данных. --- # File: ads-manager-automations-search-terms --- --- title: "Автоматизации по поисковым запросам в Adapty Ads Manager" description: "Автоматически переводите выигрышные поисковые запросы в ключевые слова и исключайте их у источника, чтобы масштабировать трафик из поиска без ручного труда" --- Кампании Discovery и Search Match генерируют данные по поисковым запросам. Чтобы превратить эти данные в структурированный список ключевых слов, нужно скачивать отчёты, фильтровать запросы и вручную добавлять их в группы объявлений. Автоматизации поисковых запросов делают это автоматически: когда запрос соответствует заданным условиям, правило выполняет настроенное вами действие. Для правил поисковых запросов существует два типа действий: - **Add as keyword**: добавляет термин как ключевое слово с точным соответствием в целевую группу объявлений и при необходимости добавляет его как минус-слово в исходную кампанию, чтобы избежать дублирования расходов. - **Add as negative keyword**: добавляет термин как минус-слово напрямую, без продвижения. Используйте это, чтобы отфильтровать нерелевантные или неэффективные поисковые запросы из кампаний Discovery и Search Match. Типичный сценарий для **Add as keyword**: позволить кампаниям Discovery или Search Match собирать реальные поисковые запросы пользователей, затем использовать правило для обнаружения слов, превышающих пороговое значение эффективности, и переносить их в кампанию Probing в качестве ключевых слов с точным совпадением — при этом исключая их в исходной кампании. Кампания Probing — это кампания Apple Search Ads, предназначенная для тестирования продвигаемых ключевых слов с контролируемыми ставками. Типичный сценарий для **Add as negative keyword**: если слово встречается часто, но никогда не конвертируется (например, много показов и ноль нажатий), исключите его автоматически, чтобы перестать тратить бюджет впустую. ## Создание правила автоматизации поисковых запросов \{#create-a-search-term-automation-rule\} Правила автоматизации поисковых запросов можно создавать из шаблонов или вручную с нуля. :::note Перед созданием правила убедитесь, что у вас активно работают кампании Discovery или Search Match и собираются данные по поисковым запросам. Кампании только с точным соответствием не генерируют отчёты по поисковым запросам, поэтому правилу не на что будет реагировать. ::: ### Из шаблона \{#from-a-template\} Чтобы создать правило из шаблона: 1. На левой боковой панели перейдите в раздел **Automations** и нажмите **Templates**. 2. Выберите шаблон и нажмите **Next**. 3. Проверьте и при необходимости скорректируйте предзаполненные настройки: - **Rule name**: автоматически устанавливается на основе названия шаблона и текущей даты. - **Apply to**: выберите группы кампаний, приложения, кампании или группы объявлений, в которых правило будет искать поисковые запросы. - **Conditions**: при необходимости измените предварительно настроенные условия. - **Actions**: при необходимости скорректируйте целевые группы объявлений, ставку CPT и область действия минус-слов. - **Schedule**: задайте периодичность запуска правила. 4. Нажмите **Save**, чтобы активировать правило. ### Вручную \{#manually\} Чтобы создать пользовательское правило автоматизации поискового запроса с нуля: 1. На левой панели перейдите в **Automations**, нажмите **Create rule** и выберите **Search terms** в качестве типа правила. 2. Введите понятное **Rule name**, чтобы обозначить назначение правила. 3. В разделе **Apply to** выберите группы кампаний, приложения, кампании или группы объявлений, в которых правило должно искать поисковые запросы. 4. Нажмите **Add condition** и выберите [метрику](adapty-ads-manager-metrics) из списка. Метрики рассчитываются за выбранный временной диапазон в валюте вашего аккаунта. Данные обновляются практически в реальном времени, поэтому правила всегда используют актуальные данные о производительности. 5. Задайте временной период (например, «Предыдущие 3 дня» или «Предыдущие 7 дней»), выберите оператор сравнения и введите пороговое значение. 6. Чтобы добавить больше условий, нажмите **Add condition** и выберите оператор **And** или **Or** слева. 7. В разделе **Action** выберите, что происходит, когда поисковый запрос соответствует условиям: **Добавить как ключевое слово** Продвигает совпадающие поисковые запросы как ключевые слова с точным соответствием в целевой группе объявлений. - **Target ad groups**: Выберите группы объявлений, которые получают продвигаемые ключевые слова. Чтобы создать pipeline обнаружения, выберите группы объявлений в кампании типа Probing или другой структурированной кампании. - **CPT bid**: Установите начальную ставку cost-per-tap для каждого продвигаемого ключевого слова. Варианты: ставка по умолчанию для группы объявлений, текущий CPT поискового запроса или конкретное значение. - **Skip if keyword already exists**: При включении пропускает запросы, которые уже есть в целевой группе объявлений. - **Add as negative**: Добавляет те же запросы в качестве минус-слов, чтобы не платить за один и тот же трафик дважды. - **Scope**: Выберите группы объявлений, в которые добавляются минус-слова. :::tip Включите **Add as negative** в том же правиле — так вы одновременно продвигаете термин в структурированную кампанию и исключаете его из источника. Это позволяет поддерживать Discovery-кампании в чистоте и автоматически выстраивать воронку ключевых слов. ::: **Add as negative keyword** Исключает совпадающий поисковый запрос без его продвижения. - **Scope**: Выберите группы объявлений или кампании, в которые добавляется минус-слово. - **Match type**: Выберите **Exact** или **Broad**. Используйте это действие для исключения нерелевантных или низкокачественных поисковых запросов из кампаний Discovery и Max Conversion. Например: если запрос набрал 50+ показов и 0 нажатий, исключите его автоматически. 8. В разделе **Schedule**: - Выберите частоту: **Every day**, **Every 2 days**, **Every week** и так далее. - Укажите время запуска (все значения в UTC). Правила запускаются по расписанию в UTC. Выполнение обычно занимает несколько минут, после чего изменения можно увидеть в Logs и на главном дашборде. 9. Нажмите **Save**, чтобы создать правило. После запуска правила перейдите в **Automations** → **Logs** и откройте запись для вашего правила. При успешном выполнении для каждого проверенного поискового запроса отображаются: исходная кампания, целевая группа объявлений, результат действия и результат блокировки. Если записи отсутствуют, условия не были выполнены — проверьте пороговые значения или расширьте окно ретроспективного анализа. ## Лучшие практики \{#best-practices\} - **Используйте кампании Discovery или Search Match в качестве источника**: эти типы кампаний собирают реальные запросы пользователей, обеспечивая вашим правилам большой пул поисковых запросов для оценки. - **Согласуйте порог с окном ретроспективы**: два и более скачивания за 7 дней — разумная отправная точка. Более длинное окно (14–30 дней) снижает эффективный порог: запросы могут проходить при нечастых конверсиях. Для приложений с высоким трафиком сократите окно и повысьте порог. - **Всегда добавляйте исключение в источнике при продвижении**: если вы добавляете запрос как ключевое слово в кампанию Probing, но не исключаете его в Discovery, обе кампании будут конкурировать за один и тот же запрос. Включите **Add as negative** в том же правиле. - **Тщательно выбирайте целевые группы объявлений**: направляйте продвигаемые запросы в конкретную группу объявлений кампании Probing, а не в широкую кампанию. Это сохраняет структуру ключевых слов чистой и упрощает анализ эффективности. - **Проверяйте логи после каждого запуска**: проверьте вкладку Logs, чтобы убедиться, какие запросы были продвинуты и куда. На начальном этапе запускайте правило вручную после настройки, чтобы убедиться, что оно работает ожидаемо. О том, как читать логи, см. [Автоматизации](ads-manager-automations#explore-logs). - **Дайте продвинутым ключевым словам время до того, как на них начнут действовать правила ключевых слов**: если вы используете правила ключевых слов в тех же кампаниях, исключите недавно добавленные ключевые слова или подождите день-два перед их применением. Правило ключевых слов может сработать на свежем запросе без истории эффективности и снизить ставку до того, как он конвертируется. - **Используйте Add as negative keyword для запросов с большим количеством показов и нулевыми нажатиями**: кампании Discovery и Max Conversion часто показывают нерелевантные поисковые запросы. Правило «Impressions > 50 AND Taps = 0» автоматически отлавливает их и добавляет в исключения, прежде чем они накопят ещё больше нецелевых показов. ## Экспорт поисковых запросов \{#export-search-terms\} Чтобы экспортировать таблицу поисковых запросов в CSV, нажмите значок скачивания над таблицей и выберите **Export current page** или **Export all pages**. **Export all pages** загружает все поисковые запросы со всех страниц в один файл. Прогресс загрузки отображается в модальном окне; вы можете отменить её в любой момент. Таблица экспортируется в том виде, в котором она отображается на вашем дашборде, с выбранными вами столбцами. --- # File: ads-manager-automations-ad-group-rules --- --- title: "Правила для групп объявлений в Adapty Ads Manager" description: "Автоматически корректируйте ставки и цели CPA для групп объявлений, а также включайте или приостанавливайте группы объявлений в зависимости от результатов кампании." --- Добавьте **правило для группы объявлений**, если хотите, чтобы настройки группы автоматически менялись в зависимости от её результатов. В отличие от правил для ключевых слов, которые применяются к отдельным ключевым словам, правило для группы объявлений меняет всю группу сразу. Каждое правило связывает условие с действием. Например: если группа объявлений потратила больше $50 за три дня без единого триала — снизить ставку на 20%. Adapty проверяет ваши группы объявлений по заданному расписанию — ежечасно, ежедневно, еженедельно и так далее — и применяет действие каждый раз, когда условие выполнено. А поскольку Adapty отслеживает триалы, подписки и выручку, ваши условия могут реагировать на реальный доход, а не просто на установки. ## Доступные условия \{#available-conditions\} Правило отслеживает набор групп объявлений и срабатывает, когда их показатели пересекают заданный порог. Сначала выберите, какие группы объявлений оно отслеживает: - **Ad groups in selected campaign groups** - **Ad groups in selected apps** - **Ad groups in selected campaigns** - **Selected ad groups** Затем задайте условие, при котором правило срабатывает. Каждое условие объединяет следующие части: | Часть | Описание | Пример | | --- | --- | --- | | **Metric** | Любая [метрика, которую отслеживает Adapty Ads Manager](adapty-ads-manager-metrics) — расходы, установки, триалы, подписки, выручка и другое. | Spend | | **Time window** | Период, за который измеряется метрика. | Previous 3 days | | **Comparison** | Способ сравнения метрики с заданным значением. | is greater than | | **Threshold** | Значение, с которым происходит сравнение. | $50 | Комбинируйте несколько условий с помощью **And** или **Or** для точного таргетинга — например, spend > $50 **and** trials = 0. ## Доступные действия и их настройки \{#available-actions-and-their-settings\} Когда группа соответствует заданному условию, Adapty может изменить её настройки, запустив одно из следующих действий: | Действие | Что делает | Когда использовать | Настройка | | --- | --- | --- | --- | | **Change default bid** | Увеличивает, уменьшает или устанавливает стандартную максимальную **ставку cost-per-tap (CPT)** для группы объявлений — максимальную сумму, которую вы заплатите за один тап по объявлению. | Усиливайте группы объявлений с высокой конверсией; снижайте ставки для неэффективных. | **Action type**: Increase by, Decrease by или Set to.<br/>**Value type**: $ (абсолютное значение) или % (от текущей ставки).<br/>**Limit** (необязательно): верхний предел при увеличении или нижний предел при уменьшении — ограничивает, насколько далеко повторные запуски могут сдвинуть ставку. | | **Change CPA goal** | Увеличивает, уменьшает или устанавливает **CPA goal (cap)** для группы объявлений — целевую стоимость привлечения. | Ужесточайте потолок стоимости по мере оптимизации или расширяйте его для увеличения объёма. | **Action type**: Increase by, Decrease by или Set to.<br/>**Value type**: $ или %.<br/>**Limit** (необязательно): верхний предел при увеличении или нижний предел при уменьшении. | | **Enable ad group** | Возобновляет работу приостановленной группы объявлений. | Возвращайте группы объявлений, которые восстанавливаются после поступления дохода от поздних триалов и подписок. | Нет. | | **Pause ad group** | Приостанавливает активную группу объявлений. | Останавливайте группы объявлений, которые продолжают расходовать бюджет без конверсий. | Нет. | ## Создание правила группы объявлений \{#create-an-ad-group-rule\} 1. Перейдите в **Automations**, нажмите **Create rule** и выберите **For ad groups**. 2. Введите **Rule name**. 3. В разделе **Apply to** выберите [область действия](#available-conditions) и укажите группы объявлений. 4. В разделе **Conditions** добавьте одно или несколько [условий](#available-conditions). Условия можно объединять с помощью логических операторов. 5. В разделе **Action** выберите [действие](#available-actions-and-their-settings) и настройте его параметры. 6. В разделе **Schedule** укажите частоту запуска правила и время начала (UTC) либо выберите **Run immediately**. 7. Нажмите **Save**. После сохранения правило появляется на вкладке **Automations**. Столбцы **Date last run** и **Date next run** отображают расписание запусков. Чтобы проверить изменения статусов между запусками, откройте **Automations > Logs**. Инструкции по приостановке, дублированию, удалению правила или его немедленному запуску см. в разделе [Automations](ads-manager-automations). --- # File: ads-manager-automations-campaign-rules --- --- title: "Правила кампаний в Adapty Ads Manager" description: "Автоматически регулируйте дневные бюджеты кампаний и включайте или приостанавливайте кампании в зависимости от их эффективности." --- Добавьте **правило кампании**, если хотите, чтобы бюджет или статус кампании изменялись автоматически на основе её показателей. В отличие от правил группы объявлений, которые действуют на одну группу, правило кампании изменяет всю кампанию целиком. Каждое правило состоит из условия и действия. Например: если кампания потратила больше $200 за три дня без ни одной подписки — снизить её дневной бюджет на 20%. Adapty проверяет ваши кампании по заданному расписанию — раз в час, раз в день, раз в неделю и так далее — и применяет действие каждый раз, когда условие выполняется. А поскольку Adapty отслеживает триалы, подписки и выручку, условия можно строить на основе реального дохода, а не просто установок. ## Доступные условия \{#available-conditions\} Правило отслеживает набор кампаний и срабатывает, когда их показатели пересекают заданный порог. Сначала выберите, какие кампании оно будет отслеживать: - **Campaigns in selected campaign groups** - **Campaigns in selected apps** - **Selected campaigns** Затем задайте условие, при котором правило срабатывает. Каждое условие состоит из следующих частей: | Часть | Описание | Пример | | --- | --- | --- | | **Метрика** | Любая [метрика, которую отслеживает Adapty Ads Manager](adapty-ads-manager-metrics) — расходы, установки, пробные периоды, подписки, выручка и другие. | Spend | | **Временной период** | Период, за который измеряется метрика. | Yesterday | | **Сравнение** | Способ сравнения метрики с заданным значением. | is greater than | | **Пороговое значение** | Значение для сравнения — фиксированная сумма или собственный **Daily Budget** кампании. | Daily Budget | Объедините несколько условий с помощью **And** или **Or** для точного таргетинга — например, расходы > Daily Budget **and** ROAS < 100%. ## Доступные действия и их настройки \{#available-actions-and-their-settings\} Когда кампания удовлетворяет заданному условию, Adapty может изменить её настройки, выполнив одно из следующих действий: | Действие | Что делает | Когда использовать | Настройка | | --- | --- | --- | --- | | **Change daily budget** | Увеличивает, уменьшает или задаёт **дневной бюджет** кампании — максимальную сумму расходов в день. | Снижайте бюджет кампаний с перерасходом; увеличивайте у тех, что масштабируются с прибылью. | **Action type**: Increase by, Decrease by или Set to.<br/>**Value type**: $ (абсолютное значение) или % (от текущего бюджета).<br/>**Limit** (опционально): верхний предел при увеличении или нижний при уменьшении — ограничивает, насколько далеко повторные запуски могут сдвинуть бюджет. | | **Enable campaign** | Повторно включает приостановленную кампанию. | Возвращайте кампании, которые восстанавливаются после поступления отложенных доходов от триалов и подписок. | Нет. | | **Pause campaign** | Приостанавливает активную кампанию. | Останавливайте кампании, которые продолжают тратить деньги без конверсий. | Нет. | ## Создайте правило кампании \{#create-a-campaign-rule\} Чтобы начать с готового шаблона, нажмите **Templates** в заголовке **Automations** и выберите **Decrease budget for low-performing campaigns**, затем проверьте и сохраните. Чтобы создать правило с нуля: 1. Перейдите в **Automations**, нажмите **Create rule** и выберите **For campaigns**. 2. Введите **Rule name**. 3. В разделе **Apply to** выберите [область применения](#available-conditions) и укажите кампании. 4. В разделе **Conditions** добавьте одно или несколько [условий](#available-conditions). Условия можно комбинировать с помощью логических операторов. 5. В разделе **Action** выберите [действие](#available-actions-and-their-settings) и настройте его параметры. 6. В разделе **Schedule** задайте частоту выполнения правила и время запуска (UTC) либо выберите **Run immediately**. 7. Нажмите **Save**. После сохранения правило появляется на вкладке **Automations**. Столбцы **Date last run** и **Date next run** отображают расписание запусков. Чтобы проверить изменения статусов между запусками, откройте **Automations > Logs**. Инструкции по приостановке, дублированию, удалению правила или его немедленному запуску см. в разделе [Automations](ads-manager-automations). --- # File: ads-manager-market-intelligence --- --- title: "Рыночная разведка в Adapty Ads Manager" description: "Смотрите, по каким ключевым словам ваши конкуренты запускают рекламу Apple Ads в 50+ странах, и добавляйте их прямо в свои кампании." --- Market Intelligence показывает, по каким ключевым словам ваши конкуренты делают ставки в Apple Ads — для 50+ стран. Данные агрегируются за последние 30 дней и обновляются ежедневно. Используйте это, чтобы: - **Пропустить Discovery-кампанию**: сразу узнайте, на какие ключевые слова делают ставки конкуренты, вместо того чтобы тратить бюджет на поиск рабочих вариантов. Начните с проверенного списка ключевых слов с первого дня. - **Находить низкоконкурентные ключевые слова**: выявляйте длинные запросы, где у конкурентов низкая доля показов — меньше конкуренции, ниже цена за клик и лучший CPA. - **Защищать бренд**: узнайте, делают ли конкуренты ставки на название вашего приложения и в каких странах, и верните себе этот трафик. - **Выходить на новые рынки с данными**: проверяйте, по каким ключевым словам конкуренты работают в той или иной стране, прежде чем тратить там деньги. - **Обнаруживать незамеченных конкурентов**: ищите по ключевым словам, чтобы увидеть, кто органически ранжируется по нужным запросам в вашей категории, и добавляйте их в анализ. ## Запустите анализ Market Intelligence \{#run-a-market-intelligence-analysis\} ### 1. Выберите приложение \{#1-select-your-app\} В левой боковой панели перейдите в **Market Intelligence**. Выберите нужное приложение из выпадающего списка и нажмите **Continue**. ### 2. Выберите конкурентов \{#select-competitors\} Добавьте конкурентов, которых хотите проанализировать: - **Suggested competitors**: Adapty автоматически определяет вероятных конкурентов на основе категории вашего приложения. Просмотрите список и выберите тех, кого хотите включить. - **Поиск по ключевому слову или названию приложения**: введите ключевое слово (например, «budget tracker») или название приложения в поле поиска. При необходимости смените страну, затем выберите приложения из результатов. Повторите поиск с другими ключевыми словами, чтобы найти конкурентов по разным поисковым запросам. - **Сохранённый список**: нажмите **Create list**, чтобы сохранить до 20 конкурентов для повторного использования в будущих анализах. Также можно загрузить ранее созданный список. Выбрав конкурентов, нажмите **Run analysis**. ### 3. Изучите результаты \{#3-explore-results\} Результаты распределены по четырём вкладкам: **Overview**, **Most Contested**, **By App** и **By Country**. #### Overview \{#overview\} Вкладка по умолчанию показывает сводку анализа: - **Stats bar**: общее число проанализированных конкурентов, страны с активностью Apple Ads, уникальные ключевые слова по всем рынкам и самое конкурентное ключевое слово. - **Keywords found by country**: столбчатый график, показывающий объём ключевых слов по странам для топ-25 рынков. - **Top 10 competitors by keyword coverage**: рейтинговая таблица с общим количеством ключевых слов каждого конкурента, средним показателем Share of Voice (Avg SOV) и странами, в которых они активны. #### Наиболее оспариваемые ключевые слова \{#most-contested\} Показывает ключевые слова, по которым одновременно активны наибольшее количество конкурентов. Используйте эту вкладку, чтобы найти высокочастотные запросы в вашей категории и увидеть, где конкуренция наиболее сосредоточена. Используйте поле поиска для фильтрации списка по ключевому слову. #### По приложению \{#by-app\} Отображает данные по ключевым словам для каждого конкурента отдельно. Используйте эту вкладку, чтобы подробнее изучить стратегию ключевых слов конкретного приложения по странам. Нажмите **Add filter**, чтобы отфильтровать по приложению, стране или ключевому слову. Чтобы экспортировать данные в CSV, нажмите на значок загрузки. #### По стране \{#by-country\} Отображает данные по ключевым словам, сгруппированные по рынкам. Используйте эту вкладку, когда хотите изучить конкурентную среду конкретной страны перед выходом на рынок или расширением там. Нажмите **Add filter**, чтобы отфильтровать по приложению, стране или ключевому слову. Чтобы экспортировать данные в CSV, нажмите на иконку загрузки. ### 4. Добавление ключевых слов в кампании \{#add-keywords-to-campaigns\} Как только вы определили ключевые слова, которые стоит протестировать, добавьте их в кампании прямо из инструмента: 1. В таблице ключевых слов отметьте чекбоксы рядом с нужными словами. Чтобы выбрать все видимые ключевые слова, используйте чекбокс в заголовке таблицы. 2. Нажмите **Add to campaign**. 3. Выберите, как добавить слова: в качестве ключевых слов, минус-слов или SKAG. Затем выберите целевую кампанию и группу объявлений, задайте тип соответствия и ставку CPT и подтвердите действие. ## На что обратить внимание \{#what-to-look-for\} На эти паттерны стоит обращать внимание в результатах: - **Низкочастотные запросы с низким Share of Voice**: Ключевые слова, по которым у конкурентов низкая доля показов, менее конкурентны. Как правило, они дешевле по цене за тап и дают лучшую конверсию, поскольку намерение пользователя более конкретное. - **Ключевые слова, которых ещё нет в ваших кампаниях**: Ищите слова, по которым запускают рекламу конкуренты, но не вы. Это проверенные способы получить трафик из Apple Ads в вашей категории. - **Покрытие брендовых запросов**: Поищите название своего приложения. Если по нему показываются конкуренты, значит, они делают ставки на ваш бренд. Добавьте эти ключевые слова в свои кампании с высокими ставками, чтобы защитить трафик. - **Пробелы по странам**: Проверьте, в каких странах активны ваши конкуренты. На рынках с минимальной конкурентной активностью или без неё легче закрепиться и для этого требуется меньший бюджет. --- # File: ads-manager-cpp-ab-tests --- --- title: "CPP A/B-тесты в Adapty Ads Manager" description: "Сравнивайте кастомные страницы продукта в Apple Ads и находите лучший вариант." --- CPP A/B-тесты позволяют сравнивать кастомные страницы продукта (CPP) между собой прямо в Apple Ads. Вы выбираете от 2 до 4 страниц продукта, и [Adapty Ads Manager](adapty-ads-manager) распределяет между ними трафик, собирает данные о результатах и определяет, какая страница лучше конвертирует. Вы можете добавить **страницу продукта по умолчанию** в качестве одного из вариантов — так вы проверите, работает ли кастомная страница лучше, чем текущая дефолтная. ## Предварительные условия \{#prerequisites\} Прежде чем создавать CPP A/B-тест, убедитесь, что: - **Apple Ads Manager подключён**: Следуйте [гайду по настройке](adapty-ads-manager-get-started), если вы ещё этого не сделали. - **Исходная группа объявлений получает трафик**: Группа объявлений, которую вы тестируете, должна существовать не менее 28 дней и иметь показы, клики и установки за этот период. Apple Ads Manager использует эту историю для оценки продолжительности теста и необходимого размера выборки. - **У вас есть хотя бы одна кастомная страница продукта**: Сначала создайте CPP в App Store Connect. Apple Ads Manager автоматически их считает. ## Создание A/B-теста CPP \{#create-a-cpp-ab-test\} Чтобы создать тест, в левом сайдбаре перейдите в **CPP A/B Tests** и нажмите **Create A/B Tests**. Мастер состоит из четырёх шагов: **Ad Group(s)**, **Ad Creative(s)**, **Testing Method** и **Review**. ### 1. Группа объявлений \{#ad-groups\} Введите **Test Name** и нажмите **Select Ad Group**, чтобы выбрать группу объявлений с CPP, которые хотите протестировать. Можно выбрать до четырёх групп объявлений из одной кампании, но только если вы планируете тестировать одно рекламное объявление на всех них. Для сравнения нескольких CPP выберите одну группу объявлений. ### 2. Рекламные материалы \{#2-ad-creatives\} Выберите CPP, которые хотите сравнить. Можно включить **Default Product Page** (отмечена как **Control**) и до трёх **Custom Product Pages** — итого от 2 до 4 вариантов. - **Default Product Page**: нажмите **+ Add Default**, чтобы добавить текущую страницу продукта по умолчанию в качестве контрольного варианта. - **Custom Product Pages**: нажмите **+ Select CPP**, чтобы выбрать кастомную страницу продукта из App Store Connect. ### 3. Метод тестирования \{#3-testing-method\} Настройте способ проведения теста. Adapty Ads Manager автоматически вычисляет **Calculated Test Duration**, **Start Time** и **End Time** — значения обновляются при каждом изменении любого из трёх параметров ниже. #### Switch Time Preset \{#switch-time-preset\} Как часто система чередует варианты. Если выбранное значение слишком велико для текущего объёма трафика, система автоматически понизит его. | Интервал | Типичный уровень трафика | Длительность слота | Типичная длительность теста | |----------------|---------------------------------------------|--------------------|-----------------------------| | **Hourly** | Высокий (5 000+ показов в день) | 7 часов | Дни | | **Daily** | Обычный | 24 часа | Недели | | **Weekly** | Низкий (менее 400 показов в день) | 7 дней | Месяцы | **Слот** — это базовый промежуток времени, в течение которого вариант показывается, прежде чем система решает, переключиться ли на следующий. #### Желаемая точность \{#desired-precision\} Минимальная разница в конверсии, которую тест способен надёжно обнаружить. Варианты: **1%**, **2%**, **3%**, **4%**, **5%**. По умолчанию: **5%**. Тест с точностью 1% улавливает незначительные отклонения, но требует больше данных и длится дольше. Тест с точностью 5% завершается быстрее, однако фиксирует только существенные различия. | Точность | Когда использовать | |----------|-------------------| | 1–2% | Ожидаются небольшие различия между CPP, и у вас высокий трафик в группах объявлений. | | 3–4% | Сбалансированный вариант по умолчанию для большинства тестов. | | 5% | Ожидается явный победитель, и результаты нужны быстро. | #### Уровень доверия \{#confidence-level\} Насколько уверены вы хотите быть в том, что результат реален, а не случаен. Варианты: **80%**, **85%**, **90%**, **95%**, **99%**. По умолчанию: **90%**. Чем выше уровень доверия, тем больше данных потребуется. | Confidence | Trade-off | |------------|---------------------------------------------------------------------------------| | 80–85% | Быстрее завершается, но выше риск, что результат окажется случайным. | | 90% | Рекомендуемый показатель по умолчанию для большинства тестов. | | 95–99% | Самый консервативный вариант. Требует больше всего данных и времени на тест. | ### 4. Проверка \{#review\} Проверьте итоговую сводку — выбранные группы объявлений, креативы, метод тестирования, длительность, точность и уровень достоверности — затем нажмите **Start CPP A/B Tests**. После запуска теста система клонирует группу объявлений для каждого варианта, активирует первый вариант, и через несколько минут статус теста изменится на **Running**. ## Мониторинг запущенного теста \{#monitor-a-running-test\} Чтобы открыть список тестов, перейдите в раздел **CPP A/B Tests** в левом сайдбаре. Четыре вкладки в верхней части страницы фильтруют тесты по состоянию: - **Live**: тесты, которые выполняются прямо сейчас. - **Completed**: завершённые тесты. - **Draft**: тесты, которые ещё не запущены. - **Archive**: старые тесты, которые больше не нужны в основном представлении. На карточке каждого теста отображаются его название, статус, интервал переключения, желаемая точность и продолжительность работы. Нажмите **View metrics**, чтобы развернуть таблицу вариантов. ### Производительность вариантов \{#variant-performance\} Таблица вариантов сравнивает показатели всех вариантов в тесте: | Колонка | Описание | |--------------------------|-----------------------------------------------------------------------------------------------------| | **Variant Name** | Тестируемый CPP. Вариант A — всегда первый добавленный вами вариант. | | **Confidence Level** | Насколько близок вариант к требуемому размеру выборки, в процентах от 0 до 100. | | **Impressions** | Количество раз, когда Apple показала рекламу для этого варианта. | | **TTR** | Показатель кликабельности: нажатия, делённые на показы. | | **Tap → Download CR** | Конверсия из нажатия в загрузку. | | **CPT** | Средняя стоимость одного нажатия. | | **Avg CPA (Tap-Through)**| Средняя стоимость привлечения на основе загрузок через нажатие. | | **Spend** | Общие расходы, атрибутированные варианту. | | **Revenue** | Общая выручка, атрибутированная варианту. | | **ROAS** | Рентабельность рекламных расходов: выручка, делённая на расходы. | Пока каждый вариант не наберёт сопоставимое количество показов, Adapty Ads Manager не выделяет победителя. Пока данные ещё поступают, над таблицей отображается баннер: **Winner highlighting is paused — variants don't have comparable impressions yet.** ### Подробные метрики \{#detailed-metrics\} Чтобы глубже изучить тест, нажмите **View metrics** — откроется страница с подробными метриками. На ней представлены кривые удержания когорт, сравнение ARPPU и таблица метрик, разбитая на два раздела: - **Top of funnel · Apple Search Ads**: TTR, Download Rate, CPM, CPT и Avg CPA по каждому варианту. - **Bottom of funnel · Monetization**: Paid users, Paid CR, Cost per Paid, ARPPU, Revenue и ROAS по каждому варианту. В столбце **Winner** отображается вариант, лидирующий по каждой метрике. Вариант признаётся общим победителем только тогда, когда он лидирует по основной метрике и достигает уровня достоверности не менее 95%. Определения метрик см. в разделе [Метрики в Adapty Ads Manager](adapty-ads-manager-metrics). ## Остановить тест \{#stop-a-test\} Тест можно остановить в любой момент. Он получит статус **Stopped**, исходная группа объявлений будет восстановлена, а клонированные группы — приостановлены. Чтобы остановить запущенный тест: 1. Перейдите в раздел **CPP A/B Tests** в левом сайдбаре. 2. Нажмите **Stop A/B test** на карточке теста или откройте тест и нажмите **Stop Test**. 3. Подтвердите действие в диалоге **Stop A/B Test?**. :::important Остановка теста необратима — возобновить его не получится. Собранные к этому моменту результаты останутся доступны на вкладке **Completed**. ::: ## Статусы тестов \{#test-statuses\} Каждый тест проходит через фиксированный набор статусов: | Статус | Значение | |---------------|------------------------------------------------------------------------------------------| | **Draft** | Тест создан, но не запущен. Его можно отредактировать. | | **Starting** | Идёт настройка — система клонирует группы объявлений и создаёт объявления. | | **Running** | Тест запущен. Варианты чередуются, метрики собираются. | | **Completed** | Запланированная длительность истекла или достигнута уверенность. Исходная группа объявлений восстановлена. | | **Stopped** | Вы остановили тест вручную. Исходная группа объявлений восстановлена. | | **Failed** | Настройка не удалась или произошло слишком много последовательных ошибок. Неудавшийся тест можно перезапустить. | ## Как это работает \{#how-it-works\} Adapty Ads Manager использует метод **Ad Group Switch**: 1. Когда тест запускается, система клонирует исходную группу объявлений по одному разу для каждого варианта. Каждый клон ведёт на отдельный CPP (один из них может быть вашей страницей по умолчанию). 2. В каждый момент времени активен только один клон. Система по фиксированному расписанию переключает активный клон (ежечасно, ежедневно или еженедельно). 3. На время теста исходная группа объявлений приостанавливается. По завершении теста она возвращается в прежнее состояние. 4. Adapty Ads Manager собирает данные о показах, нажатиях и загрузках по каждому варианту и отслеживает, насколько каждый вариант приближается к статистически значимой выборке. 5. Тест завершается автоматически, как только по каждому варианту накоплено достаточно данных, или по истечении запланированного срока. ## Чего ожидать во время работы теста \{#what-to-expect-while-a-test-runs\} Вот несколько важных моментов о том, как работающий тест отображается на дашборде: - **Варианты не переключаются по жёсткому расписанию**: интервал переключения — это базовый ориентир, но Adapty Ads Manager корректирует время так, чтобы каждый вариант набрал справедливую долю показов. Вариант может оставаться активным дольше одного слота, если отстаёт по показам. - **Время окончания может сдвинуться вперёд**: если у варианта не хватает данных к плановому завершению, тест автоматически продлевается для сбора дополнительных кликов. Новое время окончания отображается на карточке теста. - **По завершении теста исходная группа объявлений восстанавливается**: все клонированные группы объявлений ставятся на паузу, а исходная группа возвращается в статус, который был у неё до теста. Результаты остаются доступны на вкладке **Completed**. --- # File: ads-manager-settings --- --- title: "Настройки в Adapty Ads Manager" description: "Настройте параметры в Adapty Ads Manager." --- Перейдите в **Settings** в левом нижнем углу дашборда Adapty Ads Manager, чтобы настроить параметры аккаунта. ## Группы кампаний \{#campaign-groups\} На вкладке **Campaign groups** вы можете видеть все аккаунты Apple Ads, подключённые к Adapty Ads Manager, и добавлять новые. Если подключить несколько аккаунтов Apple Ads, их аналитика будет агрегирована в едином дашборде Adapty Ads Manager. Чтобы добавить новый аккаунт Apple Ads, нажмите **Connect Apple Ads account** и следуйте [гайду](adapty-ads-manager-get-started). ## Управление подпиской \{#manage-subscription\} На вкладке **Manage subscription** вы можете просмотреть текущий тарифный план и обновить способ оплаты. ## Настройки пользователя \{#user-settings\} На вкладке **User settings** можно включить переключатель **Hide Paused by Default**. Когда он активен, приостановленные кампании, группы объявлений и ключевые слова скрываются — это помогает сосредоточиться на актуальных данных о производительности. Не включайте эту опцию, если вы часто экспериментируете с запуском и приостановкой кампаний, групп объявлений и ключевых слов, поскольку вам может потребоваться доступ к приостановленным элементам в любой момент. --- # File: adapty-user-acquisition --- --- title: "Adapty Attribution" description: "Устраните необходимость в MMP и считайте всю экономику приложения в одном месте." --- <CustomDocCardList ids={['user-acquisition', 'ua-analytics', 'ua-integrations', 'ua-tracking-links', 'ua-deferred-data']} /> Adapty Attribution — это решение для атрибуции, которое связывает рекламные кампании с установками приложения и доходом от подписок, объединяя данные рекламных платформ, трекинговых ссылок и вашего приложения. Оно предоставляет единый дашборд маркетинговой аналитики, в котором собраны все данные о привлечении пользователей. - Рассчитывайте ROAS (возврат на рекламные расходы) по всем каналам - Видьте всю экономику приложения в одном месте - Получайте точные данные атрибуции для взвешенных решений - Анализируйте эффективность когорт и поведение пользователей во времени :::tip Хотите узнать больше о том, как атрибуция Adapty может быть полезна для вас? [Забронируйте звонок](https://calendly.com/tnurutdinov-adapty/30min) с нами. ::: ## Почему стоит выбрать Adapty Attribution? \{#why-choose-adapty-attribution\} Измерять эффективность привлечения пользователей непросто. Данные нередко разбросаны по разным платформам, атрибуция усложняется из-за изменений в политике конфиденциальности, а создание собственных решений требует значительных временных затрат. Adapty Attribution предоставляет встроенную атрибуцию и единую аналитику в одном маркетинговом дашборде. Все ваши метрики привлечения пользователей — от рекламных расходов до установок и дохода от подписок — автоматически агрегируются и обновляются в режиме реального времени. Больше никакой сверки данных в таблицах и переключения между инструментами. Вы можете сосредоточиться на росте приложения, а не на управлении данными. ## Как это работает \{#how-it-works\} Adapty Attribution связывает установки приложения и доход от подписок с рекламными кампаниями, объединяя данные из рекламных платформ, трекинговых ссылок и вашего приложения. В общих чертах: - Рекламные платформы предоставляют структуру кампаний и данные о расходах - Трекинговые ссылки, созданные в Adapty Attribution, передают контекст кампании — от посещения сайта до установки приложения - SDK отправляет события установки и покупок из вашего приложения Флоу атрибуции работает следующим образом: 1. **Трекинговая ссылка создаётся в Adapty Attribution и добавляется в рекламную кампанию.** Ссылка содержит параметры кампании: платформу, кампанию, группу объявлений и креатив. 2. **Пользователь нажимает на объявление и устанавливает приложение из стора.** Пользователь переходит по трекинговой ссылке и устанавливает приложение из App Store или Google Play. 3. **Приложение отправляет событие установки в Adapty.** При первом запуске SDK отправляет событие установки. Adapty извлекает параметры кампании, связанные с этой установкой. 4. **Установка атрибутируется к кампании.** Используя параметры кампании из ссылки отслеживания, Adapty связывает установку с кампанией, которая её сгенерировала. 5. **Расходы на рекламу и выручка связываются.** Adapty получает данные о расходах на рекламу от поддерживаемых рекламных платформ (в настоящее время — Meta Ads и TikTok for Business) и связывает события подписок и покупок с атрибутированными установками. В результате Adapty предоставляет метрики на уровне кампании — количество установок, выручку, LTV и ROAS — в едином аналитическом дашборде. Вы можете анализировать когорты, отслеживать эффективность во времени и принимать решения на основе данных — без ручного сведения информации из разных источников. :::tip Ссылки отслеживания также могут содержать пользовательские параметры, что позволяет приложению обрабатывать [отложенные диплинки](ua-deferred-data) и реагировать на данные кампании при обработке события установки. ::: --- # File: user-acquisition --- --- title: "Начало работы с Adapty Attribution" description: "Подключитесь к Adapty Attribution, чтобы объединить расходы на рекламу и доходы от подписок и увидеть всю экономику приложения в одном месте." --- Adapty Attribution помогает связать расходы на рекламу с доходами от подписок в кампаниях web-to-app, давая полное представление об экономике приложения в одном месте. Чтобы видеть данные о доходах в Adapty Attribution, сначала нужно включить интеграцию в дашборде Adapty. Передавать API-ключи, токены или идентификаторы не требуется. Просто обновите и настройте SDK. :::important Адаптивная атрибуция Adapty доступна при использовании: - iOS, Android и Flutter SDK версии 3.9.1 и выше. - React Native и Capacitor SDK версии 3.10.0 и выше. - Unity SDK версии 3.12.0 и выше. - Kotlin Multiplatform SDK версии 3.15.0 и выше. ::: ## Прежде чем начать \{#before-you-start\} Чтобы связать данные о доходах с эффективностью кампаний, позвольте Adapty отслеживать ваши покупки: - Если вы **уже реализовали встроенные покупки с помощью Adapty**, на этом этапе ничего дополнительно делать не нужно. - Если вы **ещё не реализовали встроенные покупки и хотите использовать Adapty**, выполните шаги из [руководства по быстрому старту](quickstart), чтобы делегировать обработку покупок Adapty. - Если вы **уже реализовали встроенные покупки без Adapty** и не планируете переходить на Adapty, [установите SDK для вашей платформы в режиме наблюдателя](implement-observer-mode). На этом этапе нужно только добавить SDK в проект, активировать его с включённым режимом наблюдателя и передавать транзакции: Эта настройка обеспечивает атрибуцию из веба в приложение: - Когда пользователи устанавливают ваше приложение, SDK получает детали установки из параметров ссылки, чтобы Adapty Attribution мог получить данные о кампании - SDK знает обо всех событиях, связанных с доходом внутри приложения, и может атрибутировать их к веб-кампаниям. ## Шаг 1. Откройте Adapty Attribution \{#step-1-open-adapty-attribution\} :::important Если **Attribution** не появляется при нажатии на логотип Adapty, очистите файлы cookies и данные сайта adapty.io в настройках браузера, затем обновите страницу. ::: Нажмите на логотип Adapty в шапке и выберите **Attribution**. События подписок начнут поступать в Adapty Attribution автоматически. Данные кампаний появятся после того, как вы подключите источник данных на шаге 2. Чтобы приостановить доставку событий, откройте **Integrations > Adapty** в дашборде Adapty и выключите переключатель. ### Поддерживаемые события \{#supported-events\} По умолчанию Adapty отправляет в User Acquisition три группы событий: - Пробные периоды (Trials) - Подписки - Проблемы (Issues) Полный список поддерживаемых событий можно найти [здесь](events). <img src="/assets/shared/img/events-ua.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 2. Подключите рекламную платформу и добавьте ссылки для отслеживания \{#step-2-connect-your-ad-platform-and-add-tracking-links\} Adapty использует ссылки для отслеживания, чтобы связать установки приложения с данными кампаний. Ссылку для отслеживания нужно использовать в качестве целевого URL в каждой рекламной кампании, результаты которой вы хотите измерять в Adapty Attribution. Если вы размещаете рекламу на нескольких платформах, настройте ссылки для отслеживания для каждой платформы отдельно. Adapty работает с рекламными платформами двумя способами: - **Нативные интеграции (Meta Ads, TikTok Ads).** Adapty подключается напрямую к рекламной платформе. Трекинговые ссылки создаются автоматически, а параметры кампании заполняются динамически в зависимости от того, где используется ссылка. Одну и ту же ссылку можно использовать в разных кампаниях, группах объявлений или креативах — Adapty автоматически получит актуальные данные о кампании и расходах на рекламу. - **Только трекинговые ссылки (все остальные рекламные платформы).** Adapty не подключается к рекламной платформе напрямую. Трекинговые ссылки создаются вручную, и все параметры кампании нужно задавать явно при создании ссылки. Данные о расходах на рекламу для этих платформ недоступны. <Tabs> <TabItem value="meta" label="Meta Ads" default> Чтобы создать трекинговую ссылку для Meta Ads: 1. Перейдите в [Integrations > Meta](https://app.adapty.io/ua/integrations/facebook/accounts) в дашборде атрибуции Adapty и нажмите **Continue with Facebook**. 2. Войдите с помощью аккаунта Facebook и нажмите **Continue**. 3. Ознакомьтесь с запрашиваемыми разрешениями и нажмите **Save**. 4. Перейдите на вкладку **Web campaigns** и нажмите **Create campaign**. Выберите приложение и нажмите **Save**. 5. На вкладке **General** раскройте раздел **iOS** и/или **Android** и вставьте URL-адреса приложения из App Store и/или Google Play. Затем нажмите **Save**. 6. Скопируйте значение поля **Click link** для **одной ссылки** или для ссылки под конкретную платформу. Затем откройте ваше объявление в Meta Ads Manager и вставьте эту ссылку в качестве URL назначения. :::important В поле **Website URL** вставьте `https://api-ua.adapty.io/api/v1/attribution/click`. Остальную часть ссылки вставьте в поле **URL parameters** в разделе **Tracking**. Это поможет вашему объявлению в Meta пройти модерацию. Подробнее см. [рекомендации по настройке объявлений в Meta Ads Manager](meta-create-campaign). ::: 7. Теперь, когда вы запустите рекламу в Meta Ads, её данные станут доступны для анализа в дашборде Adapty Attribution. </TabItem> <TabItem value="tiktok" label="TikTok for Business"> Чтобы создать ссылку отслеживания для TikTok for Business: 1. Перейдите в [Integrations > TikTok Ads](https://app.adapty.io/ua/integrations/tiktok/accounts) в дашборде Adapty Attribution и нажмите **Continue with TikTok**. 2. Войдите с помощью своего аккаунта TikTok и нажмите **Continue**. 3. Ознакомьтесь с запрашиваемыми разрешениями и нажмите **Save**. 4. Перейдите на вкладку **Web campaigns** и нажмите **Create campaign**. Выберите приложение и нажмите **Save**. 5. На вкладке **General** разверните раздел **iOS** и/или **Android** и вставьте URL приложения из App Store и/или Google Play. Затем нажмите **Save**. 6. Скопируйте значение поля **Click link** для **одной ссылки** или для платформенной ссылки. Затем в TikTok Ads Manager при создании объявления вставьте это значение в поле **Tracking URL** в разделе **Advanced Settings**. Это позволит Adapty связывать установки и покупки с рекламой в TikTok. Смотрите [гайд по настройке кампании в TikTok Ads](tiktok-create-campaign). 7. Теперь, когда вы запустите рекламу в TikTok for Business, её данные станут доступны для анализа в дашборде атрибуции Adapty. </TabItem> <TabItem value="others" label="Другие рекламные платформы"> Чтобы создать трекинговую ссылку для других рекламных платформ: 1. В дашборде Adapty Attribution перейдите в **Tracking links** через боковое меню и нажмите **Create link**. 2. Выберите приложение из списка и нажмите **Next**. 3. Заполните параметры ссылки, чтобы привязать её к нужной кампании и объявлению. 4. По умолчанию создаётся One Link — он автоматически определяет платформу пользователя и перенаправляет его в App Store или Google Play после отслеживания клика. Если вы хотите использовать отдельные URL переадресации для каждой платформы, снимите флажок **One Link** и укажите ссылки на сторы для каждой платформы вручную. 5. Нажмите **Create**. 6. Откройте страницу трекинговой ссылки и скопируйте **Click link** из одного из разделов: - **One link** — используйте эту ссылку для отслеживания кликов и автоматического перенаправления пользователей в нужный стор. - **iOS link** или **Android link** — опциональные версии для каждой платформы отдельно, если вам нужны разные ссылки для каждого стора. 7. Перейдите на рекламную платформу и вставьте ссылку в объявление в качестве URL назначения. </TabItem> </Tabs> ## Шаг 3. Запустите кампанию web-to-app и смотрите результаты \{#step-3-launch-your-web-to-app-campaign-and-view-results\} Как только кампания запущена и пользователи начинают устанавливать приложение, Adapty начинает атрибутировать установки и доходы к вашим кампаниям. В [дашборде аналитики Adapty UA](ua-analytics) вы увидите метрики на уровне кампании: - Установки и конверсии - Доход от подписок и покупок - Разбивка показателей по рекламной платформе, кампании, группе объявлений и креативу Метрики появляются сразу после получения событий об установках и доходах из вашего приложения. Данные о расходах на рекламу доступны для платформ с нативными интеграциями. ## Узнать больше \{#learn-more\} Продолжите изучение с подробной документацией по аналитике атрибуции Adapty и практическими гайдами по запуску кампаний на крупнейших рекламных платформах: - [**Аналитика в Adapty Attribution**](ua-analytics): Как эффективно использовать дашборд аналитики. - [**Метрики в Adapty Attribution**](ua-metrics): Метрики, доступные для анализа привлечения пользователей. - [**Интеграции**](ua-integrations): Рекламные платформы и интеграции, поддерживаемые Adapty Attribution. - [**Запуск рекламы в Meta Ads Manager**](meta-create-campaign): Как настроить и запустить кампании в Meta Ads Manager. - [**Запуск рекламы в TikTok for Business**](tiktok-create-campaign): Как настроить и запустить кампании в TikTok for Business. --- # File: ua-metrics --- --- title: "Метрики в Adapty Attribution" description: "Узнайте о метриках, доступных в Adapty Attribution." --- Adapty Attribution предоставляет исчерпывающий набор **метрик** для оценки эффективности кампаний и поведения пользователей. Метрики доступны как стандартные значения, а некоторые из них также представлены в виде **когортных метрик** для анализа групп пользователей в динамике. ## Стандартные метрики \{#standard-metrics\} | **Метрика** | Описание | Когорта | |----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| | **Spend** | Суммарные расходы на клики пользователей по вашей рекламе. | Нет | | **Impressions** | Количество показов вашей рекламы за выбранный период. | Нет | | **Clicks** | Количество кликов пользователей по вашей рекламе за отчётный период. | Нет | | **CPI** | **CPI (Cost per Install)** — сумма, которую вы платите за каждую установку. <br/>**Формула**: `Spend / Installs` | Нет | | **CPC** | **CPC (Cost per Click)** — сумма, которую вы платите за каждый клик по рекламе. <br/>**Формула**: `Spend / Clicks` | Нет | | **CPM** | **CPM (Cost per Mille)** — сумма, которую вы платите за тысячу показов рекламы. <br/>**Формула**: `Spend / (Impressions / 1000)` | Нет | | **ICR** | **ICR (Install Conversion Rate)** — доля кликов по рекламе, завершившихся установкой. <br/>**Формула**: `(Installs / Clicks) × 100%` | Нет | | **IPM** | **IPM (Installs per Mille)** — количество установок на тысячу показов рекламы. <br/>**Формула**: `(Installs / Impressions) × 1000` | Нет | | **CTR** | **CTR (Click-Through Rate)** — доля показов, завершившихся кликом. <br/>**Формула**: `(Clicks / Impressions) × 100%` | Нет | | **Inline link clicks** | Количество кликов пользователей по встроенным ссылкам в рекламных материалах или странице приложения. | Нет | | **Cost per inline link click** | Средняя сумма, которую вы платите за один клик по встроенной ссылке. <br/>**Формула**: `Spend / Inline Link Clicks` | Нет | | **Inline link click CTR** | Доля показов, завершившихся кликом по встроенной ссылке. <br/>**Формула**: `(Inline Link Clicks / Impressions) × 100%` | Нет | | **Installs** | Общее количество пользователей, установивших приложение (включая повторные установки) за отчётный период. | Нет | | **Revenue** | Общая сумма дохода от покупок, связанных с этой кампанией (до вычета комиссии стора), за выбранный период. | Да | | **ROAS** | **ROAS (Return on Ad Spend)** — отношение дохода от рекламы к рекламным расходам, выраженное в процентах. <br/>**Формула**: `(Revenue / Spend) × 100%, если Spend > 0, иначе 0%` | Да | | **ARPU** | **ARPU (Average Revenue per User)** — средний доход на пользователя в когорте. <br/>**Формула**: `Revenue / Users` | Да | | **LTV** | **LTV (Lifetime Value)** — средний доход, приходящийся на одного пользователя за всё время. <br/>**Формула**: `Revenue / Installs` | Нет | | **Cost per trial** | Средняя сумма, которую вы платите за каждый запущенный триал. <br/>**Формула**: `Spend / Count trial started` | Нет | | **Cost per subscription** | Средняя сумма, которую вы платите за каждую оформленную подписку. <br/>**Формула**: `Spend / Count subscription started` | Нет | | **Count subscription events** | Группа метрик для подсчёта событий, связанных с подпиской, за отчётный период. Метрики: <br/>- Count subscription started<br/>- Count subscription renewed<br/>- Count subscription renewal cancelled<br/>- Count subscription renewal reactivated<br/>- Count subscription expired<br/>- Count [subscription deferred](https://adapty.io/glossary/subscription-purchase-deferral/)<br/>- Count subscription refunded | Да | | **Count trial events** | Группа метрик для подсчёта событий, связанных с триалом, за отчётный период. Метрики: <br/>- Count trial started<br/>- Count trial converted<br/>- Count trial expired<br/>- Count trial renewal reactivated | Да | | **Count billing issue detected** | Количество проблем с оплатой, обнаруженных за отчётный период. | Да | | **Count entered grace period** | Количество подписок, перешедших в льготный период из-за проблем с оплатой. | Да | | **Count non-subscription events** | Группа метрик для подсчёта событий, не связанных с подпиской, за отчётный период. Метрики: <br/>- Count non-subscription purchased<br/>- Count non-subscription refunded | Да | | **Subscription events rate** | Метрики, показывающие долю событий, связанных с подпиской, относительно установок приложения за отчётный период. Метрики: <br/>- Rate subscription started<br/>- Rate subscription renewed<br/>- Rate subscription renewal cancelled<br/>- Rate subscription renewal reactivated<br/>- Rate subscription expired<br/>- Rate [subscription deferred](https://adapty.io/glossary/subscription-purchase-deferral/)<br/>- Rate subscription refunded | Да | | **Trial events rate** | Метрики, показывающие долю событий, связанных с триалом, относительно установок приложения за отчётный период. Метрики: <br/>- Rate trial started<br/>- Rate trial converted<br/>- Rate trial expired<br/>- Rate trial renewal reactivated | Да | | **Rate billing issue detected** | Доля проблем с оплатой относительно установок приложения за отчётный период. | Да | | **Rate entered grace period** | Доля подписок, перешедших в льготный период, относительно установок приложения за отчётный период. | Да | | **Non-subscription events rate** | Метрики, показывающие долю событий, не связанных с подпиской, относительно установок приложения за отчётный период. Метрики: <br/>- Rate non-subscription purchased<br/>- Rate non-subscription refunded | Да | ## Прогнозируемые метрики \{#predicted-metrics\} Прогнозируемые метрики показывают ожидаемые результаты когорты на основе исторических данных вашего приложения. Они доступны для нескольких периодов когорты: D30, D60, D90, D180 и D360, а также для произвольных периодов, которые можно задать в днях. О методологии расчёта читайте в разделе [Прогнозируемые метрики в Adapty Attribution](ua-predicted-metrics). | **Метрика** | Описание | Когорта | |---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------| | **pRevenue** | Прогнозируемая совокупная выручка, которую когорта ожидаемо сгенерирует к целевому горизонту. Моделируется на основе исторического удержания когорт приложения. | Да | | **pROAS** | Прогнозируемый возврат рекламных расходов за тот же горизонт. **Формула**: `(pRevenue / Spend) × 100%` | Да | | **pAdProfit** | Прогнозируемая выручка за вычетом рекламных расходов за горизонт. **Формула**: `pRevenue − Spend` | Да | | **pARPU** | Прогнозируемая средняя выручка на установку за горизонт (прогнозируемый LTV). **Формула**: `pRevenue / Installs` | Да | | **pARPPU** | Прогнозируемая средняя выручка на платящего пользователя за горизонт. **Формула**: `pRevenue / paying users at d{N}`, где `d{N}` соответствует выбранному горизонту. | Да | --- # File: ua-predicted-metrics --- --- title: "Прогнозируемые метрики в Adapty Attribution" description: "Прогнозирование выручки, ROAS, прибыли от рекламы и LTV для когорт в Adapty Attribution." --- :::important Эта статья описывает прогнозы в разделе Adapty Attribution. Информация о прогнозируемых LTV и выручке на странице анализа когорт — в статье [Прогнозы в когортах](predicted-ltv-and-revenue). ::: Adapty Attribution прогнозирует будущую выручку и юнит-экономику для каждой когорты, чтобы вы могли сравнивать кампании до того, как они успеют созреть. Прогнозы строятся на основе исторических данных по когортам вашего приложения и обновляются ежедневно. Они особенно полезны для оценки свежих когорт, которые ещё не завершили длинные циклы подписок. ## Прогнозируемые метрики \{#predicted-metrics\} | **Метрика** | Описание | |---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **pRevenue** | Прогнозируемая общая выручка, которую когорта должна принести к целевому горизонту. Моделируется на основе исторического удержания когорт приложения. | | **pROAS** | Прогнозируемая рентабельность рекламных расходов за тот же горизонт. **Формула**: `(pRevenue / Spend) × 100%` | | **pAdProfit** | Прогнозируемая выручка за вычетом рекламных расходов за горизонт. **Формула**: `pRevenue − Spend` | | **pARPU** | Прогнозируемая средняя выручка на установку за горизонт (прогнозируемый LTV). **Формула**: `pRevenue / Installs` | | **pARPPU** | Прогнозируемая средняя выручка на платящего пользователя за горизонт. **Формула**: `pRevenue / paying users at d{N}`, где `d{N}` соответствует выбранному горизонту. | `pRevenue` — базовое значение. Остальные четыре метрики рассчитываются на его основе с использованием наблюдаемых данных когорты — расходов, установок и платящих пользователей — а не путём отдельного запуска модели. Каждая прогнозная метрика доступна для нескольких периодов когорты: D0, D3, D7, D30, D60, D90, D180 и D360. Можно также добавить произвольный период в днях. Период определяет, насколько далеко в будущее проецируется значение относительно даты установки когорты. ## Как рассчитываются прогнозы \{#how-predictions-are-calculated\} Прогнозы строятся на основе исторических когорт каждого конкретного приложения. Модель измеряет, как росла выручка прошлых когорт после базового дня, и проецирует текущую когорту вперёд по той же траектории. ### Исходный день \{#baseline-day\} Прогнозы становятся доступны только после того, как когорта достигает своего исходного дня. Исходный день — это первый день, к которому обычно поступает 90% начальной выручки когорты. В начальную выручку засчитываются старты подписок, конверсии триалов и разовые покупки; продления в этот порог не включаются. Исходный день зависит от длины триала в приложении и состава продуктов: - **Приложения без триалов**: базовый день обычно приходится на первые несколько дней после установки. - **Приложения с короткими триалами**: базовый день, как правило, наступает вскоре после завершения триала. - **Приложения с длинными триалами**: базовый день может быть установлен через неделю и более после установки, поскольку основная часть выручки появляется только после окончания триала. ### Прогноз по типам подписки \{#projection-by-subscription-type\} На базовый день начальная выручка когорты разбивается на пять категорий — ежемесячные, годовые, еженедельные и квартальные подписки, а также разовые покупки. Каждая категория прогнозируется отдельно на основе траектории, рассчитанной по историческим когортам приложения. Модель придаёт больший вес недавним когортам и когортам со схожей экономикой — близкой выручкой на транзакцию и аналогичным набором продуктов. Таким образом, прогноз отражает реальные результаты наиболее свежих и наиболее похожих когорт приложения. ## Когда доступны прогнозы \{#when-predictions-are-available\} Прогноз отображается только при наличии достаточного количества данных в когорте. Если значение не может быть рассчитано, в столбце отображается тире (`—`). Распространённые причины недоступности прогноза: - **Когорта ещё не достигла базового дня**: модели нужно, чтобы начальная выручка когорты стабилизировалась, прежде чем строить прогноз. - **Недостаточно исторических данных по приложению**: если у приложения нет достаточного количества прошлых когорт с нужным типом подписки, модель не может подобрать надёжные коэффициенты удержания. Прогнозы пересчитываются ежедневно на основе актуальных транзакционных данных, поэтому значения для одной и той же когорты могут меняться по мере поступления новых данных о выручке. --- # File: ua-tracking-links --- --- title: "Ссылки для отслеживания в Adapty Attribution" description: "Отслеживайте свои кампании и измеряйте их эффективность." --- Трекинговые ссылки позволяют измерять источники трафика и связывать установки с рекламными кампаниями. Когда пользователь нажимает на вашу рекламу, Adapty фиксирует клик и затем сопоставляет его с событием установки, отправленным через SDK. Таким образом, вы можете видеть, какие каналы, кампании, группы объявлений и объявления приносят наибольший доход на вашей [странице аналитики](ua-analytics). Вы можете создать два типа трекинговых ссылок: - **Единая ссылка** — универсальная ссылка, которая автоматически определяет платформу пользователя, фиксирует клик и перенаправляет в App Store или Google Play. - **Ссылки для конкретного стора** — ссылки с таргетингом по платформе, которые одновременно фиксируют клик и автоматически перенаправляют пользователей в App Store или Google Play. К ним также можно добавлять параметры отложенных диплинков. ## Создание трекинговых ссылок \{#create-tracking-links\} Чтобы создать трекинговую ссылку: 1. В дашборде Adapty Attribution перейдите в раздел **Tracking links** в боковом меню и нажмите **Create link**. 2. Выберите приложение из списка и нажмите **Next**. 3. Заполните параметры ссылки, чтобы привязать её к нужной кампании и объявлению, которые вы хотите отслеживать. | Параметр | Описание | |-------------------|-------------------------------------------------------------------------------------------------------------| | **Name** | Внутреннее название трекинговой ссылки. | | **Channel** | Источник трафика, например Meta, Reddit или TikTok. Используется для группировки кампаний в аналитике. | | **Campaign ID** | Уникальный идентификатор кампании на вашей рекламной платформе. | | **Campaign name** | Читаемое название кампании. | | **Ad set ID** | Уникальный идентификатор группы объявлений на вашей рекламной платформе. | | **Ad set name** | Название группы объявлений. | | **Ad ID** | Уникальный идентификатор отдельного рекламного креатива. | | **Ad name** | Название рекламного креатива или варианта. | 4. По умолчанию создаётся One Link. Он автоматически определяет платформу пользователя и перенаправляет его в App Store или Google Play после отслеживания клика. Если вы хотите использовать отдельные URL перенаправления для каждой платформы, снимите флажок **One Link** и укажите ссылки на страницы в сторах вручную для каждой платформы. 5. Нажмите **Create**. 6. Откройте страницу вашей трекинговой ссылки и скопируйте **Click link** из одного из разделов: - **One link** – используйте эту ссылку для отслеживания кликов и автоматического перенаправления пользователей в нужный стор. - **iOS link** или **Android link** — опциональные версии для конкретных платформ, если вы хотите использовать отдельные ссылки для каждого стора. :::tip Также можно задать дополнительные параметры ссылки для [работы с отложенными данными](ua-deferred-data). Например, реализовать отложенный диплинкинг. ::: 7. Перейдите в рекламную платформу и вставьте ссылку в качестве целевого URL объявления. Теперь установки приложения будут сопоставляться с объявлениями и кампаниями, из которых они поступают, — так вы сможете оценить эффективность кампаний на странице **Analytics**. --- # File: ua-deferred-data --- --- title: "Отложенные диплинки в Adapty Attribution" description: "Настройте отложенные диплинки в Adapty Attribution." --- Отложенные диплинки позволяют передавать пользовательские данные в приложение, когда пользователи устанавливают его после клика по рекламе. Например, можно сразу после установки и первого запуска перенаправить их в нужный раздел приложения. Вот как это работает: 1. Когда пользователь кликает по рекламе, Adapty сохраняет данные клика. 2. Когда Adapty фиксирует событие установки, он получает отложенные данные из этого клика. 3. После того как пользователь устанавливает приложение и запускает его в первый раз, Adapty извлекает сохранённые данные, и приложение получает пользовательские параметры — это позволяет реагировать на их значения в коде приложения. Adapty поддерживает следующие параметры отложенных данных: - `ios_deferred_data` - `android_deferred_data` - `deferred_data_sub[1-10]` Чтобы добавить параметры отложенных данных, добавьте их к вашей ссылке в настройках кампании: 1. Откройте вашу кампанию на странице **Integrations -> Meta/TikTok Ads**. Или откройте вашу ссылку отслеживания на странице **Tracking links**. Скопируйте ссылку для клика, которую вы будете использовать в кампании. 2. В рекламной платформе (Meta, TikTok, Google Ads и т. д.) вставьте ссылку в поле URL назначения объявления, а затем добавьте к ней параметры отложенных данных в виде дополнительных query-параметров — каждый с префиксом `&`. Например, чтобы показать iOS-пользователям экран «Welcome» после установки, добавьте `&ios_deferred_data=welcome`. Итоговый URL назначения будет выглядеть так: ``` https://api-ua.adapty.io/api/v1/attribution/click?adpt_cid=__ADAPTY__ID__&ios_deferred_data=welcome&campaign_id=__CAMPAIGN_ID__&adset_id=__AID__&ad_id=__CID__&campaign_name=__CAMPAIGN_NAME__&adset_name=__AID_NAME__&ad_name=__CID_NAME__&redirect_url=__APP_LINK__ ``` 3. Обрабатывайте параметры в коде приложения. Обратите внимание, что параметры отложенных данных передаются в параметре `payload`, который является экранированным JSON — его нужно разобрать в коде приложения. Например, вот как можно обработать установки, где `ios_deferred_data` равен `welcome`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers Adapty.delegate = self nonisolated func onInstallationDetailsSuccess(_ details: AdaptyInstallationDetails) { guard let payloadStr = details.payload, let data = payloadStr.data(using: .utf8), let payload = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let deeplink = payload["ios_deferred_data"] as? String, deeplink == "welcome" else { return } DispatchQueue.main.async { print("Navigate to welcome screen") // navigate to your screen here } } ``` </TabItem> <TabItem value="android" label="Kotlin"> ```kotlin showLineNumbers Adapty.setOnInstallationDetailsListener(object : OnInstallationDetailsListener { override fun onInstallationDetailsSuccess(details: AdaptyInstallationDetails) { details.payload?.let { runCatching { val json = JSONObject(it) if (json.optString("android_deferred_data") == "welcome") { println("Navigate to welcome screen") // navigate here } }.onFailure(Throwable::printStackTrace) } } }) ``` </TabItem> <TabItem value="rn" label="React Native" default> ```typescript showLineNumbers adapty.addEventListener('onInstallationDetailsSuccess', details => { // Parse the payload JSON and navigate to welcome screen if needed try { if (details.payload) { const payload = JSON.parse(details.payload); if (payload.ios_deferred_data === 'welcome') { // Navigate to welcome screen // Replace with your app's navigation logic // For example, using React Navigation: // navigation.navigate('Welcome'); console.log('Navigate to welcome screen'); } } } catch (error) { console.error('Error parsing installation details payload:', error); } }); ``` </TabItem> <TabItem value="flutter" label="Flutter"> ```dart showLineNumbers Adapty().onUpdateInstallationDetailsSuccessStream.listen((details) { final payloadStr = details.payload; if (payloadStr == null) return; final payload = json.decode(payloadStr) as Map<String, dynamic>; if (payload['ios_deferred_data'] == 'welcome') { print('Navigate to welcome screen'); } }); ``` </TabItem> </Tabs> --- # File: ua-attribution-data --- --- title: "Получение данных атрибуции в приложении" description: "Получите данные атрибуции кампаний в своём приложении после того, как Adapty сопоставит установку с кампанией." --- Когда Adapty сопоставляет установку с кампанией, он возвращает данные атрибуции в приложение через коллбэк `onInstallationDetailsSuccess`. Используйте эти данные, чтобы персонализировать пользовательский опыт в зависимости от канала или кампании, которая привела к установке. Данные атрибуции возвращаются в виде вложенного объекта `attribution` внутри поля `payload`. Он содержит следующие поля: | Поле | Описание | |---|---| | `channel` | Канал привлечения (например, `facebook`, `tiktok`, `google`, `organic`) | | `campaign_id` | Идентификатор кампании | | `campaign_name` | Название кампании | | `adset_id` | Идентификатор группы объявлений | | `adset_name` | Название группы объявлений | | `ad_id` | Идентификатор объявления / креатива | | `ad_name` | Название объявления / креатива | Все поля необязательны. Для органических установок или когда атрибуцию определить не удалось, поле `payload` не содержит объект `attribution`. Чтобы считать данные атрибуции в приложении: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers Adapty.delegate = self nonisolated func onInstallationDetailsSuccess(_ details: AdaptyInstallationDetails) { guard let payloadDict = details.payload?.dictionary, let attribution = payloadDict["attribution"] as? [String: Any] else { return } let channel = attribution["channel"] as? String let campaignName = attribution["campaign_name"] as? String let adName = attribution["ad_name"] as? String print("Channel: \(channel ?? "organic")") } ``` </TabItem> <TabItem value="android" label="Kotlin"> ```kotlin showLineNumbers Adapty.setOnInstallationDetailsListener(object : OnInstallationDetailsListener { override fun onInstallationDetailsSuccess(details: AdaptyInstallationDetails) { val payloadStr = details.payload ?: return runCatching { val payload = JSONObject(payloadStr) val attribution = payload.optJSONObject("attribution") ?: return val channel = attribution.optString("channel") val campaignName = attribution.optString("campaign_name") val adName = attribution.optString("ad_name") println("Channel: $channel") }.onFailure(Throwable::printStackTrace) } }) ``` </TabItem> <TabItem value="rn" label="React Native"> ```typescript showLineNumbers adapty.addEventListener('onInstallationDetailsSuccess', details => { try { if (!details.payload) return; const payload = JSON.parse(details.payload); const attribution = payload.attribution; if (!attribution) return; const channel = attribution.channel; const campaignName = attribution.campaign_name; const adName = attribution.ad_name; console.log('Channel:', channel ?? 'organic'); } catch (error) { console.error('Error parsing payload:', error); } }); ``` </TabItem> <TabItem value="flutter" label="Flutter"> ```dart showLineNumbers Adapty().onUpdateInstallationDetailsSuccessStream.listen((details) { final payloadStr = details.payload; if (payloadStr == null) return; final payload = json.decode(payloadStr) as Map<String, dynamic>; final attribution = payload['attribution'] as Map<String, dynamic>?; if (attribution == null) return; final channel = attribution['channel'] as String?; final campaignName = attribution['campaign_name'] as String?; final adName = attribution['ad_name'] as String?; print('Channel: ${channel ?? 'organic'}'); }); ``` </TabItem> </Tabs> --- # File: ua-facebook --- --- title: "Интеграция Meta Ads с Adapty Attribution" description: "Подключите Meta Ads к Adapty Attribution для отслеживания и оптимизации эффективности кампаний в Facebook, Instagram, Messenger и Audience Network." --- Интеграция Adapty Attribution с Meta позволяет отслеживать и оптимизировать эффективность кампаний в Facebook, Instagram, Messenger и Audience Network. :::tip Смотрите наш [гайд по настройке рекламы в Meta Ads Manager](meta-create-campaign). ::: ## Шаг 1. Подключите аккаунт Facebook \{#step-1-connect-your-facebook-account\} Чтобы подключить Meta Ads к атрибуции Adapty, перейдите в **Integrations > Meta** в левом сайдбаре. Доступны два варианта: - **Continue with Facebook**: подключение через OAuth. Используйте этот вариант, если вы входите в Meta Ads Manager через личный или рабочий аккаунт Facebook. - **Add system token**: подключение через постоянный токен системного пользователя. Используйте этот вариант, если ваша организация управляет рекламными аккаунтами через системного пользователя Meta Business. <Tabs> <TabItem value="oauth" label="Continue with Facebook"> :::important Убедитесь, что ваш аккаунт Facebook имеет доступ к нужным кампаниям и пикселям. ::: 1. Нажмите **Continue with Facebook**. 2. Войдите с помощью аккаунта Facebook и нажмите **Continue**. 3. Проверьте запрошенные разрешения и нажмите **Save**. </TabItem> <TabItem value="system" label="Add system token"> Создайте [токен доступа системного пользователя](https://developers.facebook.com/documentation/ads-commerce/marketing-api/collaborative-ads/managed-partner-ads/api-guide/prerequisites/generate-access-token-system-user) в настройках Meta Business, затем добавьте его в Adapty Attribution. :::important Перед началом убедитесь, что в вашем портфолио Meta Business есть [системный пользователь](https://www.facebook.com/business/help/503306463479099), которому уже назначены рекламные аккаунты для отслеживания. Также необходимо добавить приложение в портфолио — вы выбираете это приложение при генерации токена. ::: **В настройках Meta Business сгенерируйте токен:** 1. Откройте **Business Settings**. 2. В разделе **Users** выберите **System users**. 3. Выберите системного пользователя и нажмите **Generate new token**. 4. Выберите ваше приложение из выпадающего списка. 5. В списке разрешений включите `ads_read`. Это единственное разрешение, необходимое Adapty Attribution для чтения данных о кампаниях и рекламе. 6. Нажмите **Generate token**. 7. Скопируйте токен и сохраните его в надёжном месте. Meta показывает его только один раз. :::note Настройка **Token expiration** определяет, как долго остаётся активным соединение. Токен с датой истечения нужно пересоздавать и переподключать до того, как он истечёт, иначе атрибуция прекратится. Токен без срока действия избавляет от этой проблемы, но является долгоживущей учётной записью. Храните его в надёжном месте и отзывайте, если он окажется скомпрометирован. ::: **В Adapty Attribution добавьте токен:** 1. Нажмите **Add system token**. 2. Вставьте токен и нажмите **Connect**. </TabItem> </Tabs> После этого все ваши рекламные аккаунты будут добавлены в Adapty Attribution. Можно переходить к добавлению кампаний. ## Шаг 2. Добавьте кампании \{#step-2-add-campaigns\} Чтобы добавить кампанию Meta в Adapty Attribution и отслеживать эффективность рекламы Meta в Adapty: 1. Перейдите на вкладку **Web Campaigns** и нажмите **Create configuration**. 2. На вкладке **General** раскройте секцию **iOS** и/или **Android** и вставьте URL-адреса приложений из App Store и/или Google Play. 3. Скопируйте значение поля **Click link**. Затем в Meta Ads Manager откройте своё объявление и вставьте эту ссылку. Это позволит Adapty связать установки и покупки с рекламой в Meta. 4. Чтобы отправлять события конверсий обратно в Meta, вы можете привязать свои пиксели Meta к кампаниям в Adapty Attribution. Для этого выберите один из существующих пикселей в выпадающем списке **Pixel**. После выбора пикселя нажмите **Send test event**, чтобы проверить соединение. ## Шаг 3. Настройте события \{#step-3-map-events\} Чтобы отправлять конверсионные события обратно в Meta для оптимизации кампаний, необходимо настроить сопоставление событий в разделе **Events names**. Это позволяет Adapty автоматически отправлять события подписок в ваш пиксель Meta, когда пользователи совершают действия в приложении. В разделе **Events names** включите события, которые хотите отслеживать в Meta Ads Manager. Для каждого включённого события выберите соответствующее событие Meta из выпадающего списка или задайте собственное. По умолчанию Adapty сопоставляет события Adapty со стандартными событиями Meta. Нажмите **Save**, чтобы применить настройки сопоставления событий. ## Дополнительная настройка \{#additional-configuration\} ### Дополнительные параметры \{#additional-parameters\} Поле **Additional parameter** позволяет добавлять пользовательские данные для анализа за пределами Adapty. Это удобно, когда нужно передать конкретные данные о кампании или пользователе во внешние инструменты аналитики или партнёрам по атрибуции. В поле **Additional parameter** введите любые пользовательские данные, которые хотите включить в отслеживание атрибуции. Дополнительный параметр будет включён во все данные атрибуции, отправляемые в Meta, и может использоваться для расширенного анализа и оптимизации кампаний. Например, если вы запускаете несколько вариантов одной кампании, можно добавить `variant=A` или `variant=B`, чтобы различать разные креативные подходы. :::important Дополнительные параметры изменяют **Click link**, который вы вставляете в Meta Ads Manager. Если вы уже скопировали эту ссылку и добавили пользовательский параметр позже, убедитесь, что вы скопировали и вставили обновлённую click link, содержащую этот параметр. ::: <br/> ### Настройки \{#settings\} Вкладка **Settings** управляет тем, как Adapty сопоставляет действия пользователей с вашими кампаниями в Meta Ads. Эти настройки определяют временные окна для детерминированного и вероятностного сопоставления атрибуции. Чтобы настроить их, перейдите на вкладку **Settings** в конфигурации кампании. Здесь вы найдёте две основные настройки: - **Окно детерминированного совпадения**: использует точные идентификаторы устройства (например, IDFA на iOS или Advertising ID на Android) для точного сопоставления пользователей с кампаниями. Установите значение 168 часов (7 дней) для максимальной точности атрибуции — это значение по умолчанию и рекомендованное. Когда пользователь нажимает на вашу рекламу в Meta и устанавливает приложение в течение этого времени, Adapty может однозначно атрибутировать установку конкретному клику по объявлению с помощью идентификаторов устройства. - **Probabilistic matching window**: Использует статистическое моделирование и цифровой отпечаток устройства для сопоставления пользователей, когда детерминированное сопоставление невозможно. Для большинства кампаний установите значение 6 часов — это значение по умолчанию, которое хорошо работает в большинстве случаев. Для кампаний с высоким объёмом кликов можно сократить его до 1–2 часов. Для пользователей, которых не удаётся сопоставить детерминированно (из-за настроек конфиденциальности или других факторов), Adapty использует вероятностное сопоставление в рамках этого более короткого окна. Нажмите **Save**, чтобы применить настройки. ### Переопределение выручки \{#revenue-override\} Если вы отслеживаете события пробного периода и хотите, чтобы Meta атрибутировала к ним выручку, используйте раздел **Revenue override**. Он появляется, когда включено событие **Trial started**. Для каждого целевого события пробного периода задайте процент от стоимости подписки, который будет передаваться как выручка. Например, при значении 30% Adapty отправляет 30% от цены подписки в качестве значения конверсии для событий пробного периода. Чтобы добавить переопределение: 1. Включите **Trial started** в разделе **Events names**. 2. В разделе **Revenue override** нажмите **Add override**. 3. Выберите целевое событие и укажите процент дохода (0–100). 4. Нажмите **Save**. ### Отправка всех событий \{#send-all-events\} По умолчанию Adapty отправляет события на ваш пиксель только для пользователей, атрибутированных к кампании Meta. Включите **Send all events**, чтобы также передавать события от органических и неатрибутированных пользователей на пиксель. При включении этой опции каждое событие установки и транзакции отправляется на пиксель вне зависимости от атрибуции кампании. Используйте это, чтобы дать Meta более широкие данные о конверсиях для моделирования аудиторий и оптимизации кампаний. Чтобы включить эту опцию, в настройках кампании выберите **Send all events (forward organic/non-attributed events to this pixel)** и нажмите **Save**. --- # File: ua-tiktok --- --- title: "Интеграция TikTok for Business с Adapty Attribution" description: "Подключите TikTok for Business к Adapty Attribution для отслеживания и оптимизации эффективности кампаний в TikTok Ads Manager." --- Интеграция Adapty Attribution с TikTok for Business позволяет отслеживать и оптимизировать эффективность рекламных кампаний в TikTok. :::tip Смотрите наш [гайд по настройке рекламы в TikTok for Business](tiktok-create-campaign). ::: ## Шаг 1. Подключите аккаунт TikTok \{#step-1-connect-your-tiktok-account\} 1. Перейдите в **Integrations > TikTok Ads** в левом сайдбаре и нажмите **Continue with TikTok**. 2. Войдите в аккаунт TikTok и нажмите **Continue**. 3. Ознакомьтесь с запрашиваемыми разрешениями и нажмите **Save**. После этого все ваши рекламные аккаунты будут добавлены в Adapty UA. Можно переходить к добавлению кампаний. ## Шаг 2. Добавьте кампании \{#step-2-add-campaigns\} Чтобы добавить кампанию TikTok for Business в Adapty Attribution и отслеживать эффективность ваших TikTok-объявлений в Adapty: 1. Перейдите на вкладку **Web Campaigns** и нажмите **Create configuration**. 2. На вкладке **General** разверните раздел **iOS** и/или **Android** и вставьте URL-адреса приложений из App Store и/или Google Play. 3. Скопируйте значение из поля **Click link**. Затем в TikTok Ads Manager при создании объявления вставьте это значение в поле **Tracking URL** в разделе **Advanced Settings**. Это позволит Adapty связывать установки и покупки с рекламой в TikTok. 4. (Необязательно) Чтобы отправлять события конверсии обратно в TikTok, вы можете связать пиксели TikTok с кампаниями в Adapty Attribution. Для этого выберите один из существующих пикселей в выпадающем списке **Pixel**. После выбора пикселя нажмите **Send test event**, чтобы проверить соединение. ## Шаг 3. Настройте маппинг событий \{#step-3-map-events\} Чтобы отправлять конверсионные события обратно в TikTok для оптимизации кампаний, необходимо настроить маппинг событий в разделе **Events names**. Это позволяет Adapty автоматически отправлять события подписок в ваш пиксель TikTok, когда пользователи совершают действия в приложении. В разделе **Events names** включите события, которые хотите отслеживать в TikTok Ads Manager. Для каждого включённого события выберите соответствующее событие TikTok из выпадающего списка или задайте собственное. По умолчанию Adapty сопоставляет свои события со стандартными событиями TikTok. Нажмите **Save**, чтобы применить настройки сопоставления событий. ## Дополнительная настройка \{#additional-configuration\} ### Дополнительные параметры \{#additional-parameters\} Поле **Additional parameter** позволяет добавлять пользовательские данные для анализа за пределами Adapty. Это полезно, когда нужно передать конкретные данные о кампании или пользователе во внешние инструменты аналитики или партнёрам по атрибуции. В поле **Additional parameter** введите любые пользовательские данные, которые хотите включить в отслеживание атрибуции. Дополнительный параметр будет передаваться во всех данных атрибуции, отправляемых в TikTok, и может использоваться для углублённого анализа и оптимизации кампаний. Например, если вы запускаете несколько вариаций одной кампании, можно добавить `variant=A` или `variant=B`, чтобы различать разные креативные подходы. :::important Дополнительные параметры изменяют **Click link**, который вы вставляете в TikTok Ads Manager. Если вы уже скопировали эту ссылку туда и добавили пользовательский параметр позже, обязательно скопируйте и вставьте обновлённую click link, содержащую этот параметр. ::: <br/> ### Настройки \{#settings\} Вкладка **Settings** управляет тем, как Adapty сопоставляет действия пользователей с вашими кампаниями TikTok Ads. Эти настройки определяют временны́е окна как для детерминированного, так и для вероятностного сопоставления атрибуции. Чтобы настроить их, перейдите на вкладку **Settings** в конфигурации кампании. Здесь вы найдёте два основных параметра: - **Детерминированное окно сопоставления**: использует точные идентификаторы устройства (например, IDFA на iOS или Advertising ID на Android) для точного сопоставления пользователей с кампаниями. Установите значение 168 часов (7 дней) для максимальной точности атрибуции — это значение по умолчанию и рекомендуемое. Когда пользователь нажимает на вашу рекламу в TikTok и устанавливает приложение в течение этого окна, Adapty однозначно атрибутирует установку конкретному клику по рекламе с помощью идентификаторов устройства. - **Probabilistic matching window**: Использует статистическое моделирование и цифровой отпечаток устройства для сопоставления пользователей, когда детерминированное сопоставление невозможно. Для большинства кампаний установите значение 6 часов — это значение по умолчанию, которое хорошо работает в большинстве случаев. Для кампаний с высоким объёмом кликов можно уменьшить его до 1–2 часов. Для пользователей, которых не удаётся сопоставить детерминированно (из-за настроек конфиденциальности или других факторов), Adapty использует вероятностное сопоставление в рамках этого сокращённого окна. Нажмите **Save**, чтобы применить настройки. ### Переопределение дохода \{#revenue-override\} Если вы отслеживаете события пробного периода и хотите, чтобы TikTok атрибутировал доход по ним, используйте раздел **Revenue override**. Он появляется, когда включено событие **Trial started**. Для каждого целевого события пробного периода укажите процент от стоимости подписки, который будет передаваться как доход. Например, при 30% Adapty отправит 30% стоимости подписки в качестве значения конверсии для событий пробного периода. Чтобы добавить переопределение: 1. Включите **Trial started** в разделе **Events names**. 2. В разделе **Revenue override** нажмите **Add override**. 3. Выберите целевое событие и укажите процент выручки (0–100). 4. Нажмите **Save**. ### Отправка всех событий \{#send-all-events\} По умолчанию Adapty отправляет события на ваш пиксель только для пользователей, атрибутированных к кампании TikTok. Включите **Send all events**, чтобы также передавать события от органических и неатрибутированных пользователей на пиксель. При включении этой опции каждое событие установки и транзакции отправляется на пиксель вне зависимости от атрибуции кампании. Используйте это, чтобы предоставить TikTok более широкие данные о конверсиях для моделирования аудитории и оптимизации кампаний. Чтобы включить эту опцию, в настройках кампании выберите **Send all events (forward organic/non-attributed events to this pixel)** и нажмите **Save**. --- # File: ua-funnelfox --- --- title: "Интеграция FunnelFox с Adapty Attribution" description: "Подключите веб-воронки FunnelFox к Adapty Attribution для отслеживания полного пути привлечения пользователей — от веб-касания до платящего подписчика." --- [FunnelFox](https://funnelfox.com) — это платформа для создания web2app-воронок, которая позволяет привлекать пользователей и принимать оплату за пределами App Store, обходя его комиссию и другие ограничения. После подключения FunnelFox отправляет события транзакций в Adapty Attribution, обеспечивая полную цепочку атрибуции — от точки касания в вебе до платящего подписчика. Чтобы настроить интеграцию, свяжите один или несколько проектов FunnelFox с вашим приложением в Adapty с помощью **Project ID**. ## Как это работает \{#how-it-works\} Когда пользователь завершает покупку в вашей воронке FunnelFox, FunnelFox отправляет событие транзакции в Adapty Attribution. Adapty использует **Project ID** для определения, к какому приложению относится транзакция. Событие сохраняется и отображается в аналитике Adapty Attribution. Каждая транзакция включает: - **Событие жизненного цикла подписки**: запущена, продлена, отменена, конвертирована из триала, возвращена и другие - **Данные атрибуции**: идентификаторы кампании, набора объявлений и объявления; UTM-параметры; клик-идентификаторы платформ (fbclid, ttclid, gclid) - **Данные воронок и экспериментов**: название воронки и эксперимента в FunnelFox, чтобы вы могли сравнивать варианты A/B-тестов Adapty автоматически определяет **канал** (Facebook, TikTok, Google или органика) по клик-идентификатору в транзакции. Настраивать это вручную не нужно. :::note Транзакции FunnelFox используют **дату первой оплаты** в качестве даты когорты вместо даты установки, поскольку у веб-покупок нет события установки приложения. ::: ## Настройка интеграции \{#configure-integration\} ### Шаг 1. Получите Project ID в FunnelFox \{#step-1-get-your-project-id-in-funnelfox\} 1. В дашборде FunnelFox нажмите **Settings** в левой боковой панели. 2. В разделе **Project info** скопируйте значение **ID**. ### Шаг 2. Добавьте проект в Adapty UA \{#step-2-add-the-project-in-adapty-ua\} 1. В Adapty UA перейдите в [**Integrations > FunnelFox**](https://app.adapty.io/ua/integrations/funnelfox). 2. Вставьте Project ID, скопированный из FunnelFox. 3. Нажмите **Save**. Чтобы подключить дополнительные проекты FunnelFox, нажмите **Add project** и повторите оба шага для каждого нового проекта. --- # File: ua-custom-s3 --- --- title: "Custom S3 в Adapty Attribution" description: "Экспортируйте данные о привлечении пользователей в собственное S3-совместимое хранилище для расширенной аналитики и отчётности." --- Интеграция Adapty Attribution с пользовательским S3-совместимым хранилищем позволяет безопасно хранить данные кампаний по привлечению пользователей в вашем собственном S3-совместимом решении. Вы сможете сохранять данные об эффективности кампаний, данные атрибуции и события привлечения пользователей в ваш S3-бакет в формате .csv. Чтобы настроить интеграцию, выполните несколько простых шагов в консоли вашего S3-совместимого хранилища и дашборде Adapty Attribution. :::note Adapty Attribution отправляет данные каждые **24 часа** в 4:00 UTC. Каждый файл содержит данные о событиях, созданных за весь предыдущий календарный день по UTC. Например, данные, экспортируемые автоматически в 4:00 UTC 8 марта, охватывают все события, созданные 7 марта с 00:00:00 до 23:59:59 по UTC. ::: ## Настройка интеграции с Custom S3 \{#set-up-custom-s3-integration\} Чтобы начать получать данные, настройте интеграцию в Adapty Attribution: 1. Перейдите в [**Integrations** -> **Custom S3**](https://app.adapty.io/ua/integrations/custom-s3) 2. Включите переключатель **Export install events to custom S3**. 3. Заполните обязательные поля для установки соединения между вашим хранилищем Custom S3 и профилями Adapty Attribution. | Поле | Описание | |:----------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Access Key ID** | Уникальный идентификатор для аутентификации пользователя или приложения при доступе к S3-совместимому хранилищу. Найдите его в консоли вашего провайдера хранилища. | | **Secret Access Key** | Закрытый ключ, используемый совместно с Access Key ID для аутентификации пользователя или приложения при доступе к S3-совместимому хранилищу. Найдите его в консоли вашего провайдера хранилища. | | **S3 Bucket Name** | Глобально уникальное имя, идентифицирующее конкретный S3-бакет в вашей среде хранения. S3-бакеты — это простое хранилище, позволяющее сохранять и получать объекты данных, такие как файлы и изображения, в облаке. | | **Region** (Необязательно) | Получите значение Region в Management Console. | | **Folder Inside the Bucket** (Необязательно) | Имя папки, которую вы хотите создать внутри выбранного S3-бакета. Обратите внимание, что S3 имитирует папки с помощью префиксов ключей объектов, которые, по сути, и являются именами папок. | | **Custom Endpoint URL** | URL эндпоинта вашего S3-совместимого хранилища. Его предоставляет ваш провайдер хранилища (например, MinIO, DigitalOcean Spaces, Wasabi и др.). | :::note Вы также можете указать вложенные директории в поле имени S3 bucket, например `adapty-ua-events/com.sample-app` ::: ## Ручной экспорт данных \{#manual-data-export\} Помимо автоматического экспорта данных о событиях в ваше S3-хранилище, Adapty UA поддерживает ручной экспорт файлов. С помощью этой функции вы можете выбрать дату и экспортировать данные о привлечении пользователей в S3-бакет вручную. Это даёт больший контроль над тем, какие данные и когда экспортировать. ## Структура таблицы \{#table-structure\} В кастомной S3-интеграции Adapty Attribution предоставляет таблицу для хранения исторических данных о событиях установки. Таблица содержит информацию о профиле пользователя, выручке и поступлениях, исходном сторе и других параметрах. :::warning Обратите внимание, что эта структура может расширяться со временем — по мере добавления новых данных нами или сторонними сервисами, с которыми мы работаем. Убедитесь, что ваш код, обрабатывающий эти данные, достаточно устойчив и опирается на конкретные поля, а не на структуру в целом. ::: Ниже приведена структура таблицы для событий: | Столбец | Описание | |--------------------------|---------------------------------------------------| | `adapty_profile_id` | Уникальный идентификатор профиля Adapty | | `install_id` | Уникальный идентификатор установки | | `created_at` | Временная метка создания записи (ISO 8601) | | `installed_at` | Временная метка установки приложения (ISO 8601) | | `store` | Стор (`ios`, `android`) | | `country` | Код страны пользователя (ISO 3166-1 alpha-2) | | `ip_address` | IP-адрес клиента | | `idfa` | iOS Identifier for Advertisers | | `idfv` | iOS Identifier for Vendors | | `gaid` | Google Advertising ID (Android) | | `android_id` | Идентификатор Android-устройства | | `app_set_id` | Android App Set ID | | `bundle_id` | Идентификатор бандла приложения (например, `com.example.app`) | | `device_brand` | Бренд устройства (например, `Apple`, `Samsung`) | | `device_model` | Модель устройства (например, `iPhone15,2`) | | `os_version` | Основная версия ОС | | `app_version` | Версия приложения, переданная Adapty SDK | | `sdk_version` | Версия Adapty SDK | | `channel` | Канал атрибуции | | `campaign_id` | Идентификатор кампании | | `campaign_name` | Название кампании | | `adset_id` | Идентификатор группы объявлений | | `adset_name` | Название группы объявлений | | `ad_id` | Идентификатор объявления | | `ad_name` | Название объявления | | `keyword_id` | Идентификатор ключевого слова | | `keyword_name` | Ключевое слово | | `asa_org_id` | ID организации Apple Search Ads | | `asa_keyword_match_type` | Тип соответствия ключевого слова ASA (`Exact`, `Broad`) | | `asa_attribution` | Данные атрибуции ASA (строка JSON) | | `asa_conversion_type` | Тип конверсии ASA | | `asa_country_or_region` | Страна или регион ASA | | `asa_creative_set_name` | Название набора креативов ASA | | `fbclid` | Facebook Click ID | | `ttclid` | TikTok Click ID | | `utm_source` | Параметр UTM source | | `utm_medium` | Параметр UTM medium | | `utm_campaign` | Параметр UTM campaign | | `utm_term` | Параметр UTM term | | `utm_content` | Параметр UTM content | --- # File: ua-amazon-s3 --- --- title: "Amazon S3 в Adapty Attribution" description: "Экспортируйте данные о привлечении пользователей в S3 для расширенной аналитики и отчётности." --- Интеграция Adapty Attribution с Amazon S3 позволяет централизованно и безопасно хранить данные кампаний по привлечению пользователей. Вы сможете сохранять данные об эффективности кампаний, данные атрибуции и события привлечения пользователей в ваш Amazon S3 bucket в виде .csv-файлов. Чтобы настроить эту интеграцию, нужно выполнить несколько простых шагов в AWS Console и дашборде Adapty Attribution. :::note Adapty Attribution отправляет данные каждые **24 часа** в 4:00 UTC. Каждый файл будет содержать данные о событиях, созданных за весь предыдущий календарный день по UTC. Например, данные, экспортируемые автоматически в 4:00 UTC 8 марта, будут содержать все события, созданные 7 марта с 00:00:00 до 23:59:59 по UTC. ::: ## Как настроить интеграцию с Amazon S3 \{#how-to-set-up-amazon-s3-integration\} Для получения данных вам потребуются следующие учётные данные: 1. Access key ID 2. Secret access key 3. Название бакета S3 4. Название папки внутри бакета S3 :::note Вложенные директории В поле названия бакета Amazon S3 можно указывать вложенные директории, например: adapty-ua-events/com.sample-app ::: ### Шаг 1. Создайте учётные данные Amazon S3 \{#step-1-create-amazon-s3-credentials\} Этот гайд поможет вам создать необходимые учётные данные в консоли AWS. #### 1.1. Создайте политику доступа \{#11-create-access-policy\} 1. Перейдите в [IAM Policy Dashboard](https://us-east-1.console.aws.amazon.com/iamv2/home?region=us-east-1#/policies) в консоли AWS 2. Выберите **Create Policy** <img src="/assets/shared/img/7af075c-CleanShot_2023-03-21_at_10.52.002x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. В редакторе политик вставьте следующий JSON и замените `adapty-s3-integration-test` на имя вашего бакета: ```json showLineNumbers title="Json" { "Version": "2012-10-17", "Statement": [ { "Sid": "AllowListObjectsInBucket", "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::adapty-s3-integration-test" }, { "Sid": "AllowAllObjectActions", "Effect": "Allow", "Action": "s3:*Object", "Resource": [ "arn:aws:s3:::adapty-s3-integration-test/*", "arn:aws:s3:::adapty-s3-integration-test" ] }, { "Sid": "AllowBucketLocation", "Effect": "Allow", "Action": "s3:GetBucketLocation", "Resource": "arn:aws:s3:::adapty-s3-integration-test" } ] } ``` <img src="/assets/shared/img/d4e474a-CleanShot_2023-03-21_at_10.56.212x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. После завершения настройки политики вы можете добавить теги (по желанию), а затем нажмите **Next**, чтобы перейти к последнему шагу 5. На этом шаге укажите название политики и нажмите **Create policy**, чтобы завершить её создание <img src="/assets/shared/img/7dcb02f-CleanShot_2023-03-21_at_11.03.372x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> #### 1.2. Создайте пользователя IAM \{#12-create-iam-user\} Чтобы Adapty Attribution мог загружать отчёты с сырыми данными в ваш бакет, вам нужно предоставить Access Key ID и Secret Access Key пользователя с правом записи в этот бакет. 1. Перейдите в консоль IAM и откройте [раздел Users](https://console.aws.amazon.com/iamv2/home#/users) 2. Нажмите кнопку **Add users** <img src="/assets/shared/img/bb612c8-CleanShot_2023-03-21_at_11.12.392x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Задайте имя пользователя, выберите **Access key – Programmatic access** и перейдите к настройке прав доступа <img src="/assets/shared/img/467ee4d-j6aoX.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. На следующем шаге выберите опцию **Add user to group**, затем нажмите кнопку **Create group** <img src="/assets/shared/img/bfd0e80-CleanShot_2023-03-21_at_11.24.592x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Затем нужно задать имя для вашей группы пользователей и выбрать политику, которую вы создали ранее 6. После выбора политики нажмите кнопку **Create group**, чтобы завершить процесс <img src="/assets/shared/img/df29c12-CleanShot_2023-03-21_at_11.28.052x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. После успешного создания группы **выберите её** и перейдите к следующему шагу <img src="/assets/shared/img/1f3722e-CleanShot_2023-03-21_at_11.36.192x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 8. Это последний шаг данного раздела — просто нажмите кнопку **Create User** <img src="/assets/shared/img/ea43722-CleanShot_2023-03-21_at_11.40.462x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 9. Наконец, вы можете **скачать учётные данные в формате .csv** или скопировать их напрямую из дашборда <img src="/assets/shared/img/bcf35e1-S3created.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Шаг 2. Настройка интеграции в Adapty Attribution 1. Перейдите в [**Integrations** -> **Amazon S3**](https://app.adapty.io/ua/integrations/s3) 2. Включите переключатель **Export install events to Amazon S3**. 3. Заполните следующие поля, чтобы установить связь между Amazon S3 и профилями Adapty Attribution: | Поле | Описание | |:-----------------------------| :----------------------------------------------------------- | | **Access Key ID** | Уникальный идентификатор для аутентификации пользователя или приложения при доступе к сервису AWS. Найдите этот идентификатор в скачанном [csv-файле](ua-amazon-s3#step-1-create-amazon-s3-credentials). | | **Secret Access Key** | Приватный ключ, используемый вместе с Access Key ID для аутентификации при доступе к сервису AWS. Найдите этот ключ в скачанном [csv-файле](ua-amazon-s3#step-1-create-amazon-s3-credentials). | | **S3 Bucket Name** | Глобально уникальное имя, идентифицирующее конкретный бакет S3 в облаке AWS. Бакеты S3 — это простое хранилище, позволяющее загружать и получать объекты данных (файлы, изображения и т. д.) в облаке. | | **Folder Inside the Bucker** | Имя папки, которую вы хотите создать внутри выбранного бакета S3. Обратите внимание, что S3 эмулирует папки с помощью префиксов ключей объектов, которые по сути и являются именами папок. | | **Region** (опционально) | Узнайте ваш регион в консоли управления AWS в разделе вашего IAM-аккаунта. | ## Ручной экспорт данных \{#manual-data-export\} Помимо автоматического экспорта данных о событиях в Amazon S3, Adapty UA поддерживает и ручной экспорт файлов. С его помощью вы можете выбрать конкретную дату для данных по привлечению пользователей и экспортировать их в свой бакет S3 вручную. Это даёт больше контроля над тем, какие данные и когда экспортировать. ## Структура таблицы \{#table-structure\} В интеграции с AWS S3 Adapty Attribution предоставляет таблицу для хранения исторических данных о событиях установки. Таблица содержит информацию о профиле пользователя, выручке и доходах, источнике стора и других параметрах. :::warning Обратите внимание, что эта структура может со временем расширяться — мы или наши партнёры можем добавлять новые данные. Убедитесь, что ваш код, обрабатывающий эти данные, достаточно устойчив и опирается на конкретные поля, а не на структуру в целом. ::: Вот структура таблицы для событий: | Столбец | Описание | |--------------------------|---------------------------------------------------| | `adapty_profile_id` | Уникальный идентификатор профиля Adapty | | `install_id` | Уникальный идентификатор установки | | `created_at` | Временная метка создания записи (ISO 8601) | | `installed_at` | Временная метка установки приложения (ISO 8601) | | `store` | Стор (`ios`, `android`) | | `country` | Код страны пользователя (ISO 3166-1 alpha-2) | | `ip_address` | IP-адрес клиента | | `idfa` | iOS Identifier for Advertisers | | `idfv` | iOS Identifier for Vendors | | `gaid` | Google Advertising ID (Android) | | `android_id` | ID устройства Android | | `app_set_id` | Android App Set ID | | `channel` | Канал атрибуции | | `campaign_id` | Идентификатор кампании | | `campaign_name` | Название кампании | | `adset_id` | Идентификатор группы объявлений | | `adset_name` | Название группы объявлений | | `ad_id` | Идентификатор объявления | | `ad_name` | Название объявления | | `keyword_id` | Идентификатор ключевого слова | | `keyword_name` | Ключевое слово | | `asa_org_id` | ID организации Apple Search Ads | | `asa_keyword_match_type` | Тип соответствия ключевого слова ASA (`Exact`, `Broad`) | | `asa_attribution` | Данные атрибуции ASA (строка JSON) | | `asa_conversion_type` | Тип конверсии ASA | | `asa_country_or_region` | Страна или регион ASA | | `asa_creative_set_name` | Название набора креативов ASA | | `fbclid` | Facebook Click ID | | `ttclid` | TikTok Click ID | | `utm_source` | Параметр UTM source | | `utm_medium` | Параметр UTM medium | | `utm_campaign` | Параметр UTM campaign | | `utm_term` | Параметр UTM term | | `utm_content` | Параметр UTM content | --- # File: ua-google-cloud-storage --- --- title: "Google Cloud Storage в Adapty Attribution" description: "Интегрируйте Google Cloud Storage с Adapty Attribution для безопасного хранения данных по привлечению пользователей." --- Интеграция Adapty Attribution с Google Cloud Storage позволяет безопасно хранить данные кампаний по привлечению пользователей в одном централизованном месте. Вы сможете сохранять данные об эффективности кампаний, данные атрибуции и события привлечения пользователей в своём бакете Google Cloud Storage в виде файлов .csv. Чтобы настроить интеграцию, нужно выполнить несколько простых шагов в Google Cloud Console и дашборде Adapty Attribution. :::note Расписание Adapty Attribution отправляет данные в Google Cloud Storage каждые 24 часа в 4:00 UTC. Каждый файл содержит данные о событиях, созданных за весь предыдущий календарный день по UTC. Например, данные, экспортируемые автоматически в 4:00 UTC 8 марта, будут содержать все события, созданные 7 марта с 00:00:00 до 23:59:59 по UTC. ::: ## Как настроить интеграцию с Google Cloud Storage \{#how-to-set-up-google-cloud-storage-integration\} ### Шаг 1. Создание учётных данных Google Cloud Storage \{#step-1-create-google-cloud-storage-credentials\} Этот гайд поможет вам создать необходимые учётные данные в Google Cloud Platform Console. Чтобы Adapty Attribution мог загружать отчёты с сырыми данными в ваш бакет, требуется ключ сервисного аккаунта и права на запись в соответствующий бакет. Предоставив ключ сервисного аккаунта и разрешив запись в бакет, вы позволяете Adapty Attribution безопасно и эффективно передавать отчёты с сырыми данными из платформы в ваше хранилище. :::warning Обратите внимание, что мы поддерживаем только авторизацию через Service Account HMAC key. Убедитесь, что вашему Service Account HMAC key добавлены роли «Storage Object Viewer», «Storage Legacy Bucket Writer» и «Storage Object Creator» — это необходимо для корректного доступа к Google Cloud Storage. ::: #### 2.1. Создайте сервисный аккаунт \{#21-create-service-account\} 1. Перейдите в раздел [IAM](https://console.cloud.google.com/projectselector2/iam-admin/serviceaccounts) вашего аккаунта Google Cloud и выберите нужный проект или создайте новый <img src="/assets/shared/img/30a81ef-CleanShot_2023-03-17_at_15.22.142x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Затем создайте новый сервисный аккаунт для атрибуции Adapty, нажав кнопку **+ CREATE SERVICE ACCOUNT** <img src="/assets/shared/img/98f8ebf-CleanShot_2023-03-17_at_15.40.062x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Заполните поля на первом шаге — права доступа будут назначены позже. Подробнее об этой странице читайте в документации [здесь](https://docs.cloud.google.com/iam/docs/service-accounts-create) <img src="/assets/shared/img/2190c50-CleanShot_2023-03-17_at_15.48.552x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Чтобы создать и скачать [приватный JSON-ключ](https://docs.cloud.google.com/iam/docs/keys-create-delete), перейдите в раздел KEYS и нажмите кнопку «ADD KEY» <img src="/assets/shared/img/8a45468-CleanShot_2023-03-17_at_15.58.092x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. В разделе DETAILS найдите значение Email, привязанное к только что созданному сервисному аккаунту, и скопируйте его. Эта информация понадобится на следующих шагах для авторизации аккаунта и предоставления ему прав на запись в бакет <img src="/assets/shared/img/6ccd0f0-CleanShot_2023-03-17_at_16.03.162x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> #### 2.2. Настройка прав доступа к бакету \{#22-configure-bucket-permissions\} 6. Перейдите на страницу [Buckets](https://console.cloud.google.com/storage/browser) в Google Cloud Storage и выберите существующий бакет или создайте новый для хранения отчётов об атрибуции из Adapty Attribution 7. Перейдите в раздел PERMISSIONS и выберите опцию [GRANT ACCESS](https://docs.cloud.google.com/identity/docs/how-to?hl=en) <img src="/assets/shared/img/3cdd937-CleanShot_2023-03-17_at_16.14.232x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 8. В разделе PERMISSIONS введите Email сервисного аккаунта, полученный на пятом шаге, затем выберите роль Storage Object Creator 9. Нажмите SAVE, чтобы сохранить изменения <img src="/assets/shared/img/62801f4-CleanShot_2023-03-17_at_16.17.312x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 10. Запомните название бакета для дальнейшего использования 11. После выполнения этих шагов вы успешно завершили необходимую настройку в Google Cloud Console! Последний шаг — ввести название бакета и скачать JSON-файл для использования в атрибуции Adapty <img src="/assets/shared/img/c967e16-CleanShot_2023-03-17_at_16.23.332x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Шаг 2. Настройте интеграцию в Adapty Attribution \{#step-2-configure-integration-in-adapty-attribution\} 1. Перейдите в [**Integrations** -> **Google Cloud Storage**](https://app.adapty.io/ua/integrations/google-cloud-storage) 2. Включите переключатель **Export install events to Google Cloud Storage** 3. Заполните обязательные поля для настройки соединения между Google Cloud Storage и Adapty Attribution: | Поле | Описание | |:------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Google Cloud service account key file** | Скачанный приватный [JSON-файл ключа](ua-google-cloud-storage#step-1-create-google-cloud-storage-credentials). | | **Google Cloud bucket name** | Название бакета в Google Cloud Storage, в котором вы хотите хранить данные. Оно должно быть уникальным в рамках Google Cloud Storage и не должно содержать пробелов. | | **Folder inside the bucket** | Название папки внутри бакета, в которой вы хотите хранить данные. Оно должно быть уникальным в рамках бакета и может использоваться для организации данных. Это поле необязательно для заполнения. | ## Ручной экспорт данных \{#manual-data-export\} Помимо автоматического экспорта событий в Google Cloud Storage, Adapty UA поддерживает ручной экспорт файлов. С его помощью можно выбрать конкретную дату и вручную экспортировать данные по привлечению пользователей в бакет GCS. Это даёт больше контроля над тем, какие данные и когда экспортировать. ## Структура таблицы \{#table-structure\} В интеграции с Google Cloud Storage Adapty Attribution предоставляет таблицу для хранения исторических данных о событиях установки. Таблица содержит информацию о профиле пользователя, выручке и доходах, источнике стора и другие данные. :::warning Обратите внимание, что эта структура может расширяться со временем — по мере добавления новых данных с нашей стороны или со стороны третьих лиц, с которыми мы работаем. Убедитесь, что ваш код, обрабатывающий эти данные, достаточно устойчив и опирается на конкретные поля, а не на структуру в целом. ::: Ниже представлена структура таблицы для событий: | Столбец | Описание | |--------------------------|----------------------------------------------------| | `adapty_profile_id` | Уникальный идентификатор профиля Adapty | | `install_id` | Уникальный идентификатор установки | | `created_at` | Временная метка создания записи (ISO 8601) | | `installed_at` | Временная метка установки приложения (ISO 8601) | | `store` | Стор (`ios`, `android`) | | `country` | Код страны пользователя (ISO 3166-1 alpha-2) | | `ip_address` | IP-адрес клиента | | `idfa` | iOS Identifier for Advertisers | | `idfv` | iOS Identifier for Vendors | | `gaid` | Google Advertising ID (Android) | | `android_id` | Идентификатор устройства Android | | `app_set_id` | Android App Set ID | | `channel` | Канал атрибуции | | `campaign_id` | Идентификатор кампании | | `campaign_name` | Название кампании | | `adset_id` | Идентификатор группы объявлений | | `adset_name` | Название группы объявлений | | `ad_id` | Идентификатор объявления | | `ad_name` | Название объявления | | `keyword_id` | Идентификатор ключевого слова | | `keyword_name` | Название ключевого слова | | `asa_org_id` | Идентификатор организации Apple Search Ads | | `asa_keyword_match_type` | Тип соответствия ключевого слова ASA (`Exact`, `Broad`) | | `asa_attribution` | Данные атрибуции ASA (строка JSON) | | `asa_conversion_type` | Тип конверсии ASA | | `asa_country_or_region` | Страна или регион ASA | | `asa_creative_set_name` | Название набора креативов ASA | | `fbclid` | Facebook Click ID | | `ttclid` | TikTok Click ID | | `utm_source` | Параметр UTM source | | `utm_medium` | Параметр UTM medium | | `utm_campaign` | Параметр UTM campaign | | `utm_term` | Параметр UTM term | | `utm_content` | Параметр UTM content | --- # File: adapty-mail --- --- title: "Adapty Mail" description: "Создаваемые с помощью ИИ email-кампании, которые превращают пользователей пробного периода в платных подписчиков." --- <CustomDocCardList ids={['mail-get-started', 'mail-brand', 'mail-collect-emails', 'mail-send-data-via-api', 'mail-sending-domain', 'mail-create-campaign', 'mail-analytics']} /> Adapty Mail превращает данные ваших пользователей из Adapty в email-последовательности, сгенерированные ИИ, которые конвертируют trial-пользователей в платных подписчиков. Сервис использует данные профилей, уже имеющиеся в вашем проекте Adapty, чтобы создавать, отправлять и атрибутировать кампании — без необходимости в отдельной email-платформе. ## Почему Adapty Mail? \{#why-adapty-mail\} Для email-рассылок с таргетингом нужны тексты, дизайн, инфраструктура отправки и атрибуция дохода. Каждое из этих требований — отдельная задача. Adapty Mail берёт всё на себя. Профиль вашего бренда формируется на основе URL вашего стора и других добавленных источников, а полная цепочка писем генерируется менее чем за 2 минуты — отправляется с вашего домена с персонализированными ссылками на оплату и атрибуцией покупок. ## Как это работает \{#how-it-works\} 1. **Сбор email-адресов**: приложение передаёт в Adapty адреса электронной почты и значения `customer_user_id` через SDK. Adapty Mail использует эти данные для идентификации получателей и атрибуции дохода к конкретному письму, которое привело к покупке. Также можно отправлять эти данные с вашего сервера через [Adapty Mail API](mail-send-data-via-api). 2. **Создание веб-пейвола**: страница оформления заказа, на которую ведёт каждое письмо. 3. **Генерация последовательности**: ИИ использует ваш бренд-профиль и создаёт от 1 до 15 писем — текст, дизайн, главные изображения и персонализированные ссылки на оформление заказа, подобранные под категорию вашего приложения и голос бренда. 4. **Запуск флоу**: выберите триггер (ни разу не совершал покупку, отмена продления, проблема с оплатой, истёкшая подписка или возврат средств) и сегмент, затем подключите кампанию. Письма начнут отправляться автоматически, а доход от покупок, совершённых через письма, будет атрибутирован к конкретному письму, которое привело к конверсии. ## Требования \{#requirements\} Для работы с Adapty Mail необходимо: - Аккаунт Adapty - Настроенный сбор email-адресов в приложении — см. [Сбор email-адресов пользователей](mail-collect-emails) - `customer_user_id`, настроенный в SDK Adapty - Домен, которым вы владеете, с доступом к его DNS-настройкам - Веб-провайдер платежей (Stripe, Paddle или PayPal) ## Начало работы \{#get-started\} Следуйте гайду [Начало работы с Adapty Mail](mail-get-started), чтобы завершить настройку и запустить свою первую кампанию. --- # File: mail-get-started --- --- title: "Начало работы с Adapty Mail" description: "Настройте Adapty Mail и запустите первый email-флоу." --- В этом гайде вы настроите Adapty Mail и запустите первый email-флоу. :::note Вы также можете отправлять данные в Adapty Mail со своего сервера без использования SDK. Если у вас уже есть email-адреса пользователей и данные о покупках на бэкенде, или вы импортируете подписчиков из другого источника, см. [Отправка email и транзакций через Adapty Mail API](mail-send-data-via-api). ::: Настройка состоит из шести частей: 1. [Настройте Adapty SDK](#1-configure-your-adapty-sdk) 2. [Настройте домен отправки](#2-set-up-your-sending-domain) 3. [Создайте веб-пейвол](#3-create-a-web-paywall) 4. [Сгенерируйте кампанию с помощью ИИ](#4-generate-a-campaign-with-ai) 5. [Запустите флоу](#5-launch-a-flow) 6. [Включите отправку](#6-enable-sending) :::tip Если вы зарегистрировались в Adapty Mail через Adapty, **профиль бренда** создаётся автоматически на основе URL стора вашего проекта. Откройте **Brand** в любой момент, чтобы просмотреть или уточнить его — см. [Бренд](mail-brand). Если вы регистрировались отдельно, настройте бренд на той же странице перед созданием кампаний или веб-пейволов. ::: ## Перед началом работы \{#before-you-start\} Убедитесь, что у вас есть всё необходимое: - **Доступ к DNS**: вы можете добавлять записи к вашему корневому домену. - **Провайдер веб-платежей**: у вас есть аккаунт Stripe, Paddle или PayPal с настроенными продуктами подписки. ## 1. Настройте Adapty SDK \{#1-configure-your-adapty-sdk\} :::important Adapty Mail — это **отдельный продукт**. Вы можете использовать его, даже если пейволы, подписки и аналитика у вас не через Adapty — переносить весь стек не обязательно. Для получения точных данных о доходах достаточно минимальной настройки: установите Adapty SDK в режиме наблюдателя и включите серверные уведомления App Store. ::: Adapty Mail требует от вашего приложения трёх вещей: данных о покупках (чтобы связывать доход с письмом, которое привело к конверсии), стабильного идентификатора пользователя и адресов электронной почты. 1. **Позвольте Adapty отслеживать вашу выручку.** Первый шаг зависит от того, реализованы ли у вас уже встроенные покупки: - Если у вас **уже реализованы встроенные покупки через Adapty**, на этом этапе ничего дополнительно делать не нужно. - Если у вас **уже реализованы встроенные покупки без Adapty** и вы не планируете переходить на Adapty, установите SDK Adapty для вашей платформы в режиме наблюдателя. На этом этапе достаточно добавить SDK в проект, активировать его с включённым режимом наблюдателя и передавать транзакции. Гайды по платформам: [iOS](implement-observer-mode), [Android](implement-observer-mode-android), [React Native](implement-observer-mode-react-native), [Flutter](implement-observer-mode-flutter), [Unity](implement-observer-mode-unity), [Kotlin Multiplatform](implement-observer-mode-kmp), [Capacitor](implement-observer-mode-capacitor). - Если у вас **встроенные покупки ещё не реализованы и вы хотите использовать Adapty**, выполните шаги из [быстрого старта](quickstart), чтобы делегировать обработку покупок Adapty. Затем [включите уведомления сервера App Store в Adapty](enable-app-store-server-notifications), чтобы получать обновления о доходах напрямую от App Store. 2. **Настройте идентификацию пользователей.** Передайте стабильный идентификатор — ID пользователя из вашего бэкенда, Firebase UID или аналогичный — либо вызвав `Adapty.identify()`, либо передав `customerUserId` в `.activate()` при инициализации SDK. По `customer_user_id` Adapty Mail связывает кампании, клики и покупки с нужным профилем. Платформенные гайды: [iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), [Unity](unity-identifying-users), [Kotlin Multiplatform](kmp-identifying-users), [Capacitor](capacitor-identifying-users). 3. **Собирайте email пользователей.** Как только пользователь указывает email в вашем приложении (например, при регистрации или оформлении покупки), передайте его в Adapty, вызвав `updateProfile` с атрибутом email. Это значение обязательно для каждого получателя кампании. Гайды по платформам: [iOS](setting-user-attributes), [Android](android-setting-user-attributes), [React Native](react-native-setting-user-attributes), [Flutter](flutter-setting-user-attributes), [Unity](unity-setting-user-attributes), [Kotlin Multiplatform](kmp-setting-user-attributes), [Capacitor](capacitor-setting-user-attributes). Если ваше приложение пока не собирает email-адреса, см. [Стратегии сбора email](mail-collect-emails#email-collection-strategies). ## 2. Настройте домен для отправки \{#set-up-your-sending-domain\} Откройте Adapty Mail: нажмите логотип Adapty в шапке и выберите **Mail**. Adapty Mail отправляет письма с вашего собственного домена. Вы один раз добавляете DNS-записи — все кампании используют один и тот же подтверждённый домен. 1. В Adapty Mail перейдите в **Settings → Email Domains**. 2. Введите корневой домен (например, `yourapp.com`) и нажмите **Preview**. Принимаются только apex-домены — поддомены вида `app.yourapp.com` будут отклонены при вводе. 3. Adapty генерирует два поддомена для отправки (`mail.yourapp.com` и `email.yourapp.com`). Нажмите **Confirm**, чтобы увидеть нужные DNS-записи. 4. В панели управления вашего регистратора домена добавьте 10 DNS-записей (по 5 на каждый поддомен): - 3 записи CNAME (DKIM) на каждый поддомен - 1 запись MX (Mail-From) на каждый поддомен - 1 запись TXT (SPF, `v=spf1 include:amazonses.com ~all`) на каждый поддомен 5. При необходимости добавьте запись DMARC TXT на корневом домене (рекомендуется). 6. Вернитесь в **Settings → Email Domains** и нажмите **Check Verification**. Краткая сводка по срокам верификации: - **Автоматическая проверка**: первая проверка запускается примерно через 5 минут после отправки. Интервалы постепенно увеличиваются до одного раза в час — до тех пор, пока записи не будут обнаружены. - **Ручная проверка**: нажмите **Check Verification** в любой момент, чтобы запустить немедленную проверку. - **Распространение DNS**: обычно занимает несколько минут, в редких случаях — до 48 часов. - **Окно верификации**: 7 дней. Если оно истечёт, DNS-записи останутся на месте — повторно введите домен в **Settings → Email Domains**, чтобы начать новое окно. Подробнее о каждом типе записи и прогреве домена читайте в разделе [Настройка домена отправки](mail-sending-domain). ## 3. Настройте отправляющий домен \{#3-set-up-your-sending-domain\} Adapty Mail отправляет письма с вашего собственного домена. DNS-записи добавляются один раз — все кампании используют один проверенный домен. 1. В Adapty Mail перейдите в **Settings → Email Domains**. 2. Введите ваш корневой домен (например, `yourapp.com`) и нажмите **Preview**. Принимаются только apex-домены — поддомены вида `app.yourapp.com` будут отклонены при вводе. 3. Adapty сгенерирует два отправляющих поддомена (`mail.yourapp.com` и `email.yourapp.com`). Нажмите **Confirm**, чтобы увидеть необходимые DNS-записи. 4. В панели управления вашего регистратора добавьте 10 DNS-записей (по 5 на каждый поддомен): - 3 CNAME-записи (DKIM) на каждый поддомен - 1 MX-запись (Mail-From) на каждый поддомен - 1 TXT-запись (SPF, `v=spf1 include:amazonses.com ~all`) на каждый поддомен 5. При желании добавьте DMARC TXT-запись на корневом домене (рекомендуется). 6. Вернитесь в **Settings → Email Domains** и нажмите **Check Verification**. Краткий обзор времени верификации: - **Автоматическая проверка**: первая проверка запускается примерно через 5 минут после отправки. Интервалы постепенно увеличиваются до одного раза в час, пока записи не будут обнаружены. - **Ручная проверка**: нажмите **Check Verification** в любое время, чтобы запустить немедленную проверку. - **Распространение DNS**: обычно занимает несколько минут, в редких случаях — до 48 часов. - **Окно верификации**: 7 дней. Если оно истечёт, ваши DNS-записи останутся на месте — повторно введите домен в **Settings → Email Domains**, чтобы начать новое окно. Подробнее о каждом типе записи и прогреве домена см. в [Настройке отправляющего домена](mail-sending-domain). ### Вариант A: Создание с помощью AI \{#option-a-generate-with-ai\} На странице отображается чеклист **Prerequisites** со встроенными кнопками — пройдите его по порядку, затем вернитесь и нажмите **Generate**. Чеклист включает вход в Paywall Builder, подключение Stripe, добавление продуктов и проверку результата. Подробное описание есть в [Настройке чекаута](mail-checkout). Когда все пункты чеклиста отмечены зелёным, нажмите **Generate**, чтобы открыть диалог создания: - **Environment**: Выберите **Production** или **Sandbox**. Sandbox использует тестовые продукты Stripe и является безопасным вариантом по умолчанию для разработки и локальных окружений. - **Plans**: Выберите до **3 планов Stripe** (каждый план — это продукт + цена). Это офферы, которые сгенерированный пейвол будет предлагать пользователям при оформлении заказа. Нажмите **Generate**, чтобы запустить сборку. После завершения откройте редактор, чтобы проверить и опубликовать пейвол. :::important Пейвол должен быть опубликован, прежде чем он сможет обрабатывать трафик оформления заказов. Неопубликованные пейволы возвращают ошибку, когда пользователи переходят по ссылкам email-чекаута. ::: ### Вариант А: Генерация с помощью ИИ \{#option-a-generate-with-ai\} 1. Выберите **Generate with AI**. 2. Нажмите **Log in to the paywall builder**. Конструктор веб-пейволов откроется в новой вкладке. Если вы ещё не вошли в систему, войдите, используя учётные данные Adapty. 3. В конструкторе включите интеграцию с вашим платёжным провайдером (Stripe, Paddle или PayPal). Подробнее см. в [Настройке веб-пейвола](web-paywall-configuration). 4. Вернитесь в Adapty Mail и нажмите **Proceed to generation**. 5. Просмотрите сгенерированный пейвол, затем сохраните и опубликуйте его. ## 4. Создайте кампанию с помощью ИИ \{#generate-a-campaign-with-ai\} ИИ создаёт для вас полную email-последовательность — тексты, дизайн, hero-изображения и персонализированные ссылки на оформление, всё адаптировано под ваш бренд. 1. В Adapty Mail перейдите в **Campaigns** и нажмите **Create**. 2. Задайте название кампании. 3. В выпадающем списке **Web paywall** выберите web-пейвол, добавленный на предыдущем шаге. 4. Нажмите **Generate emails**. 5. Заполните диалог генерации — тон, язык, необязательный пользовательский промпт (до 2 000 символов) и количество писем (1–15, по умолчанию 4). Подробнее о каждом поле — в разделе [Создание кампании](mail-create-campaign). 6. Нажмите **Generate**. Генерация обычно занимает несколько минут. Если за 5 минут завершить не удалось, система прерывает процесс — попробуйте ещё раз. 7. Просмотрите каждое письмо. В шапке предпросмотра есть **Theme toggle** (Auto, Light, Dark) — он управляет отображением превью, но сгенерированный контент одинаков во всех режимах. Можно перегенерировать отдельные письма, отредактировать текст или открыть HTML-редактор для точной настройки. 8. Нажмите **Create**, чтобы сохранить кампанию. Кампания сохраняется как **черновик** и пока не отправляется — кампании запускаются только после привязки к флоу (следующий шаг). Отдельной кнопки «Опубликовать» в редакторе кампании нет. ## 5. Запустите флоу \{#5-launch-a-flow\} Флоу связывает **триггер** (событие, например истечение срока подписки) с **сегментом** и отправляет этому сегменту выбранную **кампанию**. Adapty Mail поставляется с пятью фиксированными триггерами, каждый из которых имеет собственный вид флоу. 1. В Adapty Mail перейдите в **Flows** и откройте триггер, который хотите настроить: - **Never purchased** — пользователи, которые зарегистрировались, но ещё не совершили покупку. - **Renewal cancelled** — пользователи, отключившие авторенью, у которых ещё активна подписка. - **Billing issue** — платёж не прошёл, карта отклонена или истекла, либо наступил льготный период. - **Expired** — подписка истекла и доступ потерян. - **Refunded** — пользователи, запросившие возврат средств после покупки. Подробнее о целях и тональности каждого триггера см. в разделе [Flows](mail-flows). 2. Нажмите **Create**, чтобы открыть диалог. 3. В диалоге: - Выберите **Segment** (например, **All Users**, чтобы охватить всех, кто попадает под этот триггер, или создайте новый сегмент на основе атрибутов профиля). - Оставьте тип контента **Campaign** (вариант A/B-теста рассматривается в разделе [A/B-тестирование](mail-ab-testing)). - Выберите **Campaign**, которую вы сохранили на шаге 4. 4. Нажмите **Save**. Флоу запускается сразу — никаких отдельных шагов для старта нет. С этого момента пользователи, соответствующие сегменту, начнут получать кампанию, как только попадут под триггерное событие. :::note Вы можете добавить несколько строк «сегмент → кампания» к одному триггеру; они выполняются в порядке приоритета. Строка **All Users**, если используется, должна быть последней (с наименьшим приоритетом), чтобы охватить всех пользователей, не подпавших под более конкретный сегмент. ::: ## 6. Включите отправку \{#enable-sending\} До этого момента кампания настроена, но не запущена — **интеграция Adapty**, синхронизирующая события подписки с Adapty Mail, всё ещё отключена. Включение — это финальный переключатель: начнут поступать события, будут срабатывать сегменты, и письма начнут отправляться. Этот шаг доступен только после шага 5. До запуска флоу кнопка **Enable** в **Settings → Integrations** неактивна и отображает подсказку: *«Set up at least one flow before enabling Adapty integration.»* 1. В Adapty Mail перейдите в **Settings → Integrations**. 2. Нажмите **Enable Adapty integration** (или **Enable**, если интеграция уже была настроена ранее). После включения Adapty передаёт в Adapty Mail все события подписок — новые подписки, продления, триалы, конверсии, возвраты, проблемы с оплатой. Эти события управляют членством в сегментах, маршрутизацией кампаний и условиями остановки, которые приостанавливают последовательность при конверсии пользователя. :::note Переключатель **Adapty integration** в Settings — это *не* то же самое, что партнёрский воркспейс Adapty, через который вы вошли в Adapty Mail. Партнёрский воркспейс — это то, что создало ваш аккаунт и (если вы зарегистрировались через Adapty) ваш бренд. Переключатель интеграции здесь управляет синхронизацией событий — его нужно включать отдельно для каждого проекта. ::: ## Устранение неполадок \{#troubleshooting\} | Проблема | Решение | | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------ | | Верификация DNS зависла | Убедитесь, что записи совпадают точно — без завершающих точек, с правильными CNAME-таргетами. Подождите 5–10 минут и нажмите **Check Verification** снова | | Окно верификации истекло | Ваши записи остаются на месте. Повторно введите домен в **Settings → Email Domains**, чтобы начать новое окно | | Генерация завершилась ошибкой или истекла по таймауту | Проверьте подключение к интернету и попробуйте снова. Если проблема не исчезает, обратитесь в поддержку Adapty | ## Дополнительные материалы \{#learn-more\} - **[Сбор email-адресов пользователей](mail-collect-emails)**: стратегии получения email-адресов, если приложение их пока не собирает. - **[Настройка отправляющего домена](mail-sending-domain)**: подробности о DNS-записях, уровнях прогрева и устранении неполадок. - **[Настройка оформления заказа](mail-checkout)**: структура воронки и персонализация. - **[Аналитика кампаний](mail-analytics)**: отслеживание доставки, вовлечённости и выручки. - **[A/B-тестирование](mail-ab-testing)**: тестирование нескольких версий последовательности. --- # File: mail-collect-emails --- --- title: "Сбор email-адресов пользователей для Adapty Mail" description: "Передайте email-адреса пользователей и стабильные идентификаторы в Adapty, чтобы кампании доходили до ваших пользователей." --- Adapty Mail требует стабильный `customer_user_id` и email для каждого пользователя, которому он доставляет письма. Подключите оба значения в коде приложения до запуска кампании. ## Сбор email-адресов пользователей \{#collect-user-emails\} Для каждого пользователя в Adapty должны поступить два значения: стабильный `customer_user_id`, идентифицирующий пользователя, и сам email. Сначала необходима идентификация — без неё Adapty не знает, к какому профилю привязать email. 1. **Идентифицируйте пользователя.** Передайте стабильный ID — ID пользователя из вашего бэкенда, Firebase UID или аналогичный — либо через параметр `customerUserId` в `.activate()` при запуске SDK, либо вызвав `Adapty.identify()` позже (например, при входе в аккаунт). В любом случае ID должен быть задан до того, как пользователь увидит пейвол. Платформенные гайды: [iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), [Unity](unity-identifying-users), [Kotlin Multiplatform](kmp-identifying-users), [Capacitor](capacitor-identifying-users). 2. **Передайте email.** Как только пользователь укажет свой email, отправьте его в Adapty через `updateProfile`, используя параметр `email`. Руководства по платформам: [iOS](setting-user-attributes), [Android](android-setting-user-attributes), [React Native](react-native-setting-user-attributes), [Flutter](flutter-setting-user-attributes), [Unity](unity-setting-user-attributes), [Kotlin Multiplatform](kmp-setting-user-attributes), [Capacitor](capacitor-setting-user-attributes). :::important - Всегда передавайте **стабильный** `customer_user_id`, никогда — анонимный идентификатор. Если пользователь удалит и переустановит приложение, Adapty использует этот ID, чтобы связать переустановку с существующим профилем и корректно атрибутировать покупки нужному пользователю. - Получите явное согласие пользователя перед сбором и отправкой email-адресов в Adapty. Вы несёте ответственность за соответствие требованиям GDPR, CAN-SPAM и аналогичных регламентов на ваших целевых рынках. ::: <Details> <summary>Проверьте охват email-адресов</summary> После внедрения сбора данных проверьте охват в Adapty: 1. Перейдите в **Customers → Profiles**. 2. Отфильтруйте профили, у которых указан email. Стремитесь к охвату email не менее 30–50% среди активных пользователей, прежде чем запускать первую кампанию. Необязательно ждать 100% — запускайте, как только достигнете 30%. Пользователи, которые укажут email позже, автоматически попадут в активные кампании, как только будут соответствовать условиям. </Details> ## Стратегии сбора email-адресов \{#email-collection-strategies\} Большинство приложений по умолчанию не собирают email-адреса. Выберите подход, который подходит вашему приложению. | Стратегия | Лучше всего для | Как работает | | ---------------------------------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | **Существующая аутентификация** | Приложения с любой формой входа | Email у вас уже есть — передайте его в Adapty после того, как пользователь аутентифицируется. Где его считать — смотрите в справочнике по методам аутентификации ниже. | | **Email-форма перед пейволом** | Приложения без авторизации — здоровье, велнес, астрология, фоторедакторы | Добавьте один экран с полем для email между онбордингом и пейволом. Конверсия обычно составляет 70–90%, потому что пользователи уже вложили время. | | **Чекаут в веб-конструкторе пейвола** | Минимум работы с SDK; email собирается на вебе | Первый экран веб-конструктора пейвола запрашивает email и передаёт его в Adapty — удобно для пользователей, которые переходят по ссылке из кампании до того, как in-app форма готова. | | **Шаг онбординга** | Онбординг в формате квиза (фитнес, питание, образование) | Разместите поле для email на 2–3-м шаге онбординга. Подайте это как пользу для пользователя («Мы пришлём ваш персональный план на почту») и не делайте шаг пропускаемым. | | **Adapty Mail API** | Отправка писем с вашего сервера без SDK Adapty | Отправляйте профили на эндпоинт [Save profile](api-mail/operations/saveProfile) Adapty Mail API. Подробнее — в разделе [Отправка писем и транзакций через Adapty Mail API](mail-send-data-via-api). | ## Ограничения \{#limitations\} - **Анонимные пользователи**: пользователи без стабильного `customer_user_id` не могут получать кампании. Идентифицируйте их в момент создания аккаунта или входа в систему — с этого момента любой указанный ими email будет привязан к их профилю в Adapty. - **Пользователи без email**: профили без email исключаются из доставки кампаний и не отображаются в аналитике кампаний. Как только они укажут email, они станут доступны для будущих кампаний. --- # File: mail-send-data-via-api --- --- title: "Отправка писем и транзакций через Adapty Mail API" description: "Отправляйте профили пользователей и транзакции в Adapty Mail напрямую с вашего сервера, без SDK Adapty." --- Adapty Mail API позволяет отправлять профили пользователей и транзакции в Adapty Mail напрямую с вашего сервера, минуя SDK Adapty. Используйте его, если вы хотите: - Добавляйте подписчиков, если у вас ещё нет базы в Adapty Mail. - Повторно используйте базу подписчиков из других ваших приложений. - Передавайте данные в Adapty Mail напрямую с сервера, используя бэкенд как источник истины. :::note **API или SDK?** Большинство приложений передают данные в Adapty Mail через Adapty SDK, который автоматически собирает email-адреса и покупки. Выбирайте API, если в вашем приложении нет Adapty SDK, если данные уже хранятся на вашем сервере или если вы импортируете подписчиков из другого источника. ::: ## Перед началом работы \{#before-you-start\} :::warning Завершите настройку Adapty Mail до отправки данных — это значит создать кампанию, сегменты (если нужны), веб-пейвол и запустить флоу. Adapty Mail отправляет письма только профилям, созданным после завершения этой настройки; профили, добавленные раньше, не получат никаких писем. Сначала пройдите [Начало работы с Adapty Mail](mail-get-started), а затем возвращайтесь сюда. ::: Вам также понадобятся API-ключ и базовый URL: - **Секретный API-ключ**: В Adapty Mail перейдите в **Settings** и скопируйте секретный API-ключ. Ключ привязан к конкретному проекту, поэтому API знает, к какому проекту относятся данные. - **Базовый URL**: Все запросы отправляются на `https://api-mail.adapty.io`. - **Аутентификация**: Передавайте ключ в заголовке **Authorization** в формате `Bearer {your_secret_api_key}`. :::important Перед сбором email-адресов и их передачей в Adapty Mail получите явное согласие пользователей. Вы несёте ответственность за соответствие требованиям GDPR, CAN-SPAM и аналогичных законодательных актов в ваших регионах. ::: ## Отправка профилей пользователей \{#send-user-profiles\} Профиль содержит email пользователя и его атрибуты. Чтобы создать или обновить профиль, отправьте POST-запрос на `/api/v1/profile/save/`. Обязательные поля: - Стабильный `external_profile_id`, которым владеет ваше приложение или бэкенд - `email` — адрес, на который Adapty Mail доставляет кампании - `external_created_at` — время создания пользователя, которое можно использовать в сегментах :::important Всегда передавайте стабильный `external_profile_id` — никогда анонимный или привязанный к конкретной установке. Adapty Mail использует его, чтобы связывать письма, клики и покупки с одним профилем. ::: ```bash curl --request POST \ --url 'https://api-mail.adapty.io/api/v1/profile/save/' \ --header 'Authorization: Bearer {your_secret_api_key}' \ --header 'Content-Type: application/json' \ --data '{ "external_profile_id": "user_12345", "external_created_at": "2026-06-01T10:30:00Z", "email": "jane@example.com", "country": "US", "custom_attributes": { "plan": "trial" } }' ``` Полное описание всех доступных полей см. в справочнике [Save profile](api-mail/operations/saveProfile). ## Отправка событий транзакций \{#send-transaction-events\} :::note Для охвата пользователей во флоу **never purchased** достаточно профиля с email. Пользователям во всех остальных флоу также нужны события транзакций. ::: Все флоу, кроме **never purchased**, формируются на основе истории покупок. Отправляйте события транзакций профиля при обработке покупок, продлений и отмен — так Adapty Mail правильно определит, в какой флоу попадёт пользователь. События транзакций также обеспечивают атрибуцию выручки. Пропускайте их только в том случае, если вы запускаете исключительно кампании **never purchased**. Чтобы зафиксировать транзакцию, отправьте POST-запрос на `/api/v1/profile/transaction-event/save/`. Используйте тот же `external_profile_id`, что вы отправляли вместе с профилем, — так Adapty Mail свяжет транзакцию с нужным пользователем. ```bash curl --request POST \ --url 'https://api-mail.adapty.io/api/v1/profile/transaction-event/save/' \ --header 'Authorization: Bearer {your_secret_api_key}' \ --header 'Content-Type: application/json' \ --data '{ "event_type": "subscription_started", "event_id": "evt_abc123", "event_datetime": "2026-06-10T14:20:05Z", "external_profile_id": "user_12345", "store": "app_store", "store_product_id": "premium_monthly", "store_transaction_id": "1000000123456789", "store_original_transaction_id": "1000000123456789", "purchased_at": "2026-06-10T14:20:00Z", "originally_purchased_at": "2026-06-10T14:20:00Z", "price_usd": "9.99" }' ``` Список всех доступных полей см. в справочнике [события сохранения транзакции](api-mail/operations/saveTransactionEvent). ### Привяжите события к флоу \{#map-your-events-to-flows\} Отправляйте `event_type`, соответствующий произошедшему событию. Adapty Mail определяет состояние профиля на основе истории событий и направляет его в подходящее флоу. | `event_type` | Когда отправлять | Флоу | | --- | --- | --- | | `subscription_started` | Пользователь оформляет новую подписку. | Активна — без флоу повторного вовлечения | | `subscription_renewed` | Подписка автоматически продлевается. | Активна — без флоу повторного вовлечения | | `subscription_renewal_reactivated` | Пользователь снова включает автопродление. | Активна — без флоу повторного вовлечения | | `non_subscription_purchase` | Пользователь совершает разовую покупку. | Активна — без флоу повторного вовлечения | | `subscription_renewal_cancelled` | Пользователь отключает автопродление (подписка остаётся активной до истечения срока). | Отмена продления | | `billing_issue_detected` | Платёж за продление не проходит. | Проблема с оплатой | | `entered_grace_period` | Платёж не прошёл, но пользователь находится в льготном периоде. | Проблема с оплатой | | `subscription_expired` | Подписка истекает и доступ заканчивается. | Истекла | | `subscription_refunded` | Покупка подписки возвращается. | Возврат | | `non_subscription_purchase_refunded` | Разовая покупка возвращается. | Возврат | --- # File: mail-brand --- --- title: "Бренд в Adapty Mail" description: "Просматривайте и уточняйте профиль бренда, на основе которого генерируются письма и веб-пейволы." --- **Бренд** — это сводный профиль, который Adapty Mail собирает из публичных источников вашего приложения: страниц в App Store или Google Play, лендинга, страниц с условиями использования и политикой конфиденциальности, а также социальных профилей. Он определяет текст и тон писем, визуальное оформление, содержимое веб-пейволов и демо. В каждом проекте один бренд; все зависимые функции читают данные из одного профиля. Откройте бренд, нажав на **Brand** в боковом меню Adapty Mail. - **Если вы зарегистрировались в Adapty Mail через Adapty**: бренд был создан автоматически на основе URL стора вашего проекта в Adapty. Страница бренда откроется с заполненным профилем — его можно просмотреть и скорректировать. - **Если вы зарегистрировались напрямую**: страница бренда откроется с экраном настройки — см. [Настройка с нуля](#set-up-from-scratch). ## Что входит в профиль бренда \{#whats-in-a-brand-profile\} У бренда есть 13 разделов. Adapty Mail использует их напрямую при генерации email-рассылок и веб-пейволов. - **Идентификация**: Название приложения, краткое описание, слоган. - **Визуальная идентификация**: Цвета (основной, фоновый, вторичный, акцентный, текстовый, CTA), типографика, заметки по стилю, URL логотипа. - **Аудитория**: Демография, языки, рынки. - **Функции**: Каждая функция включает название, описание пользы и опциональное описание. - **Инсайты**: Уникальное ценностное предложение, наблюдения, типичные возражения с контраргументами и теги. - **Голос бренда**: Тон, уровень формальности, лексика, эмоциональный регистр. - **Образцы голоса**: Примеры заголовков, примеры CTA и пресеты тона, используемые при генерации писем. - **Социальные доказательства**: Количество пользователей, рейтинг, количество оценок, упоминания в прессе, ключевые метрики. - **Ссылки на социальные сети**: Twitter, Instagram, TikTok, YouTube, Facebook, LinkedIn. - **Юридические ссылки**: URL условий использования, URL политики конфиденциальности, email поддержки. - **Отзывы**: Пользовательские отзывы, извлечённые из источника в сторе — содержание, автор, рейтинг, источник. - **Болевые точки**: Формулировки проблем, основанные на отзывах или сигналах из социальных сетей. - **FAQ**: Вопросы и ответы, используемые в контенте писем и на разделах пейвола на сайте. ## Редактирование раздела вручную \{#edit-a-section-manually\} У каждого раздела в представлении бренда есть кнопка **Edit**. Нажав на неё, вы откроете встроенный редактор для этого раздела. 1. Нажмите **Edit** на том разделе, который хотите изменить. 2. Отредактируйте поля прямо на месте. Adapty Mail сохраняет несохранённые изменения как черновик. 3. Нажмите **Save** в баннере черновика вверху страницы, чтобы применить изменения. Нажмите **Discard**, чтобы отменить их. Одновременно можно открыть только один раздел. Если попытаться открыть второй, первый предложит завершить или отменить редактирование. :::important Пока источник обрабатывается, редактирование недоступно — завершение обработки источника может перезаписать внесённые изменения. Все открытые редакторы автоматически закрываются в момент начала обработки, а кнопка **Save** в баннере черновика остаётся неактивной до её завершения. ::: ## Уточнение с помощью ИИ \{#refine-with-ai\} Кнопка **Refine with AI** в правом нижнем углу открывает панель чата рядом с брендом. Опишите нужные изменения простыми словами — ИИ предложит обновлённый вариант, который можно сохранить или отклонить. 1. Нажмите **Refine with AI**. 2. Опишите изменение. Область действия чата ограничена редактированием бренда — он не отвечает на общие вопросы и не переписывает тексты за пределами профиля бренда. 3. Просмотрите предложенный вариант в основном окне. Adapty Mail подсвечивает изменённые разделы. 4. Нажмите **Save** в баннере черновика, чтобы применить изменения, или **Discard**, чтобы вернуться к сохранённому бренду. Полезные запросы: - «Сделай тон более игривым.» - «Добавь функцию офлайн-режима.» - «Усиль уникальное торговое предложение.» - «Перепиши описание аудитории для рынка США.» ## Добавьте больше источников \{#add-more-sources\} Источник из стора заполняет профиль. Дополнительные типы источников уточняют отдельные разделы — лендинги улучшают визуальный стиль и тексты, страницы условий использования и политики конфиденциальности добавляют юридические ссылки, социальные профили улучшают примеры голоса бренда и социальные доказательства. В представлении бренда панель **Sources** отображает все источники и форму **Add** под ними. Выберите тип, вставьте URL и нажмите **Add**. - **App Store**: `https://apps.apple.com/...` - **Google Play**: `https://play.google.com/store/apps/details?id=...` - **Landing page**: Ваш маркетинговый сайт, например `https://yourapp.com`. - **Terms / Privacy**: Прямая ссылка на страницу с условиями использования или политикой конфиденциальности. - **Social profile**: URL страницы в Twitter, Instagram, TikTok, YouTube, Facebook или LinkedIn. Только один источник каждого типа. После добавления источника соответствующий тип становится недоступным для выбора — до тех пор, пока обработка не завершится. Чтобы заменить источник, удалите бренд и пройдите онбординг заново — удалить отдельный источник нельзя. ## Настройка с нуля \{#set-up-from-scratch\} Если у вашего проекта ещё нет бренда (типично для отдельных регистраций в Adapty Mail), страница **Brand** откроется с экраном настройки. Источник данных — App Store или Google Play — является основой; другие типы источников можно добавить позже. 1. Выберите стор (**App Store** или **Google Play**) и вставьте URL листинга. 2. Нажмите **Build my brand**. Adapty Mail загружает страницу, анализирует отзывы и определяет голос вашего бренда. Обычно это занимает меньше минуты. 3. Когда обработка завершится, откроется представление бренда со всеми 13 заполненными разделами. Если источник недоступен (неверный URL, недоступная страница, ошибка парсера), на экране появится сообщение об ошибке и кнопка **Try again**. Исправьте URL и отправьте запрос повторно. ## Где используется бренд \{#where-the-brand-is-used\} Бренд используется во всех функциях, которым нужно знать, как звучит и выглядит ваше приложение: - **Создание писем**: текст, тон, визуальное оформление, блок отправителя и главное изображение — всё берётся из бренда. См. [Создать кампанию](mail-create-campaign). - **Конструктор веб-пейвола**: бренд обязателен для генерации пейвола — без `brand_saved` генерация заблокирована. См. [Настроить чекаут](mail-checkout). - **Онбординг**: шаг **Set up brand** в чеклисте онбординга отмечается как выполненный, когда бренд создан. ## Удаление бренда \{#delete-a-brand\} Действие **Delete brand** находится в разделе **Danger zone** внизу страницы бренда. 1. Нажмите **Delete** в разделе Danger zone. 2. Подтвердите действие в диалоговом окне. При удалении стираются профиль бренда и все источники. Отменить это невозможно — чтобы восстановить данные, вставьте URL из App Store или Google Play на стартовом экране и пройдите онбординг заново. :::warning Существующие кампании сохраняют снапшот бренда, с которым были созданы, однако создание новых кампаний и генерация пейволов будут заблокированы до тех пор, пока вы не пройдёте онбординг заново. ::: ## Ограничения \{#limitations\} - **Один бренд на проект**: каждый проект Adapty Mail содержит один бренд. Чтобы настроить другое приложение, создайте новый проект. - **Один источник каждого типа**: у бренда может быть не более одного источника для App Store, Google Play, лендинга, страницы с условиями/политикой конфиденциальности и социального профиля. - **Удаление отдельных источников недоступно**: удалить отдельный источник через интерфейс нельзя. Если нужно заменить источник, используйте **Delete brand**. - **Редактирование заблокировано во время обработки**: пока источник находится в состоянии `pending` или `processing`, редактирование разделов, сохранение через чат уточнений и удаление бренда недоступны. - **Источники с ошибками остаются в списке**: источник в состоянии `failed` продолжает отображаться на панели с сообщением об ошибке. Чтобы повторить попытку, отправьте источник того же типа заново — при успешной постановке в очередь старая запись заменяется новой. --- # File: mail-sending-domain --- --- title: "Настройка домена отправки для Adapty Mail" description: "Добавьте DNS-записи, подтвердите домен и настройте прогрев, чтобы Adapty Mail мог отправлять письма от вашего имени." --- Adapty Mail отправляет кампании с вашего домена, а не с общего адреса — так репутация отправителя остаётся под вашим контролем. Настраивается это один раз, и все кампании используют один верифицированный домен. Минимальные шаги описаны в разделе о домене в статье [Начало работы с Adapty Mail](mail-get-started#2-set-up-your-sending-domain). В этой статье рассматривается полная настройка, как работает верификация и автоматический прогрев. ## Требования \{#requirements\} - **Корневой домен (apex)**: укажите корневой домен (например, `yourapp.com`), а не поддомен. Ввод вида `app.yourapp.com` не пройдёт валидацию. - **Активные NS-записи**: домен должен резолвиться. Adapty Mail выполняет DNS-запрос при настройке и отклоняет домены без корректных NS-записей. - **Один домен на проект Adapty**: домен нельзя использовать совместно в нескольких проектах. Если домен уже зарегистрирован в каком-либо проекте — вашем или чужом — настройка завершится ошибкой. ## Настройте домен для отправки \{#set-up-your-sending-domain\} Мастер настройки состоит из трёх экранов: ввод домена, подтверждение сгенерированных поддоменов и добавление DNS-записей. Все три находятся в разделе **Settings → Email Domains**. 1. **Введите домен.** Введите ваш apex-домен в поле **Domain** и нажмите **Preview**. Adapty Mail проверяет формат (ASCII, два метки, без дефисов в начале и конце, TLD из 2+ символов) и проверяет, что DNS разрешается. 2. **Подтвердите поддомены.** Adapty Mail создаёт два поддомена для отправки с фиксированными префиксами — `mail.yourapp.com` и `email.yourapp.com` — каждый со своей SES-идентичностью. Также создаётся Mail-From поддомен для каждого (`hello.mail.yourapp.com` и `hello.email.yourapp.com`). Проверьте их и нажмите **Confirm**. 3. **Добавьте DNS-записи.** На последнем экране будет список всех записей — 10 штук: по 5 на каждый отправляющий поддомен, плюс одна опциональная DMARC-запись для корневого домена. Используйте **Download CSV**, чтобы экспортировать весь список, или копируйте записи по одной в панель управления вашего регистратора. Нажмите **Done**, когда все записи добавлены. <Details> <summary>Справочник DNS-записей</summary> Для каждого отправляющего поддомена (`mail.yourapp.com` и `email.yourapp.com`) добавьте: **DKIM — 3 CNAME-записи.** Криптографические подписи, подтверждающие, что письмо не было изменено в процессе доставки. | Поле | Формат | | ---- | ----------------------------------- | | Тип | CNAME | | Имя | `{token}._domainkey.{subdomain}` | | Значение | `{token}.dkim.amazonses.com` | **Mail-From — 1 MX-запись.** Обрабатывает возвраты писем. | Поле | Формат | | -------- | -------------------------------------------------------- | | Тип | MX | | Имя | `hello.{subdomain}` (например, `hello.mail.yourapp.com`) | | Приоритет | `10` | | Значение | `feedback-smtp.{region}.amazonses.com` | **SPF — 1 TXT-запись.** Разрешает Adapty отправлять письма от вашего имени. | Поле | Формат | | ----- | -------------------------------------- | | Type | TXT | | Name | `hello.{subdomain}` | | Value | `"v=spf1 include:amazonses.com ~all"` | На корневом домене добавьте опциональную запись DMARC: | Поле | Формат | | ----- | --------------------- | | Type | TXT | | Name | `_dmarc.{domain}` | | Value | `v=DMARC1; p=reject` | Токены, регион и все остальные значения берутся из AWS SES при настройке. Всегда копируйте их из экрана DNS-записей в Adapty Mail, а не из этого справочника. </Details> ## Как работает проверка \{#how-verification-works\} После настройки DNS-записей Adapty Mail автоматически опрашивает DNS. Вы также можете запускать проверки вручную. - **Автоматический опрос**: Проверка запускается через 5 минут после отправки, затем интервал удваивается — 10 мин, 20 мин, 40 мин — и далее не превышает 60 мин. Процесс продолжается до тех пор, пока не будут найдены записи или не истечёт 7-дневное окно. - **Ручная проверка**: Нажмите **Check Verification**, чтобы запустить проверку немедленно. Между ручными проверками действует пауза в 60 секунд — если нажать слишком быстро, появится сообщение *«Verification check is on cooldown.»* - **Статусы**: DKIM и Mail-From для каждого поддомена отслеживаются независимо и могут принимать значения **Pending**, **Success** или **Failed**. Домен считается полностью верифицированным только тогда, когда все четыре статуса отображают **Success**. - **Срок в 7 дней**: Если верификация не завершена в течение 7 дней, идентификатор получает статус **Failed**. DNS-записи остаются у регистратора — добавьте домен заново в **Settings → Email Domains**, чтобы начать новый отсчёт. - **После верификации**: Если впоследствии удалить или изменить DNS-записи, AWS SES в конечном итоге понизит статус идентификатора. Оставляйте записи активными всё время, пока планируете отправлять письма. - **Распространение DNS**: Обычно занимает несколько минут; в редких случаях — до 48 часов. ## Прогрев домена \{#domain-warm-up\} Новые домены не имеют репутации у почтовых провайдеров вроде Gmail или Yahoo, поэтому массовые рассылки с нового домена рискуют попасть в спам. Adapty Mail управляет прогревом автоматически, постепенно увеличивая дневной лимит отправки по 14 уровням. Никакой настройки не требуется. ### Как работают уровни \{#how-tiers-work\} Ваш домен начинает с **уровня 1** (200 отправок в день) и автоматически повышается, пока метрики доставляемости остаются в норме. Если растёт доля отказов или жалоб — повышение приостанавливается и может откатиться назад, пока репутация не восстановится. | Tier | Daily limit | | ---- | ----------- | | 1 | 200 | | 2 | 400 | | 3 | 800 | | 4 | 1,500 | | 5 | 2,500 | | 6 | 4,000 | | 7 | 6,000 | | 8 | 8,000 | | 9 | 10,000 | | 10 | 13,000 | | 11 | 16,000 | | 12 | 20,000 | | 13 | 25,000 | | 14 | 30,000 | Ваш текущий уровень и дневной лимит отображаются в **Settings → Email Domains**. ### Влияние на запуск в зависимости от размера аудитории \{#impact-on-launch-by-audience-size\} | Размер аудитории | Эффект при запуске | | ------------------- | ----------------------------------------------- | | До 200 пользователей | Вся аудитория охватывается в первый день | | 200–2 000 пользователей | Доставка растягивается на несколько дней | | Более 2 000 пользователей | Доставка растягивается на 1–2 недели | :::tip Запустите первую кампанию сразу после завершения верификации DNS. Чем раньше вы начнёте отправлять, тем быстрее домен пройдёт все уровни и достигнет полного дневного объёма. ::: ## Ограничения \{#limitations\} - **Один домен на проект**: В каждом проекте Adapty можно зарегистрировать только один домен для отправки. Чтобы сменить домен, обратитесь в поддержку — кнопки «изменить домен» в дашборде нет. - **Уникальность между проектами**: Домен, уже зарегистрированный в другом проекте, нельзя использовать повторно. Если видите сообщение *«Domain is already registered to another project»*, выберите другой домен или обратитесь в поддержку. - **Верифицированные домены нельзя удалить**: Как только хотя бы один поддомен получает статус **Success**, дашборд блокирует удаление. Домены в статусе ожидания удалить можно, но DNS-записи придётся убрать вручную у своего регистратора. - **Фиксированные префиксы поддоменов**: Префиксы `mail.`, `email.` и `hello.` для Mail-From заданы жёстко — изменить их нельзя. Если эти поддомены уже используются в вашем DNS, настройка завершится конфликтом. - **Только корневые домены**: Поддомены на входе, завершающие точки и однокомпонентные имена хостов не принимаются. - **Интернационализированные домены не поддерживаются**: Punycode и IDN недоступны. Домен должен состоять только из символов ASCII. ## Устранение неполадок \{#troubleshooting\} | Проблема | Решение | | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | | «Enter a valid domain (e.g. example.com)» | Проверьте ввод: только апекс-домен, только ASCII, TLD из 2+ символов, без дефисов в начале и конце. | | «Domain does not have valid DNS records» | Сам апекс-домен должен резолвиться. Убедитесь, что NS-записи активны, и повторите попытку. | | «Domain is already registered to another project» | Выберите другой домен или обратитесь в поддержку, если считаете регистрацию ошибочной. | | «Verification check is on cooldown» | Подождите 60 секунд между ручными проверками. Автоматический опрос продолжается в фоне. | | Верификация зависла в статусе Pending | Убедитесь, что DNS-записи совпадают точно — без точек в конце, с правильными целями CNAME. Распространение DNS может занять до 48 часов. | | «Cannot delete domain: one or more identities have been successfully verified» | Верифицированный домен нельзя удалить через дашборд. Обратитесь в поддержку. | | Письма попадают в спам | Убедитесь, что DMARC-запись опубликована. Новые домены требуют прогрева — изучите раздел [Прогрев домена](#domain-warm-up). | | Высокий показатель отказов | Убедитесь, что в списке аудитории только валидные адреса с подтверждённым согласием. Отказы замедляют или останавливают продвижение по уровням. | --- # File: mail-checkout --- --- title: "Настройка чекаута для Adapty Mail" description: "Создайте веб-пейвол и подключите платёжный провайдер, чтобы ваши email-кампании вели на персонализированный веб-чекаут." --- Каждое письмо, которое отправляет Adapty Mail, содержит уникальную ссылку на чекаут для конкретного получателя. При переходе пользователь попадает в воронку веб-чекаута, которая идентифицирует его по профилю, показывает ваше предложение и обрабатывает оплату. Воронки чекаута находятся в разделе **Web Paywalls** внутри Adapty Mail и редактируются во встроенном **web paywall builder**. ## Требования \{#requirements\} - Провайдер веб-платежей с настроенными продуктами подписки. **Generate with AI** поддерживает только Stripe и подключается прямо в билдере. **Use your own hosted paywall** принимает любого провайдера — Stripe, Paddle, PayPal или другого — так как оплата обрабатывается на вашей стороне. Для работы с веб-редактором пейволов отдельный аккаунт не нужен. Он входит в состав Adapty Mail: рабочее пространство создаётся автоматически при первом входе, а авторизация в редакторе выполняется с теми же учётными данными Adapty. Это никак не связано с веб-пейволами, которые вы могли настроить на странице пейволов основного дашборда Adapty — веб-пейволы Adapty Mail являются отдельными сущностями и управляются исключительно из Adapty Mail. ## Настройте воронку оформления подписки \{#set-up-your-checkout-funnel\} В Adapty Mail перейдите в **Web Paywalls → Create**. Вам доступны два варианта: - **Generate with AI**: встроенный конструктор веб-пейволов Adapty Mail создаст воронку за вас. Только для Stripe — для Paddle или PayPal используйте второй вариант. - **Use your own hosted paywall**: подключите пейвол, который вы уже размещаете на своём хостинге, с любым платёжным провайдером. ### Создание с помощью ИИ \{#generate-with-ai\} На странице создания вверху отображается панель **Prerequisites** с кнопками действий, которые проведут вас через каждое предварительное условие: готовность бренда, вход в конструктор, подключение Stripe, продукты и финальный шаг проверки и публикации. Выполните все шаги — панель обновляется по мере их завершения. Когда все условия выполнены (отмечены зелёным), нажмите **Generate**, чтобы открыть диалог генерации. Нужно сделать два выбора: - **Environment**: Выберите **Production** или **Sandbox**. Sandbox использует тестовые продукты Stripe и является безопасным вариантом по умолчанию для разработки и локальных окружений — его аккаунт изолирован от production, поэтому тестовые транзакции никогда не затрагивают рабочие данные. - **Plans**: Выберите до **3 планов Stripe**. Каждый план — это комбинация продукта и цены. Пейвол отображает их как предложения при оформлении заказа. Если вы выберете меньше 3 планов, пейвол покажет только выбранные. Нажмите **Generate**, чтобы запустить сборку. Когда она завершится, откройте редактор в билдере, проверьте результат и опубликуйте его. Затем нажмите **Save**. :::important Пейвол должен быть опубликован до того, как он начнёт обслуживать трафик из чекаута. Неопубликованные пейволы возвращают ошибку, когда пользователи переходят по ссылкам email-чекаута. ::: Для получения информации о параметрах платёжного провайдера в конструкторе (аккаунты Stripe, тестовый и боевой режимы, настройка продуктов) см. [Настройка веб-пейвола](web-paywall-configuration). ### Используйте собственный пейвол \{#use-your-own-hosted-paywall\} 1. На странице создания выберите **Enter URL manually**. 2. Вставьте URL вашего пейвола. Он должен содержать плейсхолдеры `{email}` и `{external_profile_id}` в качестве query-параметров — Adapty Mail подставляет их для каждого получателя, чтобы страница знала, кто перешёл по ссылке. Пример: ``` https://example.com/paywall?email={email}&profile={external_profile_id} ``` 3. Сохраните. Этот способ работает с любым платёжным провайдером — Adapty Mail отвечает только за редирект и подстановку параметров; оплата и персонализация полностью на вашей стороне. ## Как выглядит чекаут \{#what-the-checkout-looks-like\} Когда пользователь переходит по ссылке на чекаут, он попадает на **Main conversion page**. После попытки оплаты он увидит либо **Payment success**, либо **Payment failed** — только одну из двух страниц за одну попытку. **Main conversion page** Полноценная страница продаж. ИИ генерирует текст и изображения для каждого раздела: | Раздел | Что генерирует ИИ | |---|---| | Заголовок | Жирный заголовок, акцент на пользе | | Подзаголовок | Поддерживающее ценностное предложение | | Бейдж акции | Бейдж срочности (без придуманных цен — использует размытые промо-формулировки) | | Кнопка CTA | Призыв к действию, 2–5 слов | | Преимущества | 3–6 карточек преимуществ с эмодзи и текстом | | Функции | 3–8 описаний функций с названием и подзаголовком | | Тарифы | Заголовок выбора плана и текст таймера акции | | Социальное доказательство | Текст о сообществе и 3–5 реалистичных отзывов пользователей | | FAQ | 3–6 часто задаваемых вопросов с ответами | | Гарантия | Текст гарантии возврата денег или удовлетворённости | **Успешная оплата** Праздничное сообщение с дальнейшими шагами и изображением, сгенерированным ИИ. **Ошибка оплаты** Дружелюбное сообщение с предложением попробовать снова. Состояние чекаута сохраняется. ## Как работает персонализация \{#how-personalization-works\} Каждое письмо содержит уникальный URL оформления заказа, в который встроены `customer_user_id` получателя и адрес электронной почты в виде параметров: ``` https://your-funnel.com/?cid={{customer_user_id}}&email={{email}} ``` Adapty автоматически генерирует эти URL при отправке каждого письма — никакой настройки в конструкторе веб-пейволов не требуется. Когда пользователь переходит по ссылке, конструктор считывает параметры и идентифицирует его. После завершения покупки Adapty связывает доход с конкретным письмом, которое привело к конверсии. Эти данные отображаются в [аналитике кампаний](mail-analytics). ## Устранение неполадок \{#troubleshooting\} | Проблема | Решение | |---|---| | Ссылка на оформление покупки не открывается | Убедитесь, что пейвол опубликован в конструкторе веб-пейволов | | Пользователь не идентифицирован при оформлении покупки | Убедитесь, что `Adapty.identify()` был вызван с корректным идентификатором пользователя до отправки письма | | Покупка не привязана к письму | Проверьте, присутствует ли параметр `cid` в URL оформления покупки — если параметры отсутствуют, обратитесь в службу поддержки | --- # File: mail-email-campaigns --- --- title: "Email-кампании в Adapty Mail" description: "Создавайте многоэтапные email-последовательности, выбирайте нужный тон и целевую аудиторию." --- Кампания в Adapty Mail — это полноценная многоэтапная email-последовательность: текст, дизайн, главные изображения и задержки между письмами, — сгенерированная для вашего приложения за один проход. Сама по себе кампания ничего не отправляет: она сохраняется как **черновик** и начинает доставку только после привязки к [флоу](mail-create-flow), который задаёт триггер и аудиторию. Используйте гайды ниже, чтобы создавать кампании, выбирать нужный тон и нацеливаться на правильных пользователей. <CustomDocCardList ids={['mail-create-campaign', 'mail-suppression']} /> --- # File: mail-create-campaign --- --- title: "Создание кампании в Adapty Mail" description: "Создайте полную цепочку писем на основе метаданных вашего приложения из стора и отредактируйте её перед добавлением в поток." --- Adapty Mail генерирует полноценную email-последовательность — тексты, дизайн, hero-изображения, темы писем и задержки — на основе метаданных вашего приложения из стора. Никакого копирайтинга и дизайна не требуется. Кампания сохраняется как черновик; доставка начнётся только после того, как вы привяжете её к [флоу](mail-create-flow). ## Перед началом работы \{#before-you-start\} - **Сохранённый веб-пейвол**: каждая кампания должна быть связана с веб-пейволом. Бэкенд отклоняет кампании без него. Если у вас ещё нет веб-пейвола, см. [Создание веб-пейвола](mail-get-started#4-create-a-web-paywall). - **URL App Store или Google Play в настройках**: ИИ считывает метаданные приложения (название, категорию, описание, скриншоты) из этого URL, чтобы адаптировать последовательность. Добавьте его в **Settings → App metadata**, если он ещё не указан. ## 1. Создайте последовательность \{#1-generate-the-sequence\} 1. В Adapty Mail перейдите в **Campaigns** и нажмите **Create**. 2. Задайте название кампании. 3. В выпадающем списке **Web paywall** выберите веб-пейвол, на который будут вести ссылки из писем. 4. Нажмите **Generate emails**. 5. Заполните диалог генерации: - **Tone**: Выберите из списка. Варианты подобраны специально под категорию вашего приложения — для другого приложения список будет другим. Выбранный тон влияет на темы писем, заголовки, текст и CTA во всех письмах; на макет и изображения-обложки он не влияет. - **Language**: Выберите язык писем. - **Custom prompt** (необязательно): Свободный текст до 2 000 символов. Используйте его, чтобы указать акцию, повод, особенности аудитории, обязательные тезисы или дополнительные пожелания к тону, которые нельзя выразить стандартными пресетами. - **Number of emails**: По умолчанию AI сам определяет количество писем, опираясь на лучшие практики и контекст вашего приложения. Чтобы задать число вручную, нажмите **Set number manually** и выберите значение (**1–15**, по умолчанию **4**). После нажатия **Generate** тон фиксируется для этой кампании. Чтобы попробовать другой тон, создайте новую кампанию — каждая генерация может дать другой набор вариантов. 6. Нажмите **Generate**. Генерация обычно занимает несколько минут. Если бэкенд не успевает завершить её за 5 минут, запрос прерывается — просто попробуйте ещё раз. ## 2. Просмотр и доработка \{#2-review-and-refine\} После генерации вся последовательность отображается в предпросмотре. Для каждого письма вы увидите: - **Варианты темы письма**: три варианта темы для каждого письма. Adapty Mail тестирует их при доставке и продолжает отправлять наиболее эффективный — см. [A/B-тестирование](mail-ab-testing). - **Заголовок, текст и CTA**: основной блок контента. - **Изображение-обложка**: сгенерированное изображение, подобранное под содержание письма и ваш бренд. - **Макет и задержка**: как организовано письмо и через сколько времени после предыдущего оно отправляется. Шапка превью содержит **переключатель темы** (Авто, Светлая, Тёмная) — кнопки-иконки в правом верхнем углу области превью. Он влияет только на отображение превью; сгенерированный контент при этом не меняется. Используйте его, чтобы проверить, как выглядит каждое письмо в разных цветовых схемах, не запуская генерацию заново. Вы можете: - **Перегенерировать отдельные письма**: ИИ переписывает текст и создаёт новое главное изображение для одного письма. Позиция, тайминг и общая дизайн-система (цвета, типографика, тёмная тема) остаются прежними — меняется только выбранное письмо. - **Редактировать HTML напрямую**: Открывается редактор HTML-кода для точной правки всего, что ИИ сделал не так. :::note Письма адаптируются автоматически. Многоколоночные макеты сворачиваются в одну колонку на экранах шириной менее 620 пикселей, и каждый макет протестирован в Gmail (веб и мобильный), Apple Mail (macOS и iOS), Outlook desktop, Yahoo Mail и Samsung Mail — в светлом и тёмном режимах. ::: ## 3. Сохраните как черновик \{#save-as-a-draft\} Нажмите **Create**, чтобы сохранить кампанию как черновик. Письма ещё не отправляются — в редакторе кампаний нет отдельного действия «опубликовать» или «запустить». Статус кампании отражает, привязана ли она к активному флоу: - **draft**: Не привязана ни к одному флоу. - **live**: Привязана к флоу и в данный момент направляет пользователей. - **inactive**: Была привязана, но A/B-тест во флоу завершился. - **archived**: Удалена из дашборда. :::important Черновик кампании никогда не отправляет письма самостоятельно. Чтобы начать рассылку, нужно: - Привяжите кампанию напрямую к [флоу](mail-create-flow), или - Включите её в [A/B-тест](mail-ab-testing) и привяжите A/B-тест к флоу. Пока этого не произошло, кампания остаётся в статусе `draft` и не достигает получателей. ::: --- # File: mail-suppression --- --- title: "Отписка и подавление в Adapty Mail" description: "Как Adapty Mail прекращает отправку пользователям — через отписку, отказы SES, жалобы и механизм стоп-условия." --- Adapty Mail прекращает отправку пользователю в двух случаях: - **Подавление**: пользователь исключается из всех будущих отправок в рамках проекта (отписался, получил bounce, пожаловался, отклонён или превышен лимит). - **Стоп-условие**: текущая последовательность отменяется, потому что пользователь совершил конверсию. Подавления нет — пользователь остаётся доступен для других кампаний. Оба механизма действуют в рамках одного проекта. Подавление в одном проекте Adapty не влияет на другой. ## Отписка \{#unsubscribe\} Каждое письмо, отправленное Adapty Mail, содержит ссылку для отписки в футере. 1. Пользователь нажимает на ссылку. Adapty Mail открывает страницу подтверждения. 2. Пользователь подтверждает. Бэкенд помечает профиль значением `suppression_reason = 'unsubscribe'`, отменяет оставшиеся письма в последовательности и исключает профиль из будущих отправок в проекте. Токен в ссылке для отписки кодирует `profile_id` и `scheduled_email_id`, поэтому авторизация не требуется. :::note Adapty Mail также добавляет заголовок `List-Unsubscribe: <URL>, <mailto:>` вместе с `List-Unsubscribe-Post: List-Unsubscribe=One-Click`. Gmail и Yahoo требуют этого от массовых отправителей (RFC 8058). Почтовые клиенты, поддерживающие этот заголовок, показывают кнопку отписки в один клик прямо в интерфейсе — без страницы подтверждения. ::: ## Автоматическое подавление \{#automatic-suppression\} Adapty Mail прослушивает события доставки AWS SES через SNS и немедленно подавляет пользователя при любом из следующих событий: | Событие | Код причины | Что означает | | --------- | ----------- | ------------------------------------------------------------------------------- | | Bounce | `bounce` | Адрес электронной почты недействителен, почтовый ящик переполнен или домен не существует. | | Complaint | `complaint` | Пользователь пометил письмо как спам. | | Reject | `reject` | SES отклонил сообщение до отправки. | | Throttle | `throttle` | Превышен безопасный лимит отправки для домена. | Для каждого события результат одинаков: пользователь добавляется в список подавления, оставшаяся последовательность отменяется, и пользователь исключается из будущих отправок в проекте. :::important Adapty Mail **не** различает постоянные и временные bounce. Любой bounce — включая временные ситуации вроде переполненного ящика — немедленно подавляет пользователя. Повторных попыток нет. ::: ## Стоп-условие \{#stop-condition\} Когда пользователь совершает конверсию в середине последовательности, Adapty Mail отменяет оставшиеся письма с причиной `stop_condition`. Конверсия означает, что статус подписки пользователя достиг **Subscribed** или статус разовой покупки достиг **Purchased**. Стоп-условие отличается от подавления: - **Подавление**: исключает пользователя из всех будущих отправок в проекте. - **Стоп-условие**: отменяет только текущую последовательность. Пользователь остаётся доступен для других кампаний — например, для потока продления или win-back, нацеленного на активных подписчиков. Отмены по стоп-условию отображаются в аналитике кампаний вместе с подавлениями. ## Управление подавлением \{#managing-suppression\} В дашборде Adapty Mail нет интерфейса для просмотра или удаления подавленных пользователей. Чтобы снять подавление с профиля — например, если кто-то случайно пометил тестовое письмо как спам — обратитесь в поддержку Adapty. ## Что Adapty Mail обеспечивает для соответствия требованиям \{#what-adapty-mail-handles-for-compliance\} Adapty Mail включает: - **Ссылку для отписки**: добавляется в футер каждого письма и обрабатывается сразу после подтверждения пользователем. - **Заголовки List-Unsubscribe**: отправляются с каждым письмом для отписки в один клик из интерфейса почты (RFC 8058). - **Автоматическое подавление**: срабатывает при bounce, жалобе, отклонении и превышении лимита в SES. Ваша ответственность: - **Физический почтовый адрес**: CAN-SPAM требует его в футере письма. Adapty Mail не добавляет его автоматически — укажите его при создании кампании. - **Явное согласие на получение писем**: получите его до того, как передавать адрес пользователя в Adapty. См. [Сбор email-адресов пользователей](mail-collect-emails). - **Запросы на удаление по GDPR**: в Adapty Mail нет эндпоинта «удалить мои данные». Обратитесь в поддержку Adapty, если пользователь воспользовался правом на удаление данных. --- # File: mail-flows --- --- title: "Флоу в Adapty Mail" description: "Как флоу направляют кампании нужным пользователям в нужный момент — триггеры, сегменты и правила приоритета." --- <CustomDocCardList ids={['mail-create-flow']} /> **Флоу** превращает сохранённую кампанию в запланированные отправки. Он связывает триггерное событие (состояние подписки пользователя) с сегментом (какие пользователи подходят) и кампанией, которую они получат. Adapty Mail обрабатывает каждый флоу при каждом срабатывании соответствующего события — без поллинга, без cron-задач, без ручного запуска. ## Триггеры \{#triggers\} Adapty Mail поставляется с пятью фиксированными триггерами, каждый из которых отображается в виде отдельного флоу в разделе **Flows**: - **Никогда не платил**: Пользователи, которые зарегистрировались, но ещё ничего не купили. Цель: активация и первая конверсия. Пользователи на триале сюда не попадают — начало триала считается активной подпиской. - **Отменили продление**: Пользователи, которые отключили автопродление, но подписка ещё активна. Охватывает как платных подписчиков, так и тех, кто отменил триал до конверсии. Лучший момент, чтобы их удержать — доступ у них ещё есть. Если сообщения должны различаться, разделите аудитории через фильтры сегментов. - **Проблема с оплатой**: Платёж не прошёл — отклонённая или устаревшая карта либо льготный период после неудачного продления. Цель: срочная и ненавязчивая помощь, а не продажа. Верните их быстро — они и так хотели заплатить. - **Истёкшие**: Подписка закончилась и доступ потерян. Охватывает как истёкшие платные подписки, так и триалы, которые завершились без конверсии. Цель: вернуть пользователя. Фильтры сегментов помогут подобрать разные тексты для аудиторий с истёкшим триалом и платной подпиской. - **Вернули деньги**: Пользователи, запросившие возврат средств после покупки. Цель: разобраться, что пошло не так, и предложить более подходящий вариант. Тон должен быть скромным и любопытным, а не навязчивым. Триггеры не настраиваются — нельзя создавать собственные триггеры или расширять их список. ## Сегмент «All Users» \{#the-all-users-segment\} В Adapty Mail есть встроенный сегмент **All Users** без фильтров — в него попадают все пользователи проекта. Удобнее всего использовать его во флоу как завершающую строку, которая охватывает тех, кто не подошёл ни под один из более конкретных сегментов выше. Сегмент All Users нельзя редактировать или удалять. Подробнее читайте в разделе [Сегменты](mail-segments). ## Приоритет \{#priority\} В каждом представлении триггера хранится список строк **сегмент → кампания** (или сегмент → A/B-тест), упорядоченных по приоритету. Когда пользователь попадает под триггер, Adapty Mail: 1. Проходит по строкам сверху вниз. 2. Отправляет кампанию из первой строки, чей сегмент совпал. 3. Останавливается. Остальные строки для этого пользователя не оцениваются. Порядок важен. Широкий сегмент, стоящий выше узкого, поглощает всех пользователей, которые иначе попали бы в нижнюю строку. Чтобы изменить порядок, перетащите маркер слева от нужной строки — бэкенд автоматически переназначит приоритеты 1, 2, 3… в соответствии с сохранённым порядком. :::important Строка **All Users**, если она присутствует, должна стоять последней (с наименьшим приоритетом). Бэкенд отклонит сохранение, если **All Users** не находится на последнем месте — иначе она перехватит всех пользователей прежде, чем более специфичные сегменты успеют сработать. ::: ## Типы контента \{#content-types\} Строка может содержать либо одну кампанию, либо A/B-тест: - **Кампания**: отправляет одну кампанию всем пользователям, которые соответствуют сегменту. - **A/B-тест**: объединяет две или более кампаний с настраиваемыми весами, случайным образом распределяет входящих пользователей между ними и отслеживает метрики по каждому варианту. См. [A/B-тестирование](mail-ab-testing). ## Жизненный цикл \{#lifecycle\} Строки флоу не имеют состояния черновика. Строка становится активной сразу после сохранения — с этого момента пользователи, попавшие под триггер и соответствующие сегменту, направляются в её кампанию. - **Создание строки**: начинает доставку немедленно после сохранения. - **Редактирование строки**: изменение применяется к пользователям, которые попадут под триггер с этого момента. Пользователи, уже находящиеся в середине последовательности, продолжают работу с предыдущей конфигурацией. - **Удаление строки**: новые пользователи перестают входить в последовательность. Пользователи, уже находящиеся в середине последовательности, могут продолжать получать запланированные письма — автоматической отмены не происходит. Строки A/B-тестов имеют собственный жизненный цикл (**черновик → активный → завершённый**), управляемый отдельно от самой строки. См. [A/B-тестирование](mail-ab-testing). --- # File: mail-create-flow --- --- title: "Создание строк и управление ими в Adapty Mail" description: "Добавляйте, переупорядочивайте, редактируйте и удаляйте строки в потоке для маршрутизации кампаний к вашим пользователям." --- Каждый [поток](mail-flows) — это упорядоченный по приоритету список строк **сегмент → кампания** внутри фиксированного представления триггера. В этом гайде описано, как добавлять, переупорядочивать, редактировать и удалять эти строки. Концепции триггеров, приоритетов и типов контента описаны в разделе [Потоки](mail-flows). ## Добавление строки \{#add-a-row\} 1. В Adapty Mail перейдите в **Flows** и откройте нужный триггер. 2. Нажмите **Create**, чтобы открыть диалоговое окно. 3. В диалоговом окне: - **Segment**: выберите сегмент или **All Users** в качестве универсального варианта. - **Content type**: **Campaign** — для одной кампании, **A/B Test** — для сравнения нескольких. Подробнее в разделе [A/B-тестирование](mail-ab-testing). - **Campaign**: выберите кампанию для отправки. 4. Нажмите **Save**. Строка активируется сразу. Пользователи, которые попадают под триггер и соответствуют сегменту, начинают получать кампанию с этого момента. ## Переупорядочивание строк \{#reorder-rows\} Перетащите маркер слева от строки, чтобы изменить её приоритет. Adapty Mail автоматически присваивает `priority: 1, 2, 3…` в соответствии с сохранённым порядком. Строка **All Users** должна оставаться последней — перемещение её выше другой строки заблокировано при сохранении. ## Редактирование строки \{#edit-a-row\} Нажмите **Change content** на нужной строке, чтобы открыть диалоговое окно с предзаполненными текущими значениями. Можно изменить сегмент, тип контента и кампанию, затем нажать **Save** для применения изменений. Строку с A/B-тестом можно редактировать только пока тест находится в состоянии **draft**. После запуска теста его контент заблокирован до завершения теста. ## Удаление строки \{#delete-a-row\} Откройте меню действий строки и нажмите **Delete**. Подтверждение не запрашивается — строка удаляется немедленно. - **Строки с кампанией**: можно удалить в любое время. - **Строки с активным A/B-тестом**: удалить нельзя. Сначала завершите тест с помощью **Finish A/B test**, затем удалите строку. :::note Удаление строки останавливает попадание новых пользователей в последовательность. Пользователи, уже находящиеся в середине последовательности, могут продолжать получать запланированные письма — автоматической отмены нет. ::: --- # File: mail-segments --- --- title: "Сегменты в Adapty Mail" description: "Создавайте многократно используемые срезы аудитории на основе данных профиля и покупок для таргетинга флоу и A/B-тестов." --- **Сегмент** — это многократно используемый срез аудитории. Вы определяете его один раз — в разделе **Segments** — и затем ссылаетесь на него из флоу и A/B-тестов. Сегменты — это определения фильтров, а не статичные снимки: они вычисляются по требованию при срабатывании триггера флоу, поэтому состав сегмента всегда отражает актуальные данные профиля. ## Создание сегмента \{#create-a-segment\} 1. В Adapty Mail перейдите в **Segments** и нажмите **+ Create**. Откроется страница создания с заголовком **New Segment**. 2. Укажите **Name** (обязательно) и при желании добавьте **Description**. 3. В разделе **Filters** нажимайте **Add filter** для каждого [правила](#available-filter-fields), которое хотите добавить. Каждый фильтр становится сворачиваемой карточкой с названием **Filter 1**, **Filter 2** и так далее. 4. Для каждого фильтра выберите поле, оператор и введите значение для сравнения. 5. Сохраните сегмент. :::important Фильтры объединяются по логике **AND** — пользователь должен соответствовать каждому фильтру, чтобы попасть в сегмент. Логика OR и вложенные группы не поддерживаются. Каждое поле можно использовать в сегменте только один раз; чтобы сравнить одно поле с несколькими значениями, разбейте логику на отдельные сегменты. ::: ## Импорт сегмента из Adapty \{#import-a-segment-from-adapty\} Вместо того чтобы создавать сегмент вручную с помощью фильтров, можно импортировать готовый сегмент аудитории из основного дашборда Adapty. 1. На странице **Segments** нажмите **Import**. 2. Просмотрите список. **Importable segments** — это сегменты, доступные для импорта: у них отображается чекбокс и условия фильтрации. Сегменты, которые **нельзя импортировать**, выделены серым, а рядом указана причина — например, неподдерживаемое поле (например, данные атрибуции Apple Ads), неподдерживаемый оператор или состояние подписки, для которого нет аналога в Adapty Mail. 3. Отметьте нужные сегменты и нажмите **Import**. :::important Импорт создаёт независимую копию фильтров сегмента на момент импорта — она не синхронизируется с исходным сегментом Adapty, и повторный импорт того же сегмента каждый раз создаёт отдельную копию, поскольку Adapty Mail не отслеживает дубликаты. ::: Импортированные сегменты начинают работу в состоянии **Draft**, как и сегменты, созданные вручную, поэтому их фильтры можно сразу редактировать. ## Доступные поля фильтрации \{#available-filter-fields\} | Group | Field | Type | | -------------- | ------------------------- | ------- | | Profile | Email | String | | Profile | Age | Integer | | Profile | Country | String | | Profile | External profile ID | String | | Profile | Created at | Date | | Purchase state | Total revenue (USD) | Decimal | | Purchase state | Subscription state | Enum | | Purchase state | Subscription purchased at | Date | | Purchase state | Subscription expires at | Date | | Purchase state | One-time purchase state | Enum | | Purchase state | One-time purchased at | Date | **Значения состояния подписки**: Никогда не приобреталась, Активна, Автообновление отключено, Проблема с оплатой, Льготный период, Истекла, Возвращена. **Значения состояния разовой покупки**: Никогда не приобреталась, Куплена, Возвращена. Доступные операторы по типу поля: - **Строка**: равно, не равно, задано, не задано. - **Число**: равно, не равно, меньше, больше, меньше или равно, больше или равно, между, задано, не задано. - **Дата**: равно, не равно, до, после, не позже, не раньше, между, задано, не задано. ## Системный сегмент All Users \{#the-all-users-system-segment\} Adapty Mail включает встроенный сегмент **All Users** без каких-либо фильтров — в него попадают все пользователи проекта. Его нельзя редактировать или удалять. При использовании во флоу он служит финальной строкой-«заглушкой», перехватывающей всех остальных (подробнее о правиле приоритетов см. в разделе [Флоу](mail-flows)). ## Жизненный цикл \{#lifecycle\} Состояние сегмента определяется тем, как он используется: - **Draft**: Создан, но не привязан ни к одному флоу или A/B-тесту. - **Live**: Привязан к активному флоу или A/B-тесту. - **Inactive**: Был привязан, но A/B-тест завершился или строка флоу была удалена. - **Archived**: Мягко удалён и скрыт из основного списка. На странице сегментов в панели инструментов есть фильтр по состоянию, с помощью которого можно отфильтровать список по любому из этих состояний. ## Редактирование и удаление сегмента \{#edit-and-delete-a-segment\} - **Название и описание**: Можно редактировать в любое время. - **Фильтры черновика сегмента**: Полностью редактируемые. - **Фильтры активного сегмента**: Заблокированы. Как только сегмент используется в активной строке флоу или A/B-тесте, фильтры становятся доступны только для чтения. Вы можете лишь переименовать сегмент или обновить описание. Чтобы изменить таргетинг, создайте новый сегмент и замените им строку флоу. - **Удаление**: Мягкое удаление сегмента. Активные сегменты удалить нельзя — сначала уберите их из флоу (или завершите A/B-тест). ## Ограничения \{#limitations\} - **Без OR-логики и вложенности**: фильтры объединяются только через AND. - **Одно поле на сегмент**: в сегменте не может быть двух фильтров по одному полю (например, двух проверок страны). - **Нет предпросмотра размера**: редактор не показывает, сколько пользователей сейчас соответствуют фильтрам. - **Фильтры нельзя изменить после публикации**: активные сегменты доступны только для чтения — за исключением названия и описания. --- # File: mail-profiles --- --- title: "Профили в Adapty Mail" description: "Просматривайте всех пользователей вашего проекта — их атрибуты, статус покупок, взаимодействие с email-рассылками и полную историю активности." --- **Профиль** — это один пользователь в вашем проекте. Страница **Profiles** содержит список всех, кого знает Adapty Mail, и показывает результаты покупок, взаимодействие с email-рассылками и полную историю активности каждого пользователя. Профили создаются автоматически: из email-адресов, собранных Adapty SDK, или из данных, отправленных через Adapty Mail API. :::tip Чтобы объединить профили в многократно используемые аудитории для флоу и A/B-тестов, см. [Сегменты](mail-segments). ::: ## Как профили попадают в Adapty Mail \{#how-profiles-get-into-adapty-mail\} Adapty Mail создаёт профили автоматически из двух источников: - **Adapty SDK**: SDK собирает email-адреса и покупки из вашего приложения. См. [Сбор email-адресов пользователей](mail-collect-emails). - **Adapty Mail API**: Ваш бэкенд отправляет профили и транзакции напрямую через server-to-server. См. [Отправка данных через API](mail-send-data-via-api). Adapty Mail связывает каждое письмо, клик и покупку с профилем по его постоянному `external_profile_id`. Страница «Профили» доступна только для чтения. Вы можете просматривать профили и отписывать их, но не можете создавать, редактировать или удалять их. Эти данные принадлежат исходному приложению или API. ## Список профилей \{#the-profiles-list\} Список отображает по одной строке на каждый профиль, начиная с самых новых. Используйте строку поиска, чтобы найти профиль по email, ID профиля или внешнему ID профиля. | Столбец | Описание | | --- | --- | | Profile | Email клиента и кампания, из которой ему было отправлено больше всего писем. | | Status | Статус покупки профиля. См. [Статус профиля](#profile-status). | | Country | Страна клиента. | | Open rate | Отношение открытых писем к отправленным по всем кампаниям. Прочерк означает, что письма ещё не отправлялись. | | LTV | Lifetime value — общий доход с этого профиля из всех источников. | | Joined | Когда профиль впервые появился в Adapty Mail. | | Last activity | Когда профиль последний раз взаимодействовал с письмом: отправка, открытие или клик. | :::important **Joined** — это дата, когда профиль впервые появился в Adapty Mail; она используется как дата начала отношений с клиентом. Это время попадания в систему, а не исходная дата регистрации в вашем приложении. ::: ### Статус профиля \{#profile-status\} В столбце **Status** отображается состояние покупок профиля — какие подписки и разовые покупки есть у пользователя. | Статус | Значение | | --- | --- | | Never purchased | Профиль ещё ничего не покупал. | | Purchased | Профиль совершил разовую покупку. | | Active subscriber | У профиля активная подписка. | | Cancelling | Автопродление отключено; доступ сохраняется до конца текущего периода. | | Billing issue | Платёж за продление не прошёл. | | Grace period | Платёж не прошёл, но доступ сохраняется в течение льготного периода стора. | | Churned | Подписка истекла и доступ прекращён. | | Refunded | Покупка была возвращена. | :::note Статус покупки не зависит от статуса email-подписки. Профиль может быть **активным подписчиком**, но при этом **отписанным** от ваших писем, или **ушедшим**, но всё ещё **подписанным**. Статус email отображается на странице профиля и определяет, может ли Adapty Mail отправлять письма этому профилю. ::: ## Детали профиля \{#profile-details\} Нажмите на профиль, чтобы открыть страницу с подробной информацией. В заголовке отображаются электронная почта, страна, платформа и дата регистрации («customer since»). Там же показаны три статусных значка: статус покупки, показатель открываемости и статус подписки — **Subscribed** или **Unsubscribed**. В верхней части страницы находятся пять метрик вовлечённости: - **Sent**: количество отправленных писем. - **Delivered**: письма, принятые почтовым провайдером. - **Opened**: письма, которые профиль открыл. - **Clicked**: письма, в которых профиль перешёл по ссылке. - **Revenue**: два значения — атрибутированный доход и пожизненная ценность. :::note **Атрибутированная выручка** — это выручка, обусловленная вашими письмами: покупки, которые профиль совершил после взаимодействия с кампанией. **Пожизненная ценность (LTV)** — это совокупная выручка профиля из всех источников, независимо от того, сыграла ли роль электронная почта. В заголовке сначала отображается атрибутированная выручка, затем LTV. ::: Карточка **Profile** содержит атрибуты клиента: - **Platform**: Платформа устройства пользователя, например iOS или Android. - **Country**: Страна пользователя. - **Store country**: Страна аккаунта App Store или Google Play пользователя. - **Gender**: Пол пользователя, если известен. - **Age**: Возраст пользователя, если была указана дата рождения. - **Profile ID**: Внутренний идентификатор профиля в Adapty. - **External ID**: `external_profile_id` из вашего приложения или бэкенда. - **Custom attributes**: Любые пары ключ-значение, отправленные вместе с профилем. ### Статус покупок \{#purchase-state\} Карточка **Purchase state** показывает историю покупок профиля и его доход. Вверху отображается lifetime value, а ниже — до двух секций: - **Subscription**: цена, стор, дата начала, дата обновления или истечения, а также ID продукта для подписки профиля. - **One-time purchase**: цена, стор, дата покупки и ID продукта для последней разовой покупки. Если профиль ещё ничего не купил, карточка показывает **No purchase yet**. Карточка **Segments** перечисляет все сегменты, которым соответствует профиль, или показывает **Not in any segment**, если ни один не подходит. Принадлежность к сегментам вычисляется в реальном времени и всегда отражает актуальные данные профиля. О том, как создавать сегменты, читайте в разделе [Сегменты](mail-segments). ## Путь активности \{#the-activity-journey\} Раздел **Journey** — это хронология всего, что произошло с профилем. Она начинается с события **Profile created**, а затем чередует два типа событий: - **Email events**: каждое письмо, отправленное на профиль, с информацией о доставке, открытиях и кликах. Раскройте письмо, чтобы увидеть, по каким ссылкам переходил пользователь и какие покупки были совершены после этого письма. - **Transaction events**: ключевые события подписок и разовых покупок — запуски, продления, отмены, проблемы с оплатой, истечения срока действия и возвраты. События транзакций соответствуют следующим меткам в пути активности: | `event_type` | Journey label | | --- | --- | | `subscription_started` | Subscription started | | `subscription_renewed` | Subscription renewed | | `subscription_renewal_cancelled` | Renewal cancelled | | `subscription_renewal_reactivated` | Renewal resumed | | `billing_issue_detected` | Billing issue | | `entered_grace_period` | Entered grace period | | `subscription_expired` | Subscription expired | | `subscription_refunded` | Subscription refunded | | `non_subscription_purchase` | One-time purchase | | `non_subscription_purchase_refunded` | Purchase refunded | Эти события поступают в Adapty Mail автоматически через SDK, либо вы можете отправлять их самостоятельно через API. Подробное описание событий см. в разделе [Отправка транзакционных событий](mail-send-data-via-api#send-transaction-events). ## Отписка профиля \{#unsubscribe-a-profile\} Чтобы прекратить отправку писем профилю, откройте его страницу, нажмите **...** и выберите **Unsubscribe**. Adapty Mail помечает профиль как отписавшийся и добавляет его в список подавления, поэтому кампании и флоу его пропускают. Действие идемпотентно: профиль, который уже отписан, остаётся отписанным. Подробнее о подавлении и о том, как профили отписываются самостоятельно, читайте в разделе [Отписка и подавление](mail-suppression). --- # File: mail-ab-testing --- --- title: "A/B-тестирование в Adapty Mail" description: "Сравнивайте полноценные email-кампании между собой, подключая A/B-тест к потоку." --- A/B-тест в Adapty Mail сравнивает две и более полноценные email-кампании друг с другом. Каждый вариант — это отдельная независимая кампания. Когда пользователь соответствует сегменту теста в [потоке](mail-flows), Adapty Mail направляет его в один из вариантов согласно настроенным весам и отслеживает доставку, вовлечённость и выручку по каждому варианту. ## Что такое вариант \{#what-a-variation-is\} Каждый вариант — это полноценная кампания. Варианты могут отличаться всем, чем вообще могут отличаться кампании: текстом, главными изображениями, тоном, длиной последовательности или временными задержками между письмами. Сам A/B-тест не предоставляет эти параметры как настройки — вы создаёте кампании отдельно и добавляете их в тест как варианты. ## Создать A/B-тест \{#create-an-ab-test\} 1. Сначала создайте кампании в **Campaigns**. Каждому варианту нужна своя кампания. 2. В Adapty Mail перейдите в **A/B Tests** и нажмите **Create**. 3. Добавьте каждую кампанию как вариант и задайте её вес. Сумма весов должна составлять **100%**. 4. Назначьте сегмент, чтобы определить, для каких пользователей применяется тест. 5. Сохраните. Тест сохраняется в статусе **draft** и пока ничего не отправляет. Чтобы запустить его в работу, нужно прикрепить его к потоку. ## Запуск из потока \{#launch-from-a-flow\} A/B-тесты нельзя запустить со страницы A/B Tests — и запуск, и завершение происходят внутри строки потока. 1. В Adapty Mail перейдите в **Flows** и откройте триггер, в котором хотите запустить тест. 2. Нажмите **Create** в новой строке. В диалоге установите **Content type** в **A/B Test**, выберите сохранённый тест и нажмите **Save**. 3. В строке нажмите **Launch A/B test**. Статус теста изменится с **draft** на **live**, и входящие пользователи, соответствующие сегменту, начнут распределяться по вариантам. Подробнее о строках потока — в разделе [Создание потока](mail-create-flow). ## Как работает маршрутизация \{#how-routing-works\} Когда пользователь попадает в триггер потока и соответствует сегменту A/B-теста, Adapty Mail выбирает вариант с помощью **взвешенного случайного** выбора — вес каждого варианта определяет его долю в выборке. Маршрутизация не детерминирована для конкретного пользователя. ## Просмотр результатов \{#read-results\} На странице A/B Tests для каждого варианта отображаются исходные счётчики и производные показатели: - **Delivery**: отправки, доставки, отказы. - **Engagement**: открытия, клики, отписки. - **Revenue**: покупки, выручка. Подробнее о том, что считает каждая метрика и как атрибутируется выручка, — в разделе [Аналитика кампаний](mail-analytics). ## Завершить тест \{#finish-the-test\} Как и запуск, завершение происходит из строки потока, а не со страницы A/B Tests. 1. Откройте строку потока, в которой запущен тест. 2. Нажмите **Finish A/B test**. 3. В диалоге **Finish A/B test** выберите победившую кампанию из выпадающего списка **Replace with campaign** — или оставьте поле пустым, чтобы полностью убрать сегмент из потока. 4. Подтвердите. :::note Пользователи, которые уже находятся в середине последовательности в любом варианте — победившем или проигравшем — продолжают получать запланированные письма. Они не переключаются на победителя. ::: ## Жизненный цикл \{#lifecycle\} A/B-тест проходит через четыре состояния: - **Draft**: создан, но ещё не прикреплён к активной строке потока. - **Live**: прикреплён и запущен; распределяет входящих пользователей по вариантам. - **Finished**: остановлен через **Finish A/B test**. - **Archived**: мягко удалён из списка. --- # File: mail-analytics --- --- title: "Аналитика в Adapty Mail" description: "Анализируйте эффективность кампаний по кампании, сегменту, варианту A/B-теста, сообщению или триггеру — и отслеживайте доставку, вовлечённость и выручку в одном месте." --- Страница **Analytics** показывает, как работают ваши кампании по пяти измерениям: кампания, сегмент, вариант A/B-теста, сообщение и триггер. Метрики доставки сочетаются с выручкой, атрибутированной каждому письму, — это позволяет сравнивать варианты, находить лучшие сегменты и определять, где концентрируется доход. Вверху страницы расположен график, ниже — таблица разбивки. Нажмите на любую строку, чтобы перейти к детальному просмотру конкретной сущности. ## Выберите временной диапазон \{#pick-a-time-range\} Панель инструментов в верхней части страницы управляет временным окном и способом группировки данных: - **Date range**: предустановленные периоды (последние 7 / 14 / 30 / 90 дней, текущий месяц, прошлый месяц, последние 12 месяцев, с начала года) или произвольный диапазон через **Custom range**. По умолчанию — последние 30 дней. - **Granularity**: группировка по дням (**Daily**), неделям (**Weekly**) или месяцам (**Monthly**). При расширении диапазона группировка укрупняется автоматически: **Daily** переключается на **Weekly** после 92 дней, а оба варианта — на **Monthly** после 366 дней. - **Chart style**: линейный (**Line**), площадной (**Area**) или столбчатый (**Bar**) график. Если на странице появляется предупреждение «range too wide», сузьте диапазон дат, укрупните гранулярность или примените фильтры. ## Группировка, разбивка, фильтрация \{#group-break-down-filter\} Три элемента управления под панелью инструментов определяют, что именно отображается на графике и в таблице: - **Group by**: Измерение, по которому набор данных делится на строки. Варианты: **Campaigns**, **Segments**, **A/B variants** и **Triggers**. При выборе **No grouping** всё сворачивается в одну агрегированную строку **All**. - **Breakdown**: Второе измерение, которое делит каждую строку на подстроки. Если заданы и **Group by**, и **Breakdown**, каждую строку в таблице можно развернуть, чтобы увидеть подгруппы. В качестве Breakdown можно выбрать любое измерение — включая **Messages** — кроме того, которое уже используется в **Group by**. - **Add filter**: Ограничивает набор данных конкретными кампаниями, сегментами, вариантами A/B-тестов или триггерами. Фильтры применяются и к графику, и к таблице. :::note **Messages** доступен как детализация, но не как самостоятельный параметр **Group by** или фильтр. Чтобы проанализировать отдельные сообщения, сгруппируйте по **Campaigns** с детализацией **Messages**, затем раскройте строку кампании или откройте сообщение через drilldown. ::: ## Чтение графика \{#read-the-chart\} График отображает выбранные метрики за указанный период времени. - **Категория метрики**: Переключение между **Email actions** (Sent, Delivered, Opened, Clicked, Bounced, Unsubscribed, Converted) и **Revenue**. - **Метрики**: Выберите, какие метрики отображать. В агрегированном режиме (без группировки) можно отображать несколько метрик на одном графике. При включённой группировке график строит одну метрику — по одной линии на каждую группу — чтобы группы были визуально различимы. - **Легенда**: При включённой группировке в правой части отображается список всех групп с возможностью включать и отключать отдельные серии. Флажки видимости в таблице метрик ниже также управляют тем, какие строки отображаются на графике. ## Чтение таблицы метрик \{#read-the-metrics-table\} Под графиком расположена таблица метрик, где каждая строка соответствует отдельной группе. Строка с итогами в верхней части агрегирует все остальные строки таблицы. - **Сортируемые столбцы**: нажмите на заголовок любого столбца, чтобы отсортировать по Name, Sent, Delivered, Delivery rate, Opened, Open rate, Clicked, Click rate, Converted, Revenue, Bounced или Unsubscribed. - **Чекбокс видимости**: включает или отключает отображение строки на графике. - **Раскрытие строки**: если задан **Breakdown**, нажмите на шеврон слева от строки, чтобы раскрыть её подгруппы. - **Открытие детального просмотра**: нажмите на название строки, чтобы открыть детальный вид для этой сущности. Детальный просмотр включает: - Тот же график и селектор метрик, что и на главной странице, но в рамках одной сущности. - Восемь сводных карточек внизу: **Sent**, **Delivered** (с показателем доставки), **Opened** (с показателем открытий), **Clicked** (с показателем кликов), **Bounced**, **Unsubscribed**, **Converted** и **Revenue**. Диапазон дат и детализация переносятся с главной страницы. Используйте **Back** в хлебных крошках для возврата. ## Что отслеживается \{#whats-tracked\} :::note Adapty конвертирует другие валюты в USD по курсу [currencylayer.com](https://currencylayer.com/) (обновляется каждые 8 часов). Курс **фиксируется на момент транзакции** — последующие изменения не влияют на результат конвертации. ::: Каждая строка — на странице аналитики, в детализации и во встроенных представлениях, описанных ниже, — показывает один и тот же набор исходных показателей: - **Sent**: Письма, отправленные в SES. - **Delivered**: Доставки в почтовый ящик, подтверждённые SES. - **Bounced**: Отказы, зафиксированные SES. Жёсткие и мягкие отказы не различаются — оба считаются как один **Bounced**. - **Opened**: Загрузки пикселя. Apple Mail Privacy Protection автоматически загружает изображения в iOS 15+ и завышает этот показатель — для более надёжных сигналов ориентируйтесь на клики и выручку. - **Clicked**: Клики по ссылкам в теле письма. - **Unsubscribed**: Отписки через ссылку в футере или заголовок `List-Unsubscribe`. - **Converted**: Уникальные профили в группе с атрибутированной покупкой за указанный период. Конверсии группируются по дате покупки — клик в марте с покупкой в апреле учитывается в апреле. Профиль, совершивший несколько покупок, всё равно считается один раз. - **Revenue**: Сумма атрибутированной выручки (USD) по запускам подписок, продлениям и разовым покупкам. ## Производные показатели \{#derived-rates\} Каждый показатель вычисляется на основе приведённых выше счётчиков: | Показатель | Формула | | ---------- | ------- | | Delivery rate | Delivered / Sent | | Open rate | Opened / Delivered | | Click rate | Clicked / Delivered | В детальном просмотре те же три показателя отображаются рядом со сводными карточками. ## Атрибуция дохода \{#revenue-attribution\} Доход атрибутируется по принципу **последнего клика** на отслеживаемую ссылку: 1. Когда получатель нажимает на любую ссылку в письме, Adapty Mail сохраняет `scheduled_email_id` для этого профиля в кратковременном хранилище. 2. Если после этого приходит событие покупки без существующей атрибуции, Adapty Mail привязывает сохранённый `scheduled_email_id` к транзакции — при условии, что временна́я метка покупки позже клика. 3. Покупки без предшествующего отслеживаемого клика остаются без атрибуции. Отслеживаемый параметр — `scheduled_email_id`. URL оформления заказа также передаёт данные получателя через плейсхолдеры `{email}` и `{external_profile_id}`, чтобы веб-пейвол мог персонализировать процесс — это отдельный механизм, не связанный с атрибуцией. См. [Настройка оформления заказа](mail-checkout). ## Встроенная аналитика во Flow и A/B-тестах \{#inline-analytics-in-flows-and-ab-tests\} Те же метрики отображаются прямо в строках: - **Страница Flows**: каждая строка сегмента в представлении триггера показывает показатели доставки, вовлечённости и дохода. - **Страница A/B Tests**: варианты перечислены рядом с одинаковым набором метрик, что удобно для прямого сравнения вариантов. Используйте страницу Analytics при сравнении кампаний или детальном изучении отдельной сущности, а встроенные представления — когда вы уже работаете в конкретной строке потока или A/B-теста. Определения метрик, производные показатели и правила атрибуции, описанные выше, одинаково применяются во всех трёх представлениях. ## Ограничения \{#limitations\} - **Нет разделения на мягкие и жёсткие отказы**: любой отказ — временный или постоянный — учитывается в единой метрике **Bounced**. - **Данные обновляются не в реальном времени**: счётчики агрегируются из таблиц событий. Свежие события обычно появляются в течение нескольких минут, но потоковая передача в реальном времени не гарантируется. - **Диапазон дат ограничен**: очень широкий диапазон в сочетании с мелкой детализацией может превысить максимальное количество ячеек графика. В этом случае страница покажет предупреждение «диапазон слишком широк» — сузьте диапазон, увеличьте шаг детализации или примените фильтры. --- # File: configuration --- --- title: "Настройка сторонних интеграций" description: "Узнайте, как настроить параметры Adapty для оптимизации управления подписками." --- Интеграции Adapty позволяют передавать события подписок и данные о покупках на любую нужную вам платформу. Будь то аналитика поведения пользователей, инструменты вовлечения или углублённая продуктовая аналитика для маркетинговой команды — Adapty без лишних усилий пересылает события встроенных покупок в выбранную интеграцию. Adapty отслеживает встроенные покупки и события подписок: пробные периоды, конверсии, продления и отмены. Все эти [события](events) автоматически передаются в подключённые интеграции. Это позволяет взаимодействовать с пользователями на нужном этапе их пути и анализировать доходы прямо внутри приложения. ## Настройки интеграции \{#integration-settings\} <img src="/assets/shared/img/20bf659-CleanShot_2023-08-22_at_13.26.562x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Для каждой интеграции доступны следующие параметры конфигурации, которые влияют на все отправляемые через неё события: | Параметр | Описание | |:--------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Reporting Proceeds** | Выберите, как отображать значения выручки: за вычетом комиссий App Store и Play Store или до их удержания. Установите флажок «Send sales as proceeds», чтобы показывать продажи уже после вычета комиссий. | | **Send Trial Price** | Если флажок установлен, Adapty будет передавать цену подписки для события Trial Started. | | **Exclude Historical Events** | Позволяет исключить события, произошедшие до того, как пользователь установил приложение с Adapty SDK. Это предотвращает дублирование событий и обеспечивает точность отчётов. Например, если пользователь оформил ежемесячную подписку 10 января, а обновил приложение с Adapty SDK 6 марта, Adapty пропустит все события до 6 марта и сохранит последующие. | | **Report User's Currency** | Выберите, в какой валюте отображать продажи: в валюте пользователя или в USD. | | **Send User Attributes** | Если вы хотите передавать атрибуты пользователя (например, языковые настройки) и ваш тарифный план OneSignal поддерживает более 10 тегов, включите этот параметр. Он позволяет передавать дополнительные данные сверх стандартных 10 тегов. Обратите внимание: превышение лимита тегов может привести к ошибкам. | | **Send Attributions** | Включите этот параметр, чтобы передавать информацию об атрибуции (например, атрибуцию AppsFlyer) и получать соответствующие данные. | | **Send Play Store purchase token** | Включите этот параметр, чтобы получать токен покупки Play Store, необходимый для повторной валидации покупки. В событие будет добавлен параметр `play_store_purchase_token`. | | **Delay events with future datetime** | **Только для AppsFlyer и пользовательских вебхуков**: при включении события продления и конверсии пробного периода отправляются в дату их фактического наступления. При отключении (по умолчанию) эти события отправляются сразу при обнаружении, даже если их дата ещё не наступила. | | **Data residency** | **Только для Mixpanel и Amplitude**: выберите регион хранения данных, чтобы определить, где будут обрабатываться и храниться ваши события. | ## Настройка событий \{#configure-the-events\} Ниже раздела с учётными данными находятся три группы событий, которые можно отправлять в выбранную интеграцию из Adapty. Включите те, которые вам нужны. Обратите внимание: в одних интеграциях названия событий можно изменять, в других они фиксированы и не редактируются. Кроме того, в некоторых интеграциях — например, в [Airbridge](airbridge#configure-events-and-tags) — одному событию Adapty можно сопоставить несколько названий. Полный список событий Adapty смотрите [здесь](events). <img src="/assets/shared/img/c79f5cd-screencapture-app-adapty-io-integrations-pushwoosh-2023-08-22-13_31_07.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Мы рекомендуем использовать стандартные названия событий Adapty, однако вы можете изменить их под свои нужды. --- # File: events --- --- title: "События для отправки в сторонние интеграции" description: "Отслеживайте ключевые события подписок с помощью аналитических инструментов Adapty." --- Apple и Google отправляют события подписок напрямую на серверы через [App Store Server Notifications](enable-app-store-server-notifications) и [Real-time Developer Notifications (RTDN)](enable-real-time-developer-notifications-rtdn). Из-за этого мобильные приложения не могут надёжно отправлять события в аналитические системы в реальном времени. Например, если пользователь оформил подписку, но больше не открывал приложение, разработчик не получит никаких обновлений о статусе подписки без сервера. Adapty решает эту проблему: собирает данные о подписках и преобразует их в понятные человеку события. Эти события для интеграций отправляются в формате JSON. Все события имеют одинаковую структуру, но набор полей зависит от типа события, стора и конкретных настроек. Точный список полей для каждого события можно найти на страницах соответствующих интеграций. Чтобы понять, было ли событие успешно обработано или что-то пошло не так, ознакомьтесь со страницей [Статусы событий](event-statuses). ## Типы событий \{#event-types\} Большинство событий создаются и отправляются во все настроенные интеграции, если они включены. Однако событие **Access level updated** срабатывает только в том случае, если настроена [интеграция с вебхуком](webhook) и это событие включено. Оно будет отображаться в [Event Feed](https://app.adapty.io/event-feed) и отправляться в вебхук, но не будет передаваться в другие интеграции. Если интеграция с вебхуком не настроена или данный тип события не включён, событие **Access level updated** не будет создаваться и не появится в [Event Feed](https://app.adapty.io/event-feed). | Event name | Description | |:-----------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | subscription_started | Срабатывает, когда пользователь активирует платную подписку без пробного периода, то есть с него сразу списывается оплата. | | subscription_renewed | Происходит при продлении подписки и списании оплаты с пользователя. Это событие фиксируется начиная со второго платежа — как для пробных, так и для обычных подписок. | | subscription_renewal_cancelled | Пользователь отключил автопродление подписки. Доступ к премиум-функциям сохраняется до конца оплаченного периода. | | subscription_renewal_reactivated | Срабатывает, когда пользователь повторно включает автопродление подписки. | | subscription_expired | Срабатывает, когда подписка полностью завершается после отмены. Например, если пользователь отменил подписку 12 декабря, но она активна до 31 декабря, событие фиксируется 31 декабря, когда подписка истекает. | | subscription_paused | Происходит, когда пользователь активирует [паузу подписки](https://developer.android.com/google/play/billing/lifecycle/subscriptions#pause) (только Android). | | subscription_deferred | Срабатывает, когда покупка подписки [откладывается](https://adapty.io/glossary/subscription-purchase-deferral/), — пользователь может перенести платёж, сохраняя доступ к премиум-функциям. Функция доступна через Google Play Developer API и может использоваться для пробных периодов или в поддержку пользователей, испытывающих финансовые трудности. | | non_subscription_purchase | Любая покупка без подписки: пожизненный доступ или расходуемые покупки, например внутриигровые монеты. | | trial_started | Срабатывает, когда пользователь активирует пробную подписку. | | trial_converted | Происходит, когда пробный период заканчивается и с пользователя списывается первый платёж. Например, если пробный период действует до 14 января, но оплата проходит 7 января, событие фиксируется 7 января. | | trial_renewal_cancelled | Пользователь отключил автопродление подписки в течение пробного периода. Доступ к премиум-функциям сохраняется до конца пробного периода, но оплата не будет списана и подписка не активируется. | | trial_renewal_reactivated | Происходит, когда пользователь повторно включает автопродление подписки в течение пробного периода. | | trial_expired | Срабатывает, когда пробный период заканчивается без перехода в подписку. | | entered_grace_period | Происходит, когда попытка оплаты завершается неудачей и пользователь переходит в льготный период (если он включён). В течение этого времени доступ к премиум-функциям сохраняется. | | billing_issue_detected | Срабатывает при возникновении проблемы с оплатой во время попытки списания (например, недостаточно средств на карте). | | subscription_refunded | Срабатывает при возврате средств за подписку (например, через службу поддержки Apple). | | non_subscription_purchase_refunded | Срабатывает при возврате средств за покупку без подписки. | | access_level_updated | Происходит при обновлении уровня доступа пользователя. | Перечисленные выше события полностью описывают состояние пользователей с точки зрения покупок. Рассмотрим несколько примеров. ### Пример 1 \{#example-1\} _Пользователь активировал месячную подписку 1 апреля с 7-дневным пробным периодом. На 4-й день он отменил подписку._ В этом случае будут отправлены следующие события: 1. `trial_started` 1 апреля 2. `trial_renewal_cancelled` 4 апреля 3. `trial_expired` 7 апреля ### Пример 2 \{#example-2\} _Пользователь активировал месячную подписку 1 апреля с 7-дневным пробным периодом. На 10-й день он отменил подписку._ В этом случае будут отправлены следующие события: 1. `trial_started` 1 апреля 2. `trial_converted` 7 апреля 3. `subscription_renewal_cancelled` 10 апреля 4. `subscription_expired` 1 мая Подробное описание того, какие события срабатывают в каждом сценарии, можно найти в разделе [Потоки событий](event-flows). --- # File: event-flows --- --- title: "Потоки событий" description: "Изучите подробные схемы потоков событий подписки в Adapty. Узнайте, как генерируются и отправляются события подписок в интеграции, помогая отслеживать ключевые моменты в путях ваших клиентов." --- В Adapty вы будете получать различные события подписки на протяжении всего пути клиента в вашем приложении. Описанные ниже сценарии помогут понять, какие события генерирует Adapty, когда пользователи оформляют, отменяют или возобновляют подписку. Обратите внимание, что Apple обрабатывает платежи за подписку за несколько часов до фактического начала/продления. В приведённых ниже схемах начало/продление подписки и списание средств показаны одновременно — для наглядности. Кроме того, события, связанные с одним действием, происходят одновременно и могут появляться в вашем **Event Feed** в произвольном порядке, который может отличаться от последовательности, показанной на наших диаграммах. ## Жизненный цикл подписки \{#subscription-lifecycle\} ### Процесс первой покупки \{#initial-purchase-flow\} Этот процесс происходит, когда пользователь впервые оформляет подписку без пробного периода. В этом случае создаются следующие события: - **Subscription started** - **Access level updated** — для предоставления доступа пользователю Когда наступает дата продления подписки, подписка обновляется. При этом создаются следующие события: - **Subscription renewal** — для начала нового периода подписки - **Access level updated** — для обновления даты истечения подписки и продления доступа ещё на один период Ситуации, когда оплата не проходит или пользователь отменяет продление, описаны в [Billing Issue Outcome Flow](event-flows#billing-issue-outcome-flow) и [Subscription Cancellation Flow](event-flows#subscription-cancellation-flow) соответственно. <img src="/assets/shared/img_webhook_flows/Initial_Purchase_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Процесс отмены подписки \{#subscription-cancellation-flow\} Когда пользователь отменяет подписку, создаются следующие события: - **Subscription renewal canceled** — указывает, что подписка остаётся активной до конца текущего периода, после чего пользователь потеряет доступ - **Access level updated** — создаётся для отключения автопродления для уровня доступа Когда подписка заканчивается, срабатывает событие **Subscription expired (churned)**, фиксирующее окончание подписки. <img src="/assets/shared/img_webhook_flows/Subscription_Cancellation_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Если возврат средств одобрен, следующее событие заменяет **Subscription expired (churned)**: - **Subscription refunded** — завершает подписку и предоставляет информацию о возврате средств <img src="/assets/shared/img_webhook_flows/Subscription_Cancellation_Flow_with_a_Refund.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> В Stripe подписку можно отменить немедленно, минуя оставшийся период. В этом случае все события создаются одновременно: - **Subscription renewal cancelled** - **Subscription expired (churned)** - **Access Level updated** — для снятия доступа у пользователя Если возврат одобрен, при его подтверждении также срабатывает событие **Subscription refunded**. <img src="/assets/shared/img_webhook_flows/Subscription_Immediate_Cancellation_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Процесс реактивации подписки \{#subscription-reactivation-flow\} Если пользователь отменяет подписку, она истекает, а затем он снова покупает ту же подписку, будет создано событие **Subscription renewed**. Даже если в доступе был перерыв, Adapty рассматривает это как единую цепочку транзакций, связанных через `vendor_original_transaction_id`. Поэтому повторная покупка считается продлением. Событие **Access level updated** будет создано дважды: - в момент окончания подписки — чтобы отозвать доступ у пользователя - в момент повторной покупки подписки — чтобы предоставить доступ <img src="/assets/shared/img_webhook_flows/Subscription_Rejoin_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Поток паузы подписки (только Android) \{#subscription-pause-flow-android-only\} Этот поток применяется, когда пользователь ставит подписку на паузу, а затем возобновляет её на Android. Пауза подписки имеет отложенный эффект. Если пользователь ставит подписку на паузу до момента её продления, подписка остаётся активной, и пользователь сохраняет оплаченный доступ до конца расчётного периода. 1. Когда пользователь ставит подписку на паузу, срабатывает событие **Subscription paused (Android only)**. 2. По окончании периода подписки Adapty инициирует событие **Access level updated**, отзывая доступ пользователя. 3. Когда пользователь возобновляет подписку, срабатывают следующие события: - **Subscription renewed** - **Access level updated** — для восстановления доступа пользователя Эти подписки относятся к одной цепочке транзакций, связанных одним **vendor_original_transaction_id**. <img src="/assets/shared/img_webhook_flows/Subscription_Paused_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Триальные сценарии \{#trial-flows\} Если вы используете триальный период в приложении, вы будете получать дополнительные события, связанные с триалом. ### Пробный период с успешной конверсией \{#trial-with-successful-conversion-flow\} Самый распространённый сценарий: пользователь начинает пробный период, указывает банковскую карту и по окончании пробного периода успешно переходит на стандартную подписку. В этом случае в момент начала пробного периода создаются следующие события: - **Trial started** — фиксирует начало пробного периода - **Access level updated** — предоставляет доступ Событие **Trial converted** создаётся в момент начала стандартной подписки. <img src="/assets/shared/img_webhook_flows/Trial_Flow_with_Successful_Conversion.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Пробный период без успешной конвертации \{#trial-without-successful-conversion-flow\} Если пользователь отменяет пробный период до его конвертации в подписку, в момент отмены создаются следующие события: - **Trial renewal cancelled** — отключает автоматическую конвертацию пробного периода в подписку - **Access level updated** — отключает продление доступа Пользователь сохраняет доступ до окончания пробного периода, после чего создаётся событие **Trial expired**, фиксирующее его завершение. <img src="/assets/shared/img_webhook_flows/Trial_Flow_without_Successful_Conversion.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Повторная активация подписки после истёкшего триала \{#subscription-reactivation-after-expired-trial-flow\} Если триал истёк (из-за проблем с оплатой или отмены) и пользователь позже оформляет подписку, создаются следующие события: - **Access level updated** — открывает пользователю доступ - **Trial converted** Даже при наличии разрыва между триалом и подпиской Adapty связывает их через `vendor_original_transaction_id`. Такая конвертация считается частью непрерывной цепочки транзакций, начатой с триала с нулевой ценой. Именно поэтому создаётся событие **Trial converted**, а не **Subscription started**. <img src="/assets/shared/img_webhook_flows/Subscription_Reactivation_Flow_after_Expired_Trial.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Изменения продукта \{#product-changes\} Этот раздел охватывает любые изменения активных подписок: апгрейды, даунгрейды или покупку продукта из другой группы. ### Немедленная смена продукта \{#immediate-product-change-flow\} После того как пользователь меняет продукт, изменение может вступить в силу сразу, не дожидаясь окончания подписки (как правило, при апгрейде или замене продукта). В момент смены продукта происходит следующее: - Уровень доступа изменяется, и создаются два события **Access level updated**: 1. для отзыва доступа к первому продукту. 2. для предоставления доступа ко второму продукту. - Старая подписка завершается, и выплачивается возврат средств (создаётся событие **Subscription refunded** с `cancellation_reason` = `upgraded`). Обратите внимание, что событие **Subscription expired (churned)** не создаётся — его заменяет событие **Subscription refunded**. - Новая подписка запускается (для нового продукта создаётся событие **Subscription started**). <img src="/assets/shared/img_webhook_flows/Immediate_Product_Change_Flow_Upgrade.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Если пользователь понижает подписку, первая подписка будет действовать до конца оплаченного периода, а когда она завершится — заменится новой подпиской более низкого уровня. В этом случае сразу будет создано только событие **Access level updated**, отключающее автопродление доступа. Все остальные события будут созданы в момент фактической замены подписки: - Создаётся ещё одно событие **Access level updated** — чтобы предоставить доступ ко второму продукту. - Создаётся событие **Subscription expired (churned)** — чтобы завершить подписку на первый продукт. - Создаётся событие **Subscription started** — чтобы начать новую подписку на новый продукт. <img src="/assets/shared/img_webhook_flows/Delayed_Product_Change_Downgrade.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Отложенное изменение продукта \{#delayed-product-change-flow\} Существует также вариант, когда пользователь меняет продукт в момент обновления подписки. Этот вариант очень похож на предыдущий: одно событие **Access level updated** создаётся сразу, чтобы отключить автообновление для старого продукта. Все остальные события создаются в тот момент, когда пользователь меняет подписку и это изменение фиксируется в системе: - Создаётся ещё одно событие **Access level updated**, чтобы предоставить доступ ко второму продукту. - Создаётся событие **Subscription expired (churned)**, чтобы завершить подписку на первый продукт. - Создаётся событие **Subscription started**, чтобы начать новую подписку на новый продукт. <img src="/assets/shared/img_webhook_flows/Product_Change_on_Renewal_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Поведение системы при проблемах с оплатой \{#billing-issue-outcome-flow\} Если попытка конвертировать пробный период или продлить подписку завершается неудачей из-за проблем с оплатой, дальнейшее поведение системы зависит от того, включён ли льготный период. При наличии льготного периода: если платёж проходит успешно, пробный период конвертируется или подписка продлевается. Если платёж не проходит, стор продолжает попытки списать средства, и если они по-прежнему не удаются — стор самостоятельно завершает пробный период или подписку. Таким образом, в момент возникновения проблемы с оплатой в Adapty создаются следующие события: - **Billing issue detected** - **Entered grace period** (если льготный период включён) - **Access level updated** для предоставления доступа до конца льготного периода Если оплата в итоге проходит, Adapty фиксирует событие **Trial converted** или **Subscription renewed**, и пользователь не теряет доступ. Если оплата так и не проходит и стор отменяет подписку, Adapty генерирует следующие события: - **Trial expired** или **Subscription expired (churned)** с `cancellation_reason: billing_error` - **Access level updated** для отзыва доступа пользователя <img src="/assets/shared/img_webhook_flows/Billing_Issue_Outcome_Flow_with_Grace_Period.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Без льготного периода период повторных попыток списания (когда стор продолжает пытаться списать средства с пользователя) начинается немедленно. Если оплата так и не проходит до конца льготного периода, сценарий тот же: стор автоматически завершает подписку и создаёт те же события: - Событие **Trial expired** или **Subscription expired (churned)** с `cancellation_reason` равным `billing_error` - **Access level updated** — уровень доступа отзывается у пользователя <img src="/assets/shared/img_webhook_flows/Billing_Issue_Outcome_Flow_without_Grace_Period.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Передача покупок между аккаунтами пользователей \{#sharing-purchases-across-user-accounts-flows\} Когда <InlineTooltip tooltip="Customer User ID">[iOS](identifying-users#set-customer-user-id-on-configuration), [Android](android-identifying-users#setting-customer-user-id-on-configuration), [React Native](react-native-identifying-users#setting-customer-user-id-on-configuration), [Flutter](flutter-identifying-users#setting-customer-user-id-on-configuration) и [Unity](unity-identifying-users#setting-customer-user-id-on-configuration)</InlineTooltip> пытается восстановить или продлить подписку, уже привязанную к другому <InlineTooltip tooltip="Customer User ID">[iOS](identifying-users#set-customer-user-id-on-configuration), [Android](android-identifying-users#setting-customer-user-id-on-configuration), [React Native](react-native-identifying-users#setting-customer-user-id-on-configuration), [Flutter](flutter-identifying-users#setting-customer-user-id-on-configuration) и [Unity](unity-identifying-users#setting-customer-user-id-on-configuration)</InlineTooltip>, настройка **Sharing paid access between user accounts** в Adapty определяет, как управляется доступ. Поведение будет различаться в зависимости от выбранного параметра. :::note Для транзакций Apple Family Sharing (`in_app_ownership_type=FAMILY_SHARED`) срабатывает только событие **Access level updated** — остальные события подписки, перечисленные ниже, не срабатывают. Подробную матрицу событий см. в разделе [Apple Family Sharing](apple-family-sharing). ::: :::note Если пользователь нажимает **Restore Purchases**, но уже имеет доступ в том же профиле, восстановление не выполняется и вебхуки не отправляются. События из этого раздела срабатывают только тогда, когда доступ фактически переходит между профилями. ::: Чтобы быстро понять, какие события срабатывают, когда второй профиль подключает существующую подписку, воспользуйтесь этой таблицей. В следующих разделах приведены полные JSON-пейлоады для каждого сценария. | Событие | Включено (по умолчанию) | Перенести уровень доступа на нового пользователя | Отключено | | --- | --- | --- | --- | | Новый профиль: **Access level updated** (`is_active=true`) | Срабатывает | Срабатывает | Не срабатывает | | Старый профиль: **Access level updated** (`is_active=false`) | Не срабатывает — оба профиля сохраняют доступ | Срабатывает, когда новое идентифицированное устройство передаёт транзакцию | Не срабатывает — исходный профиль сохраняет доступ | | Поле `profiles_sharing_access_level` в новом событии | Перечисляет другие профили, которые совместно используют уровень доступа | `null` | Не применимо — событие не срабатывает | Продления, возвраты и истечения срока действия переданной подписки продолжают генерировать события `subscription_renewed`, `subscription_refunded` и `subscription_expired` для того профиля, который в данный момент владеет уровнем доступа. Само событие передачи не генерирует событие `subscription_started`, поскольку новая транзакция не записывается — изменяется только атрибуция. Подробнее о контрактах для каждого режима см. в разделе [Практический справочник](sharing-paid-access-between-user-accounts#practical-reference). ### Перенос уровня доступа на нового пользователя \{#transfer-access-to-new-user-flow\} Рекомендуемый вариант — перенести уровень доступа на нового пользователя. Это сохраняет историю транзакций исходного пользователя и обеспечивает корректность аналитики. При этом будет создано всего 2 события **Access level updated**: 1. для отзыва уровня доступа у первого пользователя 2. для предоставления уровня доступа второму пользователю <img src="/assets/shared/img_webhook_flows/Transfer_Access_to_New_User_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Ниже приведена разбивка полей, связанных с назначением и передачей уровня доступа в событиях, генерируемых в этом сценарии: - **Пользователь A: Уровень доступа обновлён (отправляется, когда пользователь A оформляет подписку в приложении)** ```json showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": UserA, "event_properties": { "profile_has_access_level": true, }, "profiles_sharing_access_level": null } ``` - **Пользователь A: уровень доступа обновлён (отправляется при переустановке приложения и входе пользователя B, что отзывает доступ пользователя A)** ```json showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": UserA, "event_properties": { "profile_has_access_level": false, }, "profiles_sharing_access_level": null } ``` - **Пользователь B: уровень доступа обновлён (отправляется, когда пользователь B входит в систему и доступ предоставляется)** ```json showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000001", "customer_user_id": UserB, "event_properties": { "profile_has_access_level": true, }, "profiles_sharing_access_level": null } ``` ### Совместный доступ нескольких пользователей \{#shared-access-between-users-flow\} Этот вариант позволяет нескольким пользователям совместно использовать один уровень доступа, если их устройство привязано к одному Apple/Google ID. Это удобно, когда пользователь переустанавливает приложение и входит с другим email — он всё равно сохраняет доступ к предыдущей покупке. При этом несколько идентифицированных пользователей могут совместно использовать один уровень доступа. Пока уровень доступа является общим, все транзакции записываются под исходным <InlineTooltip tooltip="Customer User ID">[iOS](identifying-users#set-customer-user-id-on-configuration), [Android](android-identifying-users#setting-customer-user-id-on-configuration), [React Native](react-native-identifying-users#setting-customer-user-id-on-configuration), [Flutter](flutter-identifying-users#setting-customer-user-id-on-configuration) и [Unity](unity-identifying-users#setting-customer-user-id-on-configuration)</InlineTooltip>, чтобы сохранить полную историю транзакций и аналитику. Поэтому будет создано только 1 событие: **Access level updated** для предоставления доступа второму пользователю. <img src="/assets/shared/img_webhook_flows/Share_Access_Between_Users_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Вот описание полей, связанных с назначением и передачей уровня доступа в событиях, генерируемых в этом сценарии: **Пользователь B: Access level updated (отправляется при входе пользователя B и предоставлении доступа)** ```json showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": UserA, "event_properties": { "profile_has_access_level": true, }, "profiles_sharing_access_level": [ { "profile_id": "00000000-0000-0000-0000-000000000001, "customer_user_id": UserB } ] } ``` ### Поток «Доступ не передаётся между пользователями» \{#access-not-shared-between-users-flow\} При этом варианте уровень доступа навсегда закрепляется только за первым профилем пользователя, который его получил. Это идеальное решение, если покупки нужно привязать к одному <InlineTooltip tooltip="Customer User ID">[iOS](identifying-users#set-customer-user-id-on-configuration), [Android](android-identifying-users#setting-customer-user-id-on-configuration), [React Native](react-native-identifying-users#setting-customer-user-id-on-configuration), [Flutter](flutter-identifying-users#setting-customer-user-id-on-configuration) и [Unity](unity-identifying-users#setting-customer-user-id-on-configuration)</InlineTooltip>. <img src="/assets/shared/img_webhook_flows/Share_Access_Between_Users_Disabled_Flow.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: event-statuses --- --- title: "Статусы событий интеграций" description: "" --- Adapty определяет доставляемость по HTTP-коду ответа: любой код вне диапазона `200-399` считается ошибкой. Вы можете отслеживать статусы событий интеграций в **Event List** в дашборде Adapty. Система отображает статусы для всех включённых интеграций, независимо от того, включён ли конкретный тип события для данной интеграции. - Чёрный: событие успешно отправлено. - <span style={{ color: 'grey' }}>Серый:</span> тип события отключён для этой интеграции. - <span style={{ color: 'red' }}>Красный:</span> с интеграцией возникла проблема, требующая внимания. Чтобы узнать подробности о неуспешных событиях, наведите курсор на название интеграции — появится подсказка с информацией об ошибке. <img src="/assets/shared/img/event-status.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> **Event Feed** отображает данные за последние две недели для оптимизации производительности. Это ограничение ускоряет загрузку страницы, упрощая навигацию и анализ событий. --- # File: adjust --- --- title: "Adjust" description: "Подключите Adjust к Adapty для улучшенного отслеживания подписок и аналитики." --- [Adjust](https://www.adjust.com/) — одна из ведущих платформ Mobile Measurement Partner (MMP), которая собирает и представляет данные маркетинговых кампаний. Это помогает компаниям отслеживать эффективность своих кампаний. Adapty предоставляет полный набор данных, позволяющий отслеживать [события подписки](events) из сторов в одном месте. С Adapty вы легко увидите, как ведут себя ваши подписчики, узнаете, что им нравится, и сможете общаться с ними точечно и эффективно. Данная интеграция позволяет отслеживать события подписки в Adjust и точно анализировать, какой доход приносят ваши кампании. Интеграция между Adapty и Adjust работает двумя основными способами. 1. **Adapty получает данные атрибуции от Adjust** После настройки интеграции с Adjust Adapty начнёт получать данные атрибуции от Adjust. Вы можете легко просматривать эти данные на странице профиля пользователя. <img src="/assets/shared/img/98769d9-CleanShot_2023-08-11_at_14.39.182x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. **Adapty отправляет события подписки в Adjust** Adapty может отправлять все события подписки, настроенные в вашей интеграции, в Adjust. Благодаря этому вы сможете отслеживать эти события в дашборде Adjust. Интеграция полезна для оценки эффективности рекламных кампаний. ## Настройка интеграции \{#set-up-integration\} ### Подключение Adapty к Adjust \{#connect-adapty-to-adjust\} 1. Откройте дашборд Adapty и перейдите в раздел [Integrations > Adjust](https://app.adapty.io/integrations/adjust). 2. Включите переключатель в верхней части страницы. 3. Заполните поля и укажите учётные данные для доступа. <img src="/assets/shared/img/5064125-CleanShot_2023-08-11_at_14.43.382x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Если вы включили OAuth-авторизацию на платформе Adjust, при интеграции для iOS и Android приложений необходимо указать **OAuth Token**. 4. Далее укажите **токены приложений** для iOS и Android. Откройте дашборд Adjust — там вы увидите свои приложения. <img src="/assets/shared/img/adjust-apps.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::note У вас могут быть разные приложения Adjust для iOS и Android, поэтому в Adapty есть два независимых раздела для этого. Если у вас только одно приложение Adjust, просто введите одинаковую информацию в оба поля. ::: 5. Выберите приложение из списка и скопируйте **App Token**. Вставьте токен в соответствующее поле на дашборде Adapty. <img src="/assets/shared/img/adjust-token.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Настройка событий и тегов \{#configure-events-and-tags\} Adjust работает немного иначе, чем другие платформы. Вам нужно вручную создать события в дашборде Adjust, получить токены событий и скопировать их в соответствующие события в Adapty. Поэтому первый шаг — найти токены событий для всех событий, которые вы хотите отправлять через Adapty. Для этого: 1. В дашборде Adjust откройте ваше приложение и перейдите на вкладку **Events**. <img src="/assets/shared/img/adjust-events.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. Скопируйте токен события и вставьте его в Adapty. Ниже учётных данных находятся три группы событий, которые можно отправлять из Adapty в Adjust. Полный список событий, доступных в Adapty, смотрите [здесь](events). <img src="/assets/shared/img/adjust-event-token.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Adapty будет отправлять события подписок в Adjust через серверную интеграцию, что позволит вам видеть все события подписок в дашборде Adjust и связывать их с вашими рекламными кампаниями. :::important Учтите следующее: - Adjust не поддерживает события старше 58 дней. Если событие старше 58 дней, Adapty всё равно отправит его в Adjust, но дата и время события будут заменены текущей меткой времени. - Adjust не поддерживает IPv6. Если вы отключите сбор IP-адресов в SDK в разделе **App settings** или при активации SDK, на сервер может быть отправлен только IPv6, и отслеживание может не работать — оставьте сбор IP-адресов в SDK включённым, чтобы гарантировать использование IPv4. ::: ### Подключите приложение к Adjust \{#connect-your-app-to-adjust\} После выполнения описанных выше шагов добавьте в своё приложение два следующих метода. Они обеспечат взаимодействие между вашим приложением и Adjust: 1. **Для отправки данных о подписках в Adjust**: передайте идентификатор устройства Adjust в метод SDK `setIntegrationIdentifier()` 2. **Для получения данных атрибуции от Adjust**: обновите данные атрибуции с помощью метода SDK `updateAttribution()` Для Adjust версии 5.0 и выше используйте следующий пример: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers class AdjustModuleImplementation { func updateAdjustAdid() { Adjust.adid { adid in guard let adid else { return } // Adapty SDK 4.x Adapty.setIntegrationIdentifier(.adjustDeviceId(adid)) // Adapty SDK 3.x Adapty.setIntegrationIdentifier(key: "adjust_device_id", value: adid) } } func updateAdjustAttribution() { Adjust.attribution { attribution in guard let attribution = attribution?.dictionary() else { return } // Adapty SDK 4.x Adapty.updateAttribution(attribution, source: .adjust) // Adapty SDK 3.x Adapty.updateAttribution(attribution, source: "adjust") } } } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adjust.getAdid { adid -> if (adid == null) return@getAdid Adapty.setIntegrationIdentifier("adjust_device_id", adid) { error -> if (error != null) { // handle the error } } } Adjust.getAttribution { attribution -> if (attribution == null) return@getAttribution Adapty.updateAttribution(attribution, "adjust") { error -> // handle the error } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adjust.getAdid(adid -> { if (adid == null) return; Adapty.setIntegrationIdentifier("adjust_device_id", adid, error -> { if (error != null) { // handle the error } }); }); Adjust.getAttribution(attribution -> { if (attribution == null) return; Adapty.updateAttribution(attribution, "adjust", error -> { // handle the error }); }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers var adjustConfig = new AdjustConfig(appToken, environment); // Before submiting Adjust config... adjustConfig.setAttributionCallbackListener(attribution => { // Make sure Adapty SDK is activated at this point // You may want to lock this thread awaiting of `activate` adapty.updateAttribution(attribution, "adjust"); }); // ... Adjust.create(adjustConfig); Adjust.getAdid((adid) => { if (adid) adapty.setIntegrationIdentifier("adjust_device_id", adid); }); ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```javascript showLineNumbers try { final adid = await Adjust.getAdid(); if (adid == null) { // handle the error } await Adapty().setIntegrationIdentifier( key: "adjust_device_id", value: adid, ); final attributionData = await Adjust.getAttribution(); var attribution = Map<String, String>(); if (attributionData.trackerToken != null) attribution['trackerToken'] = attributionData.trackerToken!; if (attributionData.trackerName != null) attribution['trackerName'] = attributionData.trackerName!; if (attributionData.network != null) attribution['network'] = attributionData.network!; if (attributionData.adgroup != null) attribution['adgroup'] = attributionData.adgroup!; if (attributionData.creative != null) attribution['creative'] = attributionData.creative!; if (attributionData.clickLabel != null) attribution['clickLabel'] = attributionData.clickLabel!; if (attributionData.costType != null) attribution['costType'] = attributionData.costType!; if (attributionData.costAmount != null) attribution['costAmount'] = attributionData.costAmount!.toString(); if (attributionData.costCurrency != null) attribution['costCurrency'] = attributionData.costCurrency!; if (attributionData.fbInstallReferrer != null) attribution['fbInstallReferrer'] = attributionData.fbInstallReferrer!; await Adapty().updateAttribution(attribution, source: "adjust"); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity" default> ```csharp showLineNumbers // 1. To update ADID Adjust.GetAdid((adid) => { if (adid == null) { // handle the error return; } Adapty.SetIntegrationIdentifier("adjust_device_id", adid, (error) => { if (error != null) { // handle the error return; } }); }); // 2. To update Attribution // in your adjust configuration scope: adjustConfig.AttributionChangedDelegate = AttributionChangedCallback; public void AttributionChangedCallback(AdjustAttribution attributionData) { var attribution = new Dictionary<string, string>(); if (attributionData.TrackerToken != null) attribution["trackerToken"] = attributionData.TrackerToken; if (attributionData.TrackerName != null) attribution["trackerName"] = attributionData.TrackerName; if (attributionData.Network != null) attribution["network"] = attributionData.Network; if (attributionData.Adgroup != null) attribution["adgroup"] = attributionData.Adgroup; if (attributionData.Creative != null) attribution["creative"] = attributionData.Creative; if (attributionData.ClickLabel != null) attribution["clickLabel"] = attributionData.ClickLabel; if (attributionData.CostType != null) attribution["costType"] = attributionData.CostType; if (attributionData.CostAmount != null) attribution["costAmount"] = attributionData.CostAmount.ToString(); if (attributionData.CostCurrency != null) attribution["costCurrency"] = attributionData.CostCurrency; if (attributionData.FbInstallReferrer != null) attribution["fbInstallReferrer"] = attributionData.FbInstallReferrer; // you will probably need to install Newtonsoft.Json package, if not yet var attributionJsonString = Newtonsoft.Json.JsonConvert.SerializeObject(attribution); Adapty.UpdateAttribution(attributionJsonString, "adjust", (error) => { if (error != null) { // handle the error } }); } ``` </TabItem> </Tabs> ## Структура события \{#event-structure\} Adapty отправляет выбранные события в Adjust, как настроено в разделе **Events names** на [**странице интеграции с Adjust**](https://app.adapty.io/integrations/adjust). Каждое событие имеет следующую структуру: ```json { "event_token": "EVENT_TOKEN_FROM_CONFIG", "app_token": "APP_TOKEN_FROM_CONFIG", "s2s": 1, "environment": "production", "created_at_unix": 1709294400, "currency": "USD", "revenue": 9.99, "customer_user_id": "user_12345", "external_device_id": "user_12345", "ip_address": "192.168.100.1", "user_agent": "Mozilla/5.0 (Linux; Android 14; SM-S901B) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36", "android_id": "875646c2-4a56-4211-8931-168532479006", "gps_adid": "875646c2-4a56-4211-8931-168532479006", "callback_params": "{\"integration_event_id\":\"550e8400-e29b-41d4-a716-446655440000\",\"customer_user_id\":\"user_12345\",\"vendor_product_id\":\"com.example.app.yearly.premium\",\"transaction_id\":\"GPA.3312-4512-1100-55923\",\"original_transaction_id\":\"GPA.3312-4512-1100-55923\",\"store\":\"play_store\",\"store_country\":\"US\",\"price_usd\":9.99,\"proceeds_usd\":8.49,\"price_local\":9.99,\"proceeds_local\":8.49,\"net_revenue_usd\":8.49,\"net_revenue_local\":8.49,\"tax_amount_usd\":0.0,\"tax_amount_local\":0.0,\"consecutive_payments\":3,\"rate_after_first_year\":false}", "partner_params": "{\"integration_event_id\":\"550e8400-e29b-41d4-a716-446655440000\",\"customer_user_id\":\"user_12345\",\"vendor_product_id\":\"com.example.app.yearly.premium\",\"transaction_id\":\"GPA.3312-4512-1100-55923\",\"original_transaction_id\":\"GPA.3312-4512-1100-55923\",\"store\":\"play_store\",\"store_country\":\"US\",\"price_usd\":9.99,\"proceeds_usd\":8.49,\"price_local\":9.99,\"proceeds_local\":8.49,\"net_revenue_usd\":8.49,\"net_revenue_local\":8.49,\"tax_amount_usd\":0.0,\"tax_amount_local\":0.0,\"consecutive_payments\":3,\"rate_after_first_year\":false}" } ``` Где | Параметр | Тип | Описание | |:---------------------|:--------|:---------------------------------------------------------------------------------------------------------------------------------------------| | `app_token` | String | Токен приложения Adjust из настроек интеграции. | | `event_token` | String | Токен события Adjust, сопоставленный с конкретным событием Adapty. | | `s2s` | Integer | Флаг события Server-to-Server. | | `environment` | String | `sandbox` или `production`. | | `created_at_unix` | Integer | Временная метка события в секундах. | | `currency` | String | Код валюты (например, «USD») для транзакции. Включается только если выручка превышает 0.001, поскольку Adjust требует одновременной передачи выручки и валюты. | | `revenue` | Float | Сумма выручки по транзакции. Включается только если значение превышает 0.001. Обратите внимание: события возврата отправляются без параметров выручки, так как Adjust не поддерживает отрицательные значения выручки. | | `customer_user_id` | String | Customer User ID пользователя. | | `external_device_id` | String | То же, что и `customer_user_id`. | | `ip_address` | String | IP-адрес пользователя (только IPv4). | | `user_agent` | String | Строка User Agent устройства. | | `adid` | String | Adjust Device ID (если известен). | | `android_id` | String | **Только Android**. Google Advertising ID. | | `gps_adid` | String | **Только Android**. Google Advertising ID. | | `idfa` | String | **Только iOS**. ID for Advertisers. | | `idfv` | String | **Только iOS**. ID for Vendors. | | `callback_params` | String | JSON-строка, содержащая все доступные [поля события](webhook-event-types-and-fields#for-most-event-types). Включаются только ненулевые поля. | | `partner_params` | String | То же, что и `callback_params`. | ## Устранение проблем \{#troubleshooting\} ### Расхождение в данных о доходах \{#revenue-discrepancy\} Если между Adapty и Adjust есть расхождение в данных о доходах, это может быть связано с тем, что не все пользователи используют версию приложения с Adapty SDK. Чтобы обеспечить согласованность данных, вы можете обязать пользователей обновить приложение до версии с Adapty SDK. --- # File: airbridge --- --- title: "Airbridge" description: "Подключите Adapty к Airbridge для отслеживания маркетинговых данных и атрибуции." --- [Airbridge](https://www.airbridge.io/) предоставляет комплексный анализ маркетинговой эффективности для сайтов и мобильных приложений, объединяя данные с нескольких устройств, платформ и каналов. С помощью движка Identity Resolution Engine от Airbridge можно объединить разрозненные данные об идентификации пользователей из веб- и мобильных взаимодействий в единую идентичность на основе людей, что обеспечивает более точную атрибуцию. Adapty предоставляет полный набор данных для отслеживания [событий подписки](events) из сторов в одном месте. С Adapty вы легко увидите, как ведут себя ваши подписчики, узнаете, что им нравится, и сможете использовать эту информацию для целенаправленного и эффективного общения с ними. Интеграция между Adapty и Airbridge работает двумя основными способами. 1. **Получение данных атрибуции от Airbridge** После настройки интеграции с Airbridge Adapty начнёт получать данные атрибуции от Airbridge. Вы сможете легко просматривать эти данные на странице пользователя. 2. **Отправка событий подписки в Airbridge** Adapty может отправлять все события подписки, настроенные в вашей интеграции, в Airbridge. В результате вы сможете отслеживать эти события в дашборде Airbridge. Эта интеграция полезна для оценки эффективности рекламных кампаний. ## Настройка интеграции \{#set-up-integration\} ### Подключение Adapty к Airbridge \{#connect-adapty-to-airbridge\} Чтобы интегрировать Airbridge, перейдите в [Integrations > Airbridge](https://app.adapty.io/integrations/airbridge), включите переключатель и заполните поля. Прежде всего укажите учётные данные для установки соединения между вашими профилями Airbridge и Adapty. Необходимы название приложения в Airbridge (Airbridge app name) и токен API Airbridge (Airbridge API token). <img src="/assets/shared/img/2b31d90-Untitled-1_1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Оба значения можно найти в вашем дашборде Airbridge в разделе [Third-party Integrations > Adapty](https://app.airbridge.io/app/testad/integrations/third-party/adapty). <img src="/assets/shared/img/5a2f627-Screenshot_2023-02-21_at_11.19.29_AM.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Поле Adapty API token уже заполнено — значение генерируется на бэкенде Adapty. Скопируйте его и вставьте в дашборд Airbridge в поле Adapty Authorization Token. <img src="/assets/shared/img/ff422d1-CleanShot_2023-03-01_at_17.11.412x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Настройка событий и тегов \{#configure-events-and-tags\} Ниже учётных данных расположены три группы событий, которые можно отправлять из Adapty в Airbridge. <img src="/assets/shared/img/eb4e3a9-CleanShot_2023-08-22_at_13.58.472x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Просто включите нужные. ### Подключение приложения к Airbridge \{#connect-your-app-to-airbridge\} Для интеграции нужно передать `airbridge_device_id` в профиль и вызвать `setIntegrationIdentifier`, как показано в примере ниже: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "airbridge_device_id", value: AirBridge.deviceUUID() ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Airbridge.getDeviceInfo().getUUID(object: AirbridgeCallback.SimpleCallback<String>() { override fun onSuccess(result: String) { Adapty.setIntegrationIdentifier("airbridge_device_id", result) { error -> if (error != null) { // handle the error } } } override fun onFailure(throwable: Throwable) { } }) ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers final deviceUUID = await Airbridge.state.deviceUUID; try { await Adapty().setIntegrationIdentifier( key: "airbridge_device_id", value: deviceUUID, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers try { const deviceId = await Airbridge.state.deviceUUID(); await adapty.setIntegrationIdentifier("airbridge_device_id", deviceId); } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> Подробнее об airbridgeDeviceId читайте в [документации Airbridge](https://help.airbridge.io/en/developers/airbridge-device-id-faq). После события подписки Adapty может получать данные атрибуции от Airbridge до 24 часов. На дашборде они отобразятся сразу после получения. ## Структура события \{#event-structure\} Adapty отправляет выбранные события в Airbridge в соответствии с настройками в разделе **Events names** на [**странице интеграции с Airbridge**](https://app.adapty.io/integrations/airbridge). Каждое событие имеет следующую структуру: ```json { "user": { "externalUserID": "user_12345", "externalUserEmail": "user@example.com", "attributes": { "is_premium": true } }, "device": { "deviceUUID": "550e8400-e29b-41d4-a716-446655440000", "deviceModel": "iPhone 14 Pro", "osName": "iOS", "osVersion": "17.0.1", "locale": "en-US", "timezone": "America/New_York", "ifa": "00000000-0000-0000-0000-000000000000", "ifv": "00000000-0000-0000-0000-000000000000" }, "app": { "packageName": "com.example.app", "version": "1.2.3" }, "eventUUID": "d4f6f1f4-96fb-4a31-bafd-599fef77be90", "eventTimestamp": 1709294400000, "eventData": { "goal": { "category": "airbridge.subscribe", "customAttributes": { "isTrialConverted": true }, "semanticAttributes": { "transactionID": "GPA.3383-4699-1373-07113", "totalValue": 9.99, "currency": "USD", "period": "P1M", "isRenewal": true, "renewalCount": 2, "products": [ { "productID": "yearly.premium.6999", "name": "yearly.premium.6999", "position": 1 } ] } } } } ``` Где: | Параметр | Тип | Описание | |:---------------------------------------------|:--------|:----------------------------------------------------------------------------------| | `user` | Object | Информация о пользователе. | | `user.externalUserID` | String | Customer User ID пользователя. | | `user.externalUserEmail` | String | Email-адрес пользователя (если доступен). | | `user.attributes` | Object | Пользовательские атрибуты. | | `device` | Object | Информация об устройстве. | | `device.deviceUUID` | String | UUID устройства Airbridge. | | `device.deviceModel` | String | Модель устройства (например, "iPhone 14 Pro"). | | `device.osName` | String | Название ОС (например, "iOS", "Android"). | | `device.osVersion` | String | Версия ОС. | | `device.ifa` | String | **Только iOS**. ID для рекламодателей (IFA). | | `device.ifv` | String | **Только iOS**. ID для вендоров (IFV). | | `device.gaid` | String | **Только Android**. Google Advertising ID. | | `app` | Object | Информация о приложении. | | `app.packageName` | String | Package name / bundle ID приложения. | | `app.version` | String | Версия приложения. | | `eventUUID` | String | Уникальный идентификатор события в Adapty. | | `eventTimestamp` | Long | Временная метка события в миллисекундах. | | `eventData` | Object | Детали события. | | `eventData.goal.category` | String | Категория события Airbridge (сопоставляется с событием Adapty). | | `eventData.goal.semanticAttributes` | Object | Стандартные атрибуты события. | | `...semanticAttributes.transactionID` | String | ID транзакции в сторе. | | `...semanticAttributes.totalValue` | Float | Сумма дохода. | | `...semanticAttributes.currency` | String | Код валюты (например, "USD"). | | `...semanticAttributes.period` | String | Период подписки в формате ISO 8601 duration (например, "P1M"). | | `...semanticAttributes.isRenewal` | Boolean | `true`, если это транзакция продления. | | `...semanticAttributes.renewalCount` | Integer | Количество успешных продлений. | | `...semanticAttributes.products` | Array | Список продуктов, задействованных в событии. | | `...semanticAttributes.products[].productID` | String | ID продукта в сторе (например, "yearly.premium.6999"). | | `...semanticAttributes.products[].name` | String | То же, что `productID`. | | `...semanticAttributes.products[].position` | Integer | Позиция продукта в списке (всегда равна 1). | --- # File: apple-search-ads --- --- title: "Apple Ads" description: "Интегрируйте Apple Ads с Adapty для оптимизации конверсий подписок." --- :::important Интеграция Apple Ads в **App settings** используется только для базовой аналитики, а также для интеграций SplitMetrics Acquire и Asapty. [Adapty Ads Manager](adapty-ads-manager) использует отдельное подключение. Подключите ваш аккаунт Apple Ads в [Adapty Ads Manager](adapty-ads-manager-get-started). ::: Adapty помогает получать данные атрибуции из Apple Ads и анализировать метрики с сегментацией по кампаниям и ключевым словам. Adapty автоматически собирает данные атрибуции для Apple Ads через SDK и AdServices Framework. После настройки интеграции с Apple Ads Adapty начнёт получать данные атрибуции. Просмотреть их можно на странице профилей. <img src="/assets/shared/img/ba4a3e9-CleanShot_2023-08-21_at_15.14.592x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Настройка интеграции \{#set-up-integration\} ### Подключение Adapty к фреймворку AdServices \{#connect-adapty-to-the-adservices-framework\} Apple Ads через [AdServices](https://developer.apple.com/documentation/adservices) требует настройки в дашборде Adapty, а также включения на стороне приложения. Чтобы настроить Apple Ads с использованием фреймворка AdServices через Adapty, выполните следующие шаги: #### Шаг 1: Получите публичный ключ \{#step-1-obtain-public-key\} В дашборде Adapty перейдите в [Settings -> Apple Ads.](https://app.adapty.io/settings/apple-search-ads) Найдите заранее сгенерированный публичный ключ (Adapty создаёт пару ключей за вас) и скопируйте его. <img src="/assets/shared/img/baa5998-CleanShot_2023-08-21_at_14.55.542x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::note Если вы используете сторонний сервис или собственное решение для атрибуции Apple Ads, вы можете загрузить свой приватный ключ. ::: #### Шаг 2: Настройте управление пользователями в Apple Ads \{#step-2-configure-user-management-on-apple-ads\} В вашем [аккаунте Apple Ads](https://ads.apple.com/app-store) перейдите на страницу **Settings > User Management**. Чтобы Adapty мог получать данные атрибуции, нужно пригласить дополнительный Apple ID и предоставить ему доступ API Account Manager. Можно использовать любой доступный вам аккаунт или создать новый специально для этой цели. Главное условие — вы должны иметь возможность войти в Apple Ads под этим Apple ID. <img src="/assets/shared/img/ec183b2-kdjsfldsfjkdsfdfd.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> #### Шаг 3: Генерация учётных данных API \{#step-3-generate-api-credentials\} Войдите в только что добавленный аккаунт в Apple Ads. Перейдите в Settings -> API в интерфейсе Apple Ads. Вставьте ранее скопированный публичный ключ в соответствующее поле. Сгенерируйте новые учётные данные API. #### Шаг 4: Настройка Adapty с учётными данными Apple Ads \{#step-4-configure-adapty-with-apple-ads-credentials\} Скопируйте поля Client ID, Team ID и Key ID из настроек Apple Ads. В дашборде Adapty вставьте эти данные в соответствующие поля. <img src="/assets/shared/img/7356113-CleanShot_2023-08-21_at_15.08.512x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Подключение приложения к сети AdServices \{#connect-your-app-to-the-adservices-network\} После завершения [настройки фреймворка AdServices](#connect-the-adservices-framework) Adapty автоматически начинает собирать данные атрибуции Apple Search Ads. Добавлять какой-либо код в SDK не нужно. Для iOS-приложений эти данные атрибуции **всегда** будут иметь приоритет над данными из других источников. Если такое поведение нежелательно, *отключите* атрибуцию ASA, следуя инструкциям ниже. ## Отключение интеграции \{#disable-integration\} Чтобы отключить атрибуцию Apple Search Ads, откройте вкладку [**App Settings** -> **Apple Search Ads**](https://app.adapty.io/settings/apple-search-ads) и отключите переключатель **Receive Apple Search Ads attribution**. <img src="/assets/shared/img/asa-disable.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::warning Обратите внимание: отключение этой опции полностью прекратит получение аналитики ASA. В результате ASA больше не будет использоваться в аналитике и не будет передаваться в интеграции. Кроме того, SplitMetrics Acquire и Asapty перестанут работать, поскольку они зависят от атрибуции ASA. Атрибуция, полученная до этого изменения, затронута не будет. ::: ## Загрузка собственных ключей \{#uploading-your-own-keys\} :::note Необязательно Эти шаги не требуются для атрибуции Apple Ads — только для работы с другими сервисами, например Asapty, или с собственным решением. ::: Вы можете использовать собственную пару публичного и приватного ключей, если применяете сторонние сервисы или собственное решение для атрибуции ASA. ### Шаг 1 \{#step-1\} Сгенерируйте приватный ключ в терминале: ```text showLineNumbers title="Text" openssl ecparam -genkey -name prime256v1 -noout -out private-key.pem ``` Загрузите его в Adapty Settings -> Apple Ads (кнопка Upload private key). ### Шаг 2 \{#step-2\} Сгенерируйте публичный ключ в терминале: ```text showLineNumbers title="Text" openssl ec -in private-key.pem -pubout -out public-key.pem ``` Этот публичный ключ можно использовать в настройках Apple Ads аккаунта с ролью API Account Manager. Таким образом, сгенерированные значения Client ID, Team ID и Key ID можно применять как в Adapty, так и в других сервисах. --- # File: switch-from-appsflyer-s2s-api-2-to-3 --- --- title: "Переход с AppsFlyer S2S API 2 на 3" description: "Обновите интеграцию с AppsFlyer S2S API 2 до 3 в Adapty." --- Согласно [официальному бюллетеню AppsFlyer](https://support.appsflyer.com/hc/en-us/articles/20509378973457-Bulletin-Upgrading-the-AppsFlyer-S2S-API), для повышения безопасности и снижения уровня мошенничества AppsFlyer обновил свой server-to-server (S2S) API для внутренних событий приложения. Существующий эндпоинт будет устаревшим в будущем, поэтому рекомендуем заранее спланировать переход. Adapty поддерживает AppsFlyer S2S API 3 и обеспечивает плавный переход с API 2. Обратите внимание, что переход является односторонним: вернуться к API 2 после смены не получится. Чтобы перейти с AppsFlyer S2S API 2 на 3: 1. Откройте [сайт AppsFlyer](https://www.appsflyer.com/home) и войдите в аккаунт. 2. Нажмите **Your account name** -> **Security Center** в левом верхнем углу дашборда. <img src="/assets/shared/img/be299ea-appsflyer_security_center.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. В окне **Manage your account security** нажмите кнопку **Manage your AppsFlyer API and S2S tokens**. 4. Если у вас нет S2S-токена, нажмите кнопку **New token**. Если токен уже есть, перейдите к шагу 8. <img src="/assets/shared/img/7934920-appsflyer_new_token.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. В окне **New token** введите название токена. Оно нужно только для вашего удобства. 6. В списке **Choose type** выберите **S2S**. 7. Не забудьте нажать кнопку **Create new token**, чтобы сохранить новый токен. 8. В окне **Tokens** скопируйте S2S-токен. <img src="/assets/shared/img/d014c25-appsflyer_tokens.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 9. Откройте [**Integrations** -> **AppsFlyer**](https://app.adapty.io/integrations/appsflyer) в дашборде Adapty. 10. В поле **AppsFlyer S2S API** выберите **API 3**. <img src="/assets/shared/img/c0b3e72-appsflyer_switch_API.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 11. Вставьте скопированный S2S-ключ в поля **Dev key for iOS** и **Dev key for Android**. 12. Нажмите кнопку **Save**, чтобы подтвердить переход. После этого интеграция мгновенно переключится на AppsFlyer S2S API 3, и новые события будут отправляться на новый URL: `https://api3.appsflyer.com/inappevent`. --- # File: asapty --- --- title: "Asapty" description: "Узнайте об Asapty и её роли в экосистеме подписок Adapty." --- С помощью интеграции [Asapty](https://asapty.com/) вы можете оптимизировать кампании в Search Ads. Adapty отправляет события подписок в Asapty, чтобы вы могли строить там собственные дашборды на основе атрибуции Apple Search Ads. Эта интеграция не добавляет данные атрибуции в Adapty, так как мы уже получаем всё необходимое напрямую из [ASA](apple-search-ads). ## Настройка интеграции \{#set-up-integration\} ### Подключение Adapty к Asapty \{#connect-adapty-to-asapty\} Чтобы подключить Asapty, перейдите в раздел [Integrations > Asapty](https://app.adapty.io/integrations/asapty) в дашборде Adapty и заполните поле Asapty ID. <img src="/assets/shared/img/895de2b-CleanShot_2023-08-14_at_18.57.462x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Asapty ID можно найти в разделе Settings > General в вашем аккаунте Asapty. ### Настройка событий и тегов \{#configure-events-and-tags\} Под полем с учётными данными находятся три группы событий, которые можно отправлять в Asapty из Adapty. Просто включите нужные. Полный список событий Adapty доступен [здесь](events). <img src="/assets/shared/img/58ddf41-CleanShot_2023-08-15_at_15.11.072x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Рекомендуем использовать стандартные названия событий, предложенные Asapty. При необходимости вы можете изменить их под свои нужды. ### Подключение приложения к Asapty \{#connect-your-app-to-asapty\} После выполнения описанных выше шагов Adapty автоматически начнёт получать данные атрибуции от Asapty. Явно запрашивать данные атрибуции в коде приложения не нужно. Для повышения точности атрибуции настройте Asapty так, чтобы `customerUserId` передавался вместе с данными каждого события. ## Структура событий Asapty \{#asapty-event-structure\} Adapty отправляет события в Asapty через GET-запрос с query-параметрами. URL каждого события выглядит так: ``` https://asapty.com/_api/mmpEvents/?source=adapty&asaptyid=a1b2c3d4&keywordid=12345&adgroupid=67890&campaignid=11223&conversiondate=1709294400000&event_name=subscription_renewed&install_time=1709100000&app_name=MyApp&json=%7B%22af_revenue%22%3A%229.99%22%2C%22af_currency%22%3A%22USD%22...%7D ``` Query-параметры: | Параметр | Тип | Описание | |:-----------------|:-------|:--------------------------------------------------------------| | `source` | String | Всегда "adapty". | | `asaptyid` | String | Asapty ID из ваших учётных данных. | | `keywordid` | String | Keyword ID в Apple Search Ads (если доступен). | | `adgroupid` | String | Ad Group ID в Apple Search Ads (если доступен). | | `campaignid` | String | Campaign ID в Apple Search Ads (если доступен). | | `conversiondate` | Long | Временная метка события в **миллисекундах**. | | `event_name` | String | Название события (смаппированное из события Adapty). | | `install_time` | Long | Временная метка установки в секундах. | | `app_name` | String | Название приложения из Adapty (если доступно). | | `json` | String | URL-кодированная JSON-строка с деталями события (см. ниже). | Параметр `json` — это URL-кодированная JSON-строка со следующими полями: | Параметр | Тип | Описание | |:--------------------------|:-------|:-----------------------------------------------------| | `af_revenue` | String | Сумма дохода в виде строки. | | `af_currency` | String | Код валюты (например, "USD"). | | `transaction_id` | String | ID транзакции в сторе. | | `original_transaction_id` | String | Оригинальный ID транзакции в сторе. | | `purchase_date` | Long | Временная метка покупки в миллисекундах. | | `original_purchase_date` | Long | Временная метка оригинальной покупки в миллисекундах.| | `environment` | String | `Production` или `Sandbox`. | | `vendor_product_id` | String | ID продукта в сторе. | | `profile_country` | String | Код страны на основе IP-адреса пользователя. | | `store_country` | String | Код страны стора пользователя. | ## Устранение неполадок \{#troubleshooting\} - Убедитесь, что вы настроили [Apple Search Ads](apple-search-ads) в Adapty и [загрузили учётные данные](https://app.adapty.io/settings/apple-search-ads) — без них Asapty работать не будет. - Только профили с детальной неорганической атрибуцией ASA будут отправлять события в Asapty. Если атрибуция недостаточна, вы увидите сообщение "The user profile is missing the required integration data." - Профили, созданные до настройки интеграции, не смогут отправлять события в Asapty. - Если интеграция с Adapty не работает, несмотря на корректную настройку, убедитесь, что переключатель **Receive Apple Search Ads attribution in Adapty** включён на вкладке [**App Settings** -> **Apple Search Ads**](https://app.adapty.io/settings/apple-search-ads). --- # File: branch --- --- title: "Branch" description: "Интегрируйте Branch с Adapty для отслеживания диплинков и конверсий приложения." --- [Branch](https://www.branch.io/) позволяет охватывать пользователей, взаимодействовать с ними и оценивать результаты на разных устройствах, каналах и платформах. Это удобная платформа для роста мобильного дохода с помощью специализированных ссылок, которые работают на всех устройствах, каналах и платформах. Adapty предоставляет полный набор данных, позволяющий отслеживать [события подписок](events) из сторов в одном месте. С Adapty вы легко увидите, как ведут себя ваши подписчики, поймёте их предпочтения и сможете общаться с ними целенаправленно и эффективно. Интеграция между Adapty и Branch работает двумя способами. 1. **Получение данных атрибуции из Branch** После настройки интеграции с Branch Adapty начнёт получать данные атрибуции из Branch. Их можно просмотреть на странице профиля пользователя. 2. **Отправка событий подписки в Branch** Adapty может отправлять все события подписки, настроенные в вашей интеграции, в Branch. В результате вы сможете отслеживать эти события в дашборде Branch. ## Настройка интеграции \{#set-up-integration\} ### Подключение Adapty к Branch \{#connect-adapty-to-branch\} Чтобы настроить интеграцию с Branch, перейдите в раздел [Integrations > Branch](https://app.adapty.io/integrations/branch) дашборда Adapty, включите тумблер и заполните поля. <img src="/assets/shared/img/817a051-CleanShot_2023-08-11_at_15.54.372x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Чтобы получить значение для поля **Branch Key**, откройте [настройки аккаунта](https://dashboard.branch.io/account-settings/profile) Branch и найдите поле **Branch Key**. Используйте его для поля **Key test** (для песочницы) или **Key live** (для Production) в дашборде Adapty. В Branch переключайтесь между окружениями Live и Tests, чтобы получить нужный ключ. <img src="/assets/shared/img/130e58b-CleanShot_2023-08-11_at_15.24.162x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Настройте события и теги \{#configure-events-and-tags\} Ниже блока с учётными данными находятся три группы событий, которые можно отправлять в Branch из Adapty. Просто включите нужные. Полный список доступных событий Adapty смотрите [здесь](events). Вы можете отправлять событие с показателем Proceeds (после вычета комиссии Apple/Google) или просто с выручкой. Также можно включить опцию отчётности в валюте пользователя. <img src="/assets/shared/img/a645cf8-CleanShot_2023-08-11_at_15.18.282x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Мы рекомендуем использовать названия событий по умолчанию, предоставленные Adapty. Однако вы можете изменить их под свои нужды. Adapty будет отправлять события подписок в Branch через серверную интеграцию, позволяя просматривать все события подписок в вашем дашборде Branch и связывать их с вашими рекламными кампаниями. ### Подключите приложение к Branch \{#connect-your-app-to-branch\} 1. Вызовите метод SDK `.setIntegrationIdentifier()`, чтобы инициализировать соединение. Вы можете передать свой Branch Identity ID в параметр `customerUserId`. :::note Сторонние SDK генерируют пользовательские ID асинхронно. ID может быть ещё не готов в момент вызова `Adapty.activate()`. Если ваш **Customer User ID** приходит из одного из таких SDK, вызывайте `Adapty.activate()` без него. Как только ID будет получен, вызовите `setIntegrationIdentifier()`, а затем `identify()` с CUID. ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers do { // Adapty SDK 4.x try await Adapty.setIntegrationIdentifier(.branchId(<BRANCH_IDENTITY_ID>)) // Adapty SDK 3.x try await Adapty.setIntegrationIdentifier( key: "branch_id", value: <BRANCH_IDENTITY_ID> ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers // login and update attribution and identifier Branch.getAutoInstance(this) .setIdentity("YOUR_USER_ID") { referringParams, error -> referringParams?.let { data -> Adapty.updateAttribution(data, "branch") { error -> if (error != null) { //handle the error } } } } // logout Branch.getAutoInstance(context).logout() ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```javascript showLineNumbers import 'package:flutter_branch_sdk/flutter_branch_sdk.dart'; FlutterBranchSdk.setIdentity('YOUR_USER_ID'); ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers Branch.setIdentity("your user id"); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers import branch from 'react-native-branch'; branch.setIdentity('YOUR_USER_ID'); ``` </TabItem> </Tabs> 2. Используйте метод `.updateAttribution()`, чтобы сохранить данные атрибуции. Если вы не указывали Branch user ID на предыдущем шаге, передайте его в параметр `networkUserId` здесь. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers class YourBranchImplementation { func initializeBranch() { // Pass the attribution you receive from the initializing method of Branch iOS SDK to Adapty. Branch.getInstance().initSession(launchOptions: launchOptions) { (data, error) in if let data { // Adapty SDK 4.x Adapty.updateAttribution(data, source: .branch) // Adapty SDK 3.x Adapty.updateAttribution(data, source: "branch") } } } } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers //everything is in the above snippet for Android ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers try { await Adapty().setIntegrationIdentifier( key: "branch_id", value: <BRANCH_IDENTITY_ID>, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers using AdaptySDK; ```typescript showLineNumbers import { adapty, AttributionSource } from 'react-native-adapty'; import branch from 'react-native-branch'; branch.subscribe({ enComplete: ({ params, }) => { adapty.updateAttribution(params, "branch"); }, }); ``` </TabItem> </Tabs> ## Структура события \{#event-structure\} Adapty отправляет выбранные события в Branch в соответствии с настройками в разделе **Events names** на [**странице интеграции с Branch**](https://app.adapty.io/integrations/branch). Каждое событие имеет следующую структуру: ```json { "branch_key": "key_live_kaFuWw8WvY7n1ss7...", "name": "PURCHASE", "user_data": { "os": "iOS", "developer_identity": "user_12345", "country": "US", "ip": "192.168.100.1", "idfa": "00000000-0000-0000-0000-000000000000", "idfv": "00000000-0000-0000-0000-000000000000", "aaid": "00000000-0000-0000-0000-000000000000" }, "event_data": { "transaction_id": "GPA.3383-4699-1373-07113", "revenue": 9.99, "currency": "USD" }, "custom_data": { "vendor_product_id": "yearly.premium.6999", "original_transaction_id": "GPA.3383-4699-1373-07113", "store": "play_store", "environment": "production" } } ``` Где: | Параметр | Тип | Описание | |:-------------------------------|:-------|:----------------------------------------------------------------------------------------------------------------------------------| | `branch_key` | String | Ваш Branch Key. | | `name` | String | Название события Branch (сопоставляется с событием Adapty, например, "PURCHASE"). | | `user_data` | Object | Информация о пользователе. | | `user_data.os` | String | "Android" или "iOS". | | `user_data.developer_identity` | String | Customer User ID пользователя. | | `user_data.country` | String | Код страны на основе IP-адреса пользователя. | | `user_data.ip` | String | IP-адрес пользователя. | | `user_data.idfa` | String | **Только iOS**. ID for Advertisers. | | `user_data.idfv` | String | **Только iOS**. ID for Vendors. | | `user_data.aaid` | String | **Только Android**. Google Advertising ID. | | `event_data` | Object | Стандартные метрики события (присутствует только для PURCHASE и аналогичных событий). | | `event_data.transaction_id` | String | Transaction ID стора. | | `event_data.revenue` | Float | Сумма дохода. | | `event_data.currency` | String | Код валюты (например, "USD"). | | `custom_data` | Object | Подробные атрибуты события (содержит все доступные [поля события](webhook-event-types-and-fields#for-most-event-types)). | --- # File: facebook-ads --- --- title: "Facebook Ads" description: "Интегрируйте Facebook Ads с Adapty для эффективного маркетинга подписок." --- Интеграция с Facebook Ads позволяет легко отслеживать статистику приложения в Meta Analytics. Adapty отправляет события в Meta Ads Manager, помогая формировать похожие аудитории на основе подписок для повышения отдачи от рекламы. Так вы сможете точно видеть, сколько денег приносит реклама благодаря подпискам. Интеграция Adapty и Facebook Ads работает следующим образом: Adapty отправляет все события подписок, настроенные в вашей интеграции, в Facebook Ads. Это полезно для оценки эффективности рекламных кампаний. ## Настройка интеграции \{#set-up-integration\} ### Подключение Adapty к Facebook Ads \{#connect-adapty-to-facebook-ads\} Чтобы интегрировать Facebook Ads и анализировать метрики приложения, настройте интеграцию с Meta Analytics. Отправляя события в Meta Ads Manager, вы сможете создавать похожие аудитории на основе событий подписок, например продлений. Для настройки перейдите в раздел [Integrations > Facebook Ads](https://app.adapty.io/integrations/facebookanalytics) в дашборде Adapty и укажите необходимые учётные данные. :::note Интеграция Facebook Ads работает только на iOS 14.5+ для пользователей, давших согласие на ATT. ::: <img src="/assets/shared/img/fd84ddf-CleanShot_2023-08-15_at_15.45.442x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. Чтобы найти App ID, откройте страницу вашего приложения в [App Store Connect](https://appstoreconnect.apple.com/), перейдите на страницу **App Information** в разделе **General** и найдите **Apple ID** в левом нижнем углу экрана. 2. Вам потребуется приложение на платформе [Meta for Developers](https://developers.facebook.com/). Войдите в приложение и откройте расширенные настройки. **App ID** находится в заголовке страницы. <img src="/assets/shared/img/4b326c4-001563-August-23-4tO3JVso.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Отключите клиентское отслеживание в настройках Meta SDK, чтобы избежать двойного учёта выручки в Meta Ads Manager. Эту настройку можно найти в Meta Developer Console в разделе **App Settings > Advanced Settings**. Установите **Log in-app events automatically** в значение «No». Это гарантирует, что события выручки будут отслеживаться только через интеграцию Adapty. Для отслеживания событий установки и использования необходимо активировать Meta SDK в коде. Подробности реализации — в документации Meta SDK для вашей платформы: - [iOS SDK](https://developers.facebook.com/docs/ios/getting-started) - [Android SDK](https://developers.facebook.com/docs/android/getting-started) - [Unity SDK](https://developers.facebook.com/docs/unity/getting-started/canvas) <img src="/assets/shared/img/c4eb8eb-001565-August-23-483KKBbC.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Эту интеграцию можно использовать и с Android-приложениями. Если вы настроили конфигурацию Android SDK в **App Settings**, достаточно указать **Facebook App ID**. ### Настройка событий и тегов \{#configure-events-and-tags\} Интеграция Facebook Ads предназначена для компаний, использующих Meta для рекламных кампаний и оптимизирующих их на основе поведения пользователей. Она поддерживает стандартные события Meta для целей оптимизации. Поэтому изменение названий событий в интеграции Meta Ads недоступно. Adapty автоматически сопоставляет пользовательские события с соответствующими событиями Meta для точного анализа. | Событие Adapty | Событие Meta Ads | | :---------------------------- | :-------------------------- | | Subscription initial purchase | Subscribe | | Subscription renewed | Subscribe | | Subscription cancelled | CancelSubscription | | Trial started | StartTrial | | Trial converted | Subscribe | | Trial cancelled | CancelTrial | | Non subscription purchase | fb_mobile_purchase | | Billing issue detected | billing_issue_detected | | Entered grace period | entered_grace_period | | Auto renew off | auto_renew_off | | Auto renew on | auto_renew_on | | Auto renew off subscription | auto_renew_off_subscription | | Auto renew on subscription | auto_renew_on_subscription | StartTrial, Subscribe, CancelSubscription — стандартные события. <img src="/assets/shared/img/8a5df9d-CleanShot_2023-07-04_at_12.47.312x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Чтобы включить нужные события, просто активируйте их переключателем. Если выбрано несколько названий событий, Adapty объединит данные по всем выбранным событиям в одно событие Adapty. ### Подключение приложения к Facebook Ads \{#connect-your-app-to-facebook-ads\} Если вы выполнили описанные выше шаги, Facebook будет автоматически получать данные о подписках от Adapty. После изменений в IDFA в iOS 14.5 мы рекомендуем запрашивать у пользователя `facebookAnonymousId` из Facebook. Тогда, если IDFA пользователя недоступен, интеграция продолжит работать. Следуйте гайду <InlineTooltip tooltip="гайд по установке атрибутов пользователя">[iOS](setting-user-attributes), [Android](android-setting-user-attributes), [React Native](react-native-setting-user-attributes), [Flutter](flutter-setting-user-attributes) и [Unity](unity-setting-user-attributes)</InlineTooltip>, чтобы задать этот параметр. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "facebook_anonymous_id", value: AppEvents.shared.anonymousID ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.setIntegrationIdentifier( "facebook_anonymous_id", AppEventsLogger.getAnonymousAppDeviceGUID(context) ) { error -> if (error != null) { // handle the error } } ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers try { const anonymousId = await AppEventsLogger.getAnonymousID(); await adapty.setIntegrationIdentifier("facebook_anonymous_id", anonymousId); } catch (error) { // handle `AdaptyError` } ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```text There is no official SDK for Flutter ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp anonymousID is not available in the official SDK https://github.com/facebook/facebook-sdk-for-unity/issues/676 ``` </TabItem> </Tabs> ## Структура события \{#event-structure\} Adapty отправляет события в Facebook Ads (Meta) через Graph API. Каждое событие имеет следующую структуру: ```json { "event": "CUSTOM_APP_EVENTS", "app_user_id": "user_12345", "advertiser_id": "00000000-0000-0000-0000-000000000000", "advertiser_tracking_enabled": 1, "application_tracking_enabled": 1, "custom_events": "[{\"_eventName\":\"Subscribe\",\"_logTime\":1709294400,\"fb_num_items\":1,\"fb_content_type\":\"in_app\",\"fb_content_id\":\"yearly.premium.6999\",\"fb_currency\":\"USD\",\"fb_order_id\":\"GPA.3383...\",\"fb_transaction_id\":\"GPA.3383...\",\"_valueToSum\":9.99}]", "extinfo": "[\"i2\",\"com.example.app\",\"1.0.0\",\"100\",\"17.0.1\",\"iPhone14,3\",\"en_US\",\"GMT+3\",\"\",0,0,0,0,0,0,\"GMT+3\"]", "anon_id": "facebook_anon_id_123" } ``` Где: | Параметр | Тип | Описание | |:---|:---|:---| | `event` | String | Всегда "CUSTOM_APP_EVENTS". | | `app_user_id` | String | Customer User ID пользователя. | | `advertiser_id` | String | IDFA (iOS) или Advertising ID (Android). | | `advertiser_tracking_enabled` | Integer | `1`, если отслеживание включено (ATT разрешён), `0` — в противном случае. | | `application_tracking_enabled` | Integer | Всегда `1`. | | `custom_events` | String | JSON-строка с массивом объектов событий (см. ниже). | | `extinfo` | String | JSON-строка с информацией о приложении и устройстве (версия, ОС, локаль и т.д.). | | `anon_id` | String | Facebook Anonymous ID (если доступен). | Параметр `custom_events` — это JSON-строка с массивом объектов, содержащих: | Параметр | Тип | Описание | |:---|:---|:---| | `_eventName` | String | Название события Meta Ads (например, "Subscribe"). | | `_logTime` | Long | Временна́я метка события в секундах. | | `_valueToSum` | Float | Сумма выручки. | | `fb_content_id` | String | ID продукта в сторе. | | `fb_currency` | String | Код валюты (например, "USD"). | | `fb_order_id` | String | ID исходной транзакции. | | `fb_transaction_id` | String | ID исходной транзакции. | | `fb_content_type` | String | Всегда "in_app". | | `fb_num_items` | Integer | Всегда 1 для событий покупки. | --- # File: singular --- --- title: "Singular" description: "Интегрируйте Singular с Adapty для анализа маркетинговых данных и данных о подписках." --- [Singular](https://www.singular.net/) — одна из ведущих платформ Mobile Measurement Partner (MMP), которая собирает и представляет данные из маркетинговых кампаний. Это помогает компаниям отслеживать эффективность своих кампаний. Adapty предоставляет полный набор данных, позволяющий отслеживать [события подписок](events) из сторов в одном месте. С помощью Adapty вы можете легко анализировать поведение подписчиков, понимать их предпочтения и использовать эту информацию для точечной и эффективной коммуникации. Данная интеграция позволяет отслеживать события подписок в Singular и точно анализировать, какой доход приносят ваши кампании. Adapty может отправлять все события подписок, настроенные в вашей интеграции, в Singular. В результате вы сможете отслеживать эти события в дашборде Singular. Интеграция полезна для оценки эффективности рекламных кампаний. ## Настройка интеграции \{#set-up-integration\} ### Подключение Adapty к Singular \{#connect-adapty-to-singular\} Чтобы настроить интеграцию с Singular, перейдите в раздел [Integrations > Singular](https://app.adapty.io/integrations/singular) в дашборде Adapty, включите переключатель и заполните поля. Доступны следующие учётные данные: - **Singular SDK Key**: Обязательное поле. Производственный SDK-ключ для вашего приложения в Singular. - **Singular SDK Key (Sandbox)**: Необязательное поле. SDK-ключ для вашего приложения в песочнице Singular. Если не задан, события песочницы не будут отправляться в Singular. Оба ключа можно найти в дашборде Singular в разделе **Developer tools -> SDK Keys -> SDK Key (**не** SDK Secret)**: <img src="/assets/shared/img/4bc50d1-singular_sdk_key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Ниже учётных данных расположены три группы событий, которые можно отправлять в Singular из Adapty. Полный список событий, доступных в Adapty, смотрите [здесь](events). <img src="/assets/shared/img/e67de0c-singular_events.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Рекомендуем использовать названия событий по умолчанию, предоставляемые Adapty. При необходимости вы можете изменить их под свои нужды. Adapty будет отправлять события подписок в Singular через интеграцию сервер-к-серверу, что позволит просматривать все события подписок в дашборде Singular и связывать их с вашими рекламными кампаниями. :::warning Профили, созданные до настройки интеграции, не смогут передавать свои события в Singular. ::: ### Подключение приложения к Singular \{#connect-your-app-to-singular\} Интеграция между Adapty и Singular осуществляется по схеме сервер-к-серверу. Поэтому добавлять дополнительный код в приложение не нужно. ## Структура события \{#event-structure\} Adapty отправляет события в Singular через GET-запрос с использованием параметров запроса. Каждое событие имеет следующую структуру: ```json { "n": "subscription_renewed", "a": "singular_sdk_key_123", "p": "iOS", "i": "com.example.app", "ip": "192.168.100.1", "idfa": "00000000-0000-0000-0000-000000000000", "idfv": "00000000-0000-0000-0000-000000000000", "ve": "17.0.1", "att_authorization_status": 3, "custom_user_id": "user_12345", "utime": 1709294400, "amt": 9.99, "cur": "USD", "purchase_product_id": "yearly.premium.6999", "purchase_transaction_id": "GPA.3383...", "e": "{\"is_revenue_event\":true,\"amt\":9.99,\"cur\":\"USD\",\"purchase_product_id\":\"yearly.premium.6999\",\"purchase_transaction_id\":\"GPA.3383...\"}" } ``` Где: | Параметр | Тип | Описание | |:---------------------------|:--------|:----------------------------------------------------------------| | `n` | String | Название события (сопоставленное с событием Adapty). | | `a` | String | Ваш Singular SDK Key. | | `p` | String | Платформа ("iOS" или "Android"). | | `i` | String | Store App ID (Bundle ID). | | `ip` | String | IP-адрес пользователя. | | `idfa` | String | **Только iOS**. ID для рекламодателей (в верхнем регистре). | | `idfv` | String | **Только iOS**. ID для вендоров (в верхнем регистре). | | `aifa` | String | **Только Android**. Google Advertising ID (в нижнем регистре). | | `andi` | String | **Только Android**. Android ID (в нижнем регистре). | | `asid` | String | **Только Android**. App Set ID (в нижнем регистре). | | `ve` | String | Версия ОС. | | `att_authorization_status` | Integer | **Только iOS**. Статус ATT (например, `3` — авторизован). | | `custom_user_id` | String | Customer User ID пользователя. | | `utime` | Long | UNIX-временная метка события в секундах. | | `amt` | Float | Сумма дохода. | | `cur` | String | Код валюты (например, "USD"). | | `purchase_product_id` | String | ID продукта в сторе. | | `purchase_transaction_id` | String | Оригинальный ID транзакции. | | `e` | String | JSON-строка с деталями события (см. ниже). | Параметр `e` (данные пользовательского события) — это JSON-строка, содержащая: | Параметр | Тип | Описание | |:--------------------------|:--------|:------------------------------------------------------| | `is_revenue_event` | Boolean | `true`, если событие содержит данные о доходе. | | `amt` | Float | Сумма дохода. | | `cur` | String | Код валюты. | | `purchase_product_id` | String | ID продукта в сторе. | | `purchase_transaction_id` | String | Оригинальный ID транзакции. | --- # File: tenjin --- --- title: "Интеграция с Tenjin" description: "" --- Tenjin — это платформа мобильной атрибуции и аналитики для разработчиков приложений и маркетологов. Она предоставляет инструменты для измерения и оптимизации кампаний по привлечению пользователей, предлагая подробную информацию об эффективности приложений и поведении пользователей. Благодаря прозрачному и гибкому подходу Tenjin агрегирует данные из рекламных сетей и сторов, позволяя командам анализировать ROI, отслеживать конверсии и мониторить ключевые метрики эффективности. Передавая [события подписок](events) в Tenjin, вы можете точно видеть, откуда приходят конверсии и какие кампании приносят наибольшую ценность по всем каналам, платформам и устройствам. По сути, дашборды Tenjin предлагают расширенную аналитику маркетинговых кампаний. Передавая атрибуцию Tenjin в Adapty, вы обогащаете аналитику Adapty дополнительными критериями фильтрации, которые можно использовать в анализе когорт и конверсий. Интеграция работает двумя способами: 1. **Получение данных атрибуции от Tenjin** После интеграции Adapty собирает данные атрибуции от Tenjin. Эту информацию можно просмотреть на странице профиля пользователя в дашборде Adapty. 2. **Отправка событий подписок в Tenjin** Adapty отправляет события покупок в Tenjin в режиме реального времени. Эти события помогают оценить эффективность рекламных кампаний непосредственно в дашборде Tenjin. | Характеристика интеграции | Описание | | -------------------------- | ------------------------------------------------------------ | | Расписание | В реальном времени | | Направление данных | <p>Двусторонняя передача:</p><ul><li> **События Adapty**: С сервера Adapty на сервер Tenjin</li><li> **Атрибуция Tenjin**: С SDK Tenjin на сервер Adapty</li></ul> | | Точка интеграции Adapty | <ul><li> SDK Tenjin и Adapty в коде мобильного приложения</li><li> Сервер Adapty</li></ul> | ## Настройка интеграции \{#set-up-integration\} ### Подключение Adapty к Tenjin 1. Откройте страницу [**Integrations** -> **Tenjin**](https://app.adapty.io/integrations/tenjin) в дашборде Adapty. 2. Включите тогл, чтобы активировать интеграцию. <img src="/assets/shared/img/tenjin-toggle.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Войдите в [Tenjin Dashboard](https://tenjin.com/). 4. Перейдите в **Configuration** -> **Apps** в меню навигации. <img src="/assets/shared/img/tenjin-apps.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Выберите приложение для вашей платформы (iOS или Android) и перейдите на вкладку **App and SDK**. 6. На вкладке **App and SDK** нажмите **Copy** в столбце **SDK Key**. Если у вас ещё нет SDK-ключа, нажмите кнопку **Generate SDK Key**, чтобы создать его. <img src="/assets/shared/img/tenjin-copy-sdk-key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Вернитесь в дашборд Adapty и вставьте скопированный SDK Key в соответствующее поле платформы: - Для iOS-приложений: вставьте в поле **iOS SDK Key** или **iOS Sandbox SDK Key** - Для Android-приложений: вставьте в поле **Android SDK Key** или **Android Sandbox SDK Key** :::info У Tenjin нет отдельного режима песочницы для серверной интеграции. Используйте отдельное приложение Tenjin или один и тот же ключ как для продакшн-, так и для sandbox-событий. ::: <img src="/assets/shared/img/tenjin-keys.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 8. Если у вас есть приложения на обеих платформах, повторите шаги 5–7 для другой платформы. 9. (опционально) При необходимости настройте раздел **How the revenue data should be sent**. Подробное описание параметров см. в разделе [Настройки интеграции](configuration#integration-settings). 10. Нажмите **Save**, чтобы завершить настройку. Adapty начнёт отправлять события покупок в Tenjin и получать данные атрибуции. Вы можете настроить передачу событий в разделе **Events names**. ### Настройка событий и тегов \{#configure-events-and-tags\} Tenjin принимает только события покупок и **Trial started**. В разделе **Events names** выберите, какие события следует передавать в Tenjin в соответствии с вашими целями отслеживания. <img src="/assets/shared/img/tenjin-events.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Подключите приложение к Tenjin \{#connect-your-app-to-tenjin\} Используйте метод SDK `Adapty.updateAttribution()`, чтобы получить данные атрибуции от Tenjin и передать их в Adapty. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers func updateTenjinId() { guard let tenjinId = TenjinSDK.getAnalyticsInstallationId() else { return } do { // Adapty SDK 4.x try await Adapty.setIntegrationIdentifier(.tenjinAnalyticsInstallationId(tenjinId)) // Adapty SDK 3.x try await Adapty.setIntegrationIdentifier( key: "tenjin_analytics_installation_id", value: tenjinId ) } catch { // handle the error } } func updateTenjinAttribution() { let instance = TenjinSDK.getInstance("<YOUR_TENJIN_API_TOKEN>") instance?.getAttributionInfo { info, _ in guard let info else { return } Task { do { // Adapty SDK 4.x try await Adapty.updateAttribution(info, source: .tenjin) // Adapty SDK 3.x try await Adapty.updateAttribution(info, source: "tenjin") } catch { // handle the error } } } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adapty.setIntegrationIdentifier("tenjin_analytics_installation_id", tenjinSdk.getAnalyticsInstallationId(), error -> { if (error != null) { // handle the error } }); tenjinSdk.getAttributionInfo(attribution -> { if (attribution == null) return; Adapty.updateAttribution(attribution, "tenjin", error -> { // handle the error }); }); ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers try { final tenjinId = await TenjinSDK.instance.getAnalyticsInstallationId(); if (tenjinId != null) { await Adapty().setIntegrationIdentifier( key: 'tenjin_analytics_installation_id', value: tenjinId, ); } final attribution = await TenjinSDK.instance.getAttributionInfo(); if (attribution != null) { await Adapty().updateAttribution(attribution, source: 'tenjin'); } } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers using AdaptySDK; using System.Linq; BaseTenjin instance = Tenjin.getInstance("<SDK_KEY>"); var tenjinId = instance.GetAnalyticsInstallationId(); Adapty.SetIntegrationIdentifier( "tenjin_analytics_installation_id", tenjinId, (error) => { // handle the error }); instance.GetAttributionInfo((attribution) => { var dynamicAttribution = attribution.ToDictionary( kvp => kvp.Key, kvp => (dynamic)kvp.Value ); Adapty.UpdateAttribution( dynamicAttribution, "tenjin", (error) => { // handle the error }); }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers // ... const posthog = usePostHog() // ... try { await adapty.setIntegrationIdentifier("tenjin_analytics_installation_id", await Tenjin.getAnalyticsInstallationId()); } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> ## Структура событий \{#event-structure\} Adapty отправляет выбранные события в Tenjin в соответствии с настройками в разделе **Events names** на странице [**интеграции с Tenjin**](https://app.adapty.io/integrations/tenjin). Каждое событие имеет следующую структуру: ```json showLineNumbers title="Json" { "price": 99.0, "locale": "en-US", "country": "ME", "postcut": "false", "currency": "USD", "platform": "ios", "quantity": 1, "bundle_id": "com.adapty.adaptydemoapp", "ip_address": "127.0.0.1", "os_version": "18.1.1", "product_id": "month.premium.99", "app_version": "3.2.0", "sdk_version": "server", "device_model": "iPhone 13 Mini", "advertising_id": "00000000-0000-0000-0000-000000000000", "os_version_release": "18.1.1", "developer_device_id": "00000000-0000-0000-0000-000000000000", "analytics_installation_id": "00000000-0000-0000-0000-000000000000" } ``` Where | **Параметр** | **Тип** | **Описание** | | ----------------------------- | ---------------- | ------------------------------------------------------------ | | **price** | Float | Цена единицы товара в стандартных единицах валюты (например, USD указывается в долларах). | | **locale** | String | Локаль устройства. Для Android: `Locale.getDefault().toString()`. Для iOS: `[[NSLocale currentLocale] localeIdentifier]`. | | **country** | String | Код страны по стандарту ISO (например, US для США). | | **postcut** | String (Boolean) | Указывает, была ли покупка отправлена после вычета комиссии платформы. 1 — true, 0 — false. | | **currency** | String | Код валюты по стандарту ISO (например, USD для доллара США). | | **platform** | String | Платформа устройства (например, ios, android, windows, amazon). | | **quantity** | Integer | Количество приобретённых единиц товара. | | **bundle_id** | String | Идентификатор пакета приложения (например, `com.example.app`). | | **ip_address** | String (IPv4) | IP-адрес пользователя. Используется для определения страны. | | **os_version** | String | Версия ОС устройства. Для Android: `String.valueOf(Build.VERSION.SDK_INT)`. Для iOS: `[[UIDevice currentDevice] systemVersion]`. | | **product_id** | String | Уникальный идентификатор приобретённого продукта. | | **app_version** | Float, Decimal | Версия приложения. Для Android: `context.getPackageManager().getPackageInfo()`. Для iOS: `[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]`. | | **sdk_version** | String | Используемая версия SDK, всегда равна `server`. | | **device_model** | String | Модель устройства. Для Android: `Build.MODEL`. Для iOS: `sysctl("hw.machine")`. | | **advertising_id** | UUID | Рекламный идентификатор устройства. Обязателен для Android. Для iOS может быть пустым или состоять из нулей. | | **os_version_release** | String | Релизная версия ОС. Для Android: `String.valueOf(Build.VERSION.RELEASE)`. Для iOS: `[[UIDevice currentDevice] systemVersion]`. | | **developer_device_id** | UUID | Идентификатор вендора (только для iOS). | | **analytics_installation_id** | UUID | Идентификатор установки для аналитики. Подробнее см. в документации по адресу `https://docs.tenjin.com`. | --- # File: amplitude --- --- title: "Amplitude" description: "Интегрируйте Amplitude с Adapty для более глубокого понимания поведения пользователей." --- [Amplitude](https://amplitude.com/) — мощный сервис мобильной аналитики. С помощью Adapty вы можете легко отправлять события в Amplitude, отслеживать поведение пользователей и принимать взвешенные решения. Adapty предоставляет полный набор данных, позволяющий отслеживать [события подписки](events) из сторов в одном месте и отправлять их в ваш аккаунт Amplitude. Это даёт возможность сопоставить поведение пользователей с историей их платежей в Amplitude и принимать более обоснованные продуктовые решения. ### Как настроить интеграцию с Amplitude \{#how-to-set-up-amplitude-integration\} В Adapty можно настроить отдельные флоу для **production**- и **тестовых событий** из песочницы Apple или Stripe, а также из тестового аккаунта Google. - Для production-событий введите **Production** API-ключи из дашборда Amplitude — отдельный ключ для каждой платформы: iOS, Android и Stripe. - Для тестовых событий используйте поля **Sandbox** по мере необходимости. Чтобы настроить интеграцию с Amplitude: 1. Откройте [**Integrations** -> **Amplitude**](https://app.adapty.io/integrations/amplitude) в дашборде Adapty. <img src="/assets/shared/img/3b50552-CleanShot_2023-08-15_at_16.47.102x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Включите **Amplitude integration**, переключив тумблер. 3. Заполните поля интеграции: | Поле | Описание | | ---- | -------- | | **Amplitude iOS/ Android/ Stripe API key** | Введите **API Key** Amplitude для iOS/ Android/ Stripe в Adapty. Найти его можно в разделе **Project settings** в Amplitude. Подробнее см. в [документации Amplitude](https://amplitude.com/docs/apis/authentication). Начните с ключей **Sandbox** для тестирования, затем переключитесь на **Production**-ключи после успешных тестов. | <img src="/assets/shared/img/2297782-CleanShot_2023-08-15_at_16.53.512x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Дополнительные настройки для тонкой настройки: | Параметр | Описание | | -------- | -------- | | **How the revenue data should be sent** | Выберите, отправлять валовую выручку или выручку после вычета налогов и комиссий. Подробнее см. в разделе [Комиссия стора и налоги](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue). | | **Exclude historical events** | Исключить события, произошедшие до установки Adapty SDK, чтобы избежать дублирования данных. Например, если пользователь оформил подписку 10 января, а SDK был установлен 6 марта, Adapty будет отправлять события только начиная с 6 марта. | | **Send User Attributes** | Включите эту опцию, чтобы отправлять атрибуты пользователей, например языковые предпочтения. | | **Always populate user_id** | Adapty автоматически отправляет `device_id` как `amplitudeDeviceId`. Для `user_id` это настройка определяет поведение: <ul><li>**ON**: отправляет `profile_id` из Adapty, если `amplitudeUserId` или `customer_user_id` недоступны.</li><li>**OFF**: оставляет `user_id` пустым, если ни один из идентификаторов недоступен.</li></ul> | 5. Выберите события, которые хотите получать, и [сопоставьте их названия](amplitude#events-and-tags). 6. Нажмите **Save**, чтобы сохранить изменения. После нажатия **Save** Adapty начнёт отправлять события в Amplitude. Помимо событий, Adapty отправляет [статус подписки](subscription-status) и ID продукта подписки в [свойства пользователей Amplitude](https://amplitude.com/docs/data/user-properties-and-events). ### События и теги \{#events-and-tags\} Под полями с учётными данными находятся три группы событий, которые можно отправлять в Amplitude из Adapty. Просто включите нужные. Полный список событий, предоставляемых Adapty, можно найти [здесь](events). <img src="/assets/shared/img/da67694-CleanShot_2023-08-15_at_16.52.352x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Рекомендуем использовать стандартные названия событий, предложенные Adapty. При необходимости вы можете изменить их под свои нужды. Adapty будет отправлять события подписки в Amplitude через серверную интеграцию (server-to-server), что позволит просматривать все события подписки в вашем дашборде Amplitude. ### Настройка SDK \{#sdk-configuration\} Используйте метод `setIntegrationIdentifier()`, чтобы задать параметр `amplitude_device_id`. Это обязательный шаг для настройки интеграции. Если у вас есть регистрация пользователей, вы также можете передать `amplitude_user_id`. :::note Сторонние SDK генерируют пользовательские ID асинхронно. ID может быть ещё не готов в момент вызова `Adapty.activate()`. Если ваш **Customer User ID** приходит из одного из таких SDK, вызывайте `Adapty.activate()` без него. Как только ID будет получен, вызовите `setIntegrationIdentifier()`, а затем `identify()` с CUID. ::: <Tabs groupId="current-os" queryString> <TabItem value="Swift" label="iOS (Swift)" default> **Установка amplitudeDeviceId** ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "amplitude_device_id", value: Amplitude.instance().deviceId ) } catch { // handle the error } ``` **Установка amplitudeUserId** ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "amplitude_user_id", value: "YOUR_AMPLITUDE_USER_ID" ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> **Установка amplitudeDeviceId** ```kotlin showLineNumbers //for Amplitude maintenance SDK (obsolete) val amplitude = Amplitude.getInstance() val amplitudeDeviceId = amplitude.getDeviceId() val amplitudeUserId = amplitude.getUserId() //for actual Amplitude Kotlin SDK val amplitude = Amplitude( Configuration( apiKey = AMPLITUDE_API_KEY, context = applicationContext ) ) val amplitudeDeviceId = amplitude.store.deviceId // Adapty.setIntegrationIdentifier("amplitude_device_id", amplitudeDeviceId) { error -> if (error != null) { // handle the error } } ``` **Установка amplitudeUserId** ```kotlin showLineNumbers //for Amplitude maintenance SDK (obsolete) val amplitude = Amplitude.getInstance() val amplitudeDeviceId = amplitude.getDeviceId() val amplitudeUserId = amplitude.getUserId() //for actual Amplitude Kotlin SDK val amplitude = Amplitude( Configuration( apiKey = AMPLITUDE_API_KEY, context = applicationContext ) ) val amplitudeUserId = amplitude.store.userId // Adapty.setIntegrationIdentifier("amplitude_user_id", amplitudeUserId) { error -> if (error != null) { // handle the error } } ``` </TabItem> <TabItem value="Flutter" label="Flutter (Dart)" default> **Установка amplitudeDeviceId** ```javascript showLineNumbers final Amplitude amplitude = Amplitude.getInstance(instanceName: "YOUR_INSTANCE_NAME"); try { await Adapty().setIntegrationIdentifier( key: "amplitude_device_id", value: amplitude.getDeviceId(), ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` **Установка amplitudeUserId** ```javascript showLineNumbers final Amplitude amplitude = Amplitude.getInstance(instanceName: "YOUR_INSTANCE_NAME"); try { await Adapty().setIntegrationIdentifier( key: "amplitude_user_id", value: "YOUR_AMPLITUDE_USER_ID", ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="Unity" label="Unity (C#)" default> **Установка amplitudeDeviceId** ```csharp showLineNumbers using AdaptySDK; Adapty.SetIntegrationIdentifier( "amplitude_device_id", amplitude.getDeviceId(), (error) => { // handle the error }); ``` **Установка amplitudeUserId** ```csharp showLineNumbers using AdaptySDK; Adapty.SetIntegrationIdentifier( "amplitude_user_id", "YOUR_AMPLITUDE_USER_ID", (error) => { // handle the error }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> **Установка amplitudeDeviceId** ```typescript showLineNumbers try { await adapty.setIntegrationIdentifier("amplitude_device_id", deviceId); } catch (error) { // handle `AdaptyError` } ``` **Установка amplitudeUserId** ```typescript showLineNumbers try { await adapty.setIntegrationIdentifier("amplitude_user_id", userId); } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> ## Структура события Amplitude \{#amplitude-event-structure\} Adapty отправляет события в Amplitude через HTTP API v2. Каждое событие имеет следующую структуру: ```json { "api_key": "your_amplitude_api_key", "events": [ { "partner_id": "adapty", "event_type": "subscription_renewed", "time": 1709294400000, "insert_id": "123e4567-e89b-12d3-a456-426614174000", "user_id": "user_12345", "device_id": "device_12345", "platform": "iOS", "os_name": "iOS", "productId": "yearly.premium.6999", "revenue": 9.99, "event_properties": { "vendor_product_id": "yearly.premium.6999", "original_transaction_id": "GPA.3383...", "currency": "USD", "environment": "Production", "store": "app_store" }, "user_properties": { "subscription_state": "subscribed", "subscription_product": "yearly.premium.6999" } } ] } ``` Где: | Параметр | Тип | Описание | |:---------|:----|:---------| | `api_key` | String | Ваш API-ключ Amplitude. | | `events` | Array | Список объектов событий (Adapty отправляет по одному за раз). | | `events[].partner_id` | String | Всегда «adapty». | | `events[].event_type` | String | Название события (сопоставленное с событием Adapty). | | `events[].time` | Long | Временная метка события в миллисекундах. | | `events[].insert_id` | String | Уникальный идентификатор события (UUID). | | `events[].user_id` | String | Amplitude User ID или Customer User ID. | | `events[].device_id` | String | Amplitude Device ID. | | `events[].platform` | String | Платформа (например, «iOS», «Android»). | | `events[].os_name` | String | Название ОС. | | `events[].productId` | String | ID продукта из стора. | | `events[].revenue` | Float | Сумма выручки. | | `events[].event_properties` | Object | Подробные атрибуты события (содержит все доступные [поля события](webhook-event-types-and-fields#for-most-event-types)). | | `events[].user_properties` | Object | Атрибуты пользователя, например статус подписки. | --- # File: appmetrica --- --- title: "AppMetrica" description: "Интегрируйте AppMetrica с Adapty для глубокого анализа подписок." --- [AppMetrica](https://appmetrica.yandex.com/about) — бесплатный аналитический инструмент для отслеживания поведения пользователей и анализа производительности мобильного приложения в реальном времени. Интеграция AppMetrica с Adapty позволяет получить более глубокие сведения о метриках подписок и вовлечённости пользователей. ## Как настроить интеграцию AppMetrica \{#how-to-set-up-appmetrica-integration\} Настройка интеграции AppMetrica состоит из двух основных шагов: 1. Настройте интеграцию в дашборде Adapty 2. Добавьте интеграцию в код вашего приложения ### Настройка в дашборде \{#dashboard-configuration\} Чтобы настроить интеграцию AppMetrica: 1. Откройте [список приложений AppMetrica](https://appmetrica.yandex.ru/application/list) 2. Выберите нужное приложение 3. Перейдите в **Settings > Main** и скопируйте **Application ID** и **Post API key** <img src="/assets/shared/img/appmetrica.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Откройте [Integrations > AppMetrica](https://app.adapty.io/integrations/appmetrica) в дашборде Adapty 5. Вставьте учётные данные AppMetrica. <img src="/assets/shared/img/appmetrica_creds.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### События и теги \{#events-and-tags\} Adapty позволяет отправлять в AppMetrica три группы событий. Вы можете включить нужные события для отслеживания работы приложения. Полный список доступных событий см. в [документации по событиям](events). :::note AppMetrica синхронизирует события каждые 4 часа, поэтому события могут появляться в вашем дашборде с задержкой. ::: <img src="/assets/shared/img/6ed2d88-CleanShot_2023-08-18_at_14.59.042x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::tip Мы рекомендуем использовать стандартные имена событий Adapty для единообразия, но вы можете изменить их в соответствии с вашей существующей аналитической системой. ::: ### Настройки выручки \{#revenue-settings\} По умолчанию Adapty отправляет данные о выручке в виде свойств событий, которые отображаются в отчёте Events в AppMetrica. Вы можете настроить способ расчёта и отображения этих данных: - **Revenue calculation**: Выберите, как рассчитываются значения выручки в соответствии с вашими потребностями финансовой отчётности: - **Gross revenue**: Показывает общую выручку до вычетов — полезно для отслеживания суммы, которую платят пользователи - **Proceeds after store commission**: Отображает выручку после вычета комиссии App Store/Play Store — помогает отслеживать фактический доход - **Proceeds after store commission and taxes**: Показывает чистую выручку после вычета комиссии стора и налогов — наиболее точное отображение реального дохода - **Report user's currency**: Если включено, продажи отображаются в локальной валюте пользователя, что упрощает анализ выручки по регионам. Если отключено, все продажи конвертируются в USD для единообразной отчётности по разным рынкам. - **Send revenue events**: Включите эту опцию, чтобы данные о выручке отображались не только в отчёте Events, но и в отчёте AppMetrica [In-app and ad revenue](https://appmetrica.yandex.com/docs/en/mobile-reports/revenue-report). Убедитесь, что вы не отправляете выручку из других источников, иначе данные могут задвоиться. - **Exclude historical events**: Если включено, Adapty не будет отправлять события, произошедшие до установки пользователем приложения с Adapty SDK. Это помогает избежать дублирования данных, если вы уже отправляли события в аналитику до интеграции Adapty. <img src="/assets/shared/img/appmetrica_revenue.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Настройка SDK \{#sdk-configuration\} Для подключения интеграции AppMetrica в приложении необходимо задать два идентификатора: 1. `appmetrica_device_id`: обязателен для базовой интеграции 2. `appmetrica_profile_id`: необязателен, но рекомендуется, если в приложении есть регистрация пользователей Используйте метод `setIntegrationIdentifier()` для установки этих значений. Ниже показано, как реализовать это для каждой платформы: :::note Сторонние SDK генерируют пользовательские ID асинхронно. ID может быть ещё не готов в момент вызова `Adapty.activate()`. Если ваш **Customer User ID** приходит из одного из таких SDK, вызывайте `Adapty.activate()` без него. Как только ID будет получен, вызовите `setIntegrationIdentifier()`, а затем `identify()` с CUID. ::: <Tabs groupId="current-os" queryString> <TabItem value="Swift" label="iOS (Swift)" default> **Установка appmetrica_device_id** ```swift showLineNumbers AppMetrica.requestStartupIdentifiers(on: nil) { ids, error in if let error { // handle AppMetrica error return } guard let deviceIDHash = ids?[.deviceIDHashKey] as? String else { // handle AppMetrica error return } Task { do { try await Adapty.setIntegrationIdentifier( key: "appmetrica_device_id", value: deviceIDHash ) } catch { // handle the error } } } ``` **Установка appmetrica_profile_id** ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "appmetrica_profile_id", value: "YOUR_APPMETRICA_PROFILE_ID" ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> **Установка appmetrica_device_id** ```kotlin showLineNumbers val startupParamsCallback = object: StartupParamsCallback { override fun onReceive(result: StartupParamsCallback.Result?) { val deviceIdHash = result?.deviceIdHash ?: return Adapty.setIntegrationIdentifier("appmetrica_device_id", deviceIdHash) { error -> if (error != null) { // handle the error } } } override fun onRequestError( reason: StartupParamsCallback.Reason, result: StartupParamsCallback.Result? ) { //handle the error } } AppMetrica.requestStartupParams(context, startupParamsCallback, listOf(StartupParamsCallback.APPMETRICA_DEVICE_ID_HASH)) ``` **Установка appmetrica_profile_id** ```kotlin showLineNumbers val startupParamsCallback = object: StartupParamsCallback { override fun onReceive(result: StartupParamsCallback.Result?) { val deviceIdHash = result?.deviceIdHash ?: return Adapty.setIntegrationIdentifier("appmetrica_device_id", deviceIdHash) { error -> if (error != null) { // handle the error } } Adapty.setIntegrationIdentifier("appmetrica_profile_id", "YOUR_ADAPTY_CUSTOMER_USER_ID") { error -> if (error != null) { // handle the error } } } override fun onRequestError( reason: StartupParamsCallback.Reason, result: StartupParamsCallback.Result? ) { //handle the error } } AppMetrica.requestStartupParams(context, startupParamsCallback, listOf(StartupParamsCallback.APPMETRICA_DEVICE_ID_HASH)) ``` </TabItem> <TabItem value="Flutter" label="Flutter (Dart)" default> **Установка appmetrica_device_id** ```javascript showLineNumbers final startupParams = await AppMetrica.requestStartupParams([AppMetricaStartupParams.deviceIdHashKey]); final deviceIdHash = startupParams.result?.deviceIdHash; if (deviceIdHash != null) { try { await Adapty().setIntegrationIdentifier( key: "appmetrica_device_id", value: deviceIdHash, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } } ``` **Установка appmetrica_profile_id** ```javascript showLineNumbers try { await Adapty().setIntegrationIdentifier( key: "appmetrica_profile_id", value: "YOUR_APPMETRICA_PROFILE_ID", ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="Unity" label="Unity (C#)" default> **Установка appmetrica_device_id** ```csharp showLineNumbers using AdaptySDK; using Io.AppMetrica; AppMetrica.RequestStartupParams( (result, errorReason) => { string deviceIdHash = result.DeviceIdHash; if (deviceIdHash != null) { Adapty.SetIntegrationIdentifier( "appmetrica_device_id", deviceIdHash, (error) => { // handle the error }); } }, new List<string>() { StartupParamsKey.AppMetricaDeviceIDHash } ); ``` **Установка appmetrica_profile_id** ```csharp showLineNumbers Adapty.SetIntegrationIdentifier( "appmetrica_profile_id", "YOUR_APPMETRICA_PROFILE_ID", (error) => { // handle the error }); ``` </TabItem> <TabItem value="RN" label="React Native (TS)" default> **Установка appmetrica_device_id** ```typescript showLineNumbers // ... const startupParamsCallback = async ( params?: StartupParams, reason?: StartupParamsReason ) => { const deviceIdHash = params?.deviceIdHash if (deviceIdHash) { try { await adapty.setIntegrationIdentifier("appmetrica_device_id", deviceIdHash); } catch (error) { // handle `AdaptyError` } } } AppMetrica.requestStartupParams(startupParamsCallback, [DEVICE_ID_HASH_KEY]) ``` **Установка appmetrica_profile_id** ```typescript showLineNumbers try { await adapty.setIntegrationIdentifier("appmetrica_profile_id", 'YOUR_ADAPTY_CUSTOMER_USER_ID'); } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> ## Структура событий AppMetrica \{#appmetrica-event-structure\} Adapty отправляет события в AppMetrica через POST-запросы с параметрами в строке запроса. Для каждого события Adapty AppMetrica получает до **двух отдельных запросов**: 1. **Profile event** (отправляется всегда): содержит метаданные события 2. **Revenue event** (опционально): содержит данные о выручке, если опция «Send revenue events» включена в дашборде Adapty ### Запрос Profile Event \{#profile-event-request\} Отправляется на: `https://api.appmetrica.yandex.ru/logs/v1/import/events` Пример URL с параметрами запроса: ``` POST https://api.appmetrica.yandex.ru/logs/v1/import/events?post_api_key=your_key&application_id=your_app_id&event_name=subscription_renewed&event_timestamp=1709294400&event_json=%7B%22vendor_product_id%22%3A%22yearly.premium%22...%7D&os_name=ios&ios_ifa=00000000-0000-0000-0000-000000000000&ios_ifv=12345678-1234-1234-1234-123456789012&profile_id=user_12345&session_type=foreground ``` Параметры запроса: | Параметр | Тип | Описание | |:-----------------------|:-------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `post_api_key` | String | Ваш Post API Key AppMetrica. | | `application_id` | String | Ваш Application ID AppMetrica. | | `event_name` | String | Название события (сопоставленное с событием Adapty). | | `event_timestamp` | Long | UNIX-временная метка события в секундах. Ограничена последними 7 днями при более раннем значении. | | `event_json` | String | URL-кодированная JSON-строка со всеми доступными [полями события](webhook-event-types-and-fields#for-most-event-types). Включаются только ненулевые поля. | | `os_name` | String | «ios» или «android». | | `profile_id` | String | AppMetrica Profile ID (если задан), иначе Customer User ID (если доступен). | | `appmetrica_device_id` | String | AppMetrica Device ID Hash. Отправляется только если `profile_id` недоступен. | | `session_type` | String | Всегда «foreground». | | `ios_ifa` | String | **Только iOS**. ID for Advertisers. | | `ios_ifv` | String | **Только iOS**. ID for Vendors. | | `google_aid` | String | **Только Android**. Google Advertising ID. | ### Запрос Revenue Event (опционально) \{#revenue-event-request-optional\} Отправляется на: `https://api.appmetrica.yandex.ru/logs/v1/import/revenue` Этот запрос отправляется только при включённой опции «Send revenue events» в настройках интеграции дашборда Adapty. Пример URL с параметрами запроса: ``` POST https://api.appmetrica.yandex.ru/logs/v1/import/revenue?post_api_key=your_key&application_id=your_app_id&revenue_event_type=subscription_renewed&price=9.99¤cy=USD&product_id=yearly.premium&quantity=1&transaction_id=GPA.3383...&payload=%7B%22vendor_product_id%22%3A%22yearly.premium%22...%7D&os_name=ios&ios_ifa=00000000-0000-0000-0000-000000000000&profile_id=user_12345&session_type=foreground ``` Параметры запроса: | Параметр | Тип | Описание | |:-----------------------|:--------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `post_api_key` | String | Ваш Post API Key AppMetrica. | | `application_id` | String | Ваш Application ID AppMetrica. | | `revenue_event_type` | String | Тип события выручки (например, «subscription_renewed», «refund», «intro_started»). См. [маппинг событий AppMetrica](#revenue-event-type-mapping). | | `price` | Float | Сумма выручки (в соответствии с настройками расчёта выручки). | | `currency` | String | Код валюты (например, «USD»). | | `product_id` | String | Product ID из стора. | | `quantity` | Integer | Всегда 1. | | `transaction_id` | String | Transaction ID из стора. | | `payload` | String | URL-кодированная JSON-строка с деталями события. Автоматически обрезается при превышении 30 КБ: сначала удаляются необязательные поля в порядке приоритета, чтобы сохранить наиболее важные данные. | | `os_name` | String | «ios» или «android». | | `profile_id` | String | AppMetrica Profile ID (если задан), иначе Customer User ID (если доступен). | | `appmetrica_device_id` | String | AppMetrica Device ID Hash. Отправляется только если `profile_id` недоступен. | | `session_type` | String | Всегда «foreground». | | `ios_ifa` | String | **Только iOS**. ID for Advertisers. | | `ios_ifv` | String | **Только iOS**. ID for Vendors. | | `google_aid` | String | **Только Android**. Google Advertising ID. | --- # File: firebase-and-google-analytics --- --- title: "Firebase и Google Analytics" description: "Отправляйте события подписок из Adapty в Firebase и Google Analytics — управляйте аудиториями, Remote Config, атрибуцией Google Ads и другими инструментами Firebase." --- Adapty может передавать события подписок — покупки, продления, возвраты, запуски пробных периодов — в Firebase и Google Analytics, так что одна интеграция доставляет данные сразу в оба сервиса. :::warning Вам понадобятся и проект Firebase, и связанный ресурс Google Analytics — даже если вы используете только один из них. Firebase и Google Analytics — это одни и те же данные в двух разных консолях. ::: События покупки и возврата приходят вместе с данными о выручке, валюте и продукте. Те же данные используются в инструментах Firebase для мобильных приложений (Audiences, Remote Config и т. д.) и в отчётах Google Analytics. Это интеграция для аналитики, а не инструмент атрибуции через Google Ads — см. [Ограничения](#limitations). ## Что даёт эта интеграция \{#what-you-can-do-with-this-integration\} Adapty группирует пользователей по `subscription_state` (`subscribed`, `active_trial`, `never_subscribed` и т. д.) и передаёт события жизненного цикла подписки в Firebase и Google Analytics. - **Audiences**: Создавайте аудитории подписчиков в Firebase и Google Analytics для внешних каналов — ретаргетинга в Google Ads, FCM-кампаний, построения похожих аудиторий. - **Конверсии Google Ads** *(Google Analytics)*: Используйте `purchase` и `refund` как цели конверсии в Google Ads. - **Firebase Remote Config**: Меняйте лимиты использования, тексты или флаги функций без обновления приложения — задавайте условия на основе статуса подписки. (Не путайте с [Adapty Remote Config](customize-paywall-with-remote-config), который настраивает содержимое флоу и пейволов.) - **Cloud Messaging**: Push-уведомления отписавшимся пользователям, когда приложение закрыто. - **Кросс-девайсное отслеживание** *(Google Analytics)*: Adapty передаёт `customer_user_id` в Google Analytics, чтобы Google Ads мог отслеживать одного и того же пользователя на разных устройствах. - **Воронки конверсий** *(Google Analytics)*: Смотрите, что пользователи делали в приложении перед конверсией или оттоком. - **Прогнозы**: Прогнозируйте отток и расходы на основе истории покупок. - **A/B-тестирование**: Тестируйте функции приложения на когортах подписчиков — например, выкатывайте новый паттерн навигации пользователям в триале и измеряйте продолжительность сессий. (Для тестирования вариантов пейволов используйте [A/B-тесты Adapty](ab-tests).) ## Как работает интеграция \{#how-the-integration-works\} 1. Когда пользователь впервые открывает ваше приложение, Firebase SDK создаёт уникальный идентификатор для этой установки — **Firebase App Instance ID**. Firebase и Google Analytics используют его, чтобы определить, с какой установки пришло то или иное событие. 2. Ваше приложение передаёт Firebase App Instance ID в SDK Adapty. Adapty связывает профиль пользователя с этой установкой Firebase. 3. Когда пользователь совершает покупку, серверы Adapty пересылают событие в Firebase, прикрепляя Firebase App Instance ID. Данные передаются между серверами, минуя приложение. 4. Firebase сопоставляет покупку с установкой, и вы видите покупки рядом со всеми остальными действиями пользователя в приложении. :::note Firebase App Instance ID привязан к конкретному устройству. Когда один и тот же пользователь Adapty открывает приложение на другом устройстве, новый Firebase ID перезаписывает предыдущий. Используйте [customer user ID](identifying-users), чтобы сохранять идентичность пользователя на всех устройствах. ::: ### Покупки через Stripe \{#stripe-purchases\} Покупка через Stripe попадает в Firebase только если покупатель **сначала** открыл ваше мобильное приложение. Firebase App Instance ID должен быть установлен **до** того, как произойдёт покупка через Stripe. Для покупок через App Store и Play Store это происходит автоматически. Они совершаются внутри мобильного приложения вместе с вызовом [`setIntegrationIdentifier`](#configure-your-app-code). Firebase ID уже присутствует в момент покупки. Покупки Stripe инициируются за пределами приложения — на вашем сервере. Мобильное приложение должно вызывать `setIntegrationIdentifier` при запуске — до того, как произойдёт любая покупка через Stripe. Иначе у Adapty не будет идентификатора для привязки, и покупка через Stripe никогда не попадёт в Firebase. ### Ограничения \{#limitations\} - **Это не инструмент атрибуции Google Ads.** Интеграция отправляет события Adapty в Firebase и Google Analytics для аналитики. Она не атрибутирует установки приложения к кампаниям Google Ads (UAC / Universal App Campaigns) и не разделяет платный и органический трафик. Для атрибуции установок используйте встроенную [атрибуцию Adapty](adapty-user-acquisition). - **Нет ретроактивной загрузки данных.** Adapty передаёт события с момента включения интеграции — прошлые покупки, продления и возвраты в Firebase не попадают. (Исторические данные хранятся в экспортах Adapty [S3](s3-exports) / [GCS](google-cloud-storage), но их импорт в Firebase этой интеграцией не предусмотрен.) - **Покупатели только через веб не попадают в Firebase.** Adapty передаёт покупки в Firebase через Firebase App Instance ID, который устанавливает ваше мобильное приложение. У покупателей, которые никогда не устанавливали приложение, нет этого ID — их покупки в Firebase не поступают. Подробнее — в разделе [Покупки через Stripe](#stripe-purchases) выше. Рассмотрите [интеграцию FunnelFox с Firebase](https://funnelfox.com/docs/integrations/subscription-management/adapty) или веб-поток данных Google Analytics для отслеживания веб-покупок. - **Покупки через Paddle не поддерживаются.** Интеграция пока не работает с Paddle. Покупки через Paddle остаются в Adapty Analytics и через этот путь в Firebase не передаются. - **Покупки через Stripe наследуют ограничения Stripe.** См. [ограничения интеграции со Stripe](stripe#current-limitations). ## Инструкция по настройке \{#setup-instructions\} ### Настройка Firebase \{#configure-firebase\} 1. Откройте [Firebase Console](https://console.firebase.google.com/) и выберите или создайте проект. Чтобы производственная аналитика не смешивалась с событиями из песочницы, используйте отдельный проект Firebase для dev-сборок. 2. Привяжите проект к свойству Google Analytics. Firebase предложит это при создании проекта, или добавьте позже через **Project settings** > **Integrations** > **Google Analytics**. 3. В разделе **Project settings** > **General** > **Your apps** добавьте запись для каждой платформы, на которой работает ваше приложение (iOS / Android / Web). Для Stripe добавьте запись для Web-приложения — отдельного типа для Stripe нет. Каждая запись генерирует уникальный **Firebase App ID** и соответствующий поток данных в Google Analytics. Этот ID нужно будет вставить в настройки интеграции Firebase в Adapty во время настройки. ### Настройка Adapty \{#configure-adapty\} 1. Откройте [**Integrations** > **Firebase**](https://app.adapty.io/integrations/firebase) в дашборде Adapty. 2. Включите переключатель **Firebase integration**. 3. Введите учётные данные для каждой платформы, на которой работает ваше приложение. Adapty требует **Firebase App ID** и **Google Analytics secret** для каждой платформы — значения отличаются для iOS, Android и Stripe. | Adapty Dashboard | Google Analytics | Где найти | | --- | --- | --- | | **Firebase App ID** | **App ID** | Firebase Console > **Project settings** > **General** > **Your apps** | | **Google Analytics secret** | **Measurement Protocol API secret** | Google Analytics > **Admin** > **Data streams** > **Measurement Protocol API secrets** > **Create** | 4. Настройте, как Adapty передаёт данные о доходах и пользователях. Четыре параметра расположены в одной строке на дашборде: - Выпадающий список **Revenue definition**: Gross revenue, Proceeds after store commission или [Proceeds after store commission and taxes](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue). - Переключатель **Send user properties**: когда включён, события содержат `subscription_state` и `subscription_product_id`. Чтобы использовать их в отчётах или аудиториях, см. [Использование данных о подписке в отчётах](#use-subscription-data-in-reports-and-audiences). - Переключатель **Report user's currency**: когда включён, Adapty конвертирует локальную валюту каждой транзакции в валюту отчётности вашего аккаунта перед отправкой в Google Analytics. - Переключатель **Send trial price**: старты триала ценны — большинство платящих пользователей начинают именно с триала. Но оптимизация ставок в Google Ads учитывает только события с выручкой. Включите этот переключатель, чтобы присвоить каждому триалу условную цену — тогда Google будет воспринимать их как конверсии и оптимизировать расходы на рекламу в пользу привлечения пользователей, запускающих триал. При включении появляется поле **Trial price percentage**. Укажите долю от полной цены подписки, которую Google должен засчитывать за каждый триал — например, `50%` означает, что в период триала будет передаваться половина стоимости подписки. 5. Сопоставьте события Adapty с названиями событий Firebase/Google Analytics. Adapty предоставляет отдельные таблицы соответствия событий для **iOS** и **Android**, поэтому вы можете использовать разные названия для каждой платформы. **Покупки через Stripe используют таблицу событий iOS** — отдельной таблицы для Stripe нет. Google Analytics накладывает жёсткие ограничения протокола Measurement Protocol: не более 40 символов в названии события, 24 символов в названии user-свойства и 36 символов в значении. Если лимит превышен, Google Analytics молча удаляет такие события. :::warning Часть событий использует зарезервированную ecommerce-лексику Firebase и Google Analytics — `purchase` и `refund`. От этих точных строк зависят импорт конверсий в Google Ads, отчёты по доходам в Google Analytics и прогностические аудитории. Переопределяйте значения по умолчанию только если эти функции вам не нужны. ::: 6. Нажмите **Save**. Adapty начнёт отправлять события в Firebase в течение нескольких минут. ### Настройте код вашего приложения \{#configure-your-app-code\} :::tip Убедитесь, что ваше приложение включает <InlineTooltip tooltip="Firebase SDK">[iOS](https://firebase.google.com/docs/ios/setup), [Android](https://firebase.google.com/docs/android/setup), [Flutter](https://firebase.google.com/docs/flutter/setup), [Unity](https://firebase.google.com/docs/unity/setup), [React Native](https://rnfirebase.io/) и [Capacitor](https://github.com/capawesome-team/capacitor-firebase)</InlineTooltip>. ::: Adapty необходимо передавать **Firebase App Instance ID** с каждым событием — иначе ничего не попадёт в Firebase (`MISSING_INTEGRATION_ID`). После вызовов `FirebaseApp.configure()` и `Adapty.activate()` запросите у Firebase SDK App Instance ID и передайте его в Adapty через `setIntegrationIdentifier`. Выполняйте это один раз при каждом запуске приложения, до начала любого флоу покупки. :::note Сторонние SDK генерируют пользовательские ID асинхронно. ID может быть ещё не готов в момент вызова `Adapty.activate()`. Если ваш **Customer User ID** приходит из одного из таких SDK, вызывайте `Adapty.activate()` без него. Как только ID будет получен, вызовите `setIntegrationIdentifier()`, а затем `identify()` с CUID. ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> <Tabs groupId="sdk-version" queryString> <TabItem value="v4" label="Adapty SDK v4+"> ```swift showLineNumbers FirebaseApp.configure() if let appInstanceId = Analytics.appInstanceID() { do { try await Adapty.setIntegrationIdentifier(.firebaseAppInstanceId(appInstanceId)) } catch { // handle the error } } ``` </TabItem> <TabItem value="v3" label="Adapty SDK v3" default> ```swift showLineNumbers FirebaseApp.configure() if let appInstanceId = Analytics.appInstanceID() { do { try await Adapty.setIntegrationIdentifier( key: "firebase_app_instance_id", value: appInstanceId ) } catch { // handle the error } } ``` </TabItem> </Tabs> </TabItem> <TabItem value="kotlin" label="Android (Kotlin)"> ```kotlin showLineNumbers // after Adapty.activate() FirebaseAnalytics.getInstance(context).appInstanceId.addOnSuccessListener { appInstanceId -> Adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId) { error -> if (error != null) { // handle the error } } } ``` </TabItem> <TabItem value="java" label="Android (Java)"> ```java showLineNumbers // after Adapty.activate() FirebaseAnalytics.getInstance(context).getAppInstanceId().addOnSuccessListener(appInstanceId -> { Adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId, error -> { if (error != null) { // handle the error } }); }); ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)"> ```dart showLineNumbers final appInstanceId = await FirebaseAnalytics.instance.appInstanceId; if (appInstanceId != null) { try { await Adapty().setIntegrationIdentifier( key: "firebase_app_instance_id", value: appInstanceId, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } } ``` </TabItem> <TabItem value="unity" label="Unity (C#)"> ```csharp showLineNumbers using AdaptySDK; using Firebase.Analytics; FirebaseAnalytics .GetAnalyticsInstanceIdAsync() .ContinueWithOnMainThread((task) => { if (!task.IsCompletedSuccessfully) { // handle the error return; } Adapty.SetIntegrationIdentifier( "firebase_app_instance_id", task.Result, (error) => { // handle the error } ); }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)"> ```typescript showLineNumbers try { const appInstanceId = await analytics().getAppInstanceId(); if (appInstanceId) { await adapty.setIntegrationIdentifier("firebase_app_instance_id", appInstanceId); } } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> ### Проверка интеграции \{#verify-the-integration\} Самый быстрый способ убедиться, что события поступают — Firebase DebugView: 1. На тестовом устройстве запустите приложение с [включённым режимом отладки Firebase](https://firebase.google.com/docs/analytics/debugview#enable_debug_mode). 2. Совершите покупку в песочнице или вызовите любое событие, которое вы включили в дашборде Adapty. 3. Откройте Firebase Console > **Analytics** > **DebugView**. События появляются в течение нескольких секунд вместе со всеми параметрами. Стандартные отчёты — Realtime, Reports, аудитории — заполняются в течение нескольких минут до 24 часов в зависимости от типа отчёта. DebugView — единственное место, где можно подтвердить это в реальном времени. ## Используйте данные о подписках в отчётах и аудиториях \{#use-subscription-data-in-reports-and-audiences\} Включите **Send user properties** в дашборде Adapty ([Настройте Adapty](#configure-adapty), шаг 4). Без этого Adapty не будет передавать `subscription_state` и `subscription_product_id` — и всё описанное в этом разделе работать не будет. По умолчанию Firebase и Google Analytics не раскрывают пользовательские свойства. Зарегистрируйте каждое из них как пользовательское измерение — тогда `subscription_state` и `subscription_product_id` станут доступны в отчётах, Explorations и аудиториях. Настройте измерения в Google Analytics Admin. После этого их можно запрашивать как в Firebase, так и в Google Analytics — они используют общий бэкенд. Это удобно для создания аудиторий Google Ads из платящих пользователей или для обучения прогностических моделей. После завершения настройки Adapty будет заполнять эти свойства для новых событий. Существующие события обновлены не будут. 1. В Google Analytics откройте **Admin** > **Custom definitions**. 2. Нажмите **Create custom dimensions**. 3. Для каждого свойства задайте: - **Dimension name**: любое понятное название, например «Subscription state». - **Scope**: **User**. - **User property**: `subscription_state` или `subscription_product_id`. Название должно совпадать точь-в-точь — Google Analytics чувствителен к регистру. ## Устранение неполадок \{#troubleshooting\} ### События не появляются в Firebase \{#events-dont-appear-in-firebase\} - Убедитесь, что Firebase App Instance ID задан **до** первой покупки. События без Firebase ID не попадают в Firebase и возвращают ошибку. - Убедитесь, что свойство Google Analytics, привязанное в Firebase Console, соответствует потоку данных. - Убедитесь, что набор учётных данных (ID + секрет) в Adapty соответствует платформе. ### `access_level_updated` отображается как сбой в Event Feed `access_level_updated` — это **событие только для вебхуков**. Adapty никогда не пытается отправить его в Firebase — но Event Feed всё равно отображает его как неудачную доставку. Игнорируйте эту строку. Ваша интеграция работает корректно. Чтобы использовать это событие, настройте [интеграцию с вебхуком](webhook). ### События из песочницы загрязняют продакшен-данные \{#sandbox-events-pollute-production-data\} Adapty пересылает транзакции из песочницы и продакшена в один и тот же проект Firebase. См. раздел [Настройка Firebase](#configure-firebase) — использование отдельного проекта Firebase для сборок разработки полностью решает эту проблему. ### Firebase недосчитывает доходы в приложениях на StoreKit 2 \{#firebase-undercounts-revenue-for-storekit-2-apps\} Firebase автоматически логирует событие `in_app_purchase` для каждой покупки через StoreKit 1 — без какого-либо кода. StoreKit 2 использует другой API. Firebase просто не видит эти транзакции. Последствия: приложения, активно использующие SK2 и не имеющие отдельного пайплайна для учёта доходов, занижают показатели вдвое и более — в Firebase, в Google Analytics и во всех последующих кампаниях Google Ads. Оптимизация ставок работает с неверными данными. Отчёты о доходах показывают лишь половину картины. Исправление: передайте `firebase_app_instance_id` в Adapty (см. [Настройте код приложения](#configure-your-app-code)). Adapty передаёт каждую покупку через Measurement Protocol — с информацией о доходе, валюте и продукте. ### Расхождение данных между Adapty Analytics и Firebase \{#adapty-analytics-and-firebase-numbers-diverge\} - **StoreKit 2**: Самая частая причина. См. [Firebase занижает доходы для приложений на StoreKit 2](#firebase-undercounts-revenue-for-storekit-2-apps). - **Охват SDK**: Firebase считает только события от пользователей, чьё приложение передаёт Firebase App Instance ID. Старые версии приложения этого не делают. Adapty учитывает таких пользователей, Firebase — нет. - **События песочницы**: Adapty также пересылает транзакции песочницы в Firebase. Чтобы не смешивать данные, используйте отдельный проект Firebase для dev-сборок. - **Семплирование**: Google Analytics Explorations применяет семплирование к большим наборам данных. Для точных цифр без семплирования проверяйте данные в режиме реального времени или в стандартных отчётах. ### Пользовательские названия событий отклоняются Google Analytics \{#custom-event-names-are-rejected-by-google-analytics\} Google Analytics ограничивает названия событий 40 символами: допустимы только буквы, цифры и символы подчёркивания, причём название должно начинаться с буквы. Переименуйте пользовательские события Adapty в дашборде, которые не соответствуют этим требованиям. --- # File: mixpanel --- --- title: "Mixpanel" description: "Подключите Mixpanel к Adapty для глубокой аналитики подписок." --- [Mixpanel](https://mixpanel.com/home/) — мощный сервис продуктовой аналитики. Его система отслеживания на основе событий помогает продуктовым командам получать ценные данные о лучших стратегиях привлечения, конверсии и удержания пользователей на разных платформах. Эта интеграция позволяет передавать все события Adapty в Mixpanel. В результате вы получите более полное представление о вашем подписочном бизнесе и действиях пользователей. Adapty предоставляет полный набор данных, позволяющий отслеживать [события подписки](events) из сторов в одном месте. С Adapty вы легко увидите, как ведут себя ваши подписчики, поймёте их предпочтения и сможете использовать эту информацию для целевых и эффективных коммуникаций с ними. ## Как настроить интеграцию с Mixpanel \{#how-to-set-up-mixpanel-integration\} 1. Откройте страницу [Integrations -> Mixpanel](https://app.adapty.io/integrations/mixpanel) в дашборде Adapty. 2. Включите переключатель и введите **Mixpanel Token**. Вы можете указать токен для всех платформ или ограничить его конкретными платформами, если хотите получать данные только от определённых. 3. Задайте **Mixpanel Data Residency** в соответствии с вашим проектом Mixpanel. Это поле обязательно и по умолчанию установлено в **US**. Выберите **US** для эндпоинта `api.mixpanel.com` или **Europe** для `api-eu.mixpanel.com`. :::warning Если ваш проект Mixpanel использует хранение данных в ЕС, необходимо установить **Mixpanel Data Residency** в значение **Europe**. Mixpanel отклоняет события, отправленные на американский эндпоинт из проектов ЕС. ::: <img src="/assets/shared/img/mixpanel.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Получение токена Mixpanel \{#finding-your-mixpanel-token\} Чтобы получить **Mixpanel Token**: 1. Войдите в ваш [Mixpanel Dashboard](https://mixpanel.com/settings/project/). 2. Откройте **Settings** и выберите **Organization Settings**. <img src="/assets/shared/img/mixpanel-settings.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. На левой боковой панели перейдите в **Projects** и выберите ваш проект. <img src="/assets/shared/img/mixpanel-project-id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Как работает интеграция \{#how-the-integration-works\} Adapty автоматически сопоставляет нужные свойства событий — например, ID пользователя и выручку — с [нативными свойствами Mixpanel](https://docs.mixpanel.com/docs/data-structure/user-profiles). Это обеспечивает точное отслеживание и отчётность по событиям, связанным с подписками. Кроме того, Adapty накапливает данные о выручке по каждому пользователю и обновляет их [свойства профиля](https://docs.mixpanel.com/docs/data-structure/user-profiles), включая `subscription state` и `subscription product ID`. После получения события Mixpanel обновляет соответствующие поля в реальном времени. ## События и теги \{#events-and-tags\} Ниже учётных данных находятся три группы событий, которые можно отправлять в Mixpanel из Adapty. Просто включите нужные. Полный список событий, доступных в Adapty, смотрите [здесь](events). <img src="/assets/shared/img/mixpanel-events.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Мы рекомендуем использовать названия событий по умолчанию, которые предоставляет Adapty. Однако вы можете изменить их по своему усмотрению. ## Настройка SDK \{#sdk-configuration\} Используйте метод `.setIntegrationIdentifier()`, чтобы задать `mixpanelUserId`. Если значение не задано, Adapty использует ваш пользовательский ID (`customerUserId`) или, если он равен null, — Adapty ID. Убедитесь, что ID пользователя, который вы используете для отправки данных в Mixpanel из приложения, совпадает с тем, что вы отправляете в Adapty. :::note Сторонние SDK генерируют пользовательские ID асинхронно. ID может быть ещё не готов в момент вызова `Adapty.activate()`. Если ваш **Customer User ID** приходит из одного из таких SDK, вызывайте `Adapty.activate()` без него. Как только ID будет получен, вызовите `setIntegrationIdentifier()`, а затем `identify()` с CUID. ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "mixpanel_user_id", value: Mixpanel.mainInstance().distinctId ) } catch { // handle the error } ``` </TabItem> <TabItem value="swift-callback" label="iOS (Swift-Callback)" default> ```swift showLineNumbers let builder = AdaptyProfileParameters.Builder() .with(mixpanelUserId: Mixpanel.mainInstance().distinctId) Adapty.updateProfile(params: builder.build()) ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.setIntegrationIdentifier("mixpanel_user_id", mixpanelAPI.distinctId) { error -> if (error != null) { // handle the error } } ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers final mixpanel = await Mixpanel.init("Your Token", trackAutomaticEvents: true); final distinctId = await mixpanel.getDistinctId(); try { await Adapty().setIntegrationIdentifier( key: "mixpanel_user_id", value: distinctId, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers using AdaptySDK; var distinctId = Mixpanel.DistinctId; if (distinctId != null) { Adapty.SetIntegrationIdentifier( "mixpanel_user_id", distinctId, (error) => { // handle the error }); } ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers // Если в вашем приложении уже есть общий экземпляр Mixpanel, используйте его. const trackAutomaticEvents = true; const mixpanel = new Mixpanel('YOUR_PROJECT_TOKEN', trackAutomaticEvents); await mixpanel.init(); // Это текущий distinct_id Mixpanel (генерируется автоматически или задаётся через mixpanel.identify(...)) const mixpanelUserId = await mixpanel.getDistinctId(); try { await adapty.setIntegrationIdentifier("mixpanel_user_id", mixpanelUserId); } catch (error) { // обработайте `AdaptyError` } ``` </TabItem> </Tabs> ## Структура события Mixpanel \{#mixpanel-event-structure\} Adapty отправляет события в Mixpanel с помощью метода `track`. Свойства события имеют следующую структуру: ```json { "event": "subscription_renewed", "properties": { "ip": 0, "time": 1709294400, "$insert_id": "123e4567-e89b-12d3-a456-426614174000", "vendor_product_id": "yearly.premium.6999", "original_transaction_id": "GPA.3383...", "currency": "USD", "environment": "Production", "store": "app_store", "purchase_date": "2024-03-01T12:00:00.000000+0000" } } ``` Где: | Параметр | Тип | Описание | |:-------------------------------------|:--------|:-----------------------------------------------------------| | `event` | String | Название события (сопоставлено с событием Adapty). | | `properties` | Object | Свойства события. | | `properties.ip` | Integer | IP-адрес (отправляется как 0 при server-to-server). | | `properties.time` | Long | UNIX-временная метка события в секундах. | | `properties.$insert_id` | String | Уникальный идентификатор события (UUID) для дедупликации. | | `properties.vendor_product_id` | String | ID продукта в сторе. | | `properties.original_transaction_id` | String | Идентификатор исходной транзакции. | | `properties.currency` | String | Код валюты. | | `properties.store` | String | Название стора (например, "app_store"). | | `properties.environment` | String | Среда ("Sandbox" или "Production"). | ### Обновления профиля пользователя \{#user-profile-updates\} Adapty также обновляет профиль пользователя в Mixpanel с помощью `people_set`, используя следующие свойства: | Параметр | Тип | Описание | |:--------------------------|:-------|:----------------------------------------------------------------------| | `subscription_state` | String | Текущий статус подписки (например, "subscribed"). | | `subscription_product_id` | String | ID активного продукта с подпиской. | --- # File: posthog --- --- title: "PostHog" description: "" --- PostHog — это аналитическая платформа с инструментами для отслеживания поведения пользователей, визуализации использования продукта и анализа удержания. Благодаря отслеживанию событий, анализу пользовательских потоков и флагам функций платформа помогает лучше понимать продукт и совершенствовать его. Интеграция PostHog с Adapty позволяет отслеживать события, связанные с подписками: начало триалов, продления и отмены. Отправляя эти события в PostHog, вы можете анализировать, как изменения в подписках влияют на поведение пользователей, оценивать эффективность пейволов и глубже понимать свои стратегии монетизации — всё в рамках привычного аналитического процесса. ## Характеристики интеграции \{#integration-characteristics\} | Характеристика интеграции | Описание | | ------------------------- | ------------------------------------------------------------ | | Расписание | В реальном времени; события могут появляться на дашборде PostHog не сразу. | | Направление данных | События Adapty отправляются с сервера Adapty на сервер PostHog. | | Точка интеграции Adapty | <ul><li>SDK PostHog и Adapty в коде мобильного приложения</li><li>Сервер Adapty</li></ul> | ## Структура события PostHog \{#posthog-event-structure\} Adapty отправляет выбранные события в PostHog в соответствии с настройками в разделе **Events names** на [странице интеграции с PostHog](https://app.adapty.io/integrations/posthog). Каждое событие имеет следующую структуру: ```json showLineNumbers { "distinct_id": "john.doe@example.com", "timestamp": "2025-01-08T11:06:12+00:00", "event": "subscription_started", "properties": { "$set": { "email": "user@example.com", "first_name": "John", "last_name": "Doe", "birthday": "1990-01-01", "gender": "male", "os": "iOS" }, "timezone": "America/New_York", "ip_address": "10.168.1.1", "*": "{{other_event_properties}}" } } ``` Где | **Параметр** | **Тип** | **Описание** | | --------------- | -------------------- | ------------------------------------------------------------ | | **distinct_id** | String | Уникальный идентификатор пользователя (например, `profile.posthog_distinct_user_id`, `customer_user_id` или `profile_id`). | | **timestamp** | ISO 8601 date & time | Дата и время события. | | **event** | String | Название события, которое вы задали в разделе Events names в [настройках PostHog](https://app.adapty.io/integrations/posthog). | | **properties** | Object | Содержит [properties.$set](posthog#propertiesset-parameters) и все [свойства, специфичные для события](messaging#event-properties). Каждое свойство необязательно и не будет отправлено в PostHog, если отсутствует. | ### Параметры properties.$set Каждый параметр объекта `properties.$set` является необязательным и не будет отправлен в PostHog, если отсутствует. | **Параметр** | **Тип** | **Описание** | | --------------- | -------------------- | ------------------------------------------------------------ | | **email** | String | Адрес электронной почты пользователя. | | **first_name** | String | Имя пользователя. | | **last_name** | String | Фамилия пользователя. | | **birthday** | String (Date) | Дата рождения пользователя. | | **gender** | String | Пол пользователя. | | **os** | String | Операционная система устройства пользователя. | ## Настройка интеграции с PostHog \{#setting-up-posthog-integration\} 1. Откройте страницу [Integrations -> PostHog](https://app.adapty.io/integrations/posthog) в дашборде Adapty и включите переключатель. <img src="/assets/shared/img/posthog-on.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Войдите в [дашборд PostHog](https://posthog.com/). 3. Перейдите в **Settings -> Project**. 4. В окне **Project** прокрутите вниз до раздела **Project ID** и скопируйте **Project API key**. 5. Вставьте API-ключ в поле **Project API key** в дашборде Adapty. У PostHog нет специального режима песочницы для серверной интеграции. 6. Выберите **PostHog Deployment**: | Опция | Описание | | ------ | ------------------------------------------------------------ | | us/eu | Стандартные развёртывания PostHog. | | Custom | Для self-hosted инстансов. Введите URL вашего инстанса в поле **PostHog Instance URL**. | 7. (опционально) Если вы используете self-hosted развёртывание PostHog, введите адрес вашего развёртывания в поле **PostHog Instance URL**. 8. (опционально) Настройте параметры **Reporting Proceeds**, **Exclude Historical Events**, **Report User's Currency** и **Send Trial Price**. Подробнее об этих опциях — в разделе [Настройки интеграции](configuration#integration-settings). 9. (опционально) В разделе **Events names** можно настроить, какие события отправляются в PostHog. Отключите ненужные события или переименуйте их по необходимости. 10. Нажмите **Save**, чтобы завершить настройку. ## Настройка SDK \{#sdk-configuration\} Чтобы получать данные атрибуции от PostHog, передайте значение `distinctId` в Adapty, как показано ниже: :::note Сторонние SDK генерируют пользовательские ID асинхронно. ID может быть ещё не готов в момент вызова `Adapty.activate()`. Если ваш **Customer User ID** приходит из одного из таких SDK, вызывайте `Adapty.activate()` без него. Как только ID будет получен, вызовите `setIntegrationIdentifier()`, а затем `identify()` с CUID. ::: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="Swift" default> ```swift showLineNumbers do { let distinctId = PostHogSDK.shared.getDistinctId() try await Adapty.setIntegrationIdentifier( key: "posthog_distinct_user_id", value: distinctId ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Kotlin" default> ```kotlin showLineNumbers Adapty.setIntegrationIdentifier("posthog_distinct_user_id", PostHog.distinctId()) { error -> if (error != null) { // handle the error } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers Adapty.setIntegrationIdentifier("posthog_distinct_user_id", PostHog.distinctId(), error -> { if (error != null) { // handle the error } }); ``` </TabItem> <TabItem value="flutter" label="Flutter" default> ```javascript showLineNumbers try { final distinctId = await Posthog().getDistinctId(); await Adapty().setIntegrationIdentifier( key: "posthog_distinct_user_id", value: distinctId, ); } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity" default> Официального SDK PostHog для Unity не существует. </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers // ... const posthog = usePostHog(); // ... try { await adapty.setIntegrationIdentifier("posthog_distinct_user_id", posthog.getDistinctId()); } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> Теперь Adapty будет отправлять события в PostHog и получать от него данные атрибуции. --- # File: splitmetrics --- --- title: "SplitMetrics Acquire" description: "Используйте SplitMetrics с Adapty для A/B-тестирования и оптимизации подписок." --- Интеграция с [SplitMetrics Acquire](https://splitmetrics.com/acquire/) позволяет точно отслеживать доход от подписок, полученный через Apple Search Ads, и видеть, сколько денег приносит реклама на протяжении нескольких месяцев. Кроме того, Adapty отправляет [события подписок](events) в SplitMetrics Acquire, чтобы вы могли строить кастомные дашборды и автоматизацию на основе атрибуции Apple Search Ads. Данные об атрибуции в Adapty при этом не добавляются — всё необходимое мы уже получаем напрямую из ASA. ## Как настроить интеграцию с SplitMetrics Acquire \{#how-to-set-up-splitmetrics-acquire-integration\} Чтобы подключить SplitMetrics Acquire, перейдите в [Integrations > SplitMetrics Acquire](https://app.adapty.io/integrations/splitmetrics) и введите учётные данные. <img src="/assets/shared/img/8255349-CleanShot_2023-08-14_at_17.39.422x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Откройте аккаунт SplitMetrics Acquire, наведите курсор на логотип одного из MMP и нажмите кнопку **Settings**. В открывшемся диалоге найдите Client ID в пункте **5**, скопируйте его и вставьте в поле **Client ID** в Adapty. <img src="/assets/shared/img/4d0b2b6-Adapty.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <img src="/assets/shared/img/4f8d0b8-AdaptyGuide.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Также потребуется указать Apple App ID. Чтобы найти его, откройте страницу приложения в App Store Connect, перейдите на страницу **App Information** в разделе **General** и найдите **Apple ID** в левом нижнем углу экрана. <img src="/assets/shared/img/61578ee-CleanShot_2022-04-20_at_17.55.03.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## События и теги \{#events-and-tags\} Ниже блока с учётными данными находятся три группы событий, которые можно отправлять из Adapty в SplitMetrics Acquire. Просто включите нужные. Полный список событий Adapty можно найти [здесь](events). <img src="/assets/shared/img/1b0c777-CleanShot_2023-08-11_at_14.56.362x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Рекомендуем оставить названия событий по умолчанию, предложенные Adapty. При необходимости их можно изменить. Adapty отправляет события подписок в SplitMetrics Acquire через серверную интеграцию (server-to-server), что позволяет просматривать все события подписок в дашборде SplitMetrics. ## Настройка SDK \{#sdk-configuration\} На стороне SDK ничего настраивать не нужно, однако мы рекомендуем передавать `customerUserId` в Adapty для повышения точности данных. :::warning Убедитесь, что вы настроили [Apple Search Ads](apple-search-ads) в Adapty и [загрузили учётные данные](https://app.adapty.io/settings/apple-search-ads) — без этого SplitMetrics Acquire работать не будет. ::: ## Устранение неполадок \{#troubleshooting\} Если интеграция с SplitMetrics Acquire не работает, хотя настройки верны: - Убедитесь, что переключатель **Receive Apple Search Ads attribution in Adapty** включён в разделе [App Settings -> Apple Search Ads tab](https://app.adapty.io/settings/apple-search-ads), что [Apple Search Ads](apple-search-ads) настроены в Adapty и [учётные данные загружены](https://app.adapty.io/settings/apple-search-ads) — без этого SplitMetrics работать не будет. - Убедитесь, что у профилей есть неорганическая атрибуция ASA. События в Adapty передаются только для профилей с детальной, неорганической атрибуцией ASA. ## Структура событий SplitMetrics Acquire \{#splitmetrics-acquire-event-structure\} Adapty отправляет события в SplitMetrics Acquire через GET-запрос с параметрами запроса. Каждое событие имеет следующую структуру: ```json { "source": "Apple Search Ads", "app_id": "123456789", "name": "subscription_renewed", "type": "subscription_renewed", "revenue": 9.99, "currency": "USD", "tap_time": "2024-03-01 12:00:00", "open_time": "2024-03-01 12:05:00", "event_time": "2024-03-02 12:00:00", "adaccount_id": "123456", "campaign_id": "123456789", "adgroup_id": "123456789", "keyword_id": "123456789", "creative_set_id": "123456789", "Ad_id": "123456789", "country_or_region": "US", "conversion_type": "Download", "user_id": "user_12345", "att_status": "3", "device_type": "iphone", "app_version": "1.2.3", "sdk_version": "2.10.0", "ios_version": "17.2", "event_value": "{\"vendor_product_id\":\"yearly.premium.6999\",\"original_transaction_id\":\"GPA.3383...\"}", "event_id": "123e4567-e89b-12d3-a456-426614174000" } ``` Где: | Параметр | Тип | Описание | |:--------------------|:-------|:----------------------------------------------------------------------------------------------------------------------------------| | `source` | String | Всегда "Apple Search Ads". | | `app_id` | String | Apple App ID. | | `name` | String | Название события (сопоставляется с событием Adapty). | | `type` | String | Тип события (совпадает с `name`). | | `revenue` | Float | Сумма дохода. | | `currency` | String | Код валюты. | | `tap_time` | String | Дата и время нажатия на рекламное объявление. | | `open_time` | String | Дата и время открытия приложения (установки). | | `event_time` | String | Дата и время события. | | `adaccount_id` | String | ID организации ASA. | | `campaign_id` | String | ID кампании ASA. | | `adgroup_id` | String | ID группы объявлений ASA. | | `keyword_id` | String | ID ключевого слова ASA. | | `creative_set_id` | String | ID креативного набора ASA. | | `Ad_id` | String | ID объявления ASA. | | `country_or_region` | String | Страна или регион стора. | | `conversion_type` | String | Тип конверсии (например, "Download"). | | `user_id` | String | Customer User ID или Adapty Profile ID. | | `att_status` | String | Статус разрешения отслеживания (0–3). | | `device_type` | String | Тип устройства (например, "iphone", "ipad"). | | `app_version` | String | Версия приложения. | | `sdk_version` | String | Версия Adapty SDK. | | `ios_version` | String | Версия iOS. | | `event_value` | String | JSON-строка со всеми доступными [деталями события](webhook-event-types-and-fields#for-most-event-types). | | `event_id` | String | Уникальный ID события (UUID). | --- # File: braze --- --- title: "Braze" description: "Интегрируйте Braze с Adapty для эффективного взаимодействия с клиентами и push-уведомлений." --- [Braze](https://www.braze.com/) — одно из ведущих решений для работы с клиентами: широкий набор инструментов для push-уведомлений, email, SMS и in-app сообщений. Интегрировав Adapty с Braze, вы получите все события подписки в одном месте и сможете настраивать автоматические коммуникации на их основе. Adapty предоставляет полный набор данных для отслеживания [событий подписки](events) из всех сторов в одном месте и может использоваться для обновления профилей пользователей в Braze. С Adapty вы легко увидите, как ведут себя ваши подписчики, поймёте их предпочтения и используете эту информацию для точечных и эффективных коммуникаций. Интеграция позволяет отслеживать события подписки в дашборде Braze и связывать их с [рекламными кампаниями](https://www.braze.com/product/journey-orchestration). Adapty передаёт события подписки, атрибуты пользователей и покупки в Braze, чтобы вы могли выстраивать целевые коммуникации через push-уведомления после простой и быстрой настройки, описанной ниже. ## Как настроить интеграцию с Braze \{#how-to-set-up-braze-integration\} Чтобы подключить Braze, перейдите в [Integrations -> Braze](https://app.adapty.io/integrations/braze), включите переключатель и заполните поля. Первый шаг — предоставить необходимые учётные данные для установки соединения между Braze и Adapty. Для работы интеграции потребуются **REST API Key**, **Braze Instance ID**, а также **App IDs** для iOS и Android: <img src="/assets/shared/img/5f1e62c-adapty_braze.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. **REST API Key** создаётся в **Braze Dashboard** → **Settings** → **API Keys**. При создании убедитесь, что ключу назначено разрешение `users.track`: <img src="/assets/shared/img/b5fdf16-adapty_braze_create_api_key.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <img src="/assets/shared/img/1e5b4b8-adapty_braze_api_key_users_track.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Чтобы получить **Braze Instance ID**, посмотрите на URL вашего Braze Dashboard и найдите нужный идентификатор в разделе [документации Braze](https://www.braze.com/docs/api/basics/#endpoints). Он имеет региональный формат, например US-03, EU-01 и т.д. 3. iOS и Android App IDs также находятся в Braze Dashboard → **Settings** → **API Keys**. Скопируйте их отсюда: <img src="/assets/shared/img/1e6d21b-adapty_braze_app_ids.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## События, атрибуты пользователей и покупки \{#events-user-attributes-and-purchases\} Ниже блока с учётными данными находятся три группы событий, которые можно отправлять из Adapty в Braze. Просто включите нужные. При необходимости вы можете переименовать события перед отправкой в Braze. Полный список событий Adapty доступен [здесь](events): <img src="/assets/shared/img/702e628-adapty_braze_events_names.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Adapty отправляет события подписки и атрибуты пользователей в Braze через серверную интеграцию, что позволяет просматривать их в дашборде Braze и настраивать кампании на их основе. Для событий с выручкой, таких как конверсии пробного периода и продления, Adapty передаёт эту информацию в Braze как покупки. [Здесь](messaging#event-properties) вы найдёте полные спецификации свойств событий, отправляемых в Braze. :::note Полезные атрибуты пользователей По умолчанию Adapty отправляет ряд атрибутов пользователей для интеграции с Braze. Ознакомьтесь со списком ниже, чтобы выбрать подходящие для ваших задач. ::: | Атрибут пользователя | Тип | Значение | |--------------|----|-----| | `adapty_customer_user_id` | String | Содержит уникальный идентификатор пользователя, заданный клиентом. Доступен как в [дашборде](profiles-crm) Adapty, так и в Braze. | | `adapty_profile_id` | String | Содержит уникальный идентификатор профиля пользователя Adapty, который можно найти в [дашборде](profiles-crm) Adapty. | | `environment` | String | <p>Указывает, работает ли пользователь в среде песочницы или в продакшене.</p><p></p><p>Возможные значения: `Sandbox` или `Production`</p> | | `store` | String | <p>Содержит название стора, через который была совершена покупка.</p><p></p><p>Возможные значения:</p><p>`app_store` или `play_store`.</p> | | `vendor_product_id` | String | <p>Содержит идентификатор продукта в Apple/Google стор.</p><p></p><p>Например: org.locals.12345</p> | | `subscription_expires_at` | String | <p>Содержит дату истечения последней подписки.</p><p></p><p>Формат значения:</p><p>YYYY-MM-DDTHH:mm:ss.SSS+TZ</p><p>Например: 2023-02-15T17:22:03.000+0000</p> | | `active_subscription` | String | Принимает значение `true` при любом событии покупки или продления, или `false`, если подписка истекла. | | `period_type` | String | <p>Указывает последний тип периода для покупки или продления.</p><p></p><p>Возможные значения:</p><p>`trial` для пробного периода или `normal` для остальных случаев.</p> | Все значения типа float округляются до int. Строки передаются без изменений. Помимо предопределённого набора тегов, можно также отправлять [пользовательские атрибуты](segments#custom-attributes) с помощью тегов. Это даёт дополнительную гибкость в выборе типов данных и полезно для отслеживания специфической информации о продукте или сервисе. Все пользовательские атрибуты пользователей автоматически отправляются в Braze, если на [странице интеграции](https://app.adapty.io/integrations/braze) отмечен чекбокс **Send user attributes**. ## Настройка SDK \{#sdk-configuration\} Чтобы связать профили пользователей в Adapty и Braze, необходимо либо настроить Braze SDK с тем же идентификатором пользователя, что и в Adapty, либо использовать метод `.changeUser()`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers let braze = Braze(configuration: configuration) braze.changeUser(userId: "adapty_customer_user_id") ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Braze.getInstance(context).changeUser("adapty_customer_user_id") ``` </TabItem> </Tabs> --- # File: onesignal --- --- title: "OneSignal" description: "Интегрируйте OneSignal с Adapty для улучшения взаимодействия через push-уведомления." --- [OneSignal](https://onesignal.com/) — ведущая платформа для взаимодействия с клиентами, поддерживающая push-уведомления, email, SMS и in-app-сообщения. Интеграция Adapty с OneSignal позволяет собирать все события подписок в одном месте и автоматически запускать коммуникации на их основе. С помощью Adapty вы можете отслеживать [события подписки](events) в нескольких сторах, анализировать поведение пользователей и использовать эти данные для более целевых коммуникаций. Интеграция позволяет мониторить события подписки в дашборде OneSignal и связывать их с вашими [кампаниями привлечения](https://documentation.onesignal.com/docs/en/automated-messages). Adapty обновляет теги OneSignal на основе событий подписки, что позволяет отправлять персонализированные push-уведомления с минимальными настройками. **Характеристики интеграции** | Характеристика интеграции | Описание | | :------------------------- | :----------------------------------------------------------- | | Расписание | Обновления в реальном времени | | Направление данных | Одностороннее: из Adapty на сервер OneSignal | | Точка интеграции Adapty | <ul><li>SDK OneSignal и Adapty в коде мобильного приложения</li><li>Сервер Adapty</li></ul>| ## Настройка интеграции с One Signal \{#setting-up-one-signal-integration\} Чтобы настроить интеграцию: 1. Откройте [Integrations → OneSignal](https://app.adapty.io/integrations/onesignal) в дашборде Adapty. <img src="/assets/shared/img/onesignal-on.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Включите переключатель интеграции. 3. Введите ваш **OneSignal App ID**. Чтобы настроить интеграцию с OneSignal, перейдите в раздел [Integrations -> OneSignal](https://app.adapty.io/integrations/onesignal) дашборда Adapty, включите переключатель и укажите учётные данные для интеграции. ## Получение вашего OneSignal App ID \{#retrieving-your-onesignal-app-id\} Найдите ваш **OneSignal App ID** в [дашборде OneSignal](https://dashboard.onesignal.com/login): 1. Перейдите в **Settings** → **Keys & IDs**. <img src="/assets/shared/img/onesignal-dashboard.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Скопируйте ваш **OneSignal App ID** и вставьте его в поле **App ID** в дашборде Adapty. <img src="/assets/shared/img/onesignal-id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Подробнее о OneSignal ID можно узнать в [официальной документации.](https://documentation.onesignal.com/docs/en/keys-and-ids) ### Настройка событий \{#configuring-events\} Adapty позволяет отправлять в OneSignal три группы событий. Включите нужные в дашборде Adapty. Полный список доступных событий с подробным описанием можно найти [здесь](events). <img src="/assets/shared/img/onesignal.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Adapty отправляет события подписок в OneSignal через серверную интеграцию, что позволяет отслеживать всю активность, связанную с подписками, прямо в OneSignal. :::warning С 17 апреля 2023 года бесплатный план OneSignal больше не поддерживает эту интеграцию. Она доступна только на планах **Growth**, **Professional** и **выше**. Подробнее см. в [OneSignal Pricing](https://onesignal.com/pricing). ::: ## Пользовательские теги \{#custom-tags\} Интеграция обновляет и назначает различные свойства ваших пользователей Adapty в виде тегов, которые затем отправляются в OneSignal. Ознакомьтесь со списком тегов ниже, чтобы выбрать те, которые лучше всего соответствуют вашим потребностям. :::warning В OneSignal есть ограничение на количество тегов. Оно распространяется как на теги, созданные Adapty, так и на любые существующие теги в OneSignal. Превышение лимита может вызвать ошибки при отправке событий. ::: | Тег | Тип | Описание | |---|----|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `adapty_customer_user_id` | String | Уникальный идентификатор пользователя в вашем приложении. Должен совпадать в вашей системе, Adapty и OneSignal. | | `adapty_profile_id` | String | ID профиля пользователя в Adapty, доступен в [дашборде Adapty](profiles-crm). | | `environment` | String | `Sandbox` или `Production` — среда, в которой находится пользователь. | | `store` | String | Стор, в котором был куплен продукт. Варианты: **app_store**, **play_store**, **stripe** или название вашего [кастомного стора](custom-store). | | `vendor_product_id` | String | ID продукта в сторе (например, `org.locals.12345`). | | `subscription_expires_at` | String | Дата истечения последней подписки (`YYYY-MM-DDTHH:MM:SS+0000`, например `2023-02-10T17:22:03.000000+0000`). | | `last_event_type` | String | Последний тип события из [списка событий Adapty](events).<br/> Обратите внимание:<br/>- Для события **Subscription expired** Adapty передаёт свойство `last_event_type` как `subscription_cancelled`.<br/>- Для **Trial renew canceled** — как `auto_renew_off`<br/>- Для **Subscription renew canceled** — как `auto_renew_off_subscription` | | `purchase_date` | String | Дата последней транзакции (`YYYY-MM-DDTHH:MM:SS+0000`, например `2023-02-10T17:22:03.000000+0000`). | | `active_subscription` | String | `true`, если у пользователя есть активная подписка, и `false`, если подписка истекла. | | `period_type` | String | Указывает наиболее актуальный тип периода для покупки или продления. Возможные значения: `trial` — пробный период, `normal` — все остальные случаи. | Все значения с плавающей точкой округляются до целых чисел. Строки остаются без изменений. Помимо предустановленных тегов, вы можете отправлять [пользовательские атрибуты](segments#custom-attributes) в качестве тегов, что даёт больше гибкости в управлении передаваемыми данными. Это удобно для отслеживания специфических деталей, связанных с вашим продуктом или сервисом. Пользовательские атрибуты автоматически отправляются в OneSignal, если на [странице интеграции](https://app.adapty.io/integrations/onesignal) включён чекбокс **Send user attributes**. Если он выключен, Adapty отправляет ровно 10 тегов. Если включён — можно отправлять более 10 тегов, что позволяет собирать расширенные данные. ## Настройка SDK \{#sdk-configuration\} Есть два способа интегрировать OneSignal с Adapty: 1. **Устаревший (до v5):** Использует `playerId` (устарело в [OneSignal SDK v5](https://github.com/OneSignal/OneSignal-iOS-SDK/releases/tag/5.0.0)). 2. **Актуальный (v5+):** Использует `subscriptionId`. :::warning Обязательно передавайте `playerId` (для OneSignal SDK до v5) или `subscriptionId` (для OneSignal SDK v5+) в Adapty. Без этого теги OneSignal не будут обновляться, и интеграция не будет работать корректно. ::: <Tabs groupId="current-version" queryString> <TabItem value="v5+" label="OneSignal SDK v5+ (актуальный)" default> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers // SubscriptionID OneSignal.Notifications.requestPermission({ accepted in Task { // Adapty SDK 4.x try await Adapty.setIntegrationIdentifier(.oneSignalSubscriptionId(OneSignal.User.pushSubscription.id)) // Adapty SDK 3.x try await Adapty.setIntegrationIdentifier( key: "one_signal_subscription_id", value: OneSignal.User.pushSubscription.id ) } }, fallbackToSettings: true) ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers // SubscriptionID val oneSignalSubscriptionObserver = object: IPushSubscriptionObserver { override fun onPushSubscriptionChange(state: PushSubscriptionChangedState) { Adapty.setIntegrationIdentifier("one_signal_subscription_id", state.current.id) { error -> if (error != null) { // handle the error } } } } ``` </TabItem> <TabItem value="java" label="(Android) Java" default> ```java showLineNumbers // SubscriptionID IPushSubscriptionObserver oneSignalSubscriptionObserver = state -> { Adapty.setIntegrationIdentifier("one_signal_subscription_id", state.getCurrent().getId(), error -> { if (error != null) { // handle the error } }); }; ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers // 1. Since OneSignal.User.pushSubscription.id may return null if called too early, // OneSignal suggests to listen for the updates: OneSignal.User.pushSubscription.addObserver((state) { if (state.current.optedIn) { // now you can try to retrieve subscriptionId } }); // 2. Then you can push subscriptionId to Adapty: final subscriptionId = OneSignal.User.pushSubscription.id; if (subscriptionId != null) { await Adapty().setIntegrationIdentifier(key: "one_signal_subscription_id", value: subscriptionId); } ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers using AdaptySDK; using OneSignalSDK; var pushUserId = OneSignal.Default.PushSubscriptionState.userId; Adapty.SetIntegrationIdentifier( "one_signal_player_id", pushUserId, (error) => { // handle the error }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers OneSignal.User.pushSubscription.addEventListener('change', (subscription) => { const subscriptionId = subscription.current.id; if (subscriptionId) { adapty.setIntegrationIdentifier("one_signal_subscription_id", subscriptionId); } }); ``` </TabItem> </Tabs> </TabItem> <TabItem value="pre-v5" label="OneSignal SDK v. до 4.x (legacy)" default> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers // PlayerID // in your OSSubscriptionObserver implementation func onOSSubscriptionChanged(_ stateChanges: OSSubscriptionStateChanges) { if let playerId = stateChanges.to.userId { Task { // Adapty SDK 4.x try await Adapty.setIntegrationIdentifier(.oneSignalPlayerId(playerId)) // Adapty SDK 3.x try await Adapty.setIntegrationIdentifier( key: "one_signal_player_id", value: playerId ) } } } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers // PlayerID val osSubscriptionObserver = OSSubscriptionObserver { stateChanges -> stateChanges?.to?.userId?.let { playerId -> Adapty.setIntegrationIdentifier("one_signal_player_id", playerId) { error -> if (error != null) { // handle the error } } } } ``` </TabItem> <TabItem value="java" label="Java" default> ```java showLineNumbers // PlayerID OSSubscriptionObserver osSubscriptionObserver = stateChanges -> { OSSubscriptionState to = stateChanges != null ? stateChanges.getTo() : null; String playerId = to != null ? to.getUserId() : null; if (playerId != null) { Adapty.setIntegrationIdentifier("one_signal_player_id", playerId, error -> { if (error != null) { // handle the error } }); } }; ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers // PlayerID (pre-v5 OneSignal SDK) // in your OSSubscriptionObserver implementation func onOSSubscriptionChanged(_ stateChanges: OSSubscriptionStateChanges) { if let playerId = stateChanges.to.userId { Task { try await Adapty.setIntegrationIdentifier( key: "one_signal_player_id", value: playerId ) } } } ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers OneSignal.addSubscriptionObserver(event => { const playerId = event.to.userId; adapty.setIntegrationIdentifier("one_signal_player_id", playerId); }); ``` </TabItem> </Tabs> </TabItem> </Tabs> Подробнее читайте в документации OneSignal: - [Push subscription ID](https://documentation.onesignal.com/docs/en/mobile-sdk-reference#user-pushsubscription-id) - [Push subscription changes](https://documentation.onesignal.com/docs/en/mobile-sdk-reference#addobserver-push-subscription-changes) ## Работа с несколькими устройствами \{#dealing-with-multiple-devices\} Если пользователь использует несколько устройств, отслеживать события покупок и подписки может быть непросто. OneSignal предлагает способ решить эту проблему через [внешние идентификаторы пользователей](https://documentation.onesignal.com/docs/en/users). Чтобы данные пользователя оставались согласованными на всех устройствах: 1. Сопоставьте разные устройства на **стороне сервера** и передайте эти данные в OneSignal. 2. Используйте [customer_user_id](identifying-users) Adapty в качестве [externalUserId](https://documentation.onesignal.com/docs/en/users#external-id) в OneSignal. Если в вашем приложении нет системы регистрации, используйте другой уникальный идентификатор, который остаётся неизменным на всех устройствах пользователя. Важно поддерживать согласованность идентификатора пользователя на всех устройствах и обновлять OneSignal при каждом изменении ID пользователя. Это упрощает отслеживание активности и подписок, обеспечивает согласованность уведомлений, а также повышает точность аналитики и улучшает пользовательский опыт. Подробнее см. в [документации OneSignal по внешним идентификаторам пользователей](https://documentation.onesignal.com/docs/en/users). --- # File: pushwoosh --- --- title: "Pushwoosh" description: "Интегрируйте Pushwoosh с Adapty для удобного отслеживания push-уведомлений." --- Adapty использует события подписки для обновления тегов профиля в [Pushwoosh](https://www.pushwoosh.com/), что позволяет выстраивать целевую коммуникацию с пользователями через push-уведомления — после простой и быстрой настройки интеграции, описанной ниже. ## Как настроить интеграцию с Pushwoosh \{#how-to-set-up-pushwoosh-integration\} Чтобы подключить Pushwoosh, перейдите в [**Integrations** -> **Pushwoosh**](https://app.adapty.io/integrations/pushwoosh), включите переключатель и заполните поля. Для начала укажите учётные данные, необходимые для соединения между вашими профилями Pushwoosh и Adapty. Потребуются App ID и Auth token приложения Pushwoosh. <img src="/assets/shared/img/64e48a1-CleanShot_2023-08-18_at_11.13.212x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 1. **App ID** можно найти в дашборде Pushwoosh. <img src="/assets/shared/img/ee27687-CleanShot_2023-08-18_at_14.37.442x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. **Auth token** можно найти в разделе API Access в настройках Pushwoosh. <img src="/assets/shared/img/50e634b-CleanShot_2023-08-18_at_14.35.022x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## События и теги \{#events-and-tags\} Ниже учётных данных расположены три группы событий, которые можно отправлять из Adapty в Pushwoosh. Просто включите нужные. При необходимости вы можете переименовать события перед отправкой в Pushwoosh. Полный список событий Adapty доступен [здесь](events). <img src="/assets/shared/img/392dc31-screencapture-app-adapty-io-integrations-pushwoosh-2023-08-22-13_31_07.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Adapty будет отправлять события подписки в Pushwoosh через серверную интеграцию, что позволит просматривать все события подписки в дашборде Pushwoosh. :::note Пользовательские теги В рамках интеграции с Pushwoosh вы также можете использовать собственные теги. Ознакомьтесь со списком тегов ниже, чтобы выбрать подходящий для ваших задач. ::: | Тег | Тип | Значение | |---|----|-----| | `adapty_customer_user_id` | String | Содержит уникальный идентификатор пользователя, который можно найти на стороне Pushwoosh. | | `adapty_profile_id` | String | Содержит уникальный идентификатор профиля пользователя Adapty, который можно найти в вашем [дашборде](profiles-crm) Adapty. | | `environment` | String | <p>Указывает, в какой среде работает пользователь — песочнице или продакшене.</p><p></p><p>Возможные значения: `Sandbox` или `Production`.</p> | | `store` | String | <p>Содержит название стора, через который была совершена покупка.</p><p></p><p>Возможные значения:</p><p>`app_store` или `play_store`.</p> | | `vendor_product_id` | String | <p>Содержит Product ID в Apple/Google стор.</p><p></p><p>Например: org.locals.12345</p> | | `subscription_expires_at` | String | <p>Содержит дату истечения последней подписки.</p><p></p><p>Формат значения:</p><p>year-month dayThour:minute:second</p><p>Например: 2023-02-10T17:22:03.000000+0000</p> | | `last_event_type` | String | Указывает тип последнего полученного события из списка стандартных [событий Adapty](events), включённых для интеграции. | | `purchase_date` | String | <p>Содержит дату последней транзакции (первоначальной покупки или продления).</p><p></p><p>Формат значения:</p><p>year-month dayThour:minute:second</p><p>Например: 2023-02-10T17:22:03.000000+0000</p> | | `original_purchase_date` | String | <p>Содержит дату первой покупки согласно транзакции.</p><p></p><p>Формат значения:</p><p>year-month dayThour:minute:second</p><p>Например: 2023-02-10T17:22:03.000000+0000</p> | | `active_subscription` | String | Принимает значение `true` при любом событии покупки или продления, или `false`, если подписка истекла. | | `period_type` | String | <p>Указывает последний тип периода для покупки или продления.</p><p></p><p>Возможные значения:</p><p>`trial` для пробного периода или `normal` для остальных.</p> | Все значения с плавающей точкой округляются до целых. Строки остаются без изменений. Помимо предопределённого списка тегов, можно отправлять [пользовательские атрибуты](segments#custom-attributes) в виде тегов. Это даёт больше гибкости в выборе передаваемых данных и полезно для отслеживания специфической информации о продукте или сервисе. Все пользовательские атрибуты пользователя автоматически отправляются в Pushwoosh, если отмечен чекбокс **Send user custom attributes** на [странице интеграции](https://app.adapty.io/integrations/pushwoosh). ## Настройка SDK \{#sdk-configuration\} Чтобы связать Adapty с Pushwoosh, необходимо передать значение `HWID`: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (Swift)" default> ```swift showLineNumbers do { try await Adapty.setIntegrationIdentifier( key: "pushwoosh_hwid", value: Pushwoosh.sharedInstance().getHWID() ) } catch { // handle the error } ``` </TabItem> <TabItem value="kotlin" label="Android (Kotlin)" default> ```kotlin showLineNumbers Adapty.setIntegrationIdentifier("pushwoosh_hwid", Pushwoosh.getInstance().hwid) { error -> if (error != null) { // handle the error } } ``` </TabItem> <TabItem value="java" label="Android (Java)" default> ```java showLineNumbers Adapty.setIntegrationIdentifier("pushwoosh_hwid", Pushwoosh.getInstance().getHwid(), error -> { if (error != null) { // handle the error } }); ``` </TabItem> <TabItem value="flutter" label="Flutter (Dart)" default> ```javascript showLineNumbers final hwid = await Pushwoosh.getInstance.getHWID; try { await Adapty().setIntegrationIdentifier( key: "pushwoosh_hwid", value: hwid, ); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="unity" label="Unity (C#)" default> ```csharp showLineNumbers using AdaptySDK; Adapty.SetIntegrationIdentifier( "pushwoosh_hwid", Pushwoosh.Instance.HWID, (error) => { // handle the error }); ``` </TabItem> <TabItem value="rn" label="React Native (TS)" default> ```typescript showLineNumbers // ... try { await adapty.setIntegrationIdentifier("pushwoosh_hwid", hwid); } catch (error) { // handle `AdaptyError` } ``` </TabItem> </Tabs> --- # File: slack --- --- title: "Slack" description: "Интегрируйте Slack с Adapty, чтобы получать уведомления о событиях подписки в режиме реального времени." --- [Slack](https://slack.com/) — корпоративный мессенджер и платформа для совместной работы, которую вряд ли нужно представлять. С помощью этой интеграции вы будете получать уведомления в Slack каждый раз, когда Adapty фиксирует событие, связанное с доходом. Это удобно, если вы хотите отслеживать каждый рост MRR или следить за отменой триалов, проблемами с оплатой, возвратами и другими событиями. ## Как настроить интеграцию со Slack \{#how-to-set-up-slack-integration\} Вам потребуется: - создать приложение в вашем Slack-воркспейсе - дать ему права на публикацию сообщений - и передать необходимые данные в Adapty в разделе [Integrations → Slack](https://app.adapty.io/integrations/slack). ### 1\. Создайте приложение в Slack \{#1-create-an-app-in-slack\} 1. Перейдите в [Slack API dashboard](https://api.slack.com/apps) и создайте приложение: <img src="/assets/shared/img/f43aedc-CleanShot_2024-01-04_at_18.27.412x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <img src="/assets/shared/img/08fa9e6-CleanShot_2024-01-04_at_18.28.142x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Дайте ему любое имя (например, `Adapty`) и добавьте в свой воркспейс: <img src="/assets/shared/img/5002bb1-CleanShot_2024-01-04_at_18.29.132x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 2\. Дайте разрешение на публикацию и получите токен для приложения \{#2-give-permission-to-post-and-get-a-token-for-your-app\} После этого вы будете перенаправлены на страницу вашего приложения в Slack. 1. Прокрутите вниз и нажмите **Permissions**: <img src="/assets/shared/img/9750451-CleanShot_2024-01-04_at_18.48.072x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. После перехода прокрутите вниз до раздела **Scopes** и нажмите **Add an OAuth Scope**: <img src="/assets/shared/img/db5b5f4-CleanShot_2024-01-04_at_18.50.262x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Добавьте разрешения `chat:write`, `chat:write.public` и `chat:write.customize`. Они необходимы для публикации сообщений в каналах и их кастомизации: <img src="/assets/shared/img/d97ccb9-CleanShot_2024-01-04_at_18.51.572x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Прокрутите страницу обратно вверх и нажмите **Install to Workspace**: <img src="/assets/shared/img/14608e3-CleanShot_2024-01-04_at_19.17.58.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Нажмите **Allow**: <img src="/assets/shared/img/143967e-CleanShot_2024-01-04_at_18.53.292x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> После этого вы снова окажетесь на той же странице, но теперь там будет доступен OAuth-токен (`xoxb-...`). Именно он нужен для завершения настройки: <img src="/assets/shared/img/59b33ee-CleanShot_2024-01-04_at_18.55.222x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 3\. Настройте интеграцию в Adapty \{#3-configure-the-integration-in-adapty\} 1. Перейдите в [**Integrations** → **Slack**](https://app.adapty.io/integrations/slack): <img src="/assets/shared/img/b4ffd71-CleanShot_2024-01-04_at_19.05.222x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Вставьте токен `xoxb-...` из предыдущего шага и выберите каналы, в которые приложение будет публиковать сообщения. Вы можете настроить получение событий только для продакшна, только для песочницы или для обоих режимов. Также можно выбрать валюту для отображения сумм (оригинальная или конвертированная в USD). :::note Если вы хотите отправлять сообщения от Adapty в приватный канал, необходимо вручную добавить созданное вами приложение `Adapty` в этот канал — иначе отправка не сработает. ::: 3. Наконец, выберите события, о которых хотите получать уведомления, в разделе **Events**: <img src="/assets/shared/img/970a7bb-CleanShot_2024-01-04_at_19.09.472x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Готово! События будут поступать в указанные вами каналы. Там же вы увидите информацию о доходе (где применимо) и сможете перейти к профилю пользователя в Adapty: <img src="/assets/shared/img/852b8c8-CleanShot_2024-01-04_at_19.11.332x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: s3-exports --- --- title: "Amazon S3" description: "Экспортируйте данные о подписках в S3 для расширенной аналитики и отчётности." --- Интеграция Adapty с Amazon S3 позволяет безопасно хранить данные о событиях и посещениях пейволов в одном центральном месте. Вы сможете сохранять свои [события подписки](events) в виде .csv-файлов в бакете Amazon S3. Чтобы настроить эту интеграцию, нужно выполнить несколько простых шагов в AWS Console и дашборде Adapty. :::note Расписание Adapty отправляет данные каждые **24 часа** в 4:00 UTC. Каждый файл содержит данные о событиях, созданных за весь предыдущий календарный день по UTC. Например, данные, автоматически экспортированные в 4:00 UTC 8 марта, будут содержать все события, созданные 7 марта с 00:00:00 до 23:59:59 по UTC. ::: ## Как настроить интеграцию с Amazon S3 \{#how-to-set-up-amazon-s3-integration\} Для получения данных вам понадобятся следующие учётные данные: 1. Access key ID 2. Secret access key 3. S3 bucket name 4. Folder name inside the S3 bucket :::note Вложенные директории В поле имени бакета Amazon S3 можно указывать вложенные директории, например: adapty-events/com.sample-app ::: Чтобы подключить Amazon S3, перейдите в [**Integrations** -> **Amazon S3**](https://app.adapty.io/integrations/s3), включите переключатель и заполните поля. Сначала укажите учётные данные для установки соединения между Amazon S3 и профилями Adapty. В дашборде Adapty для настройки подключения нужны следующие поля: | Поле | Описание | | :--------------------------- | :----------------------------------------------------------- | | **Access Key ID** | Уникальный идентификатор для аутентификации пользователя или приложения при доступе к сервису AWS. Найдите этот идентификатор в загруженном [csv-файле](s3-exports#how-to-create-amazon-s3-credentials). | | **Secret Access Key** | Приватный ключ, используемый совместно с Access Key ID для аутентификации при доступе к сервису AWS. Найдите этот ключ в загруженном [csv-файле](s3-exports#how-to-create-amazon-s3-credentials). | | **S3 Bucket Name** | Глобально уникальное имя, идентифицирующее конкретный S3-бакет в облаке AWS. S3-бакеты — это простой сервис хранения, позволяющий хранить и получать объекты данных (файлы, изображения и т. д.) в облаке. | | **Folder Inside the Bucker** | Название папки, которую вы хотите создать внутри выбранного S3-бакета. Обратите внимание: S3 имитирует папки с помощью префиксов ключей объектов, которые по сути и являются именами папок. | ## Как создать учётные данные Amazon S3 \{#how-to-create-amazon-s3-credentials\} Этот гайд поможет вам создать необходимые учётные данные в консоли AWS. ### 1\. Создайте политику доступа \{#create-access-policy\} Сначала перейдите на [страницу управления политиками IAM](https://us-east-1.console.aws.amazon.com/iamv2/home?region=us-east-1#/policies) в консоли AWS и выберите **Create Policy**. <img src="/assets/shared/img/7af075c-CleanShot_2023-03-21_at_10.52.002x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> В редакторе политики вставьте следующий JSON и замените `adapty-s3-integration-test` на название вашего бакета: ```json showLineNumbers title="Json" { "Version": "2012-10-17", "Statement": [ { "Sid": "AllowListObjectsInBucket", "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::adapty-s3-integration-test" }, { "Sid": "AllowAllObjectActions", "Effect": "Allow", "Action": "s3:*Object", "Resource": [ "arn:aws:s3:::adapty-s3-integration-test/*", "arn:aws:s3:::adapty-s3-integration-test" ] }, { "Sid": "AllowBucketLocation", "Effect": "Allow", "Action": "s3:GetBucketLocation", "Resource": "arn:aws:s3:::adapty-s3-integration-test" } ] } ``` <img src="/assets/shared/img/d4e474a-CleanShot_2023-03-21_at_10.56.212x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> После завершения настройки политики вы можете добавить теги (необязательно), а затем нажать **Next**, чтобы перейти к последнему шагу. На этом шаге нужно указать название политики и нажать кнопку **Create policy**, чтобы завершить создание. <img src="/assets/shared/img/7dcb02f-CleanShot_2023-03-21_at_11.03.372x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### 2\. Создайте пользователя IAM \{#2-create-iam-user\} Чтобы Adapty мог загружать отчёты с сырыми данными в ваш бакет, необходимо предоставить Access Key ID и Secret Access Key пользователя с правом записи в этот бакет. Для этого перейдите в IAM Console и откройте раздел [Users](https://console.aws.amazon.com/iamv2/home#/users). Затем нажмите кнопку **Add users**. Дайте пользователю имя, выберите **Access key – Programmatic access** и перейдите к настройке прав доступа. <img src="/assets/shared/img/467ee4d-j6aoX.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> На следующем шаге выберите опцию **Add user to group**, затем нажмите кнопку **Create group**. <img src="/assets/shared/img/bfd0e80-CleanShot_2023-03-21_at_11.24.592x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Далее укажите имя для вашей группы пользователей и выберите политику, созданную ранее. После выбора политики нажмите кнопку **Create group**, чтобы завершить процесс. <img src="/assets/shared/img/df29c12-CleanShot_2023-03-21_at_11.28.052x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> После успешного создания группы **выберите её** и перейдите к следующему шагу. <img src="/assets/shared/img/1f3722e-CleanShot_2023-03-21_at_11.36.192x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Это последний шаг данного раздела — просто нажмите кнопку **Create User**. Наконец, вы можете **скачать учётные данные в формате .csv** или скопировать и вставить их прямо с дашборда. <img src="/assets/shared/img/bcf35e1-S3created.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Экспорт данных вручную \{#manual-data-export\} Помимо автоматического экспорта событий в Amazon S3, Adapty также поддерживает ручной экспорт файлов. С помощью этой функции вы можете выбрать конкретный временной интервал и экспортировать данные о событиях в свой S3-бакет вручную. Это даёт вам больше контроля над тем, какие данные и когда экспортировать. Указанный диапазон дат используется для экспорта событий, созданных с Date A 00:00:00 UTC по Date B 23:59:59 UTC. <img src="/assets/shared/img/466bd29-CleanShot_2023-03-21_at_12.35.252x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Структура таблицы \{#table-structure\} В интеграции с AWS S3 Adapty предоставляет таблицу для хранения исторических данных о транзакционных событиях и посещениях пейвола. Таблица содержит информацию о профиле пользователя, выручке и чистом доходе, источнике стора и других параметрах. По сути, эти таблицы фиксируют все транзакции, сгенерированные приложением за определённый период времени. :::warning Обратите внимание, что эта структура может расширяться со временем — по мере добавления новых данных нами или третьими сторонами, с которыми мы работаем. Убедитесь, что ваш код, обрабатывающий её, достаточно устойчив и опирается на конкретные поля, а не на структуру в целом. ::: Ниже представлена структура таблицы для событий: :::note Adapty конвертирует другие валюты в USD по курсу [currencylayer.com](https://currencylayer.com/) (обновляется каждые 8 часов). Курс **фиксируется на момент транзакции** — последующие изменения не влияют на результат конвертации. ::: | Столбец | Описание | |---------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **profile_id** | ID пользователя Adapty. | | **event_type** | Название события в нижнем регистре. Типы событий описаны в разделе [События](events). | | **event_datetime** | Дата в формате ISO 8601. | | **transaction_id** | Уникальный идентификатор транзакции — покупки или продления. | | **original_transaction_id** | Идентификатор транзакции первоначальной покупки. | | **subscription_expires_at** | Дата истечения подписки. Как правило, в будущем. | | **environment** | Sandbox или Production. | | **revenue_usd** | Выручка в USD. Может быть пустым. | | **proceeds_usd** | Поступления в USD. Может быть пустым. | | **net_revenue_usd** | Чистая выручка (доход после уплаты налогов) в USD. Может быть пустым. | | **tax_amount_usd** | Сумма налоговых отчислений в USD. Может быть пустым. | | **revenue_local** | Выручка в местной валюте. Может быть пустым. | | **proceeds_local** | Поступления в местной валюте. Может быть пустым. | | **net_revenue_local** | Чистая выручка (доход после уплаты налогов) в местной валюте. Может быть пустым. | | **tax_amount_local** | Сумма налоговых отчислений в местной валюте. Может быть пустым. | | **customer_user_id** | ID пользователя на стороне разработчика. Например, UUID, email или любой другой идентификатор. Null, если не задан. | | **store** | Может быть _app_store_ или _play_store_. | | **product_id** | ID продукта в Apple App Store, Google Play Store или Stripe. | | **base_plan_id** | [ID базового плана](https://support.google.com/googleplay/android-developer/answer/12154973) в Google Play Store или [ID цены](https://docs.stripe.com/products-prices/how-products-and-prices-work#use-products-and-prices) в Stripe. | | **developer_id** | ID пейвола (SDK) разработчика, с которого была совершена транзакция. | | **ab_test_name** | Название A/B-теста, в рамках которого была совершена транзакция. | | **ab_test_revision** | Ревизия A/B-теста, в рамках которого была совершена транзакция. | | **paywall_name** | Название пейвола, с которого была совершена транзакция. | | **paywall_revision** | Ревизия пейвола, с которого была совершена транзакция. | | **profile_county** | Страна профиля, определённая Adapty по IP-адресу. | | **install_date** | Дата установки в формате ISO 8601. | | **idfv** | [identifierForVendor](https://developer.apple.com/documentation/uikit/uidevice/identifierforvendor) на устройствах iOS. | | **idfa** | [advertisingIdentifier](https://developer.apple.com/documentation/adsupport/asidentifiermanager/advertisingidentifier) на устройствах iOS. | | **advertising_id** | Advertising ID — уникальный код, присваиваемый операционной системой Android, который рекламодатели могут использовать для идентификации устройства пользователя. | | **ip_address** | IP-адрес устройства (IPv4 или IPv6; при наличии предпочтение отдаётся IPv4). Обновляется при каждой смене IP-адреса устройства. | | **cancellation_reason** | <p>Причина отмены подписки пользователем.</p><p></p><p>Возможные значения:</p><p>**iOS & Android** _voluntarily_cancelled_, _billing_error_, _refund_</p><p>**iOS** _price_increase_, _product_was_not_available_, _unknown_, _upgraded_</p><p>**Android** _new_subscription_replace_, _cancelled_by_developer_</p> | | **android_app_set_id** | [AppSetId](https://developer.android.com/design-for-safety/privacy-sandbox/reference/adservices/appsetid/AppSetId) — уникальный, сбрасываемый пользователем идентификатор на уровне устройства и аккаунта разработчика для рекламных сценариев без монетизации. | | **android_id** | На Android 8.0 (API level 26) и выше — 64-битное число (в шестнадцатеричном формате), уникальное для каждой комбинации ключа подписи приложения, пользователя и устройства. Подробнее см. [документацию для разработчиков Android](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID). | | **device** | Название модели устройства, отображаемое конечному пользователю. | | **currency** | Трёхбуквенный код валюты транзакции (ISO-4217). | | **store_country** | Страна профиля, определённая Apple/Google store. | | **attribution_source** | Источник атрибуции. | | **attribution_network_user_id** | ID пользователя, присвоенный источником атрибуции. | | **attribution_status** | Может быть organic, non_organic или unknown. | | **attribution_channel** | Название маркетингового канала. | | **attribution_campaign** | Название маркетинговой кампании. | | **attribution_ad_group** | Группа объявлений атрибуции. | | **attribution_ad_set** | Набор объявлений атрибуции. | | **attribution_creative** | Ключевое слово креатива атрибуции. | | **attributes** | JSON с [пользовательскими атрибутами](setting-user-attributes#custom-user-attributes). Включает все пользовательские атрибуты, настроенные для отправки из мобильного приложения. Чтобы их отправлять, включите опцию **Send User Attributes** на странице [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook). | | **integration_ids** | Все интеграционные ID, связанные с профилем. Словарь. Пример: {'mixpanel_user_id': 'mixpanelUserId-test', 'facebook_anonymous_id': 'facebookAnonymousId-test'} | Here is the table structure for the paywall visits: | Столбец | Описание | | :-------------------- | :-------------------------------------------------------------------------------------------------------------------- | | **profile_id** | Идентификатор пользователя Adapty. | | **customer_user_id** | Идентификатор пользователя, заданный разработчиком. Например, это может быть UUID, email или любой другой ID. Null, если не задан. | | **profile_country** | Страна профиля, определённая стором Apple/Google. | | **install_date** | Дата установки в формате ISO 8601. | | **store** | Может быть _app_store_ или _play_store_. | | **paywall_showed_at** | Дата, когда пейвол был показан пользователю. | | **developer_id** | Developer (SDK) ID пейвола, из которого совершена транзакция. | | **ab_test_name** | Название A/B-теста, из которого совершена транзакция. | | **ab_test_revision** | Ревизия A/B-теста, из которого совершена транзакция. | | **paywall_name** | Название пейвола, из которого совершена транзакция. | | **paywall_revision** | Ревизия пейвола, из которого совершена транзакция. | ## События и теги \{#events-and-tags\} Вы можете управлять тем, какие данные передаются в рамках интеграции. Доступны следующие параметры настройки: | Настройка | Описание | | :--------------------------------- | :----------------------------------------------------------- | | **Exclude Historical Events** | Исключить события, произошедшие до установки приложения с Adapty SDK. Это предотвращает дублирование событий и обеспечивает точность отчётов. Например, если пользователь активировал ежемесячную подписку 10 января, а обновил приложение с Adapty SDK 6 марта, Adapty пропустит события до 6 марта и сохранит последующие. | | **Include events without profile** | Включить транзакции, не связанные с профилем пользователя в Adapty. Сюда могут входить покупки, совершённые до установки Adapty SDK, или транзакции, полученные из серверных уведомлений стора, которые невозможно сразу сопоставить с конкретным пользователем. | | **Send User Attributes** | Если вы хотите передавать атрибуты пользователей, например языковые настройки, и ваш тарифный план OneSignal поддерживает более 10 тегов, выберите эту опцию. При включении можно передавать дополнительную информацию сверх стандартных 10 тегов. Учтите, что превышение лимита тегов может приводить к ошибкам. | <img src="/assets/shared/img/s3-settings.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Ниже настроек интеграции находятся три группы событий, которые можно экспортировать, отправлять и хранить в Amazon S3 из Adapty. Просто включите нужные. Полный список событий, предоставляемых Adapty, доступен [здесь](events). <img src="/assets/shared/img/fd5ccb9-CleanShot_2023-08-17_at_14.49.282x.webp" style={{ border: '1px solid #727272', /* ширина и цвет границы */ width: '700px', /* ширина изображения */ display: 'block', /* для выравнивания */ margin: '0 auto' /* выравнивание по центру */ }} /> --- # File: google-cloud-storage --- --- title: "Google Cloud Storage" description: "Интегрируйте Google Cloud Storage с Adapty для безопасного хранения данных." --- Включите интеграцию с Google Cloud Storage, чтобы безопасно хранить [события подписок](events) и [данные о посещениях пейвола](paywall-metrics) в одном месте — вашем бакете Google Cloud Storage. Каждый день в 4:00 UTC Adapty загружает .csv-файлы с данными за предыдущий день в ваши бакеты. Вы можете выбрать, какие данные получать: **событий**, **посещений пейвола** или **оба варианта**. Также можно экспортировать данные [вручную](#manual-data-export) в любое время за любой период. Чтобы настроить интеграцию, [создайте ключ доступа к бакету](#create-google-cloud-storage-credentials) в Google Cloud Console и [добавьте его в настройки Adapty](#set-up-google-cloud-storage-integration). ## Расписание и продолжительность загрузки \{#upload-schedule-and-duration\} Adapty загружает данные в Google Cloud Storage каждые 24 часа в 04:00 UTC. Файлы содержат данные о событиях, созданных в течение предыдущего календарного дня (UTC). Файл, загруженный 8 марта, будет содержать все события, созданные 7 марта с 00:00:00 до 23:59:59 UTC. Процесс может занять несколько часов — в зависимости от общего числа файлов в очереди и объёма запрошенных вами данных. Если Adapty включает в первую загрузку исторические данные, это займёт больше времени, чем последующие ежедневные загрузки. ## Настройка интеграции с Google Cloud Storage \{#set-up-google-cloud-storage-integration\} Вам понадобится действующий ключ сервисного аккаунта Google Cloud с **правом на запись**. Чтобы его создать, следуйте инструкциям в разделе [создания учётных данных](#create-google-cloud-storage-credentials). :::warning Для событий и посещений пейволов можно использовать разные бакеты с разными учётными данными. Однако если **хотя бы один** набор учётных данных окажется недействительным, [**обе загрузки завершатся ошибкой**](#troubleshooting). ::: Перейдите в [**Integrations** -> **Google Cloud Storage**](https://app.adapty.io/integrations/google-cloud-storage) и откройте нужную вкладку (**Events** или **Paywall visits**). Включите интеграцию. Загрузите файл с вашим **ключом сервисного аккаунта Google Cloud**. Укажите целевой **бакет** и **папку**. Сохраните изменения. ### Дополнительные настройки для данных о событиях \{#optional-settings-for-event-data\} Вы можете указать, какие события включать в отчёт, и задать для них пользовательские названия. Полный список доступных событий см. в статье [Events](events). | Название | Значение по умолчанию | Описание | | ------------------------------ | ----------------- | ----------- | | Exclude historical events | true | Исключить информацию о событиях, произошедших до интеграции Adapty SDK в ваше приложение. <br /> <br />Если ваша аналитическая платформа получала события подписок **до** того, как вы начали использовать Adapty, этот параметр предотвращает появление дублирующих событий. <Details summary="Практический пример"><p>Пользователь оформил ежемесячную подписку 10 января. Обновление приложения от 1 марта было первым, включавшим Adapty SDK. <br /> <br /> Если этот параметр **включён**, отчёт не будет содержать событие «подписка оформлена» от января и событие «подписка продлена» от февраля. **Будет** включено событие «подписка продлена» от 10 марта.</p> </Details> | | Include events without profile | false | Включить транзакции, не связанные с профилем пользователя или не привязанные к конкретному пользователю. Сюда могут входить покупки, совершённые до установки Adapty SDK, или транзакции, полученные через серверные уведомления. | | Send user attributes | false | Включить [пользовательские атрибуты](setting-user-attributes), например данные о пользователе и использовании приложения. Выберите этот параметр, если ваш тарифный план поддерживает более 10 тегов. Превышение лимита тегов может привести к ошибкам. | ## Создание учётных данных Google Cloud Storage \{#create-google-cloud-storage-credentials\} Это руководство поможет вам создать необходимые учётные данные в Google Cloud Platform Console. Чтобы Adapty мог загружать отчёты с необработанными данными в указанный бакет, требуется ключ сервисного аккаунта и права на запись в соответствующий бакет. Предоставив ключ сервисного аккаунта и выдав права на запись, вы позволяете Adapty безопасно и эффективно передавать отчёты с необработанными данными из своей платформы в ваше хранилище. :::warning Обратите внимание, что поддерживается только авторизация через HMAC-ключ сервисного аккаунта. Убедитесь, что вашему HMAC-ключу присвоены роли «Storage Object Viewer», «Storage Legacy Bucket Writer» и «Storage Object Creator» — это необходимо для корректного доступа к Google Cloud Storage. ::: 1. Для начала перейдите в раздел [IAM](https://console.cloud.google.com/projectselector2/iam-admin/serviceaccounts) вашего аккаунта Google Cloud и выберите нужный проект или создайте новый. 1. Затем создайте новый сервисный аккаунт для Adapty, нажав кнопку "+ CREATE SERVICE ACCOUNT". 2. Заполните поля на первом шаге — доступ будет предоставлен позже. Подробнее об этой странице читайте в [документации](https://docs.cloud.google.com/iam/docs/service-accounts-create). 3. Чтобы создать и скачать [приватный JSON-ключ](https://docs.cloud.google.com/iam/docs/keys-create-delete), перейдите в раздел KEYS и нажмите кнопку "ADD KEY". 4. В разделе DETAILS найдите значение Email, связанное с только что созданным сервисным аккаунтом, и скопируйте его. Эта информация потребуется на следующих шагах для авторизации аккаунта и предоставления прав на запись в бакет. 5. Перейдите на страницу [Buckets](https://console.cloud.google.com/storage/browser) в Google Cloud Storage, выберите существующий бакет или создайте новый для хранения отчётов о событиях или посещениях из Adapty. Затем перейдите в раздел PERMISSIONS и выберите [GRANT ACCESS](https://docs.cloud.google.com/identity/docs/how-to?hl=en). 6. В разделе PERMISSIONS введите Email сервисного аккаунта, полученный на пятом шаге, выберите роль Storage Object Creator и нажмите SAVE для сохранения изменений. Запомните название бакета — оно понадобится в будущем. ## Ручной экспорт данных \{#manual-data-export\} Помимо автоматического экспорта данных о событиях в Google Cloud Storage, Adapty поддерживает ручной экспорт файлов. С его помощью вы можете выбрать конкретный временной интервал и экспортировать данные о событиях в бакет GCS вручную. Это даёт вам больше контроля над тем, какие данные и когда экспортировать. Указанный диапазон дат используется для экспорта событий, созданных с Даты А 00:00:00 UTC до Даты Б 23:59:59 UTC. ## Структура данных \{#data-structure\} Adapty использует файлы `.csv` для экспорта данных в табличном формате. :::warning Состав событий может меняться со временем — мы или наши партнёры можем добавлять новые данные. Убедитесь, что ваш код, обрабатывающий эти данные, достаточно гибок и опирается на конкретные поля, а не на структуру в целом. ::: ### События \{#events\} Вы можете [изменить](#optional-settings-for-event-data) список событий, которые включаются в ваши отчёты. :::note Adapty конвертирует другие валюты в USD по курсу [currencylayer.com](https://currencylayer.com/) (обновляется каждые 8 часов). Курс **фиксируется на момент транзакции** — последующие изменения не влияют на результат конвертации. ::: | Столбец | Описание | |------|-----------| | **profile_id** | Идентификатор пользователя Adapty. | | **event_type** | Название события в нижнем регистре. Типы событий см. в разделе [Events](events). | | **event_datetime** | Дата в формате ISO 8601. | | **transaction_id** | Уникальный идентификатор транзакции (например, покупки или продления). | | **original_transaction_id** | Идентификатор транзакции оригинальной покупки. | | **subscription_expires_at** | Дата истечения срока действия подписки. Как правило, в будущем. | | **environment** | Может быть Sandbox или Production. | | **revenue_usd** | Выручка в USD. Может быть пустым. | | **proceeds_usd** | Поступления в USD. Может быть пустым. | | **net_revenue_usd** | Чистая выручка (после вычета налогов) в USD. Может быть пустым. | | **tax_amount_usd** | Сумма налоговых вычетов в USD. Может быть пустым. | | **revenue_local** | Выручка в местной валюте. Может быть пустым. | | **proceeds_local** | Поступления в местной валюте. Может быть пустым. | | **net_revenue_local** | Чистая выручка (после вычета налогов) в местной валюте. Может быть пустым. | | **tax_amount_local** | Сумма налоговых вычетов в местной валюте. Может быть пустым. | | **customer_user_id** | Идентификатор пользователя разработчика. Например, UUID пользователя, email или любой другой идентификатор. Null, если не задан. | | **store** | Может быть *app_store* или *play_store*. | | **product_id** | Идентификатор продукта в Apple App Store, Google Play Store или Stripe. | | **base_plan_id** | [Идентификатор базового плана](https://support.google.com/googleplay/android-developer/answer/12154973) в Google Play Store или [идентификатор цены](https://docs.stripe.com/products-prices/how-products-and-prices-work#use-products-and-prices) в Stripe. | | **developer_id** | ID разработчика (SDK) пейвола, из которого пришла транзакция. | | **ab_test_name** | Название A/B-теста, из которого пришла транзакция. | | **ab_test_revision** | Ревизия A/B-теста, из которого пришла транзакция. | | **paywall_name** | Название пейвола, из которого пришла транзакция. | | **paywall_revision** | Ревизия пейвола, из которого пришла транзакция. | | **profile_country** | Страна профиля, определённая Adapty по IP-адресу. | | **install_date** | Дата установки в формате ISO 8601. | | **idfv** | [identifierForVendor](https://developer.apple.com/documentation/uikit/uidevice/identifierforvendor) на устройствах iOS | | **idfa** | [advertisingIdentifier](https://developer.apple.com/documentation/adsupport/asidentifiermanager/advertisingidentifier) на устройствах iOS | | **advertising_id** | Уникальный код, присваиваемый операционной системой Android, который рекламодатели могут использовать для однозначной идентификации устройства пользователя | | **ip_address** | IP-адрес устройства (может быть IPv4 или IPv6; при наличии предпочтение отдаётся IPv4). Обновляется при каждом изменении IP-адреса устройства | | **cancellation_reason** | <p>Причина отмены подписки пользователем.</p><p></p><p>Возможные значения:</p><p>**iOS и Android** — *voluntarily_cancelled*, *billing_error*, *refund*</p><p>**Только iOS** — *price_increase*, *product_was_not_available*, *unknown*, *upgraded*</p><p>**Только Android** — *new_subscription_replace*, *cancelled_by_developer*</p> | | **android_app_set_id** | [AppSetId](https://developer.android.com/design-for-safety/privacy-sandbox/reference/adservices/appsetid/AppSetId) — уникальный, сбрасываемый пользователем идентификатор для каждого устройства и аккаунта разработчика, предназначенный для некоммерческих рекламных сценариев. | | **android_id** | На Android 8.0 (API level 26) и выше — 64-битное число (в шестнадцатеричном формате), уникальное для каждой комбинации ключа подписи приложения, пользователя и устройства. Подробнее см. в [документации для разработчиков Android](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID). | | **device** | Название модели устройства, отображаемое конечному пользователю. | | **currency** | Трёхбуквенный код валюты транзакции (ISO-4217). | | **store_country** | Страна профиля, определённая Apple/Google store. | | **attribution_source** | Источник атрибуции. | | **attribution_network_user_id** | Идентификатор пользователя, присвоенный источником атрибуции. | | **attribution_status** | Может быть organic, non_organic или unknown. | | **attribution_channel** | Название маркетингового канала. | | **attribution_campaign** | Название маркетинговой кампании. | | **attribution_ad_group** | Группа объявлений атрибуции. | | **attribution_ad_set** | Набор объявлений атрибуции. | | **attribution_creative** | Ключевое слово креатива атрибуции. | | **attributes** | JSON с [пользовательскими атрибутами](setting-user-attributes#custom-user-attributes). Включает любые пользовательские атрибуты, настроенные для отправки из мобильного приложения. Для отправки включите параметр **Send User Attributes** на странице [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook). | | **integration_ids** | Все идентификаторы интеграций, связанные с профилем. Словарь. Пример: {'mixpanel_user_id': 'mixpanelUserId-test', 'facebook_anonymous_id': 'facebookAnonymousId-test'} | ### Посещения пейволов \{#paywall-visits\} | Столбец | Описание | | :-------------------- | :----------------------------------------------------------------------------------------------------------- | | **profile_id** | Идентификатор пользователя Adapty. | | **customer_user_id** | Идентификатор пользователя разработчика. Например, UUID пользователя, email или любой другой идентификатор. Null, если не задан. | | **profile_country** | Страна профиля, определённая Apple/Google store. | | **install_date** | Дата установки в формате ISO 8601. | | **store** | Может быть *app_store* или *play_store*. | | **paywall_showed_at** | Дата, когда пейвол был показан пользователю. | | **developer_id** | ID разработчика (SDK) пейвола, из которого пришла транзакция. | | **ab_test_name** | Название A/B-теста, из которого пришла транзакция. | | **ab_test_revision** | Ревизия A/B-теста, из которого пришла транзакция. | | **paywall_name** | Название пейвола, из которого пришла транзакция. | | **paywall_revision** | Ревизия пейвола, из которого пришла транзакция. | ## Устранение неполадок \{#troubleshooting\} Adapty проверяет действительность ключей доступа **до** начала загрузки. Если хотя бы один ключ Google Cloud Storage окажется недействительным, Adapty **прерывает загрузку** и выдаёт ошибку. Чтобы загрузки проходили без перебоев, заменяйте ключи до истечения срока их действия. Если вы обновляете ключ для **событий**, не забудьте обновить ключ для **посещений пейволов**, и наоборот. --- # File: webhook-event-types-and-fields --- --- title: "Типы событий и поля вебхука" description: "" --- Adapty отправляет вебхуки в ответ на события подписки. В этом разделе описаны типы таких событий и данные, которые содержатся в каждом вебхуке. ## Типы событий вебхука \{#webhook-event-types\} Вы можете отправлять в вебхук все типы событий или выбрать только нужные. Ознакомьтесь с нашими [потоками событий](event-flows), чтобы понять, какие данные ожидать и как выстроить вокруг них бизнес-логику. Ненужные типы событий можно отключить при [настройке интеграции с вебхуком](set-up-webhook-integration#configure-webhook-integration-in-the-adapty-dashboard). Там же можно заменить стандартные идентификаторы событий Adapty на собственные, если это необходимо. | Event name | Description | |:-----------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | subscription_started | Срабатывает, когда пользователь активирует платную подписку без пробного периода, то есть с него сразу списывается оплата. | | subscription_renewed | Происходит при продлении подписки и списании оплаты с пользователя. Это событие фиксируется начиная со второго платежа — как для пробных, так и для обычных подписок. | | subscription_renewal_cancelled | Пользователь отключил автопродление подписки. Доступ к премиум-функциям сохраняется до конца оплаченного периода. | | subscription_renewal_reactivated | Срабатывает, когда пользователь повторно включает автопродление подписки. | | subscription_expired | Срабатывает, когда подписка полностью завершается после отмены. Например, если пользователь отменил подписку 12 декабря, но она активна до 31 декабря, событие фиксируется 31 декабря, когда подписка истекает. | | subscription_paused | Происходит, когда пользователь активирует [паузу подписки](https://developer.android.com/google/play/billing/lifecycle/subscriptions#pause) (только Android). | | subscription_deferred | Срабатывает, когда покупка подписки [откладывается](https://adapty.io/glossary/subscription-purchase-deferral/), — пользователь может перенести платёж, сохраняя доступ к премиум-функциям. Функция доступна через Google Play Developer API и может использоваться для пробных периодов или в поддержку пользователей, испытывающих финансовые трудности. | | non_subscription_purchase | Любая покупка без подписки: пожизненный доступ или расходуемые покупки, например внутриигровые монеты. | | trial_started | Срабатывает, когда пользователь активирует пробную подписку. | | trial_converted | Происходит, когда пробный период заканчивается и с пользователя списывается первый платёж. Например, если пробный период действует до 14 января, но оплата проходит 7 января, событие фиксируется 7 января. | | trial_renewal_cancelled | Пользователь отключил автопродление подписки в течение пробного периода. Доступ к премиум-функциям сохраняется до конца пробного периода, но оплата не будет списана и подписка не активируется. | | trial_renewal_reactivated | Происходит, когда пользователь повторно включает автопродление подписки в течение пробного периода. | | trial_expired | Срабатывает, когда пробный период заканчивается без перехода в подписку. | | entered_grace_period | Происходит, когда попытка оплаты завершается неудачей и пользователь переходит в льготный период (если он включён). В течение этого времени доступ к премиум-функциям сохраняется. | | billing_issue_detected | Срабатывает при возникновении проблемы с оплатой во время попытки списания (например, недостаточно средств на карте). | | subscription_refunded | Срабатывает при возврате средств за подписку (например, через службу поддержки Apple). | | non_subscription_purchase_refunded | Срабатывает при возврате средств за покупку без подписки. | | access_level_updated | Происходит при обновлении уровня доступа пользователя. | :::note `subscription_renewal_reactivated` содержит **предыдущий** идентификатор продукта — тот, который был активен на момент отмены, — даже если пользователь впоследствии реактивировал подписку, купив другой продукт. Apple сохраняет один и тот же `original_transaction_id` на протяжении всей цепочки «отмена → реактивация», поэтому событие отражает исходный продукт. Новый продукт появится в следующем событии `subscription_renewed`, когда начнётся списание за новый продукт. ::: ## Структура события вебхука \{#webhook-event-structure\} Adapty будет отправлять только те события, которые вы выбрали в разделе **Events names** на странице [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook). События webhook сериализуются в JSON. Тело `POST`-запроса к вашему серверу будет содержать сериализованное событие, обёрнутое в структуру ниже. Все события следуют одной и той же структуре, но их поля различаются в зависимости от типа события, стора и вашей конкретной конфигурации. Атрибуты пользователя — это [пользовательские атрибуты](setting-user-attributes#custom-user-attributes), которые вы настроили, поэтому они содержат именно те данные, которые вы задали. Поля данных атрибуции одинаковы для всех типов событий, однако список атрибуций зависит от того, какие источники атрибуции вы используете в мобильном приложении. Ниже приведён пример события: ```json title="Json" showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": "UserIdInYourSystem", "idfv": "00000000-0000-0000-0000-000000000000", "idfa": "00000000-0000-0000-0000-000000000000", "advertising_id": "00000000-0000-0000-0000-000000000000", "profile_install_datetime": "2000-01-31T00:00:00.000000+0000", "user_agent": "ExampleUserAgent/1.0 (Device; OS Version) Browser/Engine", "email": "john.doe@company.com", "event_type": "subscription_started", "event_datetime": "2000-01-31T00:00:00.000000+0000", "event_properties": { "store": "play_store", "currency": "USD", "price_usd": 4.99, "profile_id": "00000000-0000-0000-0000-000000000000", "cohort_name": "All Users", "environment": "Production", "price_local": 4.99, "original_price_usd": 4.99, "original_price_local": 4.99, "discount_amount_usd": 0, "discount_amount_local": 0, "base_plan_id": "b1", "developer_id": "onboarding_placement", "ab_test_name": "onboarding_ab_test", "ab_test_revision": 1, "paywall_name": "UsedPaywall", "proceeds_usd": 4.2315, "variation_id": "00000000-0000-0000-0000-000000000000", "purchase_date": "2024-11-15T10:45:36.181000+0000", "store_country": "AR", "event_datetime": "2000-01-31T00:00:00.000000+0000", "proceeds_local": 4.2415, "tax_amount_usd": 0, "transaction_id": "0000000000000000", "net_revenue_usd": 4.2415, "profile_country": "AR", "paywall_revision": "1", "profile_event_id": "00000000-0000-0000-0000-000000000000", "tax_amount_local": 0, "net_revenue_local": 4.2415, "vendor_product_id": "onemonth_no_trial", "profile_ip_address": "10.10.1.1", "consecutive_payments": 1, "rate_after_first_year": false, "original_purchase_date": "2000-01-31T00:00:00.000000+0000", "original_transaction_id": "0000000000000000", "subscription_expires_at": "2000-01-31T00:00:00.000000+0000", "profile_has_access_level": true, "profile_total_revenue_usd": 4.99, "promotional_offer_id": null, "store_offer_category": null, "store_offer_discount_type": null }, "event_api_version": 1, "profiles_sharing_access_level": [{"profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": "UserIdInYourSystem"}], "attributions": { "appsflyer": { "ad_set": "Keywords 1.12", "status": "non_organic", "channel": "Google Ads", "ad_group": null, "campaign": "Social media influencers - Rest of the world", "creative": null, "created_at": "2000-01-31T00:00:00.000000+0000" } }, "user_attributes": {"Favourite_color": "Violet", "Pet_name": "Fluffy"}, "integration_ids": {"firebase_app_instance_id": "val1", "branch_id": "val2", "one_signal_player_id": "val3"}, "play_store_purchase_token": { "product_id": "product_123", "purchase_token": "token_abc_123", "is_subscription": true } } ``` ### Поля события \{#event-fields\} Параметры события одинаковы для всех типов событий. | **Поле** | **Тип** | **Описание** | |---|---|---| | **advertising_id** | UUID | Advertising ID (только Android). | | **attributions** | JSON | [Данные атрибуции](webhook-event-types-and-fields#attributions). Включается, если в [настройках вебхука](https://app.adapty.io/integrations/customwebhook) включён параметр **Send Attribution**. | | **customer_user_id** | String | ID пользователя из вашего приложения (UUID, email или другой идентификатор), если вы задаёте его в коде приложения при [идентификации пользователей](ios-quickstart-identify). Если пользователи не идентифицируются или конкретный пользователь анонимен (не вошёл в систему), поле равно `null`. | | **email** | String | Email пользователя, если вы задаёте его с помощью метода [`updateProfile`](setting-user-attributes) в SDK Adapty или при создании/обновлении профилей через серверный API. Если значение `email` не передаётся в метод SDK или API, поле равно `null`. | | **event_api_version** | Integer | Версия API Adapty (текущая: `1`). | | **event_datetime** | ISO 8601 | Фактическое (бизнес-) время события: например, дата покупки для события покупки или дата истечения для события истечения — не момент получения или отправки события в Adapty. Формат [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) (например, `2020-07-10T15:00:00.000000+0000`). Подробнее о порядке событий см. примечание ниже. | | **event_properties** | JSON | [Свойства события](webhook-event-types-and-fields#event-properties). | | **event_type** | String | Название события в формате Adapty. Полный список см. в разделе [Типы событий вебхука](webhook-event-types-and-fields#webhook-event-types). | | **idfa** | UUID | Advertising ID (только Apple). **IDFA** в профиле в [дашборде Adapty](https://app.adapty.io/profiles/users). Может быть `null`, если недоступен из-за ограничений отслеживания, детского режима или настроек конфиденциальности. | | **idfv** | UUID | Identifier for Vendors (IDFV), уникальный для каждого разработчика. **IDFV** в профиле в [дашборде Adapty](https://app.adapty.io/profiles/users). | | **integration_ids** | JSON | Идентификаторы интеграций пользователя, если вы задаёте их с помощью метода `setIntegrationIdentifier` в SDK Adapty или при создании/обновлении профилей через серверный API. `null`, если недоступно или интеграции отключены. | | **play_store_purchase_token** | JSON | [Токен покупки Play Store](webhook-event-types-and-fields#play-store-purchase-token). Включается, если в [настройках вебхука](https://app.adapty.io/integrations/customwebhook) включён параметр **Send Play Store purchase token**. | | **profile_id** | UUID | ID профиля, автоматически генерируемый Adapty для каждого профиля. Один Apple/Google ID может быть связан с разными profile ID, если пользователи не идентифицируются или совершают покупки до входа в систему. Подробнее о том, [как Adapty работает с родительскими и производными профилями](how-profiles-work#parent-and-inheritor-profiles). | | **profile_install_datetime** | ISO 8601 | Временная метка установки в формате [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) (например, `2020-07-10T15:00:00.000000+0000`). | | **profiles_sharing_access_level** | JSON | Список пользователей, [совместно использующих уровень доступа](general#6-sharing-paid-access-between-user-accounts), за исключением текущего профиля. Если совместное использование уровней доступа включено для вашего приложения, список содержит другие профили, привязанные к тому же Apple/Google ID.<br/>Формат: <ul><li>**profile_id**: (UUID) Adapty ID</li><li>**customer_user_id**: (String) Customer User ID, если указан</li></ul> | | **user_agent** | String | User-agent браузера устройства. | | **user_attributes** | JSON | Пользовательские данные для обогащения профилей информацией, специфичной для приложения. Обычно используются для отслеживания предпочтений пользователя (например, тема, язык) или поведенческих флагов (завершение онбординга, использование функций). <br/>Задаются в формате ключ-значение, где ключи — строки, а значения — строки или числа (например, `{"Favourite_color": "Violet", "Pet_name": "Fluffy"}`). <br/>Пользовательские атрибуты можно задавать вручную в дашборде Adapty для отдельных профилей, программно через метод `updateProfile` в SDK Adapty или через серверный API при создании/обновлении профилей. <br/>Включается, если в [настройках вебхука](https://app.adapty.io/integrations/customwebhook) включён параметр **Send User Attributes**. <p>Хотя в коде мобильного приложения значения пользовательских атрибутов могут задаваться как float или строки, атрибуты, полученные через серверный API или исторический импорт, могут иметь другие форматы. В этом случае булевы и целочисленные значения будут преобразованы в float.</p> | :::note `event_datetime` отражает момент, когда событие произошло в жизненном цикле подписки, а не когда Adapty его обработал или доставил. Из-за этого события могут иметь одинаковое значение `event_datetime` или поступать не в хронологическом порядке. Например, событие `subscription_expired` может иметь более раннее значение `event_datetime`, чем событие `subscription_renewal_cancelled`, которое Adapty доставит раньше него. Не используйте `event_datetime` для упорядочивания событий. Вместо этого сортируйте события по времени получения на своей стороне и дедуплицируйте их с помощью `profile_event_id` или идентификаторов транзакций. ::: ### Атрибуции \{#attributions\} Чтобы отправлять данные атрибуции, включите опцию **Send Attribution** на странице [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook). Если вы включили отправку данных атрибуции и настроили [интеграции атрибуции](attribution-integration), данные ниже будут отправляться вместе с событием для каждого источника. Одни и те же данные атрибуции отправляются для всех типов событий. ```json title="Json" showLineNumbers { "attributions": { "appsflyer": { "ad_set": "sample_ad_set_123", "status": "non_organic", "channel": "sample_channel", "ad_group": "sample_ad_group_456", "campaign": "sample_ios_campaign", "creative": "sample_creative_789", "created_at": "2000-01-31T00:00:00.000000+0000", "network_user_id": "0000000000000-0000000" } } } ``` | Название поля | Тип поля | Описание | | :------------------ | :------------ | :------------------------------------------------- | | **ad_set** | String | Рекламный набор атрибуции. | | **status** | String | Может быть `organic`, `non_organic,` или `unknown`. | | **channel** | String | Название маркетингового канала. | | **ad_group** | String | Рекламная группа атрибуции. | | **campaign** | String | Название маркетинговой кампании. | | **creative** | String | Ключевое слово креатива атрибуции. | | **created_at** | ISO 8601 date | Дата и время создания записи атрибуции. | | **network_user_id** | String | ID, присвоенный пользователю источником атрибуции. | ### Идентификаторы интеграций \{#integration-ids\} Следующие идентификаторы интеграций используются в событиях: - `adjust_device_id` - `airbridge_device_id` - `amplitude_device_id` - `amplitude_user_id` - `appmetrica_device_id` - `appmetrica_profile_id` - `appsflyer_id` - `branch_id` - `facebook_anonymous_id` - `firebase_app_instance_id` - `mixpanel_user_id` - `pushwoosh_hwid` - `one_signal_player_id` - `one_signal_subscription_id` - `tenjin_analytics_installation_id` - `posthog_distinct_user_id` ### Токен покупки Play Store \{#play-store-purchase-token\} Это поле содержит все данные, необходимые для повторной валидации покупки при необходимости. Оно отправляется только если включена опция **Send Play Store purchase token** в [настройках интеграции с Webhook](https://app.adapty.io/integrations/customwebhook). | Field | Type | Description | | :------------------ | :------ | :----------------------------------------------------------- | | **product_id** | String | Уникальный идентификатор продукта (SKU), приобретённого в Play Store. | | **purchase_token** | String | Токен, сгенерированный Google Play для уникальной идентификации транзакции покупки. | | **is_subscription** | Boolean | Указывает, является ли приобретённый продукт подпиской (`true`) или разовой покупкой (`false`). | ### Свойства событий \{#event-properties\} Свойства событий могут различаться в зависимости от типа события и даже между событиями одного типа. Например, событие из App Store не будет содержать Android-специфичные свойства, такие как `base_plan_id`. У события [Access Level Updated](webhook-event-types-and-fields#for-access-level-updated-event) есть особые свойства, поэтому мы выделили для него отдельный раздел. Аналогично, мы вынесли [Дополнительные свойства событий налогов и выручки](webhook-event-types-and-fields#additional-tax-and-revenue-event-properties) в отдельный раздел, поскольку они характерны лишь для отдельных типов событий. #### Для большинства типов событий \{#for-most-event-types\} Свойства событий для большинства типов событий одинаковы (кроме события **Access Level Updated**, которое описано в отдельном разделе). Ниже представлена подробная таблица свойств с указанием, к каким событиям они относятся. :::note Adapty конвертирует другие валюты в USD по курсу [currencylayer.com](https://currencylayer.com/) (обновляется каждые 8 часов). Курс **фиксируется на момент транзакции** — последующие изменения не влияют на результат конвертации. ::: | Поле | Тип | Описание | |:------------------------------|:--------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **ab_test_name** | String | Название [A/B-теста Adapty](ab-tests), в рамках которого произошла транзакция. | | **ab_test_revision** | Integer | Ревизия A/B-теста, в рамках которого произошла транзакция. | | **base_plan_id** | String | [Идентификатор базового плана](https://support.google.com/googleplay/android-developer/answer/12154973) в Google Play Store или [идентификатор цены](https://docs.stripe.com/products-prices/how-products-and-prices-work#use-products-and-prices) в Stripe. | | **cancellation_reason** | String | <p>Возможные причины отмены: `voluntarily_cancelled`, `billing_error`, `price_increase`, `product_was_not_available`, `refund`, `cancelled_by_developer`, `new_subscription_replace`, `upgraded`, `unknown`, `adapty_revoked`.</p><p>Присутствует в следующих типах событий:</p>`subscription_cancelled`, `subscription_refunded` и `trial_cancelled`. | | **cohort_name** | String | Название [аудитории](audience), которая определила, какой пейвол был показан пользователю. | | **consecutive_payments** | Integer | Количество периодов, в течение которых пользователь подписан без перерывов. Включает текущий период. | | **currency** | String | Локальная валюта. | | **developer_id** | String | ID [плейсмента](placements), в рамках которого произошла транзакция. | | **discount_amount_local** | Float | Скидка, применённая к транзакции: стандартная цена минус фактически списанная сумма (до комиссии Apple/Google), в локальной валюте. `0` при покупке по полной цене. Для бесплатного пробного периода равна полной стандартной цене (`original_price_local`), так как ничего не списывается. `null`, если скидка применялась, но стандартная цена неизвестна (см. `original_price_local`). Всегда `null` для офферов App Store с единовременной предоплатой: единый платёж охватывает несколько расчётных периодов, поэтому его нельзя сравнить с ценой за отдельный период. | | **discount_amount_usd** | Float | Значение `discount_amount_local` в долларах США. | | **environment** | String | Возможные значения: `Sandbox` или `Production`. | | **event_datetime** | ISO 8601 date | Дата и время события. Совпадает со значением на корневом уровне события. | | **original_price_local** | Float | Стандартная цена продукта без скидки до комиссии Apple/Google, в локальной валюте. Для подписок — это цена продления. Равна `price_local` при покупке по полной цене и всегда равна `price_local` для разовых покупок, поскольку сторы не предоставляют отдельную стандартную цену для них. `null` при покупке со скидкой, если стор не возвращает достоверную стандартную цену (например, автопродление отключено, продление всё ещё содержит оффер или ожидается смена продукта). | | **original_price_usd** | Float | То же, что `original_price_local`, но в долларах США. | | **original_purchase_date** | ISO 8601 date | Для возобновляемых подписок исходная покупка — это первая транзакция в цепочке; её ID (original transaction ID) связывает всю цепочку продлений, а последующие транзакции являются её продолжением. Дата исходной покупки — это дата и время этой первой транзакции. | | **original_transaction_id** | String | <p>Для возобновляемых подписок — это ID исходной транзакции, связывающий всю цепочку продлений. Исходная транзакция — первая в цепочке; последующие являются её продолжением.</p><p>Если продлений нет, `original_transaction_id` совпадает со `store_transaction_id`.</p> | | **paywall_name** | String | Название пейвола, в рамках которого произошла транзакция. | | **paywall_revision** | String | Ревизия пейвола, в рамках которого произошла транзакция. Значение по умолчанию — 1. | | **price_local** | Float | Сумма, списанная за транзакцию до комиссии Apple/Google, в локальной валюте. `null` для бесплатных пробных периодов, так как ничего не списывается. | | **price_usd** | Float | Сумма, списанная за транзакцию до комиссии Apple/Google, в долларах США. `null` для бесплатных пробных периодов, так как ничего не списывается. | | **profile_country** | String | Определяется Adapty на основе IP-адреса профиля. | | **profile_event_id** | UUID | Уникальный ID события, который можно использовать для дедупликации. | | **profile_has_access_level** | Boolean | Булевое значение, указывающее, есть ли у профиля активный уровень доступа. | | **profile_id** | UUID | ID профиля, сгенерированный Adapty. Совпадает со значением на корневом уровне события. | | **profile_ip_address** | String | IP-адрес профиля (может быть IPv4 или IPv6, при наличии предпочтение отдаётся IPv4). `null`, если опция **Collect users' IP addresses** отключена в [настройках приложения](https://app.adapty.io/settings/general). | | **profile_total_revenue_usd** | Float | Общий доход по профилю с вычетом возвратов. | | **promotional_offer_id** | String | Adapty ID использованного [promotional offer](offers). Этот ID задаётся при создании оффера в дашборде. | | **purchase_date** | ISO 8601 date | Дата и время покупки продукта. | | **rate_after_first_year** | Boolean | Булевое значение, указывающее, что подписка соответствует условиям пониженной комиссии (как правило, 15%) после одного года непрерывного продления. Ставки комиссии варьируются в зависимости от программы и страны. Подробнее см. в разделе [Комиссия стора и налоги](controls-filters-grouping-compare-proceeds#display-gross-or-net-revenue). | | **store** | String | Стор, в котором был куплен продукт. Стандартные значения: **app_store**, **play_store**, **stripe**, **paddle**. <br/>Если вы задаёте [пользовательские транзакции стора](api-adapty/operations/setTransaction) через серверный API, используется значение из параметра **store**. | | **store_country** | String | Страна, переданная нам сторовым магазином приложений. | | **store_offer_category** | String | Категория применённого оффера. Возможные значения: `introductory`, `promotional`, `winback`. | | **store_offer_discount_type** | String | Тип применённого оффера. Возможные значения: `free_trial`, `pay_as_you_go` и `pay_up_front`. | | **store_offer_number_of_periods** | Integer | Количество базовых расчётных периодов, на которые распространяется скидка оффера (1 и более). Присутствует только при наличии оффера. `null` для офферов App Store с единовременной предоплатой и в случаях, когда стор не предоставляет информацию о длительности оффера. | | **subscription_expires_at** | ISO 8601 date | Дата истечения срока подписки. Как правило, в будущем. | | **transaction_id** | String | Уникальный идентификатор транзакции. | | **trial_duration** | String | Длительность пробного периода в днях. Передаётся в формате «{} days», например «7 days». Присутствует только в событиях, связанных с пробным периодом: `trial_started`, `trial_converted`, `trial_cancelled`. | | **variation_id** | UUID | Уникальный ID пейвола, на котором была совершена покупка. | | **vendor_product_id** | String | <p>ID продукта в Apple App Store, Google Play Store или Stripe.</p><p>Если доступ был предоставлен без реальной транзакции в сторе, `vendor_product_id` принимает одно из следующих значений:</p><ul><li>`adapty_server_side_product` — доступ предоставлен через [серверный API](api-adapty/operations/grantAccessLevel).</li><li>`adapty_dashboard_product` — доступ [предоставлен вручную](give-access-level-to-specific-customer) в дашборде Adapty.</li><li>`adapty_promotion` — устаревшее значение.</li></ul> | #### Дополнительные свойства событий для налогов и выручки \{#additional-tax-and-revenue-event-properties\} Перечисленные ниже свойства событий, связанные с налогами и выручкой, — это дополнительные поля, которые применяются только к определённым типам событий. Это означает, что указанные типы событий включают [свойства событий для большинства типов событий](webhook-event-types-and-fields#for-most-event-types), а также дополнительные поля, перечисленные ниже. Типы событий, для которых применяются свойства налогов и выручки: - `subscription_renewed` - `subscription_initial_purchase` (также называется `subscription_started` — то же самое событие) - `subscription_refunded` - `non_subscription_purchase` | Поле | Тип | Описание | | :-------------------- | :---- | :----------------------------------------------------------- | | **net_revenue_local** | Float | Чистый доход (после вычета комиссии Apple/Google и налогов) в местной валюте. | | **net_revenue_usd** | Float | Чистый доход (после вычета комиссии Apple/Google и налогов) в USD. | | **proceeds_local** | Float | Цена продукта после вычета комиссии Apple/Google в местной валюте. | | **proceeds_usd** | Float | Цена продукта после вычета комиссии Apple/Google. | | **tax_amount_local** | Float | Сумма удержанного налога в местной валюте. | | **tax_amount_usd** | Float | Сумма удержанного налога в USD. | #### Пример полезной нагрузки `non_subscription_purchase` \{#non_subscription_purchase-example-payload\} `non_subscription_purchase` следует той же структуре, что и события подписки, но отражает разовую или расходуемую покупку. Поля, специфичные для подписок, не применяются: `cancellation_reason`, `will_renew`, `is_in_grace_period`, `is_refund`, `is_lifetime` и `trial_duration` отсутствуют. Поле `subscription_expires_at` присутствует, но равно `null`. Поля налогов и выручки (`net_revenue_*`, `proceeds_*`, `tax_amount_*`) включены. <details> <summary>Пример полезной нагрузки (нажмите, чтобы развернуть)</summary> ```json title="Json" showLineNumbers { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": "UserIdInYourSystem", "event_type": "non_subscription_purchase", "event_datetime": "2000-01-31T00:00:00.000000+0000", "event_properties": { "store": "app_store", "currency": "USD", "price_usd": 4.99, "price_local": 4.99, "original_price_usd": 4.99, "original_price_local": 4.99, "discount_amount_usd": 0, "discount_amount_local": 0, "proceeds_usd": 4.2415, "proceeds_local": 4.2415, "net_revenue_usd": 4.2415, "net_revenue_local": 4.2415, "tax_amount_usd": 0, "tax_amount_local": 0, "profile_id": "00000000-0000-0000-0000-000000000000", "environment": "Production", "vendor_product_id": "100coins", "transaction_id": "0000000000000000", "original_transaction_id": "0000000000000000", "purchase_date": "2024-11-15T10:45:36.181000+0000", "original_purchase_date": "2024-11-15T10:45:36.181000+0000", "subscription_expires_at": null, "store_country": "US", "profile_country": "US", "profile_ip_address": "10.10.1.1", "profile_has_access_level": false, "profile_total_revenue_usd": 4.99, "consecutive_payments": 1, "rate_after_first_year": false, "profile_event_id": "00000000-0000-0000-0000-000000000000" }, "event_api_version": 1 } ``` </details> #### Для события Access Level Updated \{#for-access-level-updated-event\} Событие **Access Level Updated** — это специфическое webhook-событие, которое генерируется только при активной интеграции Webhook и включённом типе этого события. Если оно включено, оно отправляется на настроенный Webhook и отображается в **Event Feed**. Если не включено, событие не создаётся. Если вы включили [общий доступ к уровням доступа](general#6-sharing-paid-access-between-user-accounts), событие **access level updated** будет отправлено для всех профилей, имеющих доступ к данному уровню доступа. :::tip Используйте это событие для обновления уровня доступа пользователя в вашей базе данных, предоставления или отзыва премиум-функций на бэкенде и синхронизации доступа между устройствами или платформами. ::: | Свойство | Тип | Описание | | ---------------------------------- | ------------- | ------------------------------------------------------------ | | **ab_test_name** | String | Название A/B-теста, в рамках которого была совершена транзакция. | | **access_level_id** | String | Идентификатор уровня доступа. | | **activated_at** | ISO 8601 date | Дата и время последней активации доступа. | | **active_introductory_offer_type** | String | Тип применённого introductory offer. Возможные значения: `free_trial`, `pay_as_you_go`, `pay_up_front`. | | **active_promotional_offer_id** | String | Идентификатор promotional offer, указанный в разделе Product дашборда Adapty. | | **active_promotional_offer_type** | String | Тип применённого promotional offer. Возможные значения: `free_trial`, `pay_as_you_go`, `pay_up_front`. | | **base_plan_id** | String | [Base plan ID](https://support.google.com/googleplay/android-developer/answer/12154973) в Google Play Store или [price ID](https://docs.stripe.com/products-prices/how-products-and-prices-work#use-products-and-prices) в Stripe. | | **billing_issue_detected_at** | ISO 8601 date | Дата и время обнаружения проблемы с оплатой. | | **cancellation_reason** | String | Возможные причины отмены: `voluntarily_cancelled`, `billing_error`, `price_increase`, `product_was_not_available`, `refund`, `cancelled_by_developer`, `new_subscription_replace`, `upgraded`, `unknown`, `adapty_revoked`. | | **cohort_name** | String | Название аудитории, к которой относится профиль. | | **currency** | String | Локальная валюта (по умолчанию USD). | | **developer_id** | String | Идентификатор плейсмента, в рамках которого была совершена транзакция. | | **environment** | String | Возможные значения: `Sandbox` или `Production`. | | **event_datetime** | ISO 8601 date | Дата и время события. | | **expires_at** | ISO 8601 date | Дата и время истечения доступа. | | **is_active** | Boolean | Признак того, активен ли уровень доступа. | | **is_in_grace_period** | Boolean | Признак того, находится ли профиль в льготном периоде. | | **is_lifetime** | Boolean | Признак того, является ли уровень доступа пожизненным. | | **is_refund** | Boolean | Признак того, является ли транзакция возвратом средств. | | **original_purchase_date** | ISO 8601 date | Для возобновляемых подписок первоначальная покупка — это первая транзакция в цепочке; её идентификатор (original transaction ID) связывает всю цепочку продлений. Последующие транзакции являются её продолжением. Дата первоначальной покупки — это дата и время этой первой транзакции. | | **original_transaction_id** | String | <p>Для возобновляемых подписок это исходный идентификатор транзакции, связывающий цепочку продлений. Исходная транзакция — первая в цепочке; последующие являются её продолжением.</p><p>Если продлений не было, `original_transaction_id` совпадает со store_transaction_id.</p>Идентификатор транзакции первоначальной покупки. | | **paywall_name** | String | Название пейвола, в рамках которого была совершена транзакция. | | **paywall_revision** | String | Ревизия пейвола, в рамках которого была совершена транзакция. Значение по умолчанию — 1. | | **profile_country** | String | Определяется Adapty на основе IP-адреса профиля. | | **profile_event_id** | UUID | Уникальный идентификатор события, который можно использовать для дедупликации. | | **profile_has_access_level** | Boolean | Признак того, есть ли у профиля активный уровень доступа. | | **profile_id** | UUID | Внутренний идентификатор профиля пользователя в Adapty. | | **profile_ip_address** | String | IP-адрес профиля (может быть IPv4 или IPv6; IPv4 имеет приоритет при наличии). `null`, если **Collect users' IP addresses** отключено в [настройках приложения](https://app.adapty.io/settings/general). | | **profile_total_revenue_usd** | Float | Общий доход по профилю с учётом возвратов. | | **purchase_date** | ISO 8601 date | Дата и время покупки продукта. | | **renewed_at** | ISO 8601 date | Дата и время продления доступа. | | **starts_at** | ISO 8601 date | Дата и время начала уровня доступа. | | **store** | String | Стор, в котором был приобретён продукт. Стандартные значения: **app_store**, **play_store**, **stripe**, **paddle**. <br/>Если вы задаёте [пользовательские транзакции стора](api-adapty/operations/setTransaction) через серверный API, используется значение из параметра **store**. | | **store_country** | String | Страна, переданная в Adapty магазином приложений. | | **subscription_expires_at** | ISO 8601 date | Дата истечения подписки. | | **transaction_id** | String | Уникальный идентификатор транзакции. | | **trial_duration** | String | Длительность пробного периода в днях (например, «7 days»). | | **variation_id** | UUID | Идентификатор варианта, используемый для атрибуции покупок к данному пейволу. | | **vendor_product_id** | String | <p>Идентификатор продукта в сторе (Apple/Google/Stripe).</p><p>Если доступ был предоставлен без реальной транзакции в сторе, `vendor_product_id` принимает одно из следующих значений:</p><ul><li>`adapty_server_side_product` — предоставлен через [серверный API](api-adapty/operations/grantAccessLevel).</li><li>`adapty_dashboard_product` — [предоставлен вручную](give-access-level-to-specific-customer) в дашборде Adapty.</li><li>`adapty_promotion` — устаревшее значение.</li></ul> | | **will_renew** | Boolean | Признак того, будет ли платный уровень доступа продлён. | :::warning Обратите внимание, что эта структура может расширяться со временем — по мере того как мы или наши партнёры добавляем новые данные. Убедитесь, что ваш код, обрабатывающий её, достаточно устойчив и опирается на конкретные поля, а не на всю структуру целиком. ::: --- # File: set-up-webhook-integration --- --- title: "Настройка интеграции с webhook" description: "Настройте интеграцию с webhook в Adapty для автоматического отслеживания событий." --- [Интеграция с webhook](webhook) в Adapty состоит из следующих шагов: <img src="/assets/shared/img/webhook-setup.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '300px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> 1. **Вы настраиваете эндпоинт:** 1. Убедитесь, что ваш сервер может обрабатывать запросы Adapty с заголовком **Content-Type**, установленным в `application/json`. 2. Настройте сервер на получение верификационного запроса от Adapty и ответ с любым статусом `2xx` и телом в формате JSON. 3. [Обрабатывайте события подписки](#subscription-events) после подтверждения соединения. 2. **Вы настраиваете и включаете интеграцию с вебхуком** в [дашборде Adapty](#configure-webhook-integration-in-the-adapty-dashboard). Там же можно [сопоставить события Adapty с пользовательскими названиями событий](#configure-webhook-integration-in-the-adapty-dashboard). Рекомендуем сначала протестировать в среде **Sandbox**, прежде чем переходить на продакшн. 3. **Adapty отправляет верификационный запрос** на ваш сервер. 4. **Ваш сервер отвечает** статусом `2XX` и телом в формате JSON. 5. **После получения корректного ответа Adapty начинает отправлять события подписки.** ## Настройте сервер для обработки запросов от Adapty \{#set-up-your-server-to-process-adapty-requests\} Adapty будет отправлять на ваш webhook-эндпоинт запросы двух типов: 1. [Запрос верификации](#verification-request): первоначальный запрос, подтверждающий корректность настройки подключения. Он не содержит никаких событий и отправляется в момент нажатия кнопки **Save** в настройках интеграции Webhook в дашборде Adapty. Чтобы подтвердить успешное получение запроса верификации, ваш эндпоинт должен вернуть соответствующий ответ. 2. [Событие подписки](#subscription-events): стандартный запрос, который сервер Adapty отправляет каждый раз при создании нового события. Сервер не ожидает от вашего сервера никакого специального ответа — ему достаточно получить стандартный HTTP-ответ с кодом 200, подтверждающий успешное получение сообщения. ### Запрос верификации После того как вы включите интеграцию с вебхуком в дашборде Adapty, Adapty отправит POST-запрос верификации с пустым JSON-объектом `{}` в теле. Настройте эндпоинт так, чтобы **заголовок Content-Type** был `application/json`, то есть ваш сервер должен ожидать входящий вебхук-запрос с полезной нагрузкой в формате JSON. Ваш сервер должен ответить кодом статуса 2xx и вернуть любой валидный JSON, например: ```json title="Json" {} ``` После того как Adapty получит ответ верификации в правильном формате и с кодом статуса 2xx, интеграция вебхука Adapty будет полностью настроена. ### События подписок \{#subscription-events\} События подписок отправляются с заголовком **Content-Type** равным `application/json` и содержат данные события в формате JSON. Описание возможных типов событий и структур запросов см. в разделе [Типы и поля событий вебхука](webhook-event-types-and-fields). ## Настройка интеграции с вебхуком в дашборде Adapty \{#configure-webhook-integration-in-the-adapty-dashboard\} В Adapty можно настроить отдельные потоки для продакшн-событий и тестовых событий, получаемых из песочницы Apple или Stripe, либо из тестового аккаунта Google. :::tip Adapty поддерживает один URL вебхука на каждую среду (продакшн и песочница). Чтобы отправлять события в несколько сервисов, направьте вебхук на собственный бэкенд и раздавайте события уже оттуда. ::: Для production-событий используйте поле **Production endpoint URL**, указав URL, на который будут отправляться коллбэки. Также настройте поле **Authorization header value for production endpoint** — заголовок для аутентификации событий Adapty на вашем сервере. Обратите внимание: значение из поля **Authorization header value for production endpoint** будет использовано как заголовок `Authorization` ровно в том виде, в каком оно указано, без каких-либо изменений или дополнений. Для тестовых событий используйте поля **Sandbox endpoint URL** и **Authorization header value for sandbox endpoint** соответственно. Чтобы настроить интеграцию с вебхуком: 1. Откройте [Integrations -> Webhook](https://app.adapty.io/integrations/customwebhook) в дашборде Adapty. <img src="/assets/shared/img/webhook_integration.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Включите переключатель, чтобы активировать интеграцию. 4. Заполните поля интеграции: | Поле | Описание | | ------------------------------------------------------ | ------------------------------------------------------------ | | **Production endpoint URL** | URL, на который Adapty отправляет HTTP POST-запросы для событий в продакшене. | | **Authorization header value for production endpoint** | <p>Заголовок, который ваш сервер будет использовать для аутентификации запросов от Adapty в продакшене. Значение из этого поля будет передано в заголовок `Authorization` ровно в том виде, в каком оно указано — без изменений и дополнений.</p><p></p><p>Хотя это поле не обязательно, настоятельно рекомендуем его заполнить для повышения безопасности.</p> | Кроме того, для тестирования в среде песочницы доступны ещё два поля: | Поле для тестирования | Описание | | --------------------------------------------------- | ------------------------------------------------------------ | | **Sandbox endpoint URL** | URL, на который Adapty отправляет HTTP POST-запросы для событий в среде песочницы. | | **Authorization header value for sandbox endpoint** | <p>Заголовок, который ваш сервер будет использовать для аутентификации запросов от Adapty при тестировании в среде песочницы. Обратите внимание: значение из этого поля передаётся в заголовке `Authorization` ровно так, как указано, без каких-либо изменений или дополнений.</p><p></p><p>Хотя это поле не обязательно, настоятельно рекомендуем его заполнить для повышения безопасности.</p> | 4. (Опционально) Выберите события, которые хотите получать, и задайте их названия. Ознакомьтесь с разделом [Потоки событий](event-flows), чтобы узнать, какие события срабатывают в разных ситуациях. Если идентификаторы событий в вашей системе отличаются от используемых в Adapty, оставьте свои идентификаторы как есть и замените стандартные идентификаторы Adapty на ваши в разделе **Events names** на странице [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook). ID события может быть любой строкой — главное, чтобы ID события на вашем сервере обработки вебхуков совпадал с тем, что вы указали в дашборде Adapty. Оставлять поле ID события пустым для включённых событий нельзя. <img src="/assets/shared/img/86942b8-event_names_renaming.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Дополнительные поля и параметры не обязательны — используйте их по мере необходимости: | Настройка | Описание | | :--------------------------------- | :----------------------------------------------------------- | | **Send Trial Price** | Если включено, Adapty будет передавать цену подписки в полях `price_local` и `price_usd` для события **Trial Started**. | | **Exclude Historical Events** | Позволяет исключить события, произошедшие до установки приложения с Adapty SDK. Это предотвращает дублирование событий и обеспечивает точную отчётность. Например, если пользователь активировал ежемесячную подписку 10 января, а обновил приложение с Adapty SDK 6 марта, Adapty пропустит события до 6 марта и сохранит последующие. | | **Send user attributes** | Включите этот параметр, чтобы отправлять пользовательские атрибуты, например языковые настройки. Они будут отображаться в поле `user_attributes`. Подробнее см. в разделе [Поля события](webhook-event-types-and-fields#event-fields). | | **Send attribution** | Включите этот параметр, чтобы добавлять данные атрибуции (например, данные AppsFlyer) в поле `attributions`. Подробнее см. в разделе [Данные атрибуции](webhook-event-types-and-fields#attributions). | | **Send Play Store purchase token** | Включите этот параметр, чтобы получать токен Play Store, необходимый для повторной валидации покупок. После включения в событие добавится параметр `play_store_purchase_token`. Подробнее о его содержимом см. в разделе [Токен покупки Play Store](webhook-event-types-and-fields#play-store-purchase-token). | 6. Не забудьте нажать кнопку **Save**, чтобы сохранить изменения. Как только вы нажмёте **Save**, Adapty отправит запрос верификации и будет ждать ответа от вашего сервера. ### Выберите события для отправки и настройте маппинг имён событий \{#choose-events-to-send-and-map-event-names\} Выберите события, которые хотите получать на своём сервере, включив соответствующий тумблер. Если имена событий в вашей системе отличаются от используемых в Adapty, вы можете настроить маппинг — просто замените стандартные названия событий Adapty на собственные в разделе **Events names** на странице [Integrations -> Webhooks](https://app.adapty.io/integrations/customwebhook). <img src="/assets/shared/img/86942b8-event_names_renaming.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Названием события может быть любая строка. Оставлять поля пустыми для включённых событий нельзя. Если вы случайно удалили название события Adapty, его всегда можно скопировать из раздела [События для отправки в сторонние интеграции](events). ## Обработка событий вебхука \{#handle-webhook-events\} Вебхуки обычно доставляются в течение 5–60 секунд после события. Исключение — события отмены: они могут приходить с задержкой до 2 часов после того, как пользователь отменил подписку. Если код ответа вашего сервера выходит за пределы диапазона 200–404, Adapty повторяет доставку с экспоненциальной задержкой. Первая попытка происходит примерно через **1 минуту** после первого сбоя, каждая следующая — вдвое позже. Всего до 9 попыток в течение 24 часов. Рекомендуем настроить вебхук так, чтобы перед ответом он выполнял только базовую валидацию тела события от Adapty. Если сервер не может обработать событие и вы не хотите повторных попыток от Adapty, используйте код ответа в диапазоне 200–404. Ресурсоёмкие задачи обрабатывайте асинхронно и отвечайте Adapty как можно быстрее. Если Adapty не получит ответ в течение 10 секунд, попытка считается неудачной и будет повторена. --- # File: test-webhook --- --- title: "Тестирование интеграции с webhook" description: "Тестируйте интеграции с webhook в Adapty для автоматического отслеживания событий подписки." --- После настройки интеграции пришло время её протестировать. Можно тестировать как интеграцию в песочнице, так и в продакшене. Рекомендуем начать с песочницы и максимально всё проверить на ней: - События отправляются и успешно доставляются. - Вы правильно настроили параметры для исторических событий, цену подписки для события **Trial started**, атрибуцию, пользовательские атрибуты и токен покупки Google Play Store — отправляются они с событием или нет. - Вы правильно задали названия событий, и ваш сервер может их обрабатывать. ## Как тестировать \{#how-to-test\} Перед началом тестирования убедитесь, что вы уже: 1. Настроили интеграцию с webhook, как описано в разделе [Настройка интеграции с webhook](set-up-webhook-integration). 2. Подготовили окружение, как описано в разделах [Тестирование встроенных покупок в Apple App Store](test-purchases-in-sandbox) и [Тестирование встроенных покупок в Google Play Store](testing-on-android). Убедитесь, что тестовое приложение собрано в окружении песочницы, а не в продакшене. 3. Совершили покупку / начали пробный период / оформили возврат — то есть выполнили действие, которое вызовет событие, выбранное для отправки на webhook. Например, чтобы получить событие **Subscription started**, оформите новую подписку. ## Проверка результата \{#validation-of-the-result\} ### Успешная отправка событий \{#successful-sending-events-result\} При успешной интеграции событие появится в разделе **Last sent events** и будет иметь статус **Success**. <img src="/assets/shared/img/6ccc3bb-webhook_integration_success.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Неудачная отправка событий \{#unsuccessful-sending-events-result\} | Проблема | Решение | |-----|--------| | Событие не появилось | Покупка не была совершена, поэтому событие не было создано. Обратитесь к разделу [Устранение неполадок с тестовыми покупками](troubleshooting-test-purchases). | | Событие появилось со статусом **Sending failed** | <p>Доставка определяется на основе HTTP-статуса: всё **вне диапазона 200–399** считается ошибкой.</p><p>Чтобы узнать подробности, наведите курсор на статус **Sending failed** неудачного события, как показано ниже.</p> | <img src="/assets/shared/img/12ff189-hover_sending_failed.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: handle-integration-errors --- --- title: "Обработка ошибок в интеграциях" description: "Обработка ошибок в интеграциях" --- При использовании интеграций с атрибуцией, сервисами сообщений или аналитикой вы можете столкнуться с типичными ошибками. В этом гайде описаны способы их устранения. ## Расхождение данных \{#data-discrepancy\} **Причина**: Это может происходить из-за того, что не все пользователи используют версию приложения с Adapty SDK. **Решение**: Чтобы обеспечить согласованность данных, вы можете принудительно обновить приложение пользователей до версии с Adapty SDK. ## Сетевые ошибки \{#network-errors\} **Причина**: Скорее всего, это связано с отсутствием интернет-соединения между сервером Adapty и сервером интеграции. **Решение**: Такие проблемы обычно быстро проходят и затрагивают лишь небольшое число событий. ## Сервер интеграции не смог обработать событие \{#integration-server-failed-to-process-the-event\} **Причина**: Интеграция настроена некорректно. **Решение**: Обратитесь к статье об этой интеграции в нашей документации. Убедитесь, что вы выполнили все шаги настройки как в дашборде Adapty, так и на стороне стороннего инструмента и в коде приложения. ## Отсутствующие данные интеграции \{#missing-integration-data\} **Причина**: В профиле отсутствует ID, специфичный для данной интеграции. Это может происходить, если интеграция настроена некорректно в коде приложения. **Решение**: Обратитесь к статье об этой интеграции в нашей документации. Убедитесь, что вы реализовали методы из примеров кода в своём приложении и что эти методы действительно взаимодействуют с профилями пользователей. ## Отсутствующие учётные данные интеграции \{#missing-integration-credentials\} **Причина**: Некоторые учётные данные интеграции отсутствуют или указаны неверно. **Решение**: Проверьте все учётные данные для этой интеграции в дашборде Adapty. Проблема может быть связана с несоответствием версии или среды. ## Срок действия события истёк \{#the-event-has-expired\} **Причина**: В настройках интеграции включена опция **Exclude historical events**, а дата создания события предшествует дате создания профиля в нашей системе. Это может происходить, если цепочка транзакций, начавшаяся много лет назад, поступает в Adapty через валидацию чека для профиля, созданного недавно. **Решение**: Убедитесь, что это не происходит для новых событий. Если вы хотите отправлять исторические события в интеграцию, отключите **Exclude historical events**. ## Отключённый или неподдерживаемый тип события \{#disabledunsupported-event-type\} **Причина**: Либо это событие не поддерживается данной интеграцией, либо вы отключили его при настройке интеграции. Например, события `access_level_updated` не поддерживаются большинством интеграций. **Решение**: Проверьте в документации интеграции, поддерживает ли она данный тип события. Если да, убедитесь в дашборде Adapty, что этот тип события включён в настройках интеграции. --- # File: manage-adapty-with-ai --- --- title: "Управление Adapty с помощью AI-агентов и инструментов разработки" description: "Все способы использования Adapty с AI — интеграция SDK с помощью coding-агента, получение аналитики через LLM и передача документации Adapty в ваш AI-инструмент." --- Adapty работает с AI-инструментами для разработки и агентами. Используйте их для интеграции SDK, получения ответов по аналитике или поиска в документации Adapty, не выходя из редактора. На этой странице перечислено всё доступное и для кого каждый инструмент предназначен. ## Интеграция SDK Adapty с помощью AI \{#integrate-the-adapty-sdk-with-ai\} Два способа добавить SDK Adapty в приложение с помощью AI-инструмента для разработки. Оба работают с Cursor, Claude и другими AI-ассистентами. ### Интеграция на основе скилла \{#skill-based-integration\} Скилл интеграции SDK Adapty выполняет всю интеграцию из вашего AI-инструмента одной командой. Используйте его, когда хотите пройти через настройку автоматически и с подсказками. Выберите платформу: [iOS](adapty-sdk-integration-skill) · [Android](adapty-sdk-integration-skill-android) · [React Native](adapty-sdk-integration-skill-react-native) · [Flutter](adapty-sdk-integration-skill-flutter) · [Unity](adapty-sdk-integration-skill-unity) · [Kotlin Multiplatform](adapty-sdk-integration-skill-kmp) · [Capacitor](adapty-sdk-integration-skill-capacitor) ### Пошаговая интеграция \{#step-by-step-integration\} Проведите AI-инструмент через интеграцию поэтапно, передавая нужную документацию в правильном порядке. Используйте этот способ, если хотите проверять каждый шаг по мере выполнения. Выберите платформу: [iOS](adapty-cursor) · [Android](adapty-cursor-android) · [React Native](adapty-cursor-react-native) · [Flutter](adapty-cursor-flutter) · [Unity](adapty-cursor-unity) · [Kotlin Multiplatform](adapty-cursor-kmp) · [Capacitor](adapty-cursor-capacitor) ## Управление Adapty из командной строки \{#manage-adapty-from-the-command-line\} [Adapty Developer CLI](developer-cli-quickstart) позволяет управлять сущностями Adapty — приложениями, уровнями доступа, продуктами, пейволами и плейсментами — из терминала, не открывая дашборд. Поскольку это инструмент командной строки, ваш AI coding-агент может запускать его напрямую. ## Запросы к вашим данным \{#ask-about-your-data\} Направьте AI coding-агента на Export Analytics API, чтобы запрашивать ваши метрики на обычном языке — выручку, удержание, LTV и многое другое. Сервер MCP не нужен. [Спросите AI о ваших аналитических данных](export-analytics-with-ai) ## Передача документации Adapty в AI-инструмент \{#give-your-ai-tool-the-adapty-docs\} ### Документация в виде обычного текста \{#plain-text-docs\} Каждая страница документации Adapty доступна в формате Markdown — добавьте `.md` к URL страницы или нажмите **Copy for LLM** под заголовком. Для более широкого контекста передайте инструменту индекс [`llms.txt`](https://adapty.io/docs/ru/llms.txt) или платформенный подмножественный файл, например [`ios-llms.txt`](https://adapty.io/docs/ru/ios-llms.txt). ### Context7 \{#context7\} [Context7](https://context7.com/adaptyteam/adapty-docs) — это MCP-сервер, который передаёт документацию Adapty в ваш AI-инструмент, однако индексирует только фрагменты кода, а не полный текст. Используйте его для быстрых примеров кода; для полного руководства передавайте инструменту документацию в виде обычного текста, описанную выше. Context7 работает с Cursor, Claude Code, Windsurf и другими инструментами, поддерживающими MCP. --- # File: export-analytics-with-ai --- --- title: "Задавайте вопросы ИИ о своей аналитике" description: "Запрашивайте аналитику Adapty на естественном языке с помощью ИИ-агента, используя Export Analytics API." --- Ask an AI coding agent about your Adapty analytics in plain language — revenue, conversions, retention, LTV — and let it pull the numbers for you. Point a tool that can make API calls at the [Export Analytics API](https://adapty.io/docs/ru/export-analytics-api.md), and it queries your metrics on demand. ## Что можно запрашивать \{#what-you-can-ask-about\} Export Analytics API возвращает те же метрики, что вы видите на графиках дашборда Adapty. Для каждой метрики предусмотрена отдельная операция: | Метрика | Что охватывает | Операция | | --- | --- | --- | | Revenue, MRR, ARR, ARPU | Деньги, заработанные за период, сгруппированные по периоду, стране или кампании | [retrieveAnalyticsData](https://adapty.io/docs/ru/api-export-analytics/operations/retrieveAnalyticsData.md) | | Удержание когорты | Как долго подписчики из данной когорты продолжают платить | [retrieveCohortData](https://adapty.io/docs/ru/api-export-analytics/operations/retrieveCohortData.md) | | Конверсии | Сколько пользователей переходит с одного шага или канала на следующий | [retrieveConversionData](https://adapty.io/docs/ru/api-export-analytics/operations/retrieveConversionData.md) | | Отток и воронка | Где пользователи отваливаются и как быстро отписываются | [retrieveFunnelData](https://adapty.io/docs/ru/api-export-analytics/operations/retrieveFunnelData.md) | | Пожизненная ценность (LTV) | Средняя выручка на пользователя по сегментам за период | [retrieveLTVData](https://adapty.io/docs/ru/api-export-analytics/operations/retrieveLTVData.md) | | Удержание | Доля пользователей, остающихся активными спустя N дней | [retrieveRetentionData](https://adapty.io/docs/ru/api-export-analytics/operations/retrieveRetentionData.md) | Полный список параметров и фильтров см. в [справочнике API](https://adapty.io/docs/ru/api-export-analytics.md). ## Прежде чем начать \{#before-you-start\} Вам понадобятся три вещи: - **Аккаунт Adapty с данными**: API возвращает те же метрики, что и графики на дашборде, поэтому ваше приложение должно уже собирать аналитику. - **Секретный API-ключ**: Найдите его в [App settings → General](https://app.adapty.io/settings/general), в поле **Secret key**. Ключи привязаны к конкретному приложению, поэтому используйте отдельный ключ для каждого из них. Сохраните ключ в переменную окружения (например, `ADAPTY_SECRET_KEY`), чтобы агент мог его считать без необходимости вставлять в чат вручную. - **AI-инструмент с возможностью вызова API**: Например, Claude Code, Cursor или Claude Desktop с инструментом fetch. Обычные чат-инструменты вроде claude.ai или ChatGPT не могут напрямую обращаться к API. ## Дайте агенту спецификацию API \{#give-your-agent-the-api-spec\} [Спецификация OpenAPI](https://adapty.io/docs/ru/api-specs/export-analytics-api.yaml) описывает все эндпоинты, заголовок аутентификации, тело запроса и примеры ответов. Получив спецификацию, агент самостоятельно формирует корректные запросы — без написания кода с вашей стороны. Передайте агенту спецификацию по URL: - **Вставьте URL**: Если ваш агент умеет загружать URL, дайте ему `https://adapty.io/docs/ru/api-specs/export-analytics-api.yaml` и попросите прочитать спецификацию. - **Используйте инструмент fetch**: Если у вашего агента есть инструмент для загрузки URL (например, MCP fetch server), укажите ему тот же URL. Спецификация задаёт базовый URL `https://api-admin.adapty.io`, так что агенту будет достаточно этого, как только ключ окажется в переменных окружения. ## Задайте вопрос о своих данных \{#ask-about-your-data\} Загрузив спецификацию и указав ключ в переменной окружения, описывайте нужную метрику на обычном языке. Примеры запросов: ``` What was my MRR at the end of each month this year, and how does it compare to last year? Show my trial-to-paid conversion rate for the last 90 days, broken down by product. Which countries drive the most revenue from my yearly subscription? Top 10. How is week-1 retention trending for subscribers who started in the last 6 months? What's the refund rate on my annual plan since launch, by month? Compare LTV for paid-campaign users vs. organic over the last year, and export it as CSV. ``` Агент сопоставляет ваш запрос с нужной операцией, считывает ключ из переменной окружения и возвращает данные. По умолчанию ответы возвращаются в формате JSON. Попросите CSV, если вам нужен файл для таблиц — агент передаст `format` со значением `csv` в теле запроса. :::warning Храните секретный ключ в переменной окружения — не вставляйте его в чат и не коммитьте в файл правил. Ключи привязаны к конкретному приложению, поэтому при утечке пересоздайте ключ в **Settings → General**. См. [ротация API-ключей](https://adapty.io/docs/ru/export-analytics-api-authorization.md). ::: ## Настройте один раз для повторного использования \{#set-up-once-for-repeated-use\} Чтобы не повторять настройку каждый раз, сохраните спецификацию и ключ там, где агент сможет их повторно использовать: - **Сохраните ссылку на спецификацию**: добавьте URL спецификации в правила или файл памяти вашего агента (например, `CLAUDE.md` или файл правил Cursor), чтобы он загружался при каждой сессии. - **Храните ключ в переменных окружения**: добавьте `ADAPTY_SECRET_KEY` в профиль shell или хранилище секретов инструмента, чтобы больше не вводить его вручную. - **Сохраните часто используемые промпты или создайте кастомный скилл**: держите типовые вопросы в виде сохранённых промптов или оберните их в кастомный скилл или slash-команду, чтобы агент запускал отчёт по запросу. ## Ограничения \{#limits\} Учитывайте следующие ограничения: - **Ограничение частоты запросов**: API допускает 2 запроса в секунду на один API-ключ. При превышении лимита возвращается ошибка `429 Too Many Requests`. Настройте агент так, чтобы он ждал и повторял запрос при получении `429`. - **Ключи привязаны к приложению**: Каждый ключ работает только с одним приложением. Чтобы получать данные из нескольких приложений, используйте соответствующий ключ для каждого из них. - **Формат ответа**: По умолчанию ответы возвращаются в формате JSON. Чтобы экспортировать данные в CSV, укажите `format` со значением `csv` в теле запроса. Полное описание аутентификации и правил формирования запросов см. в разделе [Авторизация и формат запроса](https://adapty.io/docs/ru/export-analytics-api-authorization.md). --- # File: handle-webhooks-with-ai --- --- title: "Обработка событий подписки Adapty с помощью вебхуков" description: "Получайте и обрабатывайте события подписки Adapty на своём сервере с помощью вебхуков — настройка эндпоинта, аутентификация, полезная нагрузка и тестирование на одной странице." --- Вебхуки позволяют вашему серверу получать события подписок Adapty в режиме реального времени — покупки, продления, отмены, проблемы с оплатой и возвраты — чтобы вы могли предоставлять доступ, синхронизировать бэкенд или запускать рабочие процессы. Этот гайд проведёт вас от настройки эндпоинта до проверенной, протестированной интеграции на одной странице, а также покажет, как поручить написание обработчика AI-агенту. :::tip Используете AI-агент? Нажмите **Copy for LLM** под заголовком и вставьте всю страницу в агент — там есть всё необходимое: настройка, формат payload и логика обработчика. ::: ## Как работают вебхуки Adapty \{#how-adapty-webhooks-work\} - **Однонаправленные и в реальном времени**: Adapty отправляет HTTP `POST` на ваш сервер при наступлении события — никакого поллинга. - **Два типа запросов**: Одноразовый запрос верификации (отправляется при сохранении интеграции) и текущие события подписки. - **Один URL на окружение**: Вы настраиваете отдельный эндпоинт для продакшена и для песочницы. - **Подтверждение каждого запроса**: Ответьте статусом `2xx` как можно быстрее — при сбое Adapty повторит попытку. ## Создайте свой endpoint \{#build-your-endpoint\} Создайте публичный HTTPS-endpoint, который обрабатывает два типа запросов: - **Verification request**: отправляется один раз при сохранении интеграции. Тело запроса — пустой JSON (`{}`). В ответ верните статус `2xx` и тело в формате JSON. - **Subscription events**: постоянные `POST`-запросы с событием в теле. Верните `200` в течение 10 секунд, а всю тяжёлую работу выполняйте асинхронно. Выберите секретную строку и сохраните её как переменную окружения (например, `ADAPTY_WEBHOOK_SECRET`). При каждом запросе проверяйте, совпадает ли заголовок `Authorization` с ней, и отклоняйте запрос, если нет — тот же секрет вы введёте в дашборде чуть позже. ```javascript title="webhook.js" const app = express(); app.use(express.json()); const WEBHOOK_SECRET = process.env.ADAPTY_WEBHOOK_SECRET; app.post("/adapty/webhook", (req, res) => { // 1. Verify the shared secret Adapty echoes back. if (req.get("Authorization") !== WEBHOOK_SECRET) { return res.sendStatus(401); } // 2. Acknowledge fast, then process asynchronously. res.status(200).json({}); // 3. The verification request has an empty body — nothing to handle. const event = req.body; if (!event.event_type) return; switch (event.event_type) { case "subscription_started": case "subscription_renewed": case "trial_converted": // Grant or extend access. break; case "subscription_expired": case "subscription_refunded": // Revoke access. break; default: break; } }); app.listen(3000); ``` Задеплойте эндпойнт на публичный HTTPS-адрес до того, как настраивать интеграцию — Adapty отправляет запрос верификации в момент сохранения. ### Ключевые события и их содержимое \{#key-events-and-the-payload\} Все события используют одну и ту же оболочку. Набор полей зависит от типа события, стора и включённых вами настроек. Ниже приведён сокращённый пример события `subscription_started`: ```json title="Example event" { "profile_id": "00000000-0000-0000-0000-000000000000", "customer_user_id": "UserIdInYourSystem", "event_type": "subscription_started", "event_datetime": "2024-11-15T10:45:36.181000+0000", "event_properties": { "store": "play_store", "currency": "USD", "price_usd": 4.99, "vendor_product_id": "onemonth_no_trial", "transaction_id": "0000000000000000", "original_transaction_id": "0000000000000000", "subscription_expires_at": "2024-12-15T10:45:36.181000+0000", "profile_event_id": "00000000-0000-0000-0000-000000000000" }, "event_api_version": 1 } ``` Самые частые события, с которыми вам придётся работать: | Тип события | Срабатывает когда | | --- | --- | | `subscription_started` | Пользователь оформляет платную подписку | | `subscription_renewed` | Подписка успешно продлевается и списывается оплата | | `subscription_renewal_cancelled` | Пользователь отключает автопродление (доступ сохраняется до истечения срока) | | `subscription_expired` | Доступ прекращается после окончания не продлённой подписки | | `trial_started` | Пользователь начинает бесплатный пробный период | | `trial_converted` | Пробный период конвертируется в платную подписку | | `billing_issue_detected` | Платёж за продление не проходит | | `subscription_refunded` | Покупка подписки возвращается | Полный список событий и все поля описаны в разделе [Типы и поля событий вебхука](https://adapty.io/docs/ru/webhook-event-types-and-fields.md). :::warning Не сортируйте события по `event_datetime` — это бизнес-время события, поэтому события могут приходить не по порядку или иметь одинаковую временну́ю метку. Сортируйте по времени получения на вашей стороне и устраняйте дубликаты с помощью `profile_event_id` или идентификаторов транзакций. ::: ## Настройте вебхук в Adapty \{#configure-the-webhook-in-adapty\} 1. Откройте [Integrations → Webhook](https://app.adapty.io/integrations/customwebhook) в дашборде Adapty. 2. Включите интеграцию. 3. В поле **Production endpoint URL** введите HTTPS URL задеплоенного эндпоинта. 4. В поле **Authorization header value for production endpoint** введите тот же секрет, который проверяет ваш эндпоинт. Adapty отправляет это значение в заголовке `Authorization` с каждым запросом. Поле необязательное, но мы настоятельно рекомендуем его заполнить. 5. Чтобы сначала протестировать в песочнице, заполните **Sandbox endpoint URL** и соответствующее поле **Authorization header value**. 6. Нажмите **Save**. Adapty сразу отправит верификационный запрос на эндпоинт, который должен ответить кодом `2xx` — это завершит настройку. Чтобы выбрать события для отправки, настроить названия событий или включить дополнительные поля (цена триала, исторические события, атрибуция, атрибуты пользователя, токен Play Store), см. [Настройка интеграции с вебхуком](https://adapty.io/docs/ru/set-up-webhook-integration.md). ## Создайте обработчик с помощью AI-агента \{#build-it-with-your-ai-coding-agent\} Передайте вашему AI-агенту этот гайд и справочную документацию в формате Markdown (добавьте `.md` к любому URL страницы), укажите ваш стек и дайте ему сгенерировать обработчик: - [Типы событий и поля вебхука](https://adapty.io/docs/ru/webhook-event-types-and-fields.md) - [Настройка интеграции с вебхуком](https://adapty.io/docs/ru/set-up-webhook-integration.md) Пример промпта: ``` Read these Adapty webhook docs, then write a webhook handler for my Express app: verify the Authorization header against ADAPTY_WEBHOOK_SECRET, answer the verification request, acknowledge events with 200, and grant or revoke access based on event_type. ``` The agent writes the handler code, but it can't deploy your endpoint or configure the dashboard — host the endpoint yourself and set the URL and secret in **Integrations → Webhook**. ## Протестируйте вебхук \{#test-your-webhook\} Перед запуском в продакшн протестируйте в песочнице: 1. Настройте эндпоинт и секрет для песочницы, как описано выше. 2. В приложении для песочницы совершите покупку, запустите триал или оформите возврат, чтобы вызвать событие. 3. Откройте раздел **Last sent events** интеграции. Доставленное событие отображается со статусом **Success**. Если событие показывает **Sending failed**, ваш сервер вернул статус вне диапазона 200–399 — наведите курсор на статус для получения подробностей. Полное руководство по тестированию см. в разделе [Тест интеграции с вебхуком](https://adapty.io/docs/ru/test-webhook.md). ## Ограничения \{#limits\} - **Подтверждение в течение 10 секунд**: если Adapty не получает ответ вовремя, попытка считается неудачной и повторяется. - **Повторные попытки**: если статус ответа выходит за пределы диапазона 200–404, Adapty повторяет запрос с экспоненциальной задержкой — до 9 повторов в течение 24 часов. - **Задержка отмены**: события об отмене могут поступать с задержкой до 2 часов. - **Один URL на окружение**: чтобы доставлять события в несколько сервисов, направьте вебхук на свой бэкенд и распределяйте их уже оттуда. --- # File: server-side-api-with-ai --- --- title: "Проверка и предоставление доступа к подписке через бэкенд" description: "Используйте серверный API Adapty, чтобы проверить наличие активной подписки у пользователя и предоставить доступ вручную с помощью AI-агента." --- Используйте серверный API Adapty, чтобы из вашего бэкенда проверять наличие активной подписки у пользователя и вручную выдавать доступ. В этом гайде рассмотрены два самых распространённых запроса — `getProfile` и `grantAccessLevel` — и показано, как поручить AI-агенту написать интеграцию под ваш стек. :::tip Используете AI-агент? Нажмите **Copy for LLM** под заголовком и вставьте всю эту страницу в агент — там есть все вызовы, поля и важные нюансы. ::: ## Прежде чем начать \{#before-you-start\} - **Секретный ключ API**: Найдите его в [App settings → General](https://app.adapty.io/settings/general), в поле **Secret key**. Ключи привязаны к конкретному приложению. Сохраните его в переменную окружения (например, `ADAPTY_SECRET_KEY`) и передавайте в заголовке `Authorization: Api-Key {key}`. - **Базовый URL**: Все запросы отправляются на `https://api.adapty.io`. - **Способ идентифицировать пользователя**: Передавайте либо `adapty-customer-user-id` (ваш собственный идентификатор пользователя — работает только если вы идентифицируете пользователей в приложении), либо `adapty-profile-id` (идентификатор профиля Adapty). Они взаимозаменяемы — используйте любой из них. ## Проверка подписки \{#check-a-subscription\} Чтобы проверить статус, вызовите `getProfile` с методом `GET` и передайте идентификатор пользователя в заголовке — тело запроса не требуется. ```javascript title="check-access.js" const res = await fetch("https://api.adapty.io/api/v2/server-side-api/profile/", { headers: { "Authorization": `Api-Key ${process.env.ADAPTY_SECRET_KEY}`, "adapty-customer-user-id": userId, }, }); const { data } = await res.json(); function hasActiveAccess(profile, accessLevelId = "premium") { const level = profile.access_levels?.find(a => a.access_level_id === accessLevelId); if (!level) return false; if (level.is_in_grace_period) return true; if (!level.expires_at) return true; // lifetime / non-expiring return new Date(level.expires_at) > new Date(); // not expired yet } if (hasActiveAccess(data)) { // unlock premium features } ``` В отличие от профиля в SDK, серверный ответ **не содержит поля `is_active`**. Определяйте статус самостоятельно по `access_levels[].expires_at`: `null` означает пожизненный доступ, дата в будущем — активная подписка, дата в прошлом — истёкшая. Считайте `is_in_grace_period` активным состоянием. Полное описание полей профиля и уровней доступа см. в [getProfile](https://adapty.io/docs/ru/api-adapty/operations/getProfile.md). ## Предоставление доступа вручную \{#grant-access-manually\} Чтобы разблокировать платные функции без покупки — промокоды, доступ для инвесторов или бета-тестеров, решение обращений в поддержку — вызовите `grantAccessLevel` через `POST`. ```javascript title="grant-access.js" await fetch("https://api.adapty.io/api/v2/server-side-api/purchase/profile/grant/access-level/", { method: "POST", headers: { "Authorization": `Api-Key ${process.env.ADAPTY_SECRET_KEY}`, "adapty-customer-user-id": userId, "Content-Type": "application/json", }, body: JSON.stringify({ access_level_id: "premium" }), // add "expires_at" for temporary access }); ``` - **Уровень доступа должен уже существовать** в вашем дашборде (**Access levels**) — `access_level_id` это его идентификатор, а не новое имя. - **Ручные выдачи не отображаются в аналитике**. Они доставляются только в вебхук-интеграцию и Event Feed, поэтому в графиках доходов и конверсий они не учитываются. Подробнее о запросе и ответе см. [grantAccessLevel](https://adapty.io/docs/ru/api-adapty/operations/grantAccessLevel.md). ## Создайте это с помощью AI-агента Передайте своему AI-агенту этот гайд и спецификацию API в формате Markdown (добавьте `.md` к любому URL страницы), укажите свой стек — и пусть он напишет вызовы: - [Спецификация OpenAPI](https://adapty.io/docs/ru/api-specs/adapty-api.yaml) - [getProfile](https://adapty.io/docs/ru/api-adapty/operations/getProfile.md) - [grantAccessLevel](https://adapty.io/docs/ru/api-adapty/operations/grantAccessLevel.md) Пример промпта: ``` Using the Adapty server-side API spec, write backend functions to check whether a user has an active "premium" access level (GET /profile/, derive status from expires_at — there's no is_active field) and to grant it (grantAccessLevel). Authenticate with ADAPTY_SECRET_KEY and identify users by adapty-customer-user-id. ``` The agent writes the code, but it can't run your backend or set your keys — you provide the secret key and the user identifiers. ## Ограничения \{#limits\} - **Лимит запросов**: до 40 000 запросов в минуту на приложение. - **Ключи привязаны к приложению**: каждый ключ работает только для одного приложения — используйте соответствующий ключ для каждого из них. - **Обязательный идентификатор**: каждый запрос должен содержать `adapty-customer-user-id` или `adapty-profile-id`. --- # File: test-purchases-in-sandbox --- --- title: "Тестирование в песочнице" description: "Тестируйте покупки в среде песочницы для обеспечения бесперебойных транзакций." --- Когда вы настроили всё в дашборде Adapty и своём мобильном приложении, самое время протестировать встроенные покупки. **Примечание:** ни один из тестовых инструментов не списывает деньги с пользователей при тестировании покупки продукта. App Store не отправляет письма о покупках или возвратах, совершённых в тестовых средах. :::note **Транзакции из песочницы не отображаются в аналитических графиках.** Они по-прежнему видны на страницах отдельных профилей и в ленте событий. ::: :::info Перед тестированием встроенных покупок убедитесь, что: - Вы прошли гайды по [быстрому старту](quickstart): интеграция стора, добавление продуктов и интеграция SDK Adapty. - Ваш продукт имеет статус [**Ready to submit**](InvalidProductIdentifiers#step-2-check-products) в App Store Connect. ::: ## Тестирование в песочнице \{#sandbox-testing\} <div style={{ maxWidth: '560px', margin: '0 auto 2rem', position: 'relative', aspectRatio: '16/9', width: '100%' }}> <iframe style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%' }} src="https://www.youtube.com/embed/hq4PRU-vuik?si=m5F5Sj6iLEJ-2q6n" title="YouTube video player" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerPolicy="strict-origin-when-cross-origin" allowFullScreen /> </div> :::info Мы рекомендуем тестировать встроенные покупки на реальном устройстве. Хотя покупки в песочнице можно запускать на симуляторах, для полноценного тестирования всех флоу, включая диалоги оплаты и биометрические запросы, нужны реальные устройства. ::: Есть два основных способа тестировать встроенные покупки: - **Сборка в Xcode и запуск на тестовом устройстве**: удобно для разработчиков и QA-инженеров. - **Тестовый аккаунт в песочнице через TestFlight**: подходит для всех остальных. Оба варианта описаны в гайде ниже. ### Шаг 1. Создайте тестовый аккаунт в песочнице App Store Connect \{#step-1-create-sandbox-test-account-in-app-store-connect\} :::warning Создайте новый тестовый аккаунт в песочнице, чтобы история покупок была чистой. Если использовать существующий аккаунт, ранее купленные продукты останутся доступными и протестировать их повторную покупку не получится. ::: Создать новый тестовый аккаунт в песочнице можно в несколько кликов: 1. Перейдите в [**Users and Access** > **Sandbox** > **Test Accounts**](https://appstoreconnect.apple.com/access/users/sandbox) в App Store Connect и нажмите **+**. 2. Введите данные тестового пользователя. Обязательно укажите **Country or Region**, в которой планируете проводить тестирование, так как это влияет на доступность продуктов в регионе и валюту покупки. :::tip - Если вы пользуетесь Gmail или iCloud, вы можете использовать существующий адрес с [субадресацией через плюс](https://www.wikihow.com/Use-Plus-Addressing-in-Gmail). - Можно указать случайный несуществующий адрес, но тогда при входе на тестовом устройстве обязательно откажитесь от двухфакторной аутентификации (2FA). ::: <img src="/assets/shared/img/57c3a7c-apple_new_test_account.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Нажмите **Create**. ### Шаг 2. Включите режим разработчика \{#step-2-enable-the-developer-mode\} :::note Пропустите этот шаг, если режим разработчика **уже включён** на вашем тестовом устройстве или если у вас **нет Mac**. ::: Вам понадобится Mac с установленным Xcode и кабель для тестового устройства: 1. Откройте Xcode на Mac. Если вы планируете тестировать встроенные покупки через TestFlight, достаточно просто иметь установленный Xcode — открытый проект приложения не нужен. 2. Подключите тестовое устройство к Mac с помощью кабеля. 3. На тестовом устройстве перейдите в **Settings > Privacy & Security > Developer Mode** и включите **Developer Mode**. ### Шаг 3. Скачайте приложение из TestFlight \{#step-3-download-the-app-from-testflight\} :::info Этот шаг применим только при тестировании через TestFlight. Если вы собираете приложение в Xcode, пропустите его. ::: Подробнее о том, как отправить приложение в TestFlight, читайте в [документации Apple](https://developer.apple.com/documentation/StoreKit/testing-in-app-purchases-with-sandbox#Prepare-for-sandbox-testing). Перед загрузкой приложения через TestFlight убедитесь, что на тестовом устройстве выполнен вход с вашим реальным Apple Account. Затем скачайте тестируемое приложение из TestFlight. :::danger Не открывайте приложение после загрузки. Просто переходите к следующим шагам. Если вы случайно открыли его, удалите с тестового устройства и загрузите снова. Иначе история покупок может оказаться не чистой, и тестирование встроенных покупок приведёт к ошибкам. ::: ### Шаг 4. Переключитесь на тестовый аккаунт Sandbox \{#step-4-switch-to-sandbox-test-account\} <Details> <summary>Работаете не на Mac? Вот как это сделать</summary> Если вы не используете macOS, переключиться на аккаунт песочницы через Xcode не получится. Но это можно сделать прямо на тестовом устройстве: 1. Откройте **Settings > Your Apple Account > Media & Purchases** на тестовом устройстве. 2. В появившемся меню выберите **Sign Out**. 3. Откройте приложение, загруженное через TestFlight, и попробуйте купить продукт. 4. Когда появится запрос на вход, введите данные аккаунта песочницы, чтобы переключиться в среду песочницы. </Details> Чтобы переключиться на sandbox-аккаунт: 1. На тестовом устройстве откройте **Settings > Your Apple Account > Media & Purchases**. 2. В появившемся меню выберите **Sign Out**. 3. Перейдите в **Settings > Developer**. Если пункт **Developer** недоступен, убедитесь, что вы [включили его на шаге 2](#step-2-enable-the-developer-mode). <img src="/assets/shared/img/devmode.png" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Прокрутите вниз до раздела **Sandbox Apple Account** и нажмите **Sign In**. <img src="/assets/shared/img/sandbox-acc.png" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Войдите, используя данные вашего Sandbox Apple Account. ### Шаг 5. Очистите историю покупок \{#step-5-clear-purchase-history\} Если вы только что создали новый тестовый аккаунт в песочнице и переключились на него, этот шаг можно пропустить — он актуален только при повторном тестировании с одним и тем же тестовым аккаунтом. 1. Перейдите в **Settings > Developer > Sandbox Apple Account** на тестовом устройстве. 2. Выберите **Manage** во всплывающем меню. 3. Перейдите в **Account Settings** и нажмите **Clear Purchase History**. :::danger Этот шаг обязателен каждый раз, когда вы повторно тестируете с одним и тем же тестовым аккаунтом песочницы. В этом случае также нужно [выйти из тестового аккаунта песочницы](#step-4-switch-to-sandbox-test-account), а затем войти снова, чтобы очистить кеш истории покупок на тестовом устройстве. ::: ### Шаг 6. Сборка в Xcode и запуск \{#step-6-build-in-xcode-and-run\} :::info Этот шаг актуален только при тестировании через сборку Xcode. Если вы используете TestFlight, пропустите его. ::: 1. Подключите тестовое устройство к Mac. 2. Откройте Xcode. 3. Нажмите **Run** на панели инструментов или выберите **Product > Run**, чтобы собрать приложение и запустить его на подключённом устройстве. Если сборка прошла успешно, Xcode запустит приложение на устройстве и откроет сеанс отладки в области отладки. Теперь приложение готово к тестированию на устройстве. ### Шаг 7. Совершите тестовую покупку \{#step-7-make-test-purchase\} Откройте приложение и совершите тестовую покупку через пейвол. После этого перейдите к статье о [проверке тестовых покупок](validate-test-purchases), чтобы убедиться, что всё работает корректно. ### Шаг 8. Продолжайте тестирование \{#step-8-keep-testing\} Тестовая среда готова к работе. Если хотите протестировать снова, [очистите историю покупок тестового аккаунта в песочнице](https://developer.apple.com/help/app-store-connect/test-in-app-purchases/manage-sandbox-apple-account-settings/). ## Проблемы при тестировании \{#testing-issues\} Ниже перечислены распространённые проблемы, с которыми вы можете столкнуться при тестировании приложения. ### Проблемы с TestFlight \{#testflight-issues\} Очистить историю покупок **при использовании TestFlight без тестового аккаунта Sandbox** невозможно, что приводит к различным проблемам и некорректным результатам тестирования. Если вы случайно забыли [переключиться на тестовый аккаунт Sandbox](#step-4-switch-to-sandbox-test-account) и хотя бы раз открыли приложение, TestFlight привяжет историю покупок к вашему основному Apple Account, что вызовет непредвиденные проблемы. Чтобы исправить это, выполните следующие шаги: 1. Удалите приложение с тестового устройства. 2. Следуйте инструкциям по [тестированию в Sandbox](#sandbox-testing). :::note Важно не только переустановить приложение, но и переключиться на тестовый аккаунт Sandbox, очистить историю покупок и запустить приложение под этим тестовым аккаунтом. ::: ### Проблемы с общими уровнями доступа \{#shared-access-levels-issues\} Если вы повторно тестируете с одним и тем же Sandbox-аккаунтом, у тестового пользователя может возникнуть неожиданное поведение с [общими уровнями доступа](sharing-paid-access-between-user-accounts). Чтобы проверить, унаследовал ли пользователь уровень доступа, откройте [Profiles & Segments](https://app.adapty.io/profiles/users) в дашборде Adapty и перейдите в профиль пользователя. <img src="/assets/shared/img/profile-access-level-origin.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Если у пользователя унаследованный уровень доступа, для получения точных результатов тестирования выполните следующие шаги: 1. Удалите родительский профиль. 2. Удалите приложение с тестового устройства. 3. [Загрузите приложение из TestFlight](#step-3-download-the-app-from-testflight). 4. [Переключитесь на тестовый аккаунт Sandbox](#step-4-switch-to-sandbox-test-account). 5. [Очистите историю покупок](#step-5-clear-purchase-history). 6. [Откройте приложение и сделайте тестовую покупку](#step-6-make-test-purchase). :::note Очистка истории покупок сбрасывает покупку на стороне стора. Удаление родительского профиля удаляет только запись на стороне Adapty. О том, почему повторно используемый аккаунт сохраняет доступ и какие действия по сбросу действительно работают, читайте в разделе [Сброс подписки тестировщика](#resetting-a-testers-subscription). ::: ### Обновление приложения в TestFlight \{#updating-app-in-testflight\} Если приложение в TestFlight было обновлено: 1. Удалите приложение с тестового устройства. 2. [Загрузите приложение из TestFlight](#step-3-download-the-app-from-testflight). 3. [Переключитесь на тестовый аккаунт песочницы](#step-4-switch-to-sandbox-test-account). 4. [Очистите историю покупок](#step-5-clear-purchase-history). 5. [Откройте приложение и выполните тестовую покупку](#step-6-make-test-purchase). ## Сброс подписки тестировщика \{#resetting-a-testers-subscription\} В среде песочницы покупка привязана к **аккаунту Apple sandbox**, а не к профилю Adapty. Действия с профилем — его удаление или изменение уровня доступа — не удаляют покупку из аккаунта стора. При следующей переустановке или синхронизации SDK повторно привяжет ту же транзакцию, и тестировщик снова получит доступ. В таблице ниже показано, что именно меняет каждое действие по сбросу и что тестировщик увидит после него. | Действие | Профиль Adapty | Аккаунт Apple sandbox | Доступ тестировщика после | | :-------------------------------------------------------------------------------- | :------------------------------------------------------ | :--------------------- | :------------------------------------------------------------------------------------------------- | | Удалить профиль в дашборде Adapty | Удалён | Не затронут | **Возвращается** — при переустановке новый профиль заново привязывает ту же цепочку транзакций | | Удалить профиль через [Delete profile API](api-adapty/operations/deleteProfile) | Удалён | Не затронут | **Возвращается** — то же, что при удалении в дашборде | | Добавить прошедшую дату истечения через **Add access level** | Перезаписывается при следующей синхронизации | Не затронут | **Возвращается** при следующем обновлении — активная подписка применяет будущую дату истечения | | Вызвать [Revoke access level API](api-adapty/operations/revokeAccessLevel) | Истекает сейчас, генерирует событие `access_level_updated` (`is_active=false`) | Не затронут | **Возвращается** при следующем обновлении или переустановке — ненадёжный сброс для песочницы | | Отменить подписку в аккаунте sandbox | Прямых изменений нет | Подписка отменена | Обновления прекращаются, доступ заканчивается по истечении текущего периода, тестировщик может купить продукт снова | | Войти с новым аккаунтом Apple sandbox | Новый профиль | Новый, пустой аккаунт | **Чистый** — рекомендуется для повторного тестирования | ### Сброс тестировщика к чистому состоянию \{#reset-a-tester-to-a-clean-state\} Для повторного тестирования флоу покупок используйте новый аккаунт песочницы Apple для каждого теста вместо сброса профиля. Следуйте [Шагу 1](#step-1-create-sandbox-test-account-in-app-store-connect), чтобы создать аккаунт, и [Шагу 4](#step-4-switch-to-sandbox-test-account), чтобы переключиться на него на устройстве. Если вы повторно используете существующий аккаунт песочницы, сначала [очистите историю покупок](#step-5-clear-purchase-history) — удаление профиля Adapty её не очищает. ### Отзыв доступа у существующего тестировщика \{#remove-access-from-an-existing-tester\} Чтобы отозвать доступ у тестировщика, не нужно задним числом менять дату истечения или вызывать API отзыва уровня доступа. В песочнице подписка автоматически обновляется каждые несколько минут, и каждое обновление восстанавливает будущую дату истечения в рамках той же цепочки транзакций — то есть доступ возвращается сам по себе. API отзыва уровня доступа действительно генерирует событие `access_level_updated` (`is_active=false`), но следующее обновление подписки его перезаписывает. Чтобы действительно закрыть доступ, отмените подписку на стороне стора. На тестовом устройстве перейдите в **Settings > Developer > Sandbox Apple Account**, выберите **Manage** и отмените подписку. Продления прекратятся, а доступ закроется по истечении текущего периода. ### Почему удаление профиля возвращает доступ \{#why-deleting-the-profile-brings-access-back\} Когда тестировщик переустанавливает приложение, Adapty получает историю покупок аккаунта песочницы и привязывает новую установку к существующей покупке. Покупка привязана к аккаунту стора, а не к удалённому профилю. - **Анонимные профили**: При переустановке без `customer_user_id` уровень доступа аккаунта стора всегда наследуется, независимо от настройки [совместного доступа к платным функциям](sharing-paid-access-between-user-accounts). - **Идентифицированные профили**: Переносится ли доступ на новый `customer_user_id`, зависит от настройки совместного доступа к платным функциям. О том, как Adapty связывает эти профили в цепочку, см. [Как работают профили](how-profiles-work#parent-and-inheritor-profiles). ## Тестирование подписок \{#test-subscriptions\} При тестировании приложения через тестовый аккаунт песочницы можно настроить частоту обновления подписки для каждого тестировщика. Подробнее об изменении частоты обновления подписок в [официальной документации Apple](https://developer.apple.com/help/app-store-connect/test-in-app-purchases/manage-sandbox-apple-account-settings). По умолчанию подписки обновляются до 12 раз, после чего прекращаются, согласно следующему расписанию: | Длительность подписки | 1 неделя | 1 месяц | 2 месяца | 3 месяца | 6 месяцев | 1 год | | :-------------------------------- | :--------- | :--------- | :--------- | :--------- | :--------- | :--------- | | Скорость обновления подписки | 3 минуты | 5 минут | 10 минут | 15 минут | 30 минут | 1 час | | Длительность повторной оплаты | 10 минут | 10 минут | 10 минут | 10 минут | 10 минут | 10 минут | | Длительность льготного периода | 3 минуты | 5 минут | 5 минут | 5 минут | 5 минут | 5 минут | :::note Помните, что тестовые транзакции могут появляться в [Ленте событий](validate-test-purchases) до 10 минут. ::: Используйте песочницу, чтобы убедиться, что ваше приложение и бэкенд корректно обрабатывают продления, повторные попытки списания и льготные периоды — но не для предсказания времени продлений в продакшене. Ускоренный и ограниченный по количеству итераций график не соответствует продакшену. Чтобы воспроизвести транзакции на вашем сервере для тестирования бэкенда, используйте [Set transaction API](api-adapty/operations/setTransaction). ## Тестирование офферов \{#test-offers\} Для корректной проверки офферов необходимо удалить все чеки пользователя, чтобы критерии получения офферов работали правильно. Наиболее надёжный способ тестирования офферов — использовать полностью новый [тестовый аккаунт песочницы](#step-1-create-sandbox-test-account-in-app-store-connect). Повторное тестирование с одним и тем же тестовым аккаунтом песочницы может приводить к непредсказуемому поведению. :::danger Если вы повторно используете один и тот же тестовый аккаунт песочницы, обязательно [очистите историю покупок](#step-5-clear-purchase-history), чтобы избежать проблем с получением офферов. ::: --- # File: local-sk-files --- --- title: "Тестирование StoreKit в Xcode" description: "Тестируйте покупки в среде песочницы для обеспечения корректных транзакций." --- Тестирование StoreKit в Xcode позволяет проверять встроенные покупки локально без настройки аккаунта в песочнице. Для этого вида тестирования нужно: 1. [Создать продукт в Adapty](quickstart-products) и назначить ему **App Store product ID**. 2. В Xcode создать локальный [файл конфигурации StoreKit](https://developer.apple.com/documentation/xcode/setting-up-storekit-testing-in-xcode) и добавить в него продукт. Идентификатор продукта должен совпадать с **App Store product ID** в Adapty. 3. Добавить файл конфигурации StoreKit в схему сборки и собрать приложение. Запустить его на эмуляторе или на устройстве. ## Стоит ли использовать тестирование StoreKit в Xcode? \{#should-i-use-storekit-testing-in-xcode\} Этот способ тестирования наиболее удобен, если вы разработчик, который хочет проверить сборку на ходу или протестировать различные сценарии покупок с помощью инструментов Xcode. Однако важно помнить, что этот вид тестирования выполняется локально, поэтому никакие изменения не отобразятся в дашборде Adapty. Перед выпуском приложения в продакшн мы рекомендуем протестировать [работу с профилями](ios-quickstart-identify) в [среде песочницы](test-purchases-in-sandbox). Тестирование StoreKit **стоит** использовать, если нужно: - Протестировать логику покупок - Воспроизвести различные сценарии покупок с помощью инструментов Xcode (например, отменённый платёж или возврат средств) - Протестировать на эмуляторе Тестирование StoreKit **не стоит** использовать, если нужно: - Протестировать логику, связанную с профилями - Убедиться, что действия в приложении отображаются в дашборде Adapty - Передать приложение команде, не занимающейся разработкой, для тестирования ## Шаг 1. Создайте файл конфигурации StoreKit \{#step-1-create-a-storekit-configuration-file\} Чтобы создать файл конфигурации StoreKit в Xcode: 1. Нажмите **File > New > File from template**. Затем выберите **StoreKit Configuration File** и нажмите **Next**. 2. Задайте имя. Затем, в зависимости от того, есть ли у вас уже продукты в App Store Connect: - Выберите **Sync this file with an app in App Store Connect**: чтобы создать файл конфигурации со всеми вашими продуктами из App Store Connect для локального тестирования. - Не выбирайте **Sync this file with an app in App Store Connect**: чтобы создать пустой файл конфигурации, в который нужно будет добавить продукты вручную. Нажмите **Next**. 3. Не добавляйте приложение в качестве таргета. Просто продолжите. Если вы работаете с продуктами, синхронизированными из App Store Connect, перейдите к [Шагу 2](#step-2-add-the-configuration-file-to-the-build-scheme). 4. Если продукты не синхронизированы из App Store Connect, нажмите **+** в левом нижнем углу и выберите тип продукта. 5. Введите название группы подписок и нажмите **Next**. 6. Введите имя для ссылки. В поле **Product ID** укажите **App Store product ID** вашего продукта в Adapty. 7. Настройте цену, предложение и другие параметры продукта в файле конфигурации. При необходимости добавьте больше продуктов. ## Шаг 2. Добавьте файл конфигурации в схему сборки \{#step-2-add-the-configuration-file-to-the-build-scheme\} Чтобы собрать приложение с использованием этого файла конфигурации, нужно добавить его в схему сборки. Рекомендуется разделять схемы для тестирования и продакшна, поэтому предлагаем создать отдельную схему для тестирования: 1. Вверху нажмите на имя приложения и выберите **New scheme**. 2. Введите имя схемы и нажмите **OK**. 3. Снова нажмите на имя приложения и выберите **Edit scheme**. В поле **StoreKit configuration** выберите ваш локальный файл конфигурации, чтобы он использовался при сборке. ## Шаг 3. Соберите и протестируйте \{#step-3-build--test\} Теперь можно собрать приложение и тестировать встроенные покупки без подключения к бэкенду App Store. Вы можете совершать покупки и получать уровни доступа локально. Эти изменения не будут отражены в дашборде Adapty, но вы сможете проверить разблокировку платных функций локально. [Подробнее](https://developer.apple.com/documentation/xcode/testing-in-app-purchases-with-storekit-transaction-manager-in-code) о других возможностях тестирования StoreKit в Xcode. --- # File: testing-on-android --- --- title: "Тестирование встроенных покупок в Google Play Store" description: "Тестируйте покупки подписок на Android с помощью Adapty." --- Тестирование встроенных покупок в Android-приложении — важный шаг перед публичным релизом. Тестирование в песочнице позволяет проверить покупки без реального списания денег. В этом гайде мы разберём, как тестировать встроенные покупки в песочнице Google Play Store для Android. :::note **Транзакции из песочницы не отображаются в аналитических графиках.** Они по-прежнему видны на страницах отдельных профилей и в ленте событий. ::: ## Среда тестирования \{#testing-environment\} Для наилучших результатов рекомендуем тестировать Android-приложение на реальном устройстве, а не на эмуляторе. Несмотря на то что эмуляторы тоже работают, Google рекомендует использовать физическое устройство. Если всё же решите использовать эмулятор, убедитесь, что на нём установлен Google Play — это необходимо для корректной работы приложения. ## 1. Настройте тестовый аккаунт для тестирования приложения \{#1-set-up-test-account-for-app-testing\} Чтобы упростить тестирование на более поздних этапах разработки, вам нужно настроить тестового пользователя для тестирования встроенных покупок. Именно с этого аккаунта вы впервые войдёте на своём Android-устройстве. Обратите внимание: основной аккаунт на Android-устройстве можно сменить только через сброс до заводских настроек, который удаляет все данные. Поэтому важно заранее правильно настроить тестовый аккаунт, чтобы не прибегать к сбросу. :::important Способ настройки тестового аккаунта зависит от устройства, которое вы используете: - Если у вас есть отдельное устройство для тестирования, создайте **отдельный тестовый аккаунт (новый аккаунт Gmail)**. - Если отдельного устройства нет, можно использовать **личный аккаунт** и временно включить для него **License testing**. - Если Android-устройства нет совсем, можно **создать отдельный тестовый аккаунт и использовать его с эмулятором**. Однако этот подход не рекомендуется, так как не позволяет выявить все возможные проблемы, характерные для реальных устройств. ::: ## 2. Включите тестирование лицензий \{#enable-license-testing\} После настройки тестового аккаунта необходимо настроить тестирование лицензий для вашего приложения. Для этого выполните следующие шаги: 1. В боковой панели Google Play Console перейдите в **Settings** и выберите **License testing** в разделе **Monetization**. <img src="/assets/shared/img/android-license-testing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Выберите существующий список тестировщиков лицензий или создайте новый. <img src="/assets/shared/img/android-testers.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Добавьте в список аккаунт, который будете использовать для тестирования, и сохраните изменения. Если другие члены команды тоже должны тестировать приложение, добавьте их email-адреса в список — тогда доступ получит вся группа. <img src="/assets/shared/img/android-list.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## 3. Создайте закрытый трек и добавьте тестовый аккаунт \{#3-create-closed-track-and-add-test-account-to-it\} Чтобы начать тестирование, нужно опубликовать подписанную версию приложения в закрытом треке: 1. Откройте своё приложение и выберите в меню **Test and release > Testing > Closed testing**. Нажмите **Create track**. <img src="/assets/shared/img/android-closed-testing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Введите название трека закрытого тестирования и нажмите **Create track**. 3. Добавьте список тестировщиков в трек. 4. В разделе **How testers join your test** скопируйте ссылку и отправьте её на устройство, залогиненное в тестовый аккаунт. Откройте ссылку на тестовом устройстве, чтобы сделать пользователя тестировщиком. <img src="/assets/shared/img/android-link.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::warning Учтите следующее для успешного тестирования: - Открытие opt-in URL отмечает ваш аккаунт Play для тестирования. Если не выполнить этот шаг, продукты не загрузятся. - Разработчики часто используют другой application ID для тестовых сборок. Это создаст проблемы, так как Google Play Services использует application ID для поиска встроенных покупок. - В некоторых случаях тестовый пользователь может приобрести расходуемые покупки, но не подписки, если на тестовом устройстве не установлен PIN-код. Это может проявляться в виде загадочного сообщения «Something went wrong». Убедитесь, что на тестовом устройстве установлен PIN-код и оно авторизовано в Google Play Store. ::: ## 4. Загрузите подписанный APK в закрытый трек \{#4-upload-a-signed-apk-to-the-closed-track\} Создайте подписанный APK или используйте Android App Bundle, чтобы загрузить подписанный APK в только что созданный закрытый трек. Выпускать релиз не обязательно — достаточно просто загрузить APK. Подробнее об этом читайте в [этой](https://support.google.com/googleplay/android-developer/answer/9859348?visit_id=638929100639477968-3849460621&rd=1) справочной статье. :::important Если ваше приложение новое, возможно, потребуется сделать его доступным в вашей стране или регионе. Для этого перейдите в **Testing > Closed testing**, нажмите на нужный тестовый трек и откройте **Countries/regions**, чтобы добавить нужные страны и регионы. ::: ## 5. Тестируйте встроенные покупки \{#5-test-in-app-purchases\} После загрузки APK подождите несколько минут, пока релиз обработается. Затем откройте тестовое устройство и войдите с email-аккаунтом, добавленным в список тестировщиков. После этого можно тестировать встроенные покупки так же, как в продакшн-приложении. <img src="/assets/shared/img/a8d2da9-image.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Читайте также \{#read-more\} Дополнительные материалы по тестированию встроенных покупок в Android-приложениях: - [Периоды обновления в песочнице](https://developer.android.com/google/play/billing/test#subs) - [Тестирование разовых покупок](https://developer.android.com/google/play/billing/test#one-time) --- # File: validate-test-purchases --- --- title: "Проверка тестовых покупок" description: "Проверяйте тестовые покупки в Adapty для обеспечения корректной обработки транзакций." --- Перед выпуском мобильного приложения в продакшн важно тщательно протестировать встроенные покупки. Подробные инструкции по тестированию можно найти в статьях [Тестирование встроенных покупок в Apple App Store](test-purchases-in-sandbox) и [Тестирование встроенных покупок в Google Play Store](testing-on-android). После начала тестирования необходимо убедиться, что тестовые покупки прошли успешно. Каждый раз, совершая тестовую покупку на мобильном устройстве, проверяйте соответствующую транзакцию в [**Event Feed**](https://app.adapty.io/event-feed) в дашборде Adapty. Если покупка не отображается в **Event Feed**, значит Adapty её не отслеживает. ## Тестовая покупка прошла успешно \{#test-purchase-is-successful\} Если тестовая покупка прошла успешно, событие транзакции отобразится в **Event Feed**: <img src="/assets/shared/img/9ade2d5-event_feed_sandbox.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Если транзакции работают корректно, перейдите к [чеклисту перед релизом](release-checklist) и затем выпустите приложение. ## Тестовая покупка не прошла \{#test-purchase-is-not-successful\} Если в течение 10 минут событие транзакции не появилось или в мобильном приложении возникла ошибка, обратитесь к статье [Устранение неполадок](troubleshooting-test-purchases) и материалам по обработке ошибок: [для iOS](ios-sdk-error-handling), [для Android](android-sdk-error-handling), [для React Native](react-native-handle-errors), [для Flutter](error-handling-on-flutter-react-native-unity), [для Unity](unity-handle-errors) и [Kotlin Multiplatform](kmp-handle-errors). <img src="/assets/shared/img/31a79b2-no_events.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: troubleshooting-test-purchases --- --- title: "Устранение проблем с тестовыми покупками" description: "Устраните проблемы с тестовыми покупками в Adapty и решите распространённые ошибки встроенных транзакций." --- Если вы столкнулись с проблемами при транзакциях, сначала убедитесь, что выполнили все шаги из [чеклиста для релиза](release-checklist). Если вы всё проверили, но проблемы не исчезли, воспользуйтесь рекомендациями ниже: ## Мобильное приложение возвращает ошибку \{#an-error-is-returned-in-the-mobile-app\} Обратитесь к списку ошибок для вашей платформы: [для iOS](ios-sdk-error-handling), [для Android](android-sdk-error-handling), [для React Native](react-native-troubleshoot-purchases), [Flutter](error-handling-on-flutter-react-native-unity) и [Unity](unity-troubleshoot-purchases) — и следуйте нашим рекомендациям. ## Транзакция отсутствует в Event Feed, хотя приложение не вернуло ошибку \{#transaction-is-absent-from-the-event-feed-although-no-error-is-returned-in-the-mobile-app\} Чтобы решить эту проблему, проверьте следующее: 1. **Для iOS**: убедитесь, что используете реальное устройство, а не симулятор. 2. Убедитесь, что `Bundle ID`/`Package name` вашего приложения совпадает с тем, что указан в [**App settings**](https://app.adapty.io/settings/general). 3. Убедитесь, что `PUBLIC_SDK_KEY` в приложении совпадает с **Public SDK key** в дашборде Adapty: [**App settings** -> вкладка **General** -> раздел **API keys**](https://app.adapty.io/settings/general). 4. Убедитесь, что используете sandbox-аккаунт, а не [локальный файл конфигурации StoreKit](local-sk-files). Если вы ранее использовали локальный файл конфигурации StoreKit для тестирования, проверьте, что он не подключён в текущей сборке. ## В моём тестовом профиле нет событий \{#no-event-is-present-in-my-testing-profile\} Это нормальное поведение. Новая запись профиля пользователя автоматически создаётся в Adapty в следующих случаях: - пользователь запускает приложение впервые; - пользователь выходит из приложения. **Почему так происходит:** все транзакции и события привязаны к профилю, который совершил первую транзакцию. Это позволяет хранить всю историю транзакций (пробные периоды, покупки, продления) в рамках одного профиля. **Что вы увидите:** новые записи профилей (так называемые «неоригинальные профили») могут появляться без событий, но с сохранёнными уровнями доступа. Вы можете видеть события `access_level_updated` — это ожидаемое поведение. **При тестировании:** чтобы не плодить несколько профилей, создавайте новый тестовый аккаунт (Sandbox Apple ID) при каждой переустановке приложения. Подробнее см. в разделе [Создание профиля](how-profiles-work#profile-creation). Ниже показан пример неоригинального профиля. Обратите внимание: в разделе **User history** нет событий, но уровень доступа присутствует. <img src="/assets/shared/img/98d0dad-non-original_profile.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Цены не соответствуют тем, что установлены в App Store Connect \{#prices-do-not-reflect-the-actual-prices-set-in-app-store-connect\} В Sandbox и TestFlight (который также использует sandbox-среду для встроенных покупок) важно убедиться, что процесс покупки работает корректно, а не проверять точность цен. API Apple иногда возвращает некорректные данные, особенно когда для устройств или аккаунтов настроены разные регионы. Поскольку цены поступают напрямую из стора и бэкенд Adapty никак не влияет на цены покупок, любые расхождения в ценах при тестировании покупок через Adapty можно игнорировать. Сосредоточьтесь на проверке самого процесса покупки, а не точности цен. ## Время транзакции в Event Feed отображается некорректно \{#the-transaction-time-in-the-event-feed-is-incorrect\} В **Event Feed** используется часовой пояс, заданный в **App Settings**. Чтобы время событий совпадало с вашим местным временем, измените **Reporting timezone** в разделе [**App settings** -> вкладка **General**](https://app.adapty.io/settings/general). ## Пейволы и продукты загружаются слишком долго \{#paywalls-and-products-take-a-long-time-to-load\} Эта проблема может возникать, если у тестового аккаунта длинная история транзакций. Мы настоятельно рекомендуем каждый раз создавать новый тестовый аккаунт, как описано в разделе [Создание тестового Sandbox-аккаунта (Sandbox Apple ID) в App Store Connect](test-purchases-in-sandbox#step-1-create-sandbox-test-account-in-app-store-connect). Если создать новый аккаунт не получается, можно очистить историю транзакций текущего аккаунта на iOS-устройстве: 1. Откройте **Settings** и нажмите **App Store**. 2. Нажмите на свой **Sandbox Apple ID**. 3. Во всплывающем окне выберите **Manage**. 4. На странице **Account Settings** нажмите **Clear Purchase History**. Подробнее см. в [документации Apple Developer](https://developer.apple.com/documentation/storekit/testing-in-app-purchases-with-sandbox). --- # File: test-devices --- --- title: "Тестовые устройства" description: "Узнайте, как управлять тестовыми устройствами в Adapty для эффективного тестирования приложений." --- В целях тестирования вы можете назначить своё устройство тестовым — это отключает кэширование и гарантирует, что внесённые изменения отображаются сразу. :::note Тестовые устройства поддерживаются начиная со следующих версий SDK: - iOS: 2.11.1 - Android: 2.11.3 - React Native: 2.11.1 Поддержка Flutter и Unity будет добавлена позже. ::: ## Отметить устройство как тестовое \{#mark-your-device-as-test\} 1. Откройте [**App settings**](https://app.adapty.io/settings/general) в дашборде Adapty. 2. Прокрутите страницу вниз до раздела **Test devices** на вкладке **General**. <img src="/assets/shared/img/14c581d-test_device_add.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Нажмите кнопку **Add test device**. <img src="/assets/shared/img/f86d5e2-test_users_add_device.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. В окне **Add test device** заполните поля: | Поле | Описание | |:-----------------------------------------| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Test device name** | Название тестового устройства (или устройств) для вашего удобства. | | **ID used to identify this test device** | Выберите тип идентификатора для определения тестового устройства (или устройств). Следуйте нашим рекомендациям в разделе [Какой идентификатор использовать](test-devices#which-identifier-you-should-use) ниже, чтобы выбрать оптимальный вариант. | | **ID value** | Введите значение идентификатора. | 5. Не забудьте нажать кнопку **Add test device**, чтобы сохранить изменения. ## Какой идентификатор использовать \{#which-identifier-you-should-use\} Для идентификации устройства можно использовать несколько типов идентификаторов. Мы рекомендуем следующие: - **Customer User ID** — для устройств iOS и Android, если вы <InlineTooltip tooltip="идентифицируете пользователей в Adapty">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users) и [Unity](unity-identifying-users)</InlineTooltip>. Это наилучший выбор, особенно если у вас более одного тестового устройства для одного аккаунта в приложении. Если в качестве **ID used to identify this test device** указан Customer User ID, все устройства, привязанные к этому аккаунту, будут отмечены как тестовые. - **IDFA (iOS)** и **Advertising ID (Android)**: эти рекламные идентификаторы отлично подходят для iOS и Android соответственно, если вы уже запрашиваете у пользователей согласие на доступ к ним. Даже если у вас есть Customer User ID, рекламные идентификаторы могут быть предпочтительнее, если во время тестирования вы переключаетесь между аккаунтами в приложении. Кроме того, они удобны, когда одному аккаунту принадлежат и тестовые, и личные устройства, и вы не хотите помечать личные устройства как тестовые. Существуют и другие варианты — Adapty Profile ID, IDFV и Android ID. Они менее удобны, но могут использоваться, если Customer User ID, IDFA или Advertising ID недоступны. Рассмотрим все возможные варианты подробнее. ### Идентификаторы для всех платформ \{#identifiers-for-all-platforms\} | Идентификатор | Использование | |----------|-----| | Customer User ID | <p>Уникальный идентификатор, который вы задаёте самостоятельно для идентификации пользователей в вашей системе. Это может быть email пользователя, ваш внутренний ID или любая другая строка. Для использования этого варианта необходимо <InlineTooltip tooltip="идентифицировать пользователей в Adapty">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users) и [Unity](unity-identifying-users)</InlineTooltip>.</p><p></p><p>Это лучший выбор для идентификации тестового устройства, особенно если вы используете несколько устройств для одного аккаунта. Все устройства с этим аккаунтом будут считаться тестовыми.</p> | | Adapty profile ID | <p>Уникальный идентификатор [профиля пользователя](profiles-crm) в Adapty.</p><p></p><p>Используйте его, если не можете применить Customer User ID, IDFA для iOS или Advertising ID для Android. Обратите внимание: Adapty Profile ID может измениться при переустановке приложения или повторном входе.</p> | #### Как получить Customer User ID и Adapty profile ID \{#how-to-obtain-customer-user-id-and-adapty-profile-id\} Оба идентификатора можно найти в деталях **Profile** в дашборде Adapty: 1. Найдите профиль пользователя во вкладке [**Adapty Profiles** -> **Event feed**](https://app.adapty.io/event-feed). :::note Чтобы найти нужный профиль, совершите редкий тип транзакции. Когда она появится в [**Event Feed**](https://app.adapty.io/event-feed), вы легко её распознаете. ::: 2. Скопируйте значения полей **Customer user ID** и **Adapty ID** в деталях профиля: <img src="/assets/shared/img/345d308-test_users_CUID_adapty_ID.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Идентификаторы Apple \{#apple-identifiers\} | Идентификатор | Использование | |----------|-----| | IDFA | <p>Identifier for Advertisers (IDFA) — уникальный идентификатор устройства, присваиваемый Apple.</p><p></p><p>Идеально подходит для iOS-устройств: он не меняется сам по себе, хотя вы можете сбросить его вручную.</p><p>**Примечание**: начиная с iOS 14.5 рекламодатели обязаны запрашивать согласие пользователя на доступ к IDFA. Убедитесь, что ваше приложение запрашивает такое согласие и что вы его предоставили на тестовом устройстве.</p> | | IDFV | Identifier for Vendors (IDFV) — уникальный буквенно-цифровой идентификатор, присваиваемый Apple всем приложениям одного издателя/поставщика на одном устройстве. Может измениться при переустановке или обновлении приложения. | #### Как получить IDFA \{#how-to-obtain-the-idfa\} Apple не предоставляет IDFA по умолчанию. Его можно получить из атрибуции профиля в дашборде Adapty: 1. Найдите профиль пользователя во вкладке [**Adapty Profiles** -> **Event feed**](https://app.adapty.io/event-feed). :::note Чтобы найти нужный профиль, совершите редкий тип транзакции. Когда она появится в [**Event Feed**](https://app.adapty.io/event-feed), вы легко её распознаете. ::: 2. Откройте детали профиля и скопируйте значение поля **IDFA** в разделе **Attributes**: <img src="/assets/shared/img/ce4a63f-test_users_idfa.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Также можно [найти в App Store приложение, которое покажет вам ваш IDFA](https://www.apple.com/us/search/idfa?src=globalnav). #### Как получить Identifier for Vendors (IDFV) \{#how-to-obtain-the-identifier-for-vendors-idfv\} Чтобы получить IDFV, попросите разработчика запросить его с помощью следующего метода в вашем приложении и вывести полученный идентификатор в логи или панель отладки. ```swift showLineNumbers title="Swift" UIDevice.current.identifierForVendor ``` ### Идентификаторы Google \{#google-identifiers\} | Идентификатор | Использование | |----------|-----| | Advertising ID | <p>Advertising ID — уникальный идентификатор устройства, присваиваемый Google.</p><p>Идеально подходит для Android-устройств: он не меняется сам по себе, хотя вы можете сбросить его вручную.</p><p>**Примечание**: для использования этого идентификатора отключите параметр **Opt out of Ads Personalization** в настройках **Ads**, если вы используете Android 12 или более позднюю версию.</p>| | Android ID | Android ID — уникальный идентификатор для каждой комбинации ключа подписи приложения, пользователя и устройства. Доступен на Android 8.0 и выше. | #### Как получить Advertising ID \{#how-to-obtain-advertising-id\} Чтобы найти рекламный идентификатор устройства: 1. Откройте приложение **Settings** на вашем Android-устройстве. 2. Нажмите **Google**. 3. Выберите **Ads** в разделе **Services**. Ваш рекламный идентификатор будет указан внизу экрана. #### Как получить Android ID \{#how-to-obtain-android-id\} Чтобы получить Android ID, попросите разработчика запросить [ANDROID_ID](https://developer.android.com/reference/android/provider/Settings.Secure#ANDROID_ID) с помощью следующего метода в вашем приложении и вывести полученный идентификатор в логи или панель отладки. ```kotlin showLineNumbers title="Kotlin/Java" android.provider.Settings.Secure.getString(contentResolver, android.provider.Settings.Secure.ANDROID_ID); ``` --- # File: release-checklist --- --- title: "Чеклист перед релизом" description: "Следуйте чеклисту Adapty, чтобы обеспечить плавный процесс обновления приложения." --- Рады, что вы выбрали Adapty! Надеемся, интеграция прошла гладко. Этот гайд поможет убедиться, что приложение готово к публикации в сторах, а монетизация работает корректно. ## Что нужно подготовить заранее \{#pre-flight-essentials\} Перед началом проверки убедитесь, что у вас есть: - Реальное устройство с sandbox-аккаунтом - Доступ к дашборду Adapty - Доступ к App Store Connect / Google Play Console :::note Хотя sandbox-покупки можно тестировать на симуляторах, реальные устройства необходимы для полноценного тестирования всех сценариев — включая диалоги оплаты и биометрическую аутентификацию. ::: <Button id="test-purchases-in-sandbox"> Гайд по тестированию для App Store </Button> <Button id="testing-on-android"> Гайд по тестированию для Google Play </Button> ## Универсальные проверки \{#universal-validations\} - [ ] **Подключение стора**: Убедитесь, что вы подключили Adapty к App Store и/или Google Play: - [ ] [App Store](initial_ios) - [ ] [Google Play](initial-android) - [ ] **Доставка событий подписки**: Убедитесь, что серверные уведомления настроены: - [ ] [Серверные уведомления App Store](enable-app-store-server-notifications) - [ ] [Уведомления разработчика в реальном времени (RTDN)](enable-real-time-developer-notifications-rtdn) - [ ] **Идентификация профиля**: Проверьте логику идентификации пользователей и убедитесь, что покупки привязываются к нужному профилю: - [ ] [Убедитесь, что логика идентификации в коде приложения соответствует вашему сценарию использования](ios-quickstart-identify) - [ ] [Убедитесь, что вы понимаете логику parent/inheritor при совместном использовании платного доступа между профилями пользователей](sharing-paid-access-between-user-accounts) - [ ] **Офферы**: Если в приложении используются promotional offer из App Store, убедитесь, что вы [добавили ключ встроенных покупок](app-store-connection-configuration#step-4-for-trials-and-special-offers--set-up-promotional-offers) как в основное поле, так и в раздел **App Store promotional offers**. - [ ] **Сбор данных**: Убедитесь в соответствии требованиям конфиденциальности: - [ ] Если вам необходимо соответствовать требованиям законодательства о конфиденциальности (например, GDPR или CCPA) или приложение предназначено для детей, управляйте тем, [включён ли сбор и передача IDFA и IP-адреса](sdk-installation-ios#data-policies). - [ ] Если в приложении используется AppTrackingTransparency, убедитесь, что вы [передаёте статус авторизации в Adapty](ios-deal-with-att). - [ ] **Метки конфиденциальности**: [Узнайте подробнее](apple-app-privacy) о том, какие данные собирает Adapty и какие флаги нужно выставить при проверке. ## Проверка покупок \{#purchase-validations\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Перед запуском убедитесь, что покупки в вашем приложении работают корректно и пейвол готов к ревью в сторе. Способ проверки встроенных покупок зависит от того, как вы их реализовали: - Вы отображаете пейвол, созданный в Adapty Paywall Builder - Вы реализовали собственный пейвол и используете метод `makePurchase` внутри него для обработки покупок - Вы используете Adapty в режиме наблюдателя (как с Adapty Paywall Builder, так и с собственным пейволом) <Tabs groupId="paywall" queryString> <TabItem value="builder" label="Adapty Paywall Builder" default> **Цель**: Adapty отображает пейвол, пользователи могут покупать продукты, доступ открывается, а флоу восстановления покупок работает. - [ ] Ваше приложение [отображает пейвол](ios-present-paywalls) из того же плейсмента, который вы будете выпускать. - [ ] Пейвол отображается на экране. Если загрузка занимает слишком много времени (например, при нестабильном интернете у вас или ваших пользователей), рассмотрите возможность [настройки политики загрузки](get-pb-paywalls#fetch-paywall-designed-with-paywall-builder). - [ ] Пейвол соответствует ожидаемому варианту (аудитория/локаль, если применимо). При необходимости вы можете [изменить приоритет аудитории](change-audience-priority). - [ ] Продукты и цены отображаются на пейволе. Обратите внимание, что API Apple иногда может предоставлять некорректные цены во время тестирования (особенно при различных региональных настройках), поэтому уделяйте приоритет тестированию функциональности процесса покупки, а не точности цен, — Adapty не влияет на цены в сторе. - [ ] Покупка в песочнице завершается успешно. Получен коллбэк об успешной покупке. - [ ] Доступ открывается и сохраняется. Убедитесь, что [платный доступ предоставляется на основе текущего профиля Adapty](ios-check-subscription-status#connect-profile-with-paywall-logic). - [ ] После покупки профиль Adapty содержит активный уровень доступа. - [ ] Платные функции открываются, когда профиль содержит этот уровень доступа (а не только по коллбэку покупки). - [ ] Восстановление покупок работает. При переустановке приложения или установке на новое устройство автоматическое восстановление покупок работает согласно настройке [Sharing paid access](sharing-paid-access-between-user-accounts). Если у вас нет бэкенд-аутентификации, покупки восстанавливаются автоматически независимо от настройки. В остальных случаях убедитесь, что пользователи могут восстановить покупки после переустановки приложения. - [ ] Требования для ревью стора: - [ ] Кнопка **Restore purchases** присутствует на пейволе. Вы можете добавить её в Paywall Builder, и при нажатии она будет автоматически обрабатывать восстановление покупок. - [ ] Условия использования и политика конфиденциальности доступны с экрана пейвола, а нажатие на эти ссылки открывает их в браузере. </TabItem> <TabItem value="makepurchase" label="Пользовательский пейвол (makePurchase)" default> **Цель**: вы отрисовываете UI; Adapty обрабатывает покупки, обновления профиля и восстановления. - [ ] Идентификаторы продуктов не зашиты в коде приложения. В коде хардкодятся только идентификаторы [плейсментов](placements). - [ ] Приложение [загружает продукты](fetch-paywalls-and-products) из того же плейсмента, который будет в проде. - [ ] Список продуктов загружается успешно. Если загрузка занимает слишком много времени (например, при нестабильном интернете у вас или пользователей), рассмотрите возможность [изменить политику загрузки](fetch-paywalls-and-products#fetch-paywall-information). - [ ] Загруженные продукты соответствуют ожидаемому варианту (аудитории/локали, если применимо). При необходимости можно [изменить приоритет аудитории](change-audience-priority). - [ ] Продукты и цены отображаются на пейволе. Обратите внимание: Apple API иногда возвращает неточные цены в процессе тестирования (особенно при разных региональных настройках), поэтому при тестировании важнее проверить корректность самого процесса покупки, а не точность цен — на цены в сторе Adapty не влияет. - [ ] Покупка в [песочнице](making-purchases) через `makePurchase` завершается успешно: - [ ] Успешный результат покупки обрабатывается. - [ ] Статусы «ожидание», «ошибка» и «отмена» обрабатываются корректно. - [ ] Если вы [используете Remote Config](present-remote-config-paywalls), его значения корректно передаются на пейвол. - [ ] При показе пейвола вызывается метод [`logShowFlow` (iOS SDK v4+) / `logShowPaywall`](present-remote-config-paywalls#track-paywall-view-events). - [ ] Покупка в песочнице завершается успешно. Получен коллбэк об успешной покупке. - [ ] Доступ разблокируется и сохраняется. Убедитесь, что [платный доступ предоставляется на основе текущего профиля Adapty](ios-check-subscription-status#connect-profile-with-paywall-logic). - [ ] После покупки в профиле Adapty активен соответствующий уровень доступа. - [ ] Платные функции разблокируются при наличии этого уровня доступа в профиле, а не только по коллбэку покупки. - [ ] Восстановление покупок работает. При переустановке приложения или установке на новом устройстве автоматическое восстановление покупок работает в соответствии с настройкой [общего доступа к покупкам](sharing-paid-access-between-user-accounts). Если у вас нет серверной аутентификации, покупки восстанавливаются автоматически независимо от этой настройки. В остальных случаях убедитесь, что пользователи могут восстановить покупки после переустановки приложения. - [ ] Требования для ревью в сторе: - [ ] Кнопка **Restore purchases** доступна и [корректно обрабатывает восстановление покупок](restore-purchase). - [ ] Пользовательское соглашение и политика конфиденциальности доступны с экрана пейвола, а нажатие на соответствующие ссылки открывает их в браузере. </TabItem> <TabItem value="observer" label="Режим наблюдателя"> **Цель**: Вы самостоятельно обрабатываете покупки, обновления профиля и восстановление; Adapty получает отчёты о транзакциях. - [ ] **Ваше приложение завершает покупки через собственный флоу покупки** (StoreKit / BillingClient / бэкенд): - [ ] Покупка в песочнице успешно проходит в интерфейсе стора. - [ ] Незавершённые/неудачные/отменённые сценарии корректно обрабатываются в приложении. - [ ] **Транзакции передаются в Adapty**. - [ ] Observer mode [включён в коде приложения](implement-observer-mode). - [ ] Покупка отображается в ленте событий Adapty. - [ ] Обновления, отмены и возвраты отражаются со временем (при наличии). - [ ] **Отслеживаются просмотры пейвола**. Метод [`logShowFlow` (iOS SDK v4+) / `logShowPaywall`](present-remote-config-paywalls#track-paywall-view-events) вызывается при показе пейвола. - [ ] **Восстановление покупок работает в вашей реализации**. Переустановка приложения или смена устройства корректно восстанавливает доступ. - [ ] **Требования к проверке стором**: - [ ] Действие **Restore purchases** доступно и запускает флоу восстановления. - [ ] Условия использования и политика конфиденциальности доступны с пейвола или экрана покупки и открываются в браузере. </TabItem> </Tabs> Если у вас возникнут вопросы по интеграции SDK, воспользуйтесь AI-чатботом в правом нижнем углу или напишите нам на [support@adapty.io](mailto:support@adapty.io). --- # File: submit-app-to-app-store --- --- title: "Отправка iOS-приложения в App Store" description: "Загрузите сборку в App Store Connect и отправьте iOS-приложение с подписками на проверку Apple." --- Когда интеграция с Adapty протестирована и работает, можно загружать сборку в App Store Connect и отправлять приложение на проверку Apple. :::tip Перед отправкой убедитесь, что вы прошли [чеклист перед релизом](release-checklist): проверьте интеграцию с Adapty, покупки и требования стора к проверке. ::: ## Загрузка сборки в App Store Connect \{#upload-your-build-to-app-store-connect\} ### Шаг 1. Архивация приложения в Xcode и загрузка в App Store Connect \{#step-1-archive-your-app-in-xcode-and-upload-it-to-app-store-connect\} 1. В Xcode укажите цель сборки **Any iOS Device (arm64)**. <img src="/assets/shared/img/build-target.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> 2. В верхнем меню выберите **Product** > **Archive**. <img src="/assets/shared/img/xcode-archive.webp" style={{ border: '1px solid #727272', width: '500px', display: 'block', margin: '0 auto' }} /> 3. Дождитесь завершения архивации. Окно **Organizer** откроется автоматически. Выберите архив и нажмите **Distribute App**. <img src="/assets/shared/img/distribute-app.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> 4. В качестве метода распространения выберите **App Store Connect** и следуйте инструкциям для завершения загрузки. :::note Загрузка может завершиться ошибкой, если отсутствуют необходимые ресурсы — например, иконка приложения или экран запуска. Подробности смотрите в журнале ошибок Xcode. ::: <img src="/assets/shared/img/distribution-method.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> ### Шаг 2. Проверка сборки в App Store Connect \{#step-2-check-the-build-in-app-store-connect\} 1. Перейдите в [App Store Connect](https://appstoreconnect.apple.com) и откройте своё приложение. 2. Прокрутите до раздела **Build** и убедитесь, что только что загруженная сборка там отображается. :::note После загрузки сборка может появиться в App Store Connect через несколько минут. ::: <img src="/assets/shared/img/app-store-build.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> ## Отправка приложения и продуктов на проверку \{#submit-your-app-and-products-for-review\} После того как сборка появится в разделе **Build**, прикрепите встроенные подписки и отправьте приложение на проверку Apple. ### Шаг 1. Прикрепление продуктов к заявке \{#step-1-attach-products-to-the-submission\} Каждая подписка должна иметь статус **Ready to Submit** в App Store Connect, прежде чем её можно будет прикрепить. Если подписка всё ещё в черновике или у неё отсутствуют метаданные, она не появится в списке. 1. На той же странице прокрутите до раздела **In-App Purchases and Subscriptions**. 2. Нажмите **Select in-app purchases or subscriptions**. <img src="/assets/shared/img/app-store-select-products.webp" style={{ border: '1px solid #727272', width: '700px', display: 'block', margin: '0 auto' }} /> 3. Выберите все продукты, которые хотите включить в заявку, и нажмите **Done**. ### Шаг 2. Отправка на проверку \{#step-2-submit-for-review\} 1. Заполните все обязательные поля на странице (описание, скриншоты, ключевые слова и т. д.). 2. В разделе **App Store Version Release** выберите, как выпустить приложение после одобрения: автоматически, вручную или по расписанию. 3. Нажмите **Add for Review**, затем **Submit to App Review**. Apple проверяет приложения в течение 1–2 дней, хотя сроки могут варьироваться. ## Проверка приложения в продакшне \{#verify-your-app-in-production\} После одобрения Apple: 1. Совершите реальную покупку (или дождитесь первой покупки от пользователя). 2. Откройте [**Event Feed**](https://app.adapty.io/event-feed) в дашборде Adapty и убедитесь, что там появляются события транзакций из продакшна. 3. Проверьте, что события подписок (продления, отмены) передаются корректно — это зависит от настроенных [серверных уведомлений App Store](enable-app-store-server-notifications). Если события продакшна не появляются, проверьте [настройки подключения к App Store](app-store-connection-configuration). ## Следующие шаги \{#next-steps\} Ваше приложение запущено. Начните наращивать доход от подписок: - **[A/B-тестирование](ab-tests)**: Экспериментируйте с разными пейволами, чтобы найти наиболее конверсионный вариант. - **[Аналитика](charts)**: Отслеживайте метрики подписок: MRR, отток, конверсию. - **Интеграции**: Отправляйте события подписок на платформы [аналитики](analytics-integration) и [атрибуции](attribution-integration). --- # File: general --- --- title: "Настройки приложения" description: "Ознакомьтесь с общими настройками и конфигурациями в Adapty для удобной работы." --- Перейдите на вкладку **General** страницы **App Settings**, чтобы управлять поведением, внешним видом приложения и распределением дохода. Здесь можно изменить название и иконку приложения, управлять ключами Adapty SDK и API, настроить статус Small Business Program, а также выбрать часовой пояс для аналитики и графиков приложения. ## 1. Детали приложения \{#1-app-details\} <img src="/assets/shared/img/8fa2929-CleanShot_2023-04-21_at_15.16.222x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Выберите уникальное имя и иконку, которые будут представлять ваше приложение в интерфейсе Adapty. Обратите внимание, что имя и иконка приложения не влияют на его название и иконку в App Store или Google Play. Также выберите подходящую категорию приложения, которая точно отражает его назначение и содержание. Это поможет пользователям найти ваше приложение и обеспечит его отображение в соответствующих категориях стора. ## 2\. Участие в программе Small Business Program и сниженный сбор \{#member-of-small-business-program-and-reduced-service-fee\} <img src="/assets/shared/img/825e2be-CleanShot_2023-04-19_at_13.43.292x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Если ваша организация участвует в программе Apple [Small Business Program](app-store-small-business-program) или в программе Google [Reduced Service Fee](google-reduced-service-fee), на ваши приложения распространяется сниженный комиссионный сбор стора. Сообщите Adapty, если ваше приложение участвует в программе сниженной комиссии. Чтобы расчёты были корректными, укажите статус участия в разделе **Reduced Store Fee**. Настройка сниженной комиссии применяется только к будущим транзакциям. Измените статус **до** вступления изменений в силу — Adapty скорректирует ставку комиссии. :::warning * Если вы продлеваете участие в программе сниженной комиссии, **добавьте новый период действия**. * Если вы теряете членство в программе, **измените дату окончания** текущего периода действия. ::: Следующие статьи подробно рассматривают эту тему: * [Программа App Store Small Business Program](app-store-small-business-program) * [Google Reduced Service Fee](google-reduced-service-fee) ## 3\. Часовой пояс для отчётов \{#reporting-timezone\} <img src="/assets/shared/img/47227f9-CleanShot_2023-04-19_at_13.45.302x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Выберите часовой пояс, соответствующий вашему местоположению или региону, где аналитика и графики приложения наиболее актуальны. Рекомендуем использовать тот же часовой пояс, что и в вашем аккаунте App Store Connect или Google Play Console, — это обеспечит согласованность данных. Обратите внимание, что данная настройка часового пояса не влияет на сторонние интеграции в системе Adapty — они используют часовой пояс UTC. Настройки часового пояса находятся в разделе **Reported timezone** на вкладке **General Tab** страницы **App Settings**. Вы также можете установить единый часовой пояс для всех приложений в своём аккаунте Adapty, поставив галочку в соответствующем поле. ## 4\. Определение установок для аналитики \{#installs-definition-for-analytics\} Выберите, что считается новым событием установки в аналитике: | База | Описание | |-----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | New device_ids | <p>(Рекомендуется) Каждая установка приложения из стора на устройство считается новой установкой. Это включает как первичные установки, так и переустановки.</p><p>Установки считаются по идентификатору устройства и не зависят от аутентификации пользователя. Создание профиля (при активации SDK или выходе из аккаунта), вход в систему или обновление приложения не генерируют дополнительных событий установки.</p><p>Например, если одно и то же приложение установлено на 5 разных устройствах, в аналитике будет показано 5 установок.</p> | | New customer_user_ids | <p>Этот вариант предназначен для приложений, которые <InlineTooltip tooltip="идентифицируют пользователей в Adapty">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), [Unity](unity-identifying-users), [Kotlin Multiplatform](kmp-quickstart-identify), [Capacitor](capacitor-quickstart-identify)</InlineTooltip>. </p><p>Для авторизованных пользователей только первая установка, связанная с идентификатором пользователя (customer user ID), считается установкой. Установки на дополнительных устройствах новыми установками не считаются. </p><p>Анонимные пользователи (не вошедшие в систему) в аналитике не учитываются. </p><p>Переустановка приложения или повторный вход в аккаунт не создают дополнительных установок.</p> <p>Сторы и платформы атрибуции (такие как App Store Connect, Google Play Console и AppsFlyer) используют подход на основе устройств для подсчёта установок. Если вы считаете установки по customer user ID в Adapty, цифры могут отличаться от данных этих внешних сервисов.</p><p>⚠️ Если вы не идентифицируете пользователей в Adapty, при включении этого варианта установки учитываться не будут.</p> | | New profiles in Adapty | (Устаревший) Каждая установка приложения, переустановка и анонимные профили, созданные при выходе из аккаунта, считаются новыми установками. | Помните, что этот параметр влияет только на страницу [**Analytics**](https://app.adapty.io/analytics) и не затрагивает страницу [**Overview**](https://app.adapty.io/overview), где настройки отображения задаются отдельно. ## 5. Логика повышения цен в App Store \{#5-app-store-price-increase-logic\} Чтобы данные оставались точными и не расходились между аналитикой Adapty и App Store Connect, важно выбрать правильный вариант при настройке повышения цен в App Store Connect. Итак, вы можете выбрать логику, которая будет применяться к повышению цен на подписки в Adapty: <img src="/assets/shared/img/b766c8b-CleanShot_2023-07-18_at_19.28.18_22x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> - **Цена подписки для существующих пользователей сохраняется:** При выборе этого варианта текущая цена сохраняется для существующих подписчиков, даже если вы измените её в App Store Connect. Это значит, что существующие подписчики будут по-прежнему оплачивать подписку по первоначальной цене. - **При изменении цены подписки в App Store Connect она меняется и для существующих подписчиков:** При выборе этого варианта любые изменения цены в App Store Connect будут применяться и к существующим подписчикам. Это значит, что с существующих подписчиков будет списываться новая цена в соответствии с обновлёнными настройками в App Store Connect. :::warning Важно учитывать, что выбранный параметр влияет не только на аналитику в Adapty, но и на интеграции, а также на общую логику обработки транзакций. ::: Убедитесь, что вы выбрали нужный параметр, соответствующий вашему подходу к обработке цен подписки для существующих подписчиков. Это поможет сохранить точность данных и синхронизацию между аналитикой Adapty и данными из App Store Connect. ## 6. Совместный доступ к платным функциям между аккаунтами пользователей \{#6-sharing-paid-access-between-user-accounts\} :::link Основная статья: [Совместный доступ к платным функциям между аккаунтами пользователей](sharing-paid-access-between-user-accounts) ::: Настройка **Sharing paid access between user accounts** определяет поведение Adapty, когда более одного [профиля пользователя](identifying-users) пытается получить доступ к одной и той же покупке. Вы можете указать отдельную настройку совместного доступа для [среды песочницы](test-purchases-in-sandbox). **Включено (по умолчанию)** Идентифицированные пользователи (те, у кого задан [Customer User ID](identifying-users#set-customer-user-id-on-configuration)) могут совместно использовать один и тот же [уровень доступа](access-level), предоставленный Adapty, если их устройство привязано к одному Apple/Google ID. Это удобно, когда пользователь переустанавливает приложение и входит с другим email — он всё равно сохранит доступ к своей предыдущей покупке. При этой опции несколько идентифицированных пользователей могут совместно использовать один уровень доступа. Несмотря на то что уровень доступа является общим, все прошлые и будущие транзакции фиксируются как события в исходном Customer User ID — для обеспечения корректной аналитики и сохранения полной истории транзакций: пробных периодов, покупок подписок, продлений и прочего, привязанных к одному профилю. **Передача доступа новому пользователю** Идентифицированные пользователи сохраняют доступ к [уровню доступа](access-level), предоставленному Adapty, даже если они входят с другим [Customer User ID](identifying-users#set-customer-user-id-on-configuration) или переустанавливают приложение — при условии, что устройство привязано к одному Apple/Google ID. В отличие от предыдущей опции, Adapty передаёт покупку между идентифицированными пользователями. Это гарантирует доступность купленного контента, однако одновременно доступ может быть только у одного пользователя. Например, если UserA оформляет подписку, а UserB входит на том же устройстве и восстанавливает транзакции, UserB получит доступ к подписке, а у UserA он будет отозван. Если один из пользователей (новый или прежний) не идентифицирован, уровень доступа всё равно будет общим между этими профилями в Adapty. Несмотря на передачу уровня доступа, все прошлые и будущие транзакции фиксируются как события в исходном Customer User ID — для обеспечения корректной аналитики и сохранения полной истории транзакций: пробных периодов, покупок подписок, продлений и прочего, привязанных к одному профилю. После переключения на **Transfer access to new user** уровни доступа не будут немедленно переданы между профилями. Процесс передачи для каждого конкретного уровня доступа запускается только при получении Adapty события от стора — например, при продлении подписки, восстановлении или при валидации транзакции. **Отключено** Первый идентифицированный профиль пользователя, получивший уровень доступа, сохранит его навсегда. Это оптимальный вариант, если бизнес-логика вашего приложения требует привязки покупок к единственному Customer User ID. Обратите внимание, что уровни доступа по-прежнему остаются общими между анонимными пользователями. Вы можете «отвязать» покупку, [удалив профиль пользователя-владельца](https://adapty.io/docs/ru/api-adapty/operations/deleteProfile). После удаления уровень доступа становится доступным первому профилю, который его запросит — анонимному или идентифицированному. Отключение совместного использования распространяется только на новых пользователей. Подписки, уже разделённые между пользователями, продолжат оставаться общими даже после отключения этой опции. :::warning Apple и Google требуют, чтобы встроенные покупки были доступны для совместного использования или передачи между пользователями, поскольку они привязывают покупку к Apple/Google ID. Без совместного использования восстановление покупок после повторной установки может не работать. Отключение совместного использования может лишить пользователей возможности восстановить доступ после входа в аккаунт. Рекомендуем отключать совместное использование только в том случае, если пользователи **обязаны войти в аккаунт** до совершения покупки. В противном случае идентифицированный пользователь может оформить подписку, войти в другой аккаунт и навсегда потерять к ней доступ. ::: ### Какой вариант выбрать? \{#which-setting-should-i-choose\} | Моё приложение... | Вариант | | ------------------------------------------------------------ | ------------------------------------------------------------ | | Не имеет системы входа и использует только анонимные идентификаторы профилей Adapty. | Используйте вариант по умолчанию — уровни доступа всегда являются общими между анонимными идентификаторами профилей для всех трёх вариантов. | | Имеет необязательную систему входа и позволяет совершать покупки до создания аккаунта. | Выберите **Transfer access to new user**, чтобы пользователи, совершившие покупку без аккаунта, могли впоследствии восстановить свои транзакции. | | Требует создания аккаунта перед покупкой, но допускает привязку покупок к нескольким Customer User ID. | Выберите **Transfer access to new user**, чтобы одновременно доступ был только у одного Customer User ID, при этом пользователи могли входить с другим Customer User ID без потери оплаченного доступа. | | Требует создания аккаунта перед покупкой и жёстко привязывает покупки к единственному Customer User ID. | Выберите **Disabled**, чтобы транзакции никогда не передавались между аккаунтами. | ## 7. Ключи SDK и API \{#7-sdk-and-api-keys\} Используйте публичный ключ SDK для интеграции SDK Adapty в ваше приложение, а секретный ключ — для доступа к Server API Adapty. При необходимости можно создавать новые ключи или отзывать существующие. Для создания токенов для Developer CLI перейдите в **Settings → Developer API**. См. [Аутентификация](developer-cli-authentication). ## 8. Тестовые устройства \{#8-test-devices\} Укажите устройства для тестирования, чтобы они получали мгновенные обновления при изменении пейвола или плейсмента, минуя задержки кэширования. Подробнее см. [Тестовые устройства](test-devices). ## 9. Постоянство вариантов между плейсментами \{#cross-placement-variation-stickiness\} Укажите, как долго после завершения теста пользователь продолжает видеть варианты из этого теста. Это влияет на точность аналитики и пользовательский опыт — если показать пользователю другое предложение вместо того, которое он уже видел, это может повлиять на его решение о покупке. Максимальный и дефолтный период постоянства составляет 90 дней. :::warning Обратите внимание: - Изменение этой настройки затронет всех пользователей, которые ранее получили вариант. Они сразу же получат новый пейвол при следующем показе плейсмента, что может испортить результаты ваших текущих A/B-тестов. - Если период закрепления для пользователя истёк, ему может быть показан новый пейвол или A/B-тест. Однако даже в этом случае такой пользователь не сможет участвовать ни в каком другом кросс-плейсментном тесте. ::: ## 10. Удаление приложения \{#10-delete-the-app\} Если приложение вам больше не нужно, его можно удалить из Adapty. :::warning Обратите внимание, что это действие необратимо — восстановить приложение или его данные будет невозможно. ::: --- # File: ios-settings --- --- title: "Учётные данные Apple App Store" description: "Настройте параметры iOS в Adapty для бесперебойного управления подписками." --- Чтобы настроить учётные данные App Store и обеспечить корректную работу Adapty iOS SDK, перейдите на вкладку [iOS SDK](https://app.adapty.io/settings/ios-sdk) на странице **App Settings** дашборда Adapty. Затем настройте следующие параметры: <img src="/assets/shared/img/3d4087e-CleanShot_2023-06-26_at_13.27.042x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> | Поле | Описание | |----------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Bundle ID** | [Bundle ID вашего приложения](app-store-connection-configuration#step-1-provide-bundle-id-and-apple-app-id). | | **In-app purchase API (StoreKit 2)** | [Ключи](app-store-connection-configuration#step-2-provide-issuer-id-and-key-id) для безопасной аутентификации и проверки истории транзакций встроенных покупок. | | **App Store Server Notifications** | URL для получения [серверных уведомлений](enable-app-store-server-notifications) от App Store об изменениях статуса подписок пользователей. | | **App Store Promotional Offers** | Ключи подписки для создания [Promotional offers](generate-in-app-purchase-key) в Adapty для конкретных продуктов. | | **Apple app ID** | ID вашего приложения в App Store. Чтобы найти его, откройте страницу приложения в App Store Connect, перейдите на страницу **App Information** в левом меню и скопируйте **Apple ID**. | | **App Store Connect shared secret (LEGACY)** | <p>**Устаревший ключ для Adapty SDK версий до v2.9.0**</p><p></p><p>[Ключ](app-store-connection-configuration#step-5-enter-app-store-shared-secret) для валидации чеков и защиты от мошенничества в приложении.</p> | --- # File: google-play-store-connection-configuration --- --- title: "Настройка интеграции с Google Play Store" description: "Настройте подключение Google Play Store в Adapty для корректной обработки встроенных покупок." --- В этом разделе описан процесс интеграции вашего мобильного приложения, распространяемого через Google Play, с Adapty. Вам нужно ввести данные конфигурации приложения из Play Store в дашборд Adapty. Этот шаг необходим для валидации покупок и получения обновлений подписок из Play Store в Adapty. Вы можете выполнить этот процесс во время первоначального онбординга или внести изменения позже в разделе **App Settings** дашборда Adapty. :::danger Изменение конфигурации допустимо только до выпуска мобильного приложения с интегрированными пейволами Adapty. Изменения после релиза нарушат интеграцию, и пейволы перестанут отображаться в вашем приложении. ::: ## Шаг 1. Укажите Package name \{#step-1-provide-package-name\} Package name — это уникальный идентификатор вашего приложения в Google Play Store. Он необходим для базовой функциональности Adapty, например для обработки подписок. 1. Откройте [Google Play Developer Console](https://play.google.com/console/u/0/developers). 2. Выберите приложение, ID которого вам нужен. Откроется окно **Dashboard**. <img src="/assets/shared/img/7889edb-package_name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Найдите идентификатор продукта под названием приложения и скопируйте его. 4. Откройте [**App settings**](https://app.adapty.io/settings/android-sdk) в верхнем меню Adapty. <img src="/assets/shared/img/b00066c-package_name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. На вкладке **Android SDK** окна **App settings** вставьте скопированный **Package name**. ## Шаг 2. Загрузите файл ключа аккаунта \{#step-2-upload-the-account-key-file\} 1. Загрузите файл закрытого ключа сервисного аккаунта в формате JSON, созданный на шаге [Создание файла ключа сервисного аккаунта](create-service-account), в поле **Service account key file**. <img src="/assets/shared/img/20fdba1-service_key_file.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Не забудьте нажать кнопку **Save**, чтобы сохранить изменения. **Что дальше** - [Включите уведомления разработчика в реальном времени (RTDN) в Google Play Console](enable-real-time-developer-notifications-rtdn) --- # File: enable-real-time-developer-notifications-rtdn --- --- title: "Включение уведомлений в реальном времени (RTDN) в Google Play Console" description: "Будьте в курсе важных событий и обеспечьте точность данных, включив уведомления в реальном времени (RTDN) в Google Play Console для Adapty. Узнайте, как настроить RTDN для получения мгновенных обновлений о возвратах и других событиях из Play Store" --- Настройка уведомлений в реальном времени (RTDN) необходима для обеспечения точности данных: она позволяет мгновенно получать обновления из Play Store, включая информацию о возвратах и других событиях. ## Включение уведомлений \{#enable-notifications\} 1. Убедитесь, что **Google Cloud Pub/Sub** включён. Перейдите по [этой ссылке](https://console.cloud.google.com/flows/enableapi?apiid=pubsub) и выберите проект вашего приложения. Если вы ещё не включили **Google Cloud Pub/Sub**, сделайте это здесь. <img src="/assets/shared/img/pubsub.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Перейдите в [**App settings > Android SDK**](https://app.adapty.io/settings/android-sdk) из верхнего меню Adapty и скопируйте содержимое поля **Enable Pub/Sub API** рядом с заголовком **Google Play RTDN topic name**. <img src="/assets/shared/img/a72ff2d-copy_topic.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> :::note Если содержимое поля **Enable Pub/Sub API** имеет неправильный формат (правильный формат начинается с `projects/...`), обратитесь к разделу [Исправление неправильного формата в поле Enable Pub/Sub API](enable-real-time-developer-notifications-rtdn#fixing-incorrect-format-in-enable-pubsub-api-field) за помощью. ::: 3. Откройте [Google Play Console](https://play.google.com/console/), выберите своё приложение и перейдите в **Monetize with Play** -> **Monetization setup**. В разделе **Google Play Billing** установите флажок **Enable real-time notifications**. 4. Вставьте содержимое поля **Enable Pub/Sub API**, скопированное в **App Settings** Adapty, в поле **Topic name**. 5. Нажмите **Save changes** в Google Play Console. <img src="/assets/shared/img/e55ba0e-paste_topic_name.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Тестирование уведомлений \{#test-notifications\} Чтобы проверить, успешно ли вы подписались на уведомления в реальном времени: 1. Сохраните изменения в настройках Google Play Console. 2. В Google Play Console под полем **Topic name** нажмите **Send test notification**. <img src="/assets/shared/img/rtdn-test.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Перейдите в [**App settings > Android SDK**](https://app.adapty.io/settings/android-sdk) в Adapty. Если тестовое уведомление было отправлено, вы увидите его статус над названием топика. <img src="/assets/shared/img/rtdn-adapty-test.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Исправление неправильного формата в поле Enable Pub/Sub API \{#fixing-incorrect-format-in-enable-pubsub-api-field\} Если содержимое поля **Enable Pub/Sub API** имеет неправильный формат (правильный формат начинается с `projects/...`), выполните следующие шаги для устранения проблемы: ### 1. Проверка активации API и прав доступа \{#1-verify-api-enablement-and-permissions\} Убедитесь, что все необходимые API включены и права доступа к сервисному аккаунту настроены правильно. Даже если вы уже выполняли эти шаги, пройдите их повторно, чтобы не пропустить ни одного. Повторите шаги из следующих разделов: 1. [Включение API разработчика в Google Play Console](enabling-of-devepoler-api) 2. [Создание сервисного аккаунта в Google Cloud Console](create-service-account) 3. [Выдача прав сервисному аккаунту в Google Play Console](grant-permissions-to-service-account) 4. [Создание файла ключа сервисного аккаунта в Google Play Console](create-service-account-key-file) 5. [Настройка интеграции с Google Play Store](google-play-store-connection-configuration) ### 2. Изменение политик домена \{#2-adjust-domain-policies\} Измените политики **Domain restricted contacts** и **Domain restricted sharing**: 1. Откройте [Google Cloud Console](https://console.cloud.google.com/) и выберите проект, в котором создан сервисный аккаунт для управления вашим приложением. 2. В разделе **Quick Access** выберите **IAM & Admin**. <img src="/assets/shared/img/google-cloud-IAM-and-Admin.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. На левой панели выберите **Organization Policies**. 4. Найдите политику **Domain restricted contacts**. <img src="/assets/shared/img/google-cloud-policy-action.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Нажмите кнопку с многоточием в столбце **Actions** и выберите **Edit policy**. 6. В окне редактирования политики: 1. В разделе **Policy source** выберите переключатель **Override parent's policy**. 2. В разделе **Policy enforcement** выберите переключатель **Replace**. 3. В разделе **Rules** нажмите кнопку **ADD A RULE**. <img src="/assets/shared/img/google-cloud-edit-policy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. В разделе **New rule** -> **Policy values** выберите **Allow All**. <img src="/assets/shared/img/google-cloud-allow-all-policy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Нажмите **SET POLICY**. 7. Повторите шаги 4–6 для политики **Domain restricted sharing**. После этого пересоздайте содержимое поля **Enable Pub/Sub API** рядом с заголовком **Google Play RTDN topic name**. Теперь поле будет в правильном формате. Не забудьте вернуть **Policy source** в значение **Inherit parent's policy** для обновлённых политик после успешного включения уведомлений в реальном времени (RTDN). ## Переадресация необработанных событий \{#raw-events-forwarding\} В некоторых случаях вам может потребоваться получать необработанные S2S-события от Google. Чтобы продолжать их получать при использовании Adapty, просто добавьте свой эндпоинт в поле **URL for forwarding raw Google events** — мы будем передавать события в том виде, в котором они приходят от Google. <img src="/assets/shared/img/e388892-001774-September-22-GhkjOFbT.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- **Что дальше** Настройте Adapty SDK для: - [Android](sdk-installation-android) - [React Native](sdk-installation-reactnative) - [Flutter](sdk-installation-flutter) - [Kotlin Multiplatform](sdk-installation-kotlin-multiplatform) - [Unity](sdk-installation-unity) --- # File: apple-search-ads --- --- title: "Apple Ads" description: "Интегрируйте Apple Ads с Adapty для оптимизации конверсий подписок." --- :::important Интеграция Apple Ads в **App settings** используется только для базовой аналитики, а также для интеграций SplitMetrics Acquire и Asapty. [Adapty Ads Manager](adapty-ads-manager) использует отдельное подключение. Подключите ваш аккаунт Apple Ads в [Adapty Ads Manager](adapty-ads-manager-get-started). ::: Adapty помогает получать данные атрибуции из Apple Ads и анализировать метрики с сегментацией по кампаниям и ключевым словам. Adapty автоматически собирает данные атрибуции для Apple Ads через SDK и AdServices Framework. После настройки интеграции с Apple Ads Adapty начнёт получать данные атрибуции. Просмотреть их можно на странице профилей. <img src="/assets/shared/img/ba4a3e9-CleanShot_2023-08-21_at_15.14.592x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Настройка интеграции \{#set-up-integration\} ### Подключение Adapty к фреймворку AdServices \{#connect-adapty-to-the-adservices-framework\} Apple Ads через [AdServices](https://developer.apple.com/documentation/adservices) требует настройки в дашборде Adapty, а также включения на стороне приложения. Чтобы настроить Apple Ads с использованием фреймворка AdServices через Adapty, выполните следующие шаги: #### Шаг 1: Получите публичный ключ \{#step-1-obtain-public-key\} В дашборде Adapty перейдите в [Settings -> Apple Ads.](https://app.adapty.io/settings/apple-search-ads) Найдите заранее сгенерированный публичный ключ (Adapty создаёт пару ключей за вас) и скопируйте его. <img src="/assets/shared/img/baa5998-CleanShot_2023-08-21_at_14.55.542x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::note Если вы используете сторонний сервис или собственное решение для атрибуции Apple Ads, вы можете загрузить свой приватный ключ. ::: #### Шаг 2: Настройте управление пользователями в Apple Ads \{#step-2-configure-user-management-on-apple-ads\} В вашем [аккаунте Apple Ads](https://ads.apple.com/app-store) перейдите на страницу **Settings > User Management**. Чтобы Adapty мог получать данные атрибуции, нужно пригласить дополнительный Apple ID и предоставить ему доступ API Account Manager. Можно использовать любой доступный вам аккаунт или создать новый специально для этой цели. Главное условие — вы должны иметь возможность войти в Apple Ads под этим Apple ID. <img src="/assets/shared/img/ec183b2-kdjsfldsfjkdsfdfd.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> #### Шаг 3: Генерация учётных данных API \{#step-3-generate-api-credentials\} Войдите в только что добавленный аккаунт в Apple Ads. Перейдите в Settings -> API в интерфейсе Apple Ads. Вставьте ранее скопированный публичный ключ в соответствующее поле. Сгенерируйте новые учётные данные API. #### Шаг 4: Настройка Adapty с учётными данными Apple Ads \{#step-4-configure-adapty-with-apple-ads-credentials\} Скопируйте поля Client ID, Team ID и Key ID из настроек Apple Ads. В дашборде Adapty вставьте эти данные в соответствующие поля. <img src="/assets/shared/img/7356113-CleanShot_2023-08-21_at_15.08.512x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Подключение приложения к сети AdServices \{#connect-your-app-to-the-adservices-network\} После завершения [настройки фреймворка AdServices](#connect-the-adservices-framework) Adapty автоматически начинает собирать данные атрибуции Apple Search Ads. Добавлять какой-либо код в SDK не нужно. Для iOS-приложений эти данные атрибуции **всегда** будут иметь приоритет над данными из других источников. Если такое поведение нежелательно, *отключите* атрибуцию ASA, следуя инструкциям ниже. ## Отключение интеграции \{#disable-integration\} Чтобы отключить атрибуцию Apple Search Ads, откройте вкладку [**App Settings** -> **Apple Search Ads**](https://app.adapty.io/settings/apple-search-ads) и отключите переключатель **Receive Apple Search Ads attribution**. <img src="/assets/shared/img/asa-disable.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::warning Обратите внимание: отключение этой опции полностью прекратит получение аналитики ASA. В результате ASA больше не будет использоваться в аналитике и не будет передаваться в интеграции. Кроме того, SplitMetrics Acquire и Asapty перестанут работать, поскольку они зависят от атрибуции ASA. Атрибуция, полученная до этого изменения, затронута не будет. ::: ## Загрузка собственных ключей \{#uploading-your-own-keys\} :::note Необязательно Эти шаги не требуются для атрибуции Apple Ads — только для работы с другими сервисами, например Asapty, или с собственным решением. ::: Вы можете использовать собственную пару публичного и приватного ключей, если применяете сторонние сервисы или собственное решение для атрибуции ASA. ### Шаг 1 \{#step-1\} Сгенерируйте приватный ключ в терминале: ```text showLineNumbers title="Text" openssl ecparam -genkey -name prime256v1 -noout -out private-key.pem ``` Загрузите его в Adapty Settings -> Apple Ads (кнопка Upload private key). ### Шаг 2 \{#step-2\} Сгенерируйте публичный ключ в терминале: ```text showLineNumbers title="Text" openssl ec -in private-key.pem -pubout -out public-key.pem ``` Этот публичный ключ можно использовать в настройках Apple Ads аккаунта с ролью API Account Manager. Таким образом, сгенерированные значения Client ID, Team ID и Key ID можно применять как в Adapty, так и в других сервисах. --- # File: account --- --- title: "Данные аккаунта и биллинг" description: "Управляйте своим аккаунтом Adapty и оптимизируйте настройки для более точного отслеживания подписок." --- На странице **Account** можно управлять своим профилем, участниками команды и биллингом. На странице есть три вкладки: - [Основные настройки](#general-settings) - [Подписка и биллинг](#billing-info) - [Участники](#members) Чтобы открыть настройки аккаунта, нажмите **Account** в правом верхнем углу или перейдите по ссылке [app.adapty.io/account](https://app.adapty.io/account). <img src="/assets/shared/img/account-info.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Основные настройки \{#general-settings\} Вкладка **General** содержит настройки профиля, аккаунта, параметры отображения и конфигурацию отчётов. - **Profile**: Введите имя, фамилию и название компании. Название компании может содержать до 256 символов. - **Account settings**: Просмотрите зарегистрированный адрес электронной почты и смените пароль. - **Date & Time formats**: Выберите формат отображения дат и времени в Adapty: - **American format**: January 31, 2022 и 12-часовой формат времени (AM/PM) - **European format**: 31 January, 2022 и 24-часовой формат времени (16:00) - **Email reports**: Настройте ежедневные, еженедельные или ежемесячные отчёты для одного или всех приложений. Получайте сводные отчёты по всем приложениям сразу или детальный отчёт по каждому выбранному приложению. <img src="/assets/shared/img/account-info.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Подписка и биллинг \{#billing-info\} Вкладка **Subscription & Billing** позволяет управлять платёжными данными и доступом к функциям: - добавлять или обновлять платёжные реквизиты; - просматривать информацию о биллинге; - подключать дополнительные платные функции. Подробнее о функциях и ценах — на странице [features and pricing](https://adapty.io/pricing). ## Участники \{#members\} Вы можете управлять участниками команды в настройках аккаунта. Чтобы добавить участника, отправьте приглашение по email и назначьте ему роль. Подробнее об управлении участниками команды и их правами доступа — [здесь](members-settings). --- # File: members-settings --- --- title: "Участники" description: "Управляйте настройками и правами доступа участников в дашборде Adapty." --- :::note Эта страница посвящена участникам дашборда Adapty Если вы хотите назначить разные уровни доступа пользователям вашего приложения, изучите раздел [Уровни доступа](access-level). ::: Система участников дашборда Adapty позволяет предоставлять разные уровни доступа к Adapty и указывать приложения для каждого участника. ## Роли \{#roles\} В дашборде Adapty доступны следующие роли для участников: | Роль | Доступ к биллингу | Добавление участников | Изменение данных | Доступ ко всем разделам | |-------------|-------------------|-----------------------|------------------|-------------------------| | Owner | ✅ | ✅ | ✅ | ✅ | | Admin | ❌ | ✅ | ✅ | ✅ | | Developer | ❌ | ❌ | ✅ | ❌ | | Viewer | ❌ | ❌ | ❌ | ✅ | | Support | ❌ | ❌ | ❌ | ❌ | | ASA manager | ❌ | ❌ | ❌ | ❌ | - **Owner:** Owner — это первоначальный создатель аккаунта Adapty с наивысшим уровнем доступа и контроля. Owner имеет полный доступ к биллингу Adapty, включая управление платёжными данными и тарифными планами. Кроме того, только Owner и Admin могут задавать уровень доступа к приложению для новых участников. В каждом аккаунте Adapty может быть только один Owner. - **Admin:** Участники с ролью Admin имеют полный доступ к выбранным приложениям. Они могут выполнять различные задачи по управлению: создавать и редактировать пейволы, проводить A/B-тесты, анализировать аналитику и управлять участниками в этих приложениях. - **Developer:** Участники с ролью Developer имеют полный доступ ко всем сущностям, кроме аналитики и управления участниками аккаунта. Они не имеют доступа к настройкам биллинга. Эта роль предназначена для тех, кто настраивает пейволы, A/B-тесты и другие сущности, а также интегрирует Adapty в приложение, но не должен видеть финансовые данные. - **Viewer:** Участники с ролью Viewer имеют доступ только для чтения к выбранным приложениям. Они могут просматривать информацию, но не могут создавать или редактировать пейволы, A/B-тесты и другие функции, приглашать новых пользователей, создавать новые приложения и изменять настройки приложения. - **Support:** Участники с ролью Support имеют доступ только к профилям пользователей в выбранных приложениях. При этом они не могут добавлять новых участников или заходить в другие разделы Adapty. Эта роль особенно подходит для команд поддержки или сотрудников, которым нужно помогать клиентам с вопросами по подпискам или устранением неполадок. - **ASA manager:** Участники с ролью ASA manager имеют доступ только к дашборду [Adapty Ads Manager](adapty-ads-manager). ## Добавление участника \{#add-a-member\} В Adapty можно пригласить до 256 участников команды. Добавление новых участников бесплатно. :::note Приглашать можно только те email-адреса, которые ещё не зарегистрированы в Adapty. Если у вашего коллеги есть отдельный аккаунт, укажите другой email-адрес или обратитесь в службу поддержки Adapty, чтобы удалить существующий аккаунт. ::: Чтобы добавить участника команды: 1. Нажмите **Account** в правом верхнем углу и откройте вкладку **Members**. 2. Нажмите **Invite member**. <img src="/assets/shared/img/invite-member.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Введите email-адрес участника. 4. Выберите [роль](#roles) из списка. 5. Выберите приложения, к которым нужно предоставить доступ. 6. (Опционально) Включите **Always allow access to new apps**, чтобы автоматически открывать доступ к новым приложениям. 7. Нажмите **Save**. <img src="/assets/shared/img/add-member.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Передача прав владельца аккаунта \{#transfer-account-ownership\} Если вам нужно передать права **владельца аккаунта**, обратитесь в нашу службу поддержки по адресу [support@adapty.io](mailto:support@adapty.io). Если вам нужно передать права **владельца приложения**, ознакомьтесь с [соответствующим гайдом](transfer-apps). --- # File: set-up-app-store-connect --- --- title: "Настройка App Store Connect" description: "Гайд для начинающих разработчиков по регистрации в программе Apple Developer Program и настройке App Store Connect для встроенных покупок." --- Если вы **создаёте своё первое iOS-приложение**, вам нужно настроить аккаунт разработчика Apple и App Store Connect до того, как вы начнёте интеграцию с Adapty. :::note Если у вас уже есть аккаунт Apple Developer и приложение зарегистрировано в App Store Connect, этот гайд можно пропустить — переходите сразу к [первоначальной интеграции с App Store](initial_ios). ::: ## Шаг 1. Вступите в программу Apple Developer Program \{#step-1-enroll-in-apple-developer-program\} Чтобы публиковать приложения в App Store и продавать встроенные покупки, необходимо вступить в [Apple Developer Program](https://developer.apple.com/programs/). ### Выберите тип регистрации \{#choose-enrollment-type\} Apple предлагает два типа регистрации: | | Физическое лицо | Организация | |------------------------------------|--------------------------|------------------------------------------| | **Для кого** | Разработчики-одиночки | Компании, команды, некоммерческие организации | | **Требуется D-U-N-S номер** | Нет | Да | | **Приложения публикуются под** | Вашим личным именем | Названием вашей организации | | **Управление командой** | Недоступно | Доступно | :::tip Если вы регистрируетесь как организация, вам потребуется **D-U-N-S номер** — уникальный девятизначный бизнес-идентификатор от Dun & Bradstreet. Вы можете [проверить, есть ли у вашей организации такой номер](https://developer.apple.com/enroll/duns-lookup/), или запросить новый — ссылка находится внизу страницы поиска. Получение D-U-N-S номера может занять до 5 рабочих дней. ::: ### Зарегистрируйтесь \{#enroll\} 1. Перейдите на [страницу регистрации Apple Developer Program](https://developer.apple.com/programs/enroll/). 2. Войдите с помощью Apple ID. Если у вас его нет — сначала создайте. 3. Следуйте инструкциям для вашего типа регистрации (физическое лицо или организация). 4. Оплатите ежегодный взнос. После того как Apple обработает вашу заявку, вы получите доступ к [App Store Connect](https://appstoreconnect.apple.com). Обычно регистрация занимает до 48 часов. Для организаций процесс может быть дольше, если требуется верификация D-U-N-S номера. ## Шаг 2. Настройте приложение в App Store Connect \{#step-2-set-up-your-app-in-app-store-connect\} Прежде чем продавать встроенные покупки, завершите первоначальную настройку в App Store Connect: подпишите соглашения, добавьте платёжные данные и зарегистрируйте приложение. ### Подпишите соглашение о платных приложениях \{#sign-the-paid-applications-agreement\} Apple требует подписания соглашения Paid Applications Agreement, прежде чем вы сможете продавать в App Store. Это касается как платных приложений, так и встроенных покупок в бесплатных. 1. Перейдите на страницу **Business** в [App Store Connect](https://appstoreconnect.apple.com/business). 2. Найдите соглашение **Paid Apps** и нажмите **Review and Agree**. 3. Заполните необходимую информацию: - **Banking information**: добавьте банковский счёт, на который Apple будет перечислять ваши доходы. - **Tax information**: заполните налоговые формы для стран, в которых хотите продавать. - **Contact information**: укажите контактные данные. :::important Необходимо заполнить все три раздела (банковские данные, налоги, контакты), чтобы соглашение вступило в силу. Пока соглашение неактивно, продавать встроенные покупки невозможно. ::: ### Создайте Bundle ID \{#create-a-bundle-id\} Bundle ID уникально идентифицирует ваше приложение в экосистеме Apple. Он нужен для регистрации приложения в App Store Connect и настройки интеграции с Adapty. 1. Откройте [портал Apple Developer](https://developer.apple.com/account). 2. Перейдите в **Certificates, Identifiers & Profiles** → **Identifiers**. 3. Нажмите **+**, чтобы зарегистрировать новый идентификатор. 4. Выберите **App IDs** и нажмите **Continue**. 5. Выберите тип **App** и нажмите **Continue**. 6. Заполните поля: - **Description**: название, которое поможет вам идентифицировать этот Bundle ID (например, «My Subscription App»). - **Bundle ID**: выберите **Explicit** и введите уникальный идентификатор в формате обратного домена (например, `com.yourcompany.yourapp`). 7. В разделе **Capabilities** прокрутите вниз и отметьте **In-App Purchase**. 8. Нажмите **Continue**, затем **Register**. ### Зарегистрируйте приложение в App Store Connect \{#register-your-app-in-app-store-connect\} 1. Перейдите на страницу **Apps** в [App Store Connect](https://appstoreconnect.apple.com/apps). 2. Нажмите **+** → **New App**. 3. Заполните обязательные поля: - **Platforms**: выберите **iOS**. - **Name**: название приложения, которое будет отображаться в App Store. - **Primary language**: язык по умолчанию для метаданных вашего приложения. - **Bundle ID**: выберите Bundle ID, созданный на предыдущем шаге. - **SKU**: уникальный идентификатор приложения (не виден пользователям). Например, `my_subscription_app_2025`. 4. Нажмите **Create**. Ваше приложение зарегистрировано в App Store Connect и готово к интеграции с Adapty. ## Что дальше \{#whats-next\} - [Первоначальная интеграция с App Store](initial_ios): подключите приложение из App Store к Adapty - [Интеграция SDK](quickstart-sdk): встройте Adapty SDK в код вашего приложения - [Тестирование в песочнице](test-purchases-in-sandbox): протестируйте встроенные покупки перед релизом - [Отправка iOS-приложения в App Store](submit-app-to-app-store): загрузите сборку и отправьте на проверку Apple - [Программа Apple для малого бизнеса](app-store-small-business-program): снизьте комиссию App Store с 30% до 15% --- # File: app-store-products --- --- title: "Продукт в App Store" description: "Эффективно управляйте продуктами App Store с помощью инструментов подписок Adapty." --- На этой странице описано, как создать продукт в App Store Connect. Эта информация может не иметь прямого отношения к функциональности Adapty, но будет полезна, если у вас возникнут трудности при создании продуктов в вашем аккаунте App Store Connect. Чтобы создать продукт, который будет привязан к Adapty: 1. Откройте **App Store Connect**. Перейдите в раздел [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions) в меню слева. <img src="/assets/shared/img/148c3b5-subscriptions.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Если вы ещё не создали группу подписок, нажмите кнопку **Create** под заголовком **Subscription Groups**, чтобы начать. [Группы подписок](https://developer.apple.com/help/app-store-connect/manage-subscriptions/offer-auto-renewable-subscriptions) в App Store Connect помогают категоризировать продукты и управлять ими, позволяя пользователям без проблем переключаться между разными предложениями. Обратите внимание: создать подписку вне группы невозможно. 3. В открывшемся окне **Create Subscription Group** введите название новой группы подписок в поле **Reference Name**. Это внутренний идентификатор, который помогает вам различать группы подписок в вашем приложении. Название группы не видно пользователям — оно используется исключительно для внутренней организации. Оно позволяет легко находить нужные группы подписок при работе в интерфейсе App Store Connect. Это особенно удобно, если у вас несколько предложений подписок или вы хотите структурировать их в соответствии с логикой вашего приложения. <img src="/assets/shared/img/3f93c44-create_subscription_group.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Нажмите кнопку **Create**, чтобы подтвердить создание группы подписок. 5. Группа подписок создана и открыта. Теперь можно создавать подписки внутри неё. Нажмите кнопку **Create** под заголовком **Subscriptions**. Если вы добавляете новую подписку в существующую группу, нажмите кнопку **Plus** рядом с заголовком **Subscriptions**. <img src="/assets/shared/img/22fc643-add_subscription.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. В открывшемся окне **Create Subscription** введите название подписки в поле **Reference Name** и уникальный код подписки в поле **Product ID**. Reference Name — это уникальный идентификатор встроенной подписки в App Store Connect, не отображаемый пользователям в App Store. Рекомендуем использовать понятное, читаемое описание, точно отражающее суть создаваемой подписки. Длина названия не должна превышать 64 символа. Product ID — уникальный буквенно-цифровой идентификатор, необходимый для обращения к продукту на этапе разработки и его синхронизации с Adapty. В Product ID допускаются только буквенно-цифровые символы, точки и символы подчёркивания. <img src="/assets/shared/img/04aca55-create_subscription.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Нажмите кнопку **Create**, чтобы подтвердить создание подписки. 8. Подписка создана и открыта. Теперь выберите длительность подписки в списке **Subscription Duration**. Даже если длительность уже указана в названии подписки, не забудьте заполнить поле **Subscription Duration**. <img src="/assets/shared/img/f56cf0f-subscription_duration.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 9. Теперь нужно настроить цену подписки. Нажмите кнопку **Add Subscription Price** под заголовком Subscription Prices. Возможно, для этого придётся прокрутить страницу вниз. 10. В открывшемся окне **Subscription Price** выберите базовую страну в списке **Country or Region** и базовую валюту в списке **Price**. Позднее Apple автоматически рассчитает цены для всех 175 стран и регионов на основе этой базовой цены и актуальных обменных курсов. <img src="/assets/shared/img/de1cec8-subscription_price.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 11. Нажмите кнопку **Next**. В открывшемся окне **Price by Country or Region** вы увидите автоматически пересчитанные цены для всех стран. При необходимости их можно изменить. <img src="/assets/shared/img/2a047a6-price_by_country.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 12. После обновления региональных цен нажмите кнопку **Next** в нижней части окна. 13. В открывшемся окне **Confirm Subscription Price?** внимательно проверьте итоговые цены. Чтобы внести изменения, нажмите кнопку **Back** и вернитесь в окно **Price by Country or Region**. Если цены вас устраивают, нажмите кнопку **Confirm**. <img src="/assets/shared/img/d2b2031-confirm_prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 14. После закрытия окна **Confirm Subscription Price?** не забудьте нажать кнопку **Save** в окне подписки. Без этого подписка не будет создана, и все введённые данные будут потеряны. Обратите внимание: описанные выше шаги касаются настройки автовозобновляемой подписки. Если вы хотите настроить другие типы встроенных покупок, в боковой панели нажмите вкладку **In-App Purchases** вместо **Subscriptions**. Это откроет раздел для управления и создания различных типов встроенных покупок. <img src="/assets/shared/img/5663d85-in-app_purchases.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Добавление продуктов в Adapty \{#add-products-to-adapty\} После того как вы завершили добавление встроенных покупок, подписок и предложений в App Store Connect, следующий шаг — [добавить эти продукты в Adapty](create-product). --- # File: apple-app-privacy --- --- title: "Apple App Privacy" description: "Узнайте о политиках конфиденциальности Apple App Privacy и их влиянии на ваше приложение с подписками." --- Apple требует раскрытия информации о конфиденциальности для всех новых приложений и обновлений — как в разделе **App Privacy** в App Store Connect, так и в виде файла манифеста приложения. Adapty является сторонней зависимостью вашего приложения, поэтому вам необходимо указать, как вы используете Adapty в отношении данных пользователей. ## Манифест конфиденциальности Apple \{#apple-app-privacy-manifest\} [Файл манифеста конфиденциальности](https://developer.apple.com/documentation/bundleresources/describing-data-use-in-privacy-manifests), называемый `PrivacyInfo.xcprivacy`, описывает, какие персональные данные использует ваше приложение и с какой целью. Каждый владелец приложения обязан создать такой файл. Кроме того, если вы подключаете сторонние SDK, убедитесь, что файлы манифеста для тех из них, которые входят в список [SDK, требующих манифеста конфиденциальности и подписи](https://developer.apple.com/support/third-party-SDK-requirements/), включены в сборку. При компиляции Xcode объединит все эти файлы в один. Несмотря на то что Adapty не входит в список [SDK, требующих манифеста конфиденциальности и подписи](https://developer.apple.com/support/third-party-SDK-requirements/), версии Adapty SDK 2.10.2 и выше включают его для вашего удобства. Обновите SDK, чтобы получить манифест. Хотя Adapty не требует включения каких-либо данных в файл манифеста (известный также как отчёт о конфиденциальности приложения), если вы используете `customerUserId` Adapty для отслеживания, необходимо указать это в файле манифеста следующим образом: 1. Добавьте словарь в массив `NSPrivacyCollectedDataTypes` в файле информации о конфиденциальности. 2. Добавьте ключи `NSPrivacyCollectedDataType`, `NSPrivacyCollectedDataTypeLinked` и `NSPrivacyCollectedDataTypeTracking` в этот словарь. 3. Укажите строку `NSPrivacyCollectedDataTypeUserID` (идентификатор типа данных `UserID` в [списке категорий и типов данных, подлежащих указанию в файле манифеста](https://developer.apple.com/documentation/bundleresources/describing-data-use-in-privacy-manifests#Describe-the-data-your-app-or-third-party-SDK-collects)) для ключа `NSPrivacyCollectedDataType` в словаре `NSPrivacyCollectedDataTypes`. 4. Укажите значение `true` для ключей `NSPrivacyCollectedDataTypeTracking` и `NSPrivacyCollectedDataTypeLinked` в словаре `NSPrivacyCollectedDataTypes`. 5. Используйте строку `NSPrivacyCollectedDataTypePurposeProductPersonalization` в качестве значения ключа `NSPrivacyCollectedDataTypePurposes` в словаре `NSPrivacyCollectedDataTypes`. Если вы таргетируете пейволы на аудитории с пользовательскими атрибутами, тщательно проверьте, соответствуют ли используемые атрибуты [категориям и типам данных, подлежащим указанию в файле манифеста](https://developer.apple.com/documentation/bundleresources/describing-data-use-in-privacy-manifests). Если да — повторите шаги выше для каждого типа данных. После того как вы укажете все собираемые типы и категории данных, создайте отчёт о конфиденциальности вашего приложения, как описано в [документации Apple](https://developer.apple.com/documentation/bundleresources/describing-data-use-in-privacy-manifests#Create-your-apps-privacy-report). ## Раскрытие информации об Apple App Privacy в App Store Connect \{#apple-app-privacy-disclosure-in-app-store-connect\} 1. В [App Store Connect](https://appstoreconnect.apple.com/) откройте своё приложение и перейдите в раздел **App Privacy**. Нажмите **Get Started**. <img src="/assets/shared/img/app-privacy-get-started.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Выберите **Yes, we collect data from this app** и нажмите **Next**. <img src="/assets/shared/img/app-privacy-data-collection.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Типы данных \{#data-types\} В таблице ниже перечислены типы данных, которые Apple обязывает раскрывать, с указанием тех, которые требуются Adapty. **Это относится только к Adapty.** Если ваше приложение собирает дополнительные данные через другие SDK или собственный код, также выберите соответствующие типы данных. ✅ = Требуется Adapty 👀 = Может потребоваться (подробнее см. ниже) ❌ = Не требуется Adapty — выберите, если ваше приложение собирает эти данные другими средствами | Тип данных | Требуется | Примечание | |--------------------------------------------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------| | Identifiers | ✅ | <p>Если вы идентифицируете пользователей с помощью customerUserId, выберите 'User ID'.</p><p></p><p>Adapty собирает IDFA, поэтому необходимо выбрать 'Device ID'.</p> | | Purchases | ✅ | Adapty собирает историю покупок пользователей. | | Contact Info, в том числе имя, номер телефона или адрес электронной почты | 👀 | Требуется, если вы передаёте персональные данные, такие как имя, номер телефона или адрес электронной почты, с помощью метода **`updateProfile`**. | | Usage Data | 👀 | Может потребоваться, если вы используете аналитические SDK, такие как Amplitude, Mixpanel, AppMetrica или Firebase. | | Location | ❌ | Adapty не собирает точные данные о местоположении. Выберите, если ваше приложение их собирает. | | Health & Fitness | ❌ | Adapty не собирает данные о здоровье или физической активности. Выберите, если ваше приложение их собирает. | | Sensitive Info | ❌ | Adapty не собирает конфиденциальную информацию. Выберите, если ваше приложение её собирает. | | User Content | ❌ | Adapty не собирает пользовательский контент. Выберите, если ваше приложение его собирает. | | Diagnostics | ❌ | Adapty не собирает диагностические данные. Выберите, если ваше приложение их собирает. | | Browsing History | ❌ | Adapty не собирает историю браузера. Выберите, если ваше приложение её собирает. | | Search History | ❌ | Adapty не собирает историю поиска. Выберите, если ваше приложение её собирает. | | Contacts | ❌ | Adapty не собирает списки контактов. Выберите, если ваше приложение их собирает. | | Financial Info | ❌ | Adapty не собирает финансовую информацию. Выберите, если ваше приложение её собирает. | ### Обязательные типы данных \{#required-data-types\} #### Покупки \{#purchases\} При использовании Adapty необходимо раскрыть, что ваше приложение собирает **Purchase History**. <img src="/assets/shared/img/feb3b9f-CleanShot_2023-08-25_at_12.32.552x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> #### Идентификаторы \{#identifiers\} При использовании Adapty необходимо раскрыть следующие идентификаторы: - **Device ID** — Adapty собирает IDFA. - **User ID** — требуется, если вы идентифицируете пользователей с помощью **`customerUserId`**. <img src="/assets/shared/img/93f3daa-CleanShot_2023-08-25_at_12.35.272x.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Использование данных \{#data-usage\} После сохранения **Data types** необходимо указать, как используются данные: 1. Нажмите **Set up purchase history** в блоке **Purchases**. <img src="/assets/shared/img/purchase-privacy.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Когда Apple спросит, как используются данные истории покупок, выберите следующее для Adapty: - **Analytics** — Adapty использует историю покупок для аналитики дохода, когорт и метрик. - **Product Personalization** — Adapty использует данные о покупках для сегментации аудитории и таргетинга пейволов. - **App Functionality** — Adapty проверяет покупки, управляет уровнями доступа и отслеживает статус подписки. Выберите дополнительные цели, если ваше приложение использует данные о покупках другими способами (например, если вы отправляете события покупок на рекламные платформы через интеграции Adapty). <img src="/assets/shared/img/purchase-history.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Нажмите **Next**. 4. Для **Device ID** и **User ID** (если используется): 1. Нажмите **Set up user/device ID** в блоке **User/Device ID**. 2. Когда Apple спросит, как используются данные идентификатора, выберите следующее для Adapty: - **App Functionality** — Adapty использует идентификаторы для управления профилями пользователей, связывания покупок и отслеживания уровней доступа. Если вы отправляете данные атрибуции на сторонние платформы через интеграции Adapty (например, AppsFlyer или Adjust), также выберите **Third-Party Advertising**. Выберите дополнительные цели, если ваше приложение использует идентификаторы другими способами. <img src="/assets/shared/img/user-id-privacy.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Нажмите **Next**. --- # File: apple-family-sharing --- --- title: "Apple Family Sharing" description: "Включите Apple Family Sharing в Adapty для поддержки совместных подписок." --- Функция семейного доступа Apple позволяет распределять встроенные покупки между членами семьи — это удобный способ для пользователей групповых приложений (например, стриминговых сервисов и детских приложений) пользоваться одной подпиской без необходимости делиться Apple ID. Позволяя до пяти членов семьи пользоваться подпиской, [Family Sharing](https://developer.apple.com/documentation/storekit/supporting-family-sharing-in-your-app) способен повысить вовлечённость и удержание пользователей вашего приложения. В этом гайде мы расскажем, как подключить подписки к Family Sharing и как Adapty управляет покупками, которые используются в рамках семейного доступа. Чтобы включить Family Sharing для конкретного продукта, перейдите в [App Store Connect](https://appstoreconnect.apple.com/). По умолчанию Family Sharing отключён как для новых, так и для существующих встроенных покупок, поэтому его нужно включать отдельно для каждой встроенной покупки. Сделать это просто: откройте **страницу приложения**, перейдите на страницу нужной встроенной покупки и выберите опцию **Turn On** в разделе Family Sharing. Имейте в виду: после включения Family Sharing для продукта **его нельзя отключить**, так как это нарушит работу для пользователей, уже поделившихся подпиской с членами семьи. Также учтите, что совместное использование доступно только для некостребуемых покупок и подписок. <img src="/assets/shared/img/6db165a-CleanShot_2023-03-28_at_17.15.342x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> В появившемся модальном окне нажмите кнопку **Confirm**, чтобы завершить настройку. После этого раздел Family Sharing обновится и отобразит сообщение: «This subscription can be shared by everyone in a family group.» Это подтверждает, что подписка теперь доступна для Family Sharing и может использоваться совместно до пяти членами семьи. Adapty упрощает поддержку Family Sharing — дополнительных усилий не потребуется. Просто [настройте продукты](app-store-products) в App Store, и как только вы **включите** Family Sharing в App Store Connect, оно автоматически заработает в **Adapty** и будет передаваться как событие в вебхук. :::note Обратите внимание, что Family Sharing не поддерживается в песочнице. ::: Следует учитывать, что когда пользователь покупает подписку и открывает к ней доступ членам семьи, до момента, когда подписка становится доступна им, может пройти **до одного часа**. Apple намеренно ввела эту задержку, чтобы дать пользователю время передумать и отменить совместный доступ. Однако при продлении подписки никакой задержки для членов семьи нет. Когда пользователь приобретает продукт с поддержкой Family Sharing, транзакция появится в его чеке в обычном виде, но с добавлением нового поля `in_app_ownership_type` со значением `PURCHASED.` Кроме того, для всех членов семьи будет создана отдельная транзакция с другими значениями `web_order_line_item_id` и `original_transaction_id` по сравнению с исходной покупкой, а также с полем `in_app_ownership_type` со значением `FAMILY_SHARED.` Чтобы расчёт выручки был точным, в аналитику Adapty включаются только транзакции с `in_app_ownership_type` равным `PURCHASED`. Транзакции с типом `FAMILY_SHARED` исключаются из метрик выручки и конверсии. **События для транзакций Family Sharing.** Для транзакций `FAMILY_SHARED` отправляется только событие **Access level updated**. События подписки для конкретных продуктов для участников семейного доступа не отправляются. | Событие | `FAMILY_SHARED` | `PURCHASED` | | --- | --- | --- | | **Access level updated** | Да | Да | | **Subscription started** | Нет | Да | | **Trial started** | Нет | Да | | **Subscription renewed** | Нет | Да | | **Subscription expired** | Нет | Да | | **Subscription refunded** | Нет | Да | | **Billing issue detected** | Нет | Да | Если ваша аналитика ориентируется на **Subscription started**, участники семейной подписки там не появятся. Используйте **Access level updated**, чтобы отслеживать активных участников семейного доступа. Чтобы найти других участников семейной подписки в Adapty, нужно открыть детали события. Сначала найдите оригинальную транзакцию семейной покупки, затем в деталях события найдите другие транзакции с тем же продуктом, датой покупки и датой истечения срока. Так вы определите все семейные транзакции, связанные с исходной покупкой. --- # File: app-store-small-business-program --- --- title: "Программа App Store для малого бизнеса" description: "Узнайте о программе Apple для малого бизнеса, её влиянии на выручку и аналитику Adapty" --- :::link Об аналогичной программе в Play Store читайте в разделе [Сниженная комиссия Google](google-reduced-service-fee). ::: Организации, получающие до 1 миллиона долларов в год от App Store, имеют право участвовать в [программе Apple Small Business](https://developer.apple.com/app-store/small-business-program/). При регистрации стандартная комиссия стора в 30% снижается до **15%**. Участники программы обязаны **изменить настройки в Adapty**, чтобы обеспечить корректный расчёт выручки и правильную обработку событий интеграций. --- title: "Программа Small Business Program" description: "Настройте Adapty для работы с App Store Small Business Program и Google Play Reduced Service Fee, чтобы корректно учитывать сниженную комиссию стора." metadataTitle: "Программа Small Business Program | Документация Adapty" --- В этой статье описано: * [Как настроить Adapty](#configure-adapty), если ваше приложение участвует в программе Small Business Program * [Как вступить в программу](#apply-for-the-program), если вы хотите снизить комиссию стора ## Настройка Adapty \{#configure-adapty\} Adapty может учитывать сниженную ставку комиссии в ваших [аналитике](analytics) и [событиях интеграций](analytics-integration). Чтобы включить это, укажите статус участника программы Small Business Program для каждого приложения отдельно. :::warning Укажите статус SBP в Adapty **сразу после получения подтверждения**. Изменения, внесённые с опозданием, не перезапишут уже доставленные события вебхуков ([подробнее](#retroactive-setting-changes)). ::: 1. Откройте [**App Settings** → **General**](https://app.adapty.io/account) 2. Найдите раздел **Small Business Program**. 3. Нажмите **Add period**. 4. Выберите дату начала участия в программе. 5. Выберите дату окончания или включите флажок **At the current moment**, чтобы продлить этот статус на неопределённый срок. Если в будущем вы [потеряете право на участие](#losing-eligibility), дату окончания можно будет изменить. 6. Нажмите **Apply**. Если ваша организация по-прежнему соответствует требованиям программы, членство автоматически переносится на следующий календарный год. Однако статус членства применяется **только к указанному диапазону дат**. * Нажмите **Add period**, чтобы добавить новый период членства. * Чтобы сделать этот статус бессрочным, включите флажок **At the current moment**. Чтобы проверить настройку, откройте [график Revenue](revenue) и выберите **Proceeds after store commission**. Убедитесь, что отображаемая выручка отражает сниженную ставку комиссии. ## Подайте заявку на участие в программе \{#apply-for-the-program\} ### Требования для участия \{#eligibility-requirements\} Apple определяет право на участие в SBP на основе вашего **годового дохода** — продаж за предыдущий календарный год **после** вычета комиссии стора и налогов. Чтобы получить право на участие, суммарный годовой доход вашей организации и её <InlineTooltip tooltip="связанных аккаунтов разработчика">Аккаунты, в которых вы или ваша организация владеете долей более 50% или обладаете правом принятия решений.</InlineTooltip> не должен превышать 1 миллион долларов США. Новые организации автоматически получают право подать заявку на участие в программе. ### Перед началом работы \{#before-you-apply\} Убедитесь, что вы: - Являетесь владельцем аккаунта (Account Holder) в программе Apple Developer Program - Приняли актуальный договор Paid Applications в App Store Connect - Можете перечислить все связанные аккаунты разработчика (Associated Developer Accounts) ### Регистрация \{#enrollment\} 1. Перейдите на [страницу регистрации в программе App Store Small Business Program](https://developer.apple.com/app-store/small-business-program/). 2. Нажмите **Enroll** и войдите в свой аккаунт Apple Developer. 3. Проверьте предзаполненные данные (имя, email, Team ID) и отправьте форму. ### Проверка \{#review\} Процесс проверки может занять больше месяца. Если вы соответствуете требованиям, Apple пришлёт письмо с подтверждением. После одобрения начинается период ожидания. Сниженная комиссия вступает в силу на 15-й день [следующего финансового периода](https://adapty.io/apple-fiscal-calendar/) Apple. На более ранние транзакции она не распространяется. ### Потеря права на участие \{#losing-eligibility\} Как только общая выручка за текущий календарный год превысит 1 миллион долларов США, вы теряете членство в программе, и Apple начинает применять стандартную комиссию 30%. :::important Если ваш бизнес выходит из программы Small Business Program, **немедленно измените дату выхода** в настройках. Иначе Adapty продолжит рассчитывать комиссию по сниженной ставке. ::: Вы можете снова вступить в программу **в следующем году** после того, как ваша годовая выручка снова опустится ниже 1 миллиона долларов США. Подробнее читайте в [официальных условиях программы](https://developer.apple.com/app-store/small-business-program/). ## Ретроактивные изменения настроек \{#retroactive-setting-changes\} Когда вы меняете статус сниженной комиссии в Adapty с ретроактивной датой вступления в силу, новая ставка комиссии отображается в данных Adapty по разным расписаниям: | Где отображается ставка | Что происходит после изменения ставки | | --- | --- | | Дашборд аналитики (Revenue, Proceeds, MRR, ARR) | Adapty применяет новую ставку в течение 24 часов, когда запускается ежедневный перерасчёт. | | Экспорт в S3, GCS и BigQuery | Adapty применяет новую ставку при следующем запланированном экспорте. | | Уже доставленные события вебхуков | Adapty не может изменить события вебхуков после доставки. Они сохраняют старую ставку. | Если ваше хранилище данных содержит выручку из событий вебхуков, эти записи сохранят старую ставку комиссии. Для сверки данных получите данные за затронутый период из дашборда аналитики или создайте новый экспорт в S3, GCS или BigQuery. --- # File: android-products --- --- title: "Продукт в Play Store" description: "Управляйте продуктами Android с помощью Adapty, упрощайте встроенные покупки и оптимизируйте стратегии монетизации." --- На этой странице описано, как создать продукт в Play Store. Эта информация может не относиться напрямую к функциональности Adapty, но будет полезна, если у вас возникнут трудности при создании продуктов в Google Play Console. Продукт — это цифровой товар или услуга, которую вы предлагаете пользователям внутри приложения в Play Store, как правило, за отдельную плату. К таким продуктам относятся встроенные покупки: разовые покупки, подписки и другие цифровые товары, доступные пользователям в вашем приложении. В [биллинговой системе Google](https://developer.android.com/google/play/billing/compatibility) подписки могут включать несколько базовых планов, каждый из которых предоставляет различные скидки или предложения. Эта структура состоит из трёх основных компонентов: - **Подписки:** набор преимуществ, которыми пользователи могут пользоваться в течение определённого периода (то, что продаётся). Например, «Золотой уровень» с премиум-функциями для подписчиков. - **Базовые планы:** конкретные конфигурации периодов выставления счетов, типов возобновления и цен (то, как продаётся товар). Например, «годовой с автопродлением» или «предоплаченный месячный». - **Предложения:** скидки для подходящих пользователей, изменяющие цену базового плана. Например, «бесплатный 14-дневный пробный период для новых пользователей». ## Как создать продукт в Play Store? \{#how-to-create-a-product-in-play-store\} Продукт — это цифровой товар или услуга, которую вы предлагаете пользователям внутри приложения, как правило, за отдельную плату. К таким продуктам относятся встроенные покупки: разовые покупки, подписки и другие цифровые товары, доступные пользователям в вашем приложении. Чтобы настроить продукт для Android-устройств: 1. Откройте раздел [**Monetize** -> **Subscriptions**](https://console.cloud.google.com/iam-admin/serviceaccounts) или [**Monetize** -> **In-app products**](https://console.cloud.google.com/iam-admin/serviceaccounts) в левом меню Google Play Console. <img src="/assets/shared/img/6eff1d1-subscription_GP.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите кнопку **Create subscription**. <img src="/assets/shared/img/af7fe02-create_subscription_GP.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. В открывшемся окне **Create subscription** введите идентификатор подписки в поле **Product ID** и название подписки в поле **Name**. Product ID должен быть уникальным, начинаться с цифры или строчной буквы, а также может содержать символы подчёркивания (\_) и точки (.). Он используется для доступа к продукту в процессе разработки и его синхронизации с Adapty. После того как Product ID назначен продукту в Google Play Console, его нельзя использовать повторно для других приложений, даже если продукт удалён. При выборе Product ID рекомендуется придерживаться стандартизированного формата. Мы советуем использовать более лаконичный подход и называть продукт по схеме `<название подписки>.<уровень доступа>`. Длительность и периодичность выставления счетов можно регулировать с помощью базовых планов: еженедельного, ежемесячного и т. д. Название используется только для вашего удобства — оно будет отображаться в листинге Google Play Store, поэтому можно использовать любое понятное вам название. Максимальная длина — 55 символов. 4. Нажмите кнопку **Create**, чтобы подтвердить создание подписки. :::note Продукты подписок Google Play в Adapty Продукты Adapty соответствуют базовым планам подписок Google Play, поскольку именно их покупают пользователи. Adapty автоматически обрабатывает перенос существующих подписок Google Play вместе с соответствующими базовыми планами в продуктах — никаких дополнительных действий с вашей стороны не требуется. Однако при добавлении нового продукта в Adapty вам нужно будет указать как идентификатор базового плана, так и идентификатор продукта. ::: ### Создание базового плана \{#create-a-base-plan\} Для продуктов-подписок необходимо добавить базовый план. Базовые планы определяют период выставления счетов, цену и тип возобновления, по которым пользователи приобретают подписку. Обратите внимание, что пользователи не покупают продукт-подписку напрямую — они всегда приобретают базовый план в рамках подписки. Чтобы создать базовый план: 1. Откройте раздел [**Monetize** -> **Subscriptions**](https://console.cloud.google.com/iam-admin/serviceaccounts) в левом меню Google Play Console и найдите подписку, к которой хотите добавить базовый план. 2. Нажмите кнопку **View subscription** рядом с нужной подпиской. <img src="/assets/shared/img/4072a2a-subscriptions_GP.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. После открытия сведений о подписке нажмите кнопку **Add base plan** под заголовком **Base plans and offers**. Возможно, потребуется прокрутить страницу вниз. <img src="/assets/shared/img/b493b60-add_base_plan.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. В открывшемся окне **Add base plan** введите уникальный идентификатор базового плана в поле **Plan ID**. Он должен начинаться с цифры или строчной буквы и может содержать цифры (0–9), строчные буквы (a–z) и дефисы (-). Заполните остальные обязательные поля. <img src="/assets/shared/img/8146763-CleanShot_2023-07-20_at_16.51.412x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Укажите цены по регионам. <img src="/assets/shared/img/8b26e1d-prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. Нажмите кнопку **Save**, чтобы завершить настройку. 7. Нажмите кнопку **Activate**, чтобы активировать базовый план. Обратите внимание, что в Adapty у продуктов-подписок может быть только один базовый план с фиксированной длительностью и типом возобновления. ### Резервные продукты \{#fallback-products\} :::warning Поддержка базовых планов без обратной совместимости Старые версии Adapty SDK не поддерживают возможности Google Billing Library v5+, а именно несколько базовых планов на один продукт-подписку и предложения. С этими версиями SDK доступны только базовые планы, помеченные как **[backwards compatible](https://support.google.com/googleplay/android-developer/answer/12124625?hl=en#backwards_compatible)** в Google Play Console. Обратите внимание, что только один базовый план на подписку может быть помечен как обратно совместимый. ::: <img src="/assets/shared/img/b5e70cb-CleanShot_2023-07-20_at_17.03.252x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Чтобы в полной мере воспользоваться расширенными конфигурациями и возможностями подписок Google в Adapty, мы предоставляем возможность настройки резервного продукта с обратной совместимостью. Этот резервный продукт используется исключительно для приложений, работающих на старых версиях Adapty SDK. При создании продуктов Google Play теперь можно указать, следует ли пометить продукт как обратно совместимый в Play Console. Adapty использует эту информацию, чтобы определить, может ли продукт быть куплен старыми версиями SDK (версии 2.5 и ниже). Предположим, у вас есть подписка `subscription.premium` с двумя базовыми планами: еженедельным (обратно совместимым) и ежемесячным. Если вы добавляете продукт `subscription.premium:weekly` в Adapty, указывать обратно совместимый продукт не нужно. Однако для продукта `subscription.premium:monthly` потребуется указать обратно совместимый продукт. Если этого не сделать, в Google Billing Library 4-й версии может произойти непреднамеренная покупка продукта `subscription.premium:weekly`. Чтобы избежать этого, следует создать отдельный продукт, у которого базовый план также является ежемесячным и помечен как обратно совместимый. Это гарантирует, что пользователи, выбравшие вариант `subscription.premium:monthly`, будут корректно тарифицироваться с нужной периодичностью. ## Добавление продуктов в Adapty \{#add-products-to-adapty\} После того как вы добавили встроенные покупки, подписки и предложения в App Store Connect, следующий шаг — [добавить эти продукты в Adapty](create-product). --- # File: google-play-data-safety --- --- title: "Политика безопасности данных Google Play" description: "Обеспечьте соответствие требованиям политики безопасности данных Google Play в Adapty." --- Раздел «Безопасность данных» в Google Play предоставляет разработчикам приложений простой способ информировать пользователей о том, какие данные собирает или передаёт их приложение, а также рассказать о ключевых мерах конфиденциальности и безопасности. Эта информация помогает пользователям принимать более взвешенные решения при выборе приложений для загрузки и использования. Ниже — краткий гайд по данным, которые собирает Adapty, чтобы вы могли предоставить необходимую информацию в Google Play. ## Сбор и безопасность данных \{#data-collection-and-security\} <img src="/assets/shared/img/3508c24-image4.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> **Собирает ли ваше приложение или передаёт ли оно какие-либо из обязательных типов пользовательских данных?** Выберите «Да», так как Adapty собирает историю покупок пользователя. **Шифруются ли все пользовательские данные, собираемые вашим приложением, при передаче?** Выберите «Да», так как Adapty шифрует данные при передаче. **Предоставляете ли вы пользователям возможность запросить удаление их данных?** Если выбираете «Да», убедитесь, что у ваших пользователей есть способ связаться с вашей службой поддержки для запроса удаления данных. Вы сможете удалить пользователя напрямую из дашборда Adapty или через REST API. ## Типы данных \{#data-types\} Ниже приведён список типов данных, которые требует Google для отчётности, с указанием того, собирает ли Adapty каждый из них. | Тип данных | Подробности | | :-------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Местоположение | Не собирается Adapty | | Здоровье и фитнес | Не собирается Adapty | | Фото и видео | Не собирается Adapty | | Файлы и документы | Не собирается Adapty | | Календарь | Не собирается Adapty | | Контакты | Не собирается Adapty | | Пользовательский контент | Не собирается Adapty | | История браузера | Не собирается Adapty | | История поиска | Не собирается Adapty | | Информация о приложении и производительность | Не собирается Adapty | | Веб-браузинг | Не собирается Adapty | | Контактная информация | Не собирается Adapty | | Финансовая информация | Adapty собирает историю покупок пользователей | | Личная информация и идентификаторы | Adapty собирает User ID и другие идентифицирующие данные, включая имя, адрес электронной почты, номер телефона и т. д., если вы явно передаёте их в Adapty SDK. | | Идентификаторы устройств и другие | Adapty собирает данные об идентификаторе устройства. | ## Использование и обработка данных \{#data-usage-and-handling\} ### Идентификаторы пользователей \{#user-ids\} **1. Эти данные собираются, передаются или и то, и другое?** Эти данные собираются Adapty. Если вы используете интеграции между Adapty и третьими сторонами, которые не являются поставщиками услуг, вам может потребоваться также указать «Передаются». **2. Обрабатываются ли эти данные эфемерно?** Выберите «Нет». **3. Сбор этих данных обязателен для работы приложения, или пользователи могут отказаться?** Сбор этих данных обязателен и не может быть отключён. <img src="/assets/shared/img/2c60161-image5.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> **4. С какой целью собираются эти пользовательские данные? / С какой целью передаются эти пользовательские данные?** Отметьте чекбоксы «Функциональность приложения» и «Аналитика». <img src="/assets/shared/img/07a3c9e-image2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Финансовая информация \{#financial-info\} Если вы используете Adapty, необходимо раскрыть, что ваше приложение собирает информацию «История покупок» из раздела типов данных в Google Play Console. <img src="/assets/shared/img/1057870-image7.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### Идентификаторы устройств и другие \{#device-or-other-ids\} <img src="/assets/shared/img/d10f132-CleanShot_2023-03-01_at_17.55.312x.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <img src="/assets/shared/img/ccb1a2a-image5.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Следующие шаги \{#next-steps\} После того как вы сделаете выбор в разделе безопасности данных, Google отобразит предварительный просмотр раздела конфиденциальности вашего приложения. Если вы выбрали «Финансовая информация» и «Идентификаторы устройств и другие», как описано выше, информация о конфиденциальности должна выглядеть примерно так: <img src="/assets/shared/img/e8d9b73-image3.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Если вы готовы отправить приложение на проверку, обратитесь к нашему документу [Чеклист перед релизом](release-checklist) для получения дальнейших инструкций по подготовке приложения к публикации. --- # File: google-reduced-service-fee --- --- title: "Сниженная комиссия Google" description: "Узнайте о сниженной комиссии Google, её влиянии на вашу выручку и аналитику Adapty" --- :::link Аналогичная программа для App Store описана в разделе [Программа App Store Small Business Program](app-store-small-business-program). ::: Программа Google Play [Reduced Service Fee](https://support.google.com/googleplay/android-developer/answer/112622?hl=en) снижает комиссию с первого миллиона USD годового дохода с 30% до **15%**. Доход свыше миллиона USD в том же календарном году облагается стандартной ставкой 30%. :::note С 1 января 2022 года Google взимает 15% со всех автообновляемых подписок вне зависимости от этой программы. Reduced Service Fee в первую очередь выгодна для разовых встроенных покупок и платных приложений. ::: Участники программы должны **изменить настройки Adapty**, чтобы корректно рассчитывать доходы и обрабатывать события интеграций. В этой статье описано: * [Как настроить Adapty](#configure-adapty), если ваше приложение участвует в программе Reduced Service Fee * [Как вступить в программу](#enroll-in-the-program), если вы хотите снизить комиссию стора ## Настройка Adapty \{#configure-adapty\} Adapty может учитывать сниженную ставку комиссии в ваших [аналитических данных](analytics) и [событиях интеграций](analytics-integration). Чтобы это заработало, укажите статус Reduced Service Fee для каждого приложения отдельно. :::warning Настройте статус Reduced Service Fee в Adapty **сразу после подключения к программе**. Изменения не применяются задним числом к уже доставленным событиям вебхуков ([подробнее](#retroactive-setting-changes)). ::: 1. Откройте [**App Settings** → **General**](https://app.adapty.io/account). 2. Найдите раздел **Reduced Service Fee**. 3. Нажмите **Add period**. 4. Выберите дату начала участия в программе. 5. Выберите дату окончания или включите чекбокс **At the current moment**, чтобы продлить этот статус на неопределённый срок. Если ваш [годовой доход превысит 1 миллион USD](#exceeding-the-threshold), вы сможете изменить дату окончания. 6. Нажмите **Apply**. Статус участника применяется **только к указанному вами диапазону дат**. Программа сбрасывается каждый календарный год. * Нажмите **Add period**, чтобы добавить новый период участия. * Чтобы сделать статус бессрочным, включите флажок **At the current moment**. Чтобы проверить настройку, откройте [график Revenue](revenue) и выберите **Proceeds after store commission**. Убедитесь, что отображаемая выручка отражает сниженную ставку комиссии. ## Запись в программу \{#enroll-in-the-program\} ### Требования к участию \{#eligibility-requirements\} Google определяет право на участие на основе вашего **годового дохода** по всем аккаунтам в вашей <InlineTooltip tooltip="Группе аккаунтов">Группа аккаунтов разработчиков, чьи доходы учитываются совместно. Необходимо назначить один аккаунт разработчика основным и привязать к группе все связанные аккаунты.</InlineTooltip>. Ставка 15% применяется к первому миллиону USD совокупного годового дохода. Всё, что превышает этот порог, облагается по ставке 30%. ### Перед началом регистрации \{#before-you-enroll\} Убедитесь, что: - У вас настроен [платёжный профиль](https://support.google.com/googleplay/android-developer/answer/10632485) - Вы можете перечислить все связанные аккаунты разработчика ### Регистрация \{#enrollment\} 1. Перейдите в [Google Play Console](https://play.google.com/console/). 2. Создайте группу аккаунтов и укажите свой аккаунт разработчика как основной. 3. Привяжите дополнительные аккаунты разработчиков к группе. 4. Примите условия программы сниженной комиссии. После выполнения этих шагов Google автоматически зарегистрирует вас в программе — ручная проверка и письмо с подтверждением не требуются. Подробные инструкции см. в [гайде по регистрации](https://support.google.com/googleplay/android-developer/answer/10632485) от Google. ### Превышение порога \{#exceeding-the-threshold\} Когда суммарный годовой доход превышает 1 миллион USD, Google взимает 30% с той части дохода, которая превышает этот порог, до конца текущего календарного года. :::important Если ваш годовой доход превысил 1 миллион USD, **немедленно измените дату выхода** в настройках Adapty. В противном случае Adapty продолжит рассчитывать комиссию по сниженной ставке. ::: Программа сбрасывается каждый календарный год. Если ваш доход превысит 1 млн долларов за год, ставка 15% автоматически начнёт применяться снова к первому миллиону долларов в следующем году. Повторная регистрация не требуется. Подробнее читайте в [официальных условиях программы](https://support.google.com/googleplay/android-developer/answer/112622?hl=en). ## Ретроактивные изменения настроек \{#retroactive-setting-changes\} Когда вы меняете статус сниженной комиссии в Adapty с ретроактивной датой вступления в силу, новая ставка комиссии отображается в данных Adapty по разным расписаниям: | Где отображается ставка | Что происходит после изменения ставки | | --- | --- | | Дашборд аналитики (Revenue, Proceeds, MRR, ARR) | Adapty применяет новую ставку в течение 24 часов, когда запускается ежедневный перерасчёт. | | Экспорт в S3, GCS и BigQuery | Adapty применяет новую ставку при следующем запланированном экспорте. | | Уже доставленные события вебхуков | Adapty не может изменить события вебхуков после доставки. Они сохраняют старую ставку. | Если ваше хранилище данных содержит выручку из событий вебхуков, эти записи сохранят старую ставку комиссии. Для сверки данных получите данные за затронутый период из дашборда аналитики или создайте новый экспорт в S3, GCS или BigQuery. --- # File: google-play-quota-increase --- --- title: "Запрос на увеличение квоты Google Play Developer API" description: "Запросите увеличение квоты Google Play Developer API, если вы превышаете лимит по умолчанию при исторических импортах или при большой базе подписчиков." --- Adapty использует [Google Play Developer API](https://developers.google.com/android-publisher) для валидации покупок и синхронизации данных подписок. Квота по умолчанию для этого API составляет 3 000 запросов в минуту. Если ваше приложение превышает этот лимит, Google отправляет вам уведомление на email. Чаще всего это происходит во время [импорта исторических данных](importing-historical-data-to-adapty) или в приложениях с большим количеством активных подписчиков. Чтобы избежать сбоев, запросите увеличение квоты у Google до запуска крупного импорта или если вы получили уведомление о превышении квоты. ## Перед началом \{#before-you-start\} Включите [Real-time Developer Notifications (RTDN)](enable-real-time-developer-notifications-rtdn), если вы ещё этого не сделали. RTDN доставляет обновления подписок через push-уведомления вместо поллинга, что снижает потребление API. Google может отклонить запросы на увеличение квоты, если RTDN не включён. ## Сбор необходимых данных \{#gather-required-information\} Перед открытием формы запроса соберите следующие значения: - **Developer Account ID**: чтобы найти его, в [Google Play Console](https://play.google.com/console/) перейдите в **Settings > Developer account > Account details**. ID отображается в верхней части страницы. - **Имя пакета приложения**: имя пакета вашего Android-приложения (например, `com.example.app`). Найдите его в Google Play Console на странице **Dashboard** вашего приложения. - **Номер проекта Google Cloud**: чтобы найти его, в [Google Cloud Console](https://console.cloud.google.com/) выберите ваш проект. Номер проекта отображается на странице **Dashboard**. ## Запрос на увеличение квоты \{#request-the-quota-increase\} 1. Откройте [форму запроса на увеличение квоты Google Play Developer API](https://support.google.com/googleplay/android-developer/contact/apiqr). 2. Введите ваш Developer Account ID, имя пакета приложения и номер проекта Google Cloud. 3. Выберите API и набор квот, который необходимо увеличить. Если вы получили email от Google о превышении квоты, в нём будет указан конкретный набор. 4. В поле обоснования объясните, что вы используете сторонний сервис управления подписками, которому требуется доступ к API для валидации покупок и синхронизации данных подписок. 5. В поле запрашиваемой квоты укажите необходимый объём. Если вы не уверены, сколько запросить, проверьте текущее потребление в [Google Cloud Console](https://console.cloud.google.com/) в разделе **IAM & Admin > Quotas** (отфильтруйте по «Google Play Android Developer API»), затем отправьте данные о потреблении и количество исторических записей, которые планируете импортировать, на [support@adapty.io](mailto:support@adapty.io) — мы поможем определить нужный объём. 6. Отправьте форму. Google обычно обрабатывает запросы на увеличение квоты в течение нескольких рабочих дней. --- # File: prepare-your-app-for-store-review --- --- title: "Подготовьте приложение к проверке в сторе" description: "Советы по прохождению проверки в App Store и Google Play Store" --- В этой статье описан процесс проверки приложений в сторах и даны советы по ускорению одобрения. Информация взята из официальных руководств по публикации: * [Правила публикации в App Store](https://developer.apple.com/app-store/review/guidelines/) * [Правила публикации в Google Play Store](https://play.google/developer-content-policy/) :::important Оба стора используют схожий процесс проверки. Если правило применяется только к одному из них, это явно указано в статье. ::: Пользователям Adapty стоит уделить особое внимание требованиям, связанным с [пейволами и встроенными покупками](#iap-related-requirements) — именно они чаще всего становятся причиной отклонения приложений. ## Перед началом \{#before-you-begin\} Убедитесь, что приложение готово к публикации. В Adapty есть [чеклист для выпуска](release-checklist), который поможет подготовить приложение к публикации. Google Play Store требует, чтобы новые разработчики [протестировали приложение](https://support.google.com/googleplay/android-developer/answer/14151465?hl=en) перед отправкой на проверку. Тестирование должно охватывать не менее 12 человек и продолжаться минимум 14 дней подряд. Это требование введено в 2025 году, чтобы снизить количество сырых приложений, которые попадают на проверку к сотрудникам Google. ## Обзор процесса проверки \{#review-process-overview\} #### Шаг 1: Автоматическая проверка \{#step-1-the-automated-screening\} И App Store, и Google Play Store используют схожий двухэтапный процесс проверки. Сразу после отправки приложение проходит автоматическое сканирование, которое может занять несколько часов. Оба стора проверяют приложения на наличие вредоносного ПО, причём Google уделяет этому особое внимание. Система ищет поведенческие признаки вредоносной активности: обращения к подозрительным серверам, необоснованный доступ к пользовательским данным. Если приложение признаётся потенциально опасным, оно помечается и передаётся аналитику безопасности. Приблизительный список проверок, выполняемых на этом этапе, описан в [документации Google Play Protect](https://developers.google.com/android/play-protect/cloud-based-protections#machine-learning). Сторы также проверяют наличие необходимых метаданных, отсутствие вредоносных или устаревших зависимостей и целостность сборки. #### Шаг 2: Проверка человеком \{#step-2-the-human-review\} После прохождения автоматической проверки приложение изучает живой рецензент. Этот шаг может занять несколько дней — в зависимости от сложности приложения и текущей очереди на проверку. Приложения, обрабатывающие чувствительные данные, проверяются дольше. ## Общие требования \{#general-requirements\} ### Стабильность \{#stability\} Приложения, которые падают во время проверки, отклоняются. Рецензенты могут намеренно эмулировать нестабильное сетевое соединение, поэтому приложение должно корректно с этим справляться. ### Полнота \{#completeness\} Как Apple, так и Google устанавливают требование *полноты* («минимальной функциональности») в отношении контента в сторе. * Плейсхолдеры, экраны «скоро» и нерабочий функционал ведут к отклонению iOS-приложений. * Google [более гибок](https://support.google.com/googleplay/android-developer/answer/9898783?hl=en), особенно если ваше приложение находится в [раннем доступе](https://knowledge.workspace.google.com/admin/users/access/turn-early-access-apps-on-or-off-for-users). * Оба стора **отклоняют приложения** с минимальным или отсутствующим функционалом. Это касается приложений, отображающих единственное изображение, PDF-файл или веб-страницу. Отсутствующий контент попадает в ту же категорию. * Если приложение не делает то, что вы рекламируете, оно будет отклонено. * Если вы настроили встроенную покупку в дашборде, но не включили её в сборку, приложение будет отклонено. ### Точность метаданных \{#metadata-accuracy\} Вводящая в заблуждение, недостоверная или противоречивая информация в описании, скриншотах и других метаданных может стать причиной отказа. Не используйте страницу в сторе для рекламы функций, которые ещё не реализованы. Если приложение не предназначено для широкой аудитории, рецензент будет искать дополнительную документацию с описанием его функций. Включите понятные инструкции в метаданные приложения. ### Возрастной рейтинг \{#content-rating\} Контент внутри приложения должен соответствовать заявленному возрастному рейтингу. ### Юридические аспекты \{#legal-aspects\} * Политика конфиденциальности приложения должна быть доступна прямо из приложения. Для этого можно использовать [кнопку-ссылку](paywall-buttons#links) в Paywall Builder. * Предлагайте пользователям ознакомиться с юридическими соглашениями и принять их **до** того, как они вступят в силу. * Сообщайте о наличии рекламы в приложении. Отсутствие такого раскрытия может привести к отказу. * Если ваше iOS-приложение включает встроенные покупки, необходимо принять **Paid Apps Agreement** в дашборде App Store Connect. ### Аутентификация \{#authentication\} Если часть контента приложения доступна только после аутентификации, предоставьте рецензенту рабочие учётные данные. Невозможность получить доступ к контенту в полном объёме — основание для отказа. Если приложение позволяет создавать учётные записи, оно должно также позволять их удалять. Переадресация пользователей на email-поддержку или сайт это требование не удовлетворяет. ### Доступ и конфиденциальность \{#access-and-privacy\} В метаданных приложения необходимо чётко указывать причину запроса каждого разрешения. Наиболее чувствительные разрешения (например, доступ к SMS и журналу звонков) могут потребовать видеодемонстрации. Тот же принцип применяется к чувствительным данным пользователей: если вы их запрашиваете — объясните зачем. ## Требования, связанные с встроенными покупками \{#iap-related-requirements\} Нарушения правил бизнес-политики — одна из самых распространённых причин отклонения приложений. Если основной способ монетизации вашего приложения — подписки и встроенные покупки, к нему будет повышенное внимание. ### Требования к пейволам \{#paywall-requirements\} Ревьюеры ожидают простых и понятных пейволов. Если вас заподозрят в манипуляции пользователями, приложение будет отклонено. Если несколько проверок выявят признаки недобросовестных практик, ваш аккаунт может быть деактивирован, а приложение [приостановлено](https://support.google.com/googleplay/android-developer/community-guide/287283557/app-suspended-for-repeated-rejections?hl=en). Google Play использует [систему предупреждений](https://support.google.com/googleplay/android-developer/answer/9899234?hl=en), которая может привести к удалению всех ваших приложений. Придерживайтесь следующих принципов при проектировании пейвола: - **Будьте прозрачны и честны.** Отображайте точную цену продукта, периодичность списаний, преимущества и условия отмены до того, как предложить пользователю совершить покупку. Чётко разграничивайте разовые покупки и продукты, требующие регулярных платежей. Если продукт предусматривает бесплатный пробный период, явно указывайте его длительность и условия. Не используйте намеренно запутанные формулировки, вводящие пользователя в заблуждение. - **Будьте последовательны.** Цены на продукты должны совпадать в листинге App Store, на экранах внутри приложения, на экранах управления подпиской и в маркетинговых материалах. Любое расхождение в ценах, даже незначительное, может стать причиной отказа. Paywall Builder в Adapty автоматически синхронизирует цены между вашим пейволом и продуктом в App Store Connect. Если пейвол написан вручную, вы должны [получить цену каждого продукта](fetch-paywalls-and-products) из его массива данных. - **Показывайте все уровни на равных условиях.** Не выделяйте заранее самый дорогой вариант и не скрывайте более дешёвые. - **Избегайте «тёмных паттернов».** Не создавайте искусственного ощущения срочности или дефицита. Не вынуждайте пользователей совершать покупки, намеренно делая бесплатные возможности неудобными или труднодоступными. ### Гарантия доступа \{#access-guarantee\} Приложение должно гарантировать пользователям доступ к их покупкам. * **Немедленный доступ** Успешная покупка должна сразу открывать доступ к продукту без видимых задержек. Промежуточные состояния авторизации платежа не должны вызывать ошибки или нарушать пользовательский опыт. После успешной покупки пейвол должен сразу скрываться. Если вы продолжаете показывать пейвол после покупки, пользователь не может получить доступ к оплаченному контенту. * **Восстановление доступа** Пользователь должен иметь возможность восстановить доступ к продукту с нового устройства. Разместите кнопку восстановления на видном месте. Если вы создали пейвол с помощью [Flow Builder](adapty-flow-builder), кнопка восстановления автоматически запускает процесс восстановления. Если вы [реализовали пейвол вручную](ios-implement-paywalls-manually), добавьте код, вызывающий метод [restorePurchases](restore-purchase). Adapty восстановит уровень доступа пользователя, **если только** вы не используете SDK в [режиме наблюдателя](observer-vs-full-mode). Приложение должно распознавать встроенные покупки, совершённые со страницы продукта в сторе или в других местах магазина приложений. ### Допустимые способы оплаты \{#appropriate-payment-methods\} Оба стора запрещают продажу физических товаров через встроенные покупки и требуют использования встроенного биллинга для большинства цифровых товаров. Требование об использовании встроенного биллинга не действует в ряде географических регионов, включая США и ЕС. В зависимости от страны вы можете [полностью отказаться от встроенного биллинга стора](https://support.google.com/googleplay/android-developer/answer/16497028) или [предложить пользователю выбор](https://support.google.com/googleplay/android-developer/answer/13821247) между биллингом App Store и альтернативными способами оплаты. Некоторые категории приложений (например, читалки электронных книг или приложения для знакомств) могут быть допущены к альтернативным способам оплаты даже за пределами этих регионов. Подробнее читайте в официальных правилах сторов. :::tip В отличие от [Google](https://support.google.com/googleplay/android-developer/answer/13821247), Apple не публикует официального списка стран, в которых разрешены альтернативные способы оплаты. По мере принятия аналогичного законодательства в новых юрисдикциях этот список будет расширяться. Перед тем как продолжить, ознакомьтесь с документацией для вашей страны. ::: :::note Обратите внимание, что оба стора соблюдают правила интеграции с платёжными провайдерами и продолжают взимать комиссию за транзакции, проводимые через эти сервисы. ::: ## Обработка отклонения \{#handling-rejection\} Если ваше приложение отклонили, рецензент укажет, какие именно правила были нарушены. Прочитайте соответствующее правило целиком и исправьте нарушение: * [Руководство по проверке приложений App Store](https://developer.apple.com/app-store/review/guidelines/) * [Правила для разработчиков Google Play Store](https://play.google/developer-content-policy/) Если вы считаете, что отклонение было несправедливым, вы вправе подать апелляцию. Предоставьте доказательства соответствия требованиям и обратитесь в стор. * Не обновляйте приложение во время проверки. * Каждый раз при отправке приложения на проверку вам может попасться другой рецензент. Это может сыграть как в вашу пользу, так и против вас. * Не исправляйте проблемы по одной. Отправляйте приложение на повторную проверку только после того, как все правки внесены. * Если Google Play отклонил приложение из-за нарушений политики, обновите соответствующие данные во всех треках, даже в приостановленных или неактивных. * Повторные проверки, как правило, занимают меньше времени, чем первая. * Ускоренная проверка может быть доступна при критических ошибках и дедлайнах — используйте её с умом. ## После проверки: постоянный мониторинг \{#after-the-review-continuous-monitoring\} Оба стора продолжают следить за вашим приложением даже после прохождения модерации. Если функциональность приложения изменится после одобрения (например, из-за динамически загружаемого кода), оно будет помечено и снято с публикации. Поток негативных отзывов пользователей тоже может стать поводом для дополнительной проверки. С 2024 по 2025 год Google [удалил 47% приложений из Play Store](https://techcrunch.com/2025/04/29/google-play-sees-47-decline-in-apps-since-start-of-last-year/), чтобы повысить их среднее качество. Заброшенное приложение тоже несёт риски. Как [Google](https://www.cnet.com/tech/mobile/google-play-store-will-hide-apps-that-havent-been-updated-in-years/), так и [Apple](https://developer.apple.com/support/app-store-improvements/#:~:text=Developers%20of%20apps%20that%20have,launch%20will%20be%20removed%20immediately.) удаляют из каталога приложения, которые долго не обновляются или не набирают загрузок. ## Смотрите также \{#see-also\} * [Тестирование в песочнице](test-purchases-in-sandbox) * [Чеклист для выпуска](release-checklist) --- # File: firebase-apps --- --- title: "Firebase приложения" description: "Интегрируйте Firebase с Adapty для улучшения аналитики пользователей и отслеживания подписок в вашем мобильном приложении." --- Эта страница посвящена интеграции Adapty в приложение, работающее на Firebase. :::note С чего начать Здесь описаны не все шаги, необходимые для работы Adapty, — только несколько полезных советов по интеграции с Firebase. Если вы хотите интегрировать Adapty в своё приложение, сначала прочитайте [Quickstart Guide](quickstart). ::: ## Идентификация пользователей \{#user-identification\} Если вы используете Firebase Auth, этот фрагмент кода поможет синхронизировать пользователей между Firebase и Adapty. Обратите внимание, что это лишь пример — учитывайте особенности авторизации в вашем приложении. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS with Firebase" default> ```swift showLineNumbers @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Configure Adapty before Firebase Adapty.activate("YOUR_API_KEY") Adapty.delegate = self // Configure Firebase FirebaseApp.configure() // Add state change listener for Firebase Authentication Auth.auth().addStateDidChangeListener { (auth, user) in if let uid = user?.uid { // identify Adapty SDK with new Firebase user Adapty.identify(uid) { error in if let e = error { print("Sign in error: \(e.localizedDescription)") } else { print("User \(uid) signed in") } } } } return true } } extension AppDelegate: AdaptyDelegate { // MARK: - Adapty delegate func didReceiveUpdatedPurchaserInfo(_ purchaserInfo: PurchaserInfoModel) { // You can optionally post to the notification center whenever // purchaser info changes. // You can subscribe to this notification throughout your app // to refresh tableViews or change the UI based on the user's // subscription status NotificationCenter.default.post(name: NSNotification.Name(rawValue: "com.Adapty.PurchaserInfoUpdatedNotification"), object: purchaserInfo) } } ``` </TabItem> <TabItem value="kotlin" label="Android with Firebase" default> ```kotlin showLineNumbers class App : Application() { override fun onCreate() { super.onCreate() // Configure Adapty Adapty.activate(this, "YOUR_API_KEY") Adapty.setOnPurchaserInfoUpdatedListener(object : OnPurchaserInfoUpdatedListener { override fun onPurchaserInfoReceived(purchaserInfo: PurchaserInfoModel) { // handle any changes to subscription state } }) // Add state change listener for Firebase Authentication FirebaseAuth.getInstance().addAuthStateListener { auth -> val currentUserId = auth.currentUser?.uid if (currentUserId != null) { // identify Adapty SDK with new Firebase user Adapty.identify(currentUserId) { error -> if (error == null) { //success } } } else { Adapty.logout { } } } } } ``` </TabItem> </Tabs> --- # File: refund-saver --- --- title: "Refund Saver" description: "Используйте Adapty Refund Saver для минимизации возвратов и максимизации дохода." --- Когда пользователь запрашивает возврат средств, Apple проводит расследование. Чтобы решить, **оправдан ли возврат**, она запрашивает у разработчика информацию об активности этого пользователя. Без таких данных даже активно используемая подписка, скорее всего, будет возвращена. **Refund Saver** автоматически отвечает на [запросы Apple о потреблении](https://developer.apple.com/documentation/appstoreserverapi/send-consumption-information-v1), защищая ваш доход и **повышая шанс отклонения** несправедливых запросов. Работает для всех типов встроенных покупок Apple — автообновляемых подписок, разовых подписок, расходуемых и нерасходуемых покупок (включая продукты с пожизненным доступом). ## Как работает Refund Saver \{#how-refund-saver-works\} 1. Когда пользователь инициирует запрос на возврат, App Store отправляет уведомление с запросом данных о транзакции и использовании приложения. Если вы **проигнорируете** запрос или **задержите** ответ, Apple, скорее всего, **одобрит возврат**. 2. Adapty Refund Saver автоматически обрабатывает эти уведомления, предоставляя Apple необходимые данные. Это снижает вероятность лишних возвратов, экономит время и защищает ваш доход. 3. Adapty фиксирует каждый исход — возврат одобрен или отклонён. Эти данные используются в аналитике Refund Saver на дашборде. :::info С Refund Saver вы можете сохранить до 40% выручки от запросов на возврат средств. ::: ## Требования для использования Refund Saver \{#requirements-to-use-refund-saver\} Для использования этой функции убедитесь, что выполнены следующие условия: 1. **Обновите Политику конфиденциальности в App Store Connect:** В Политике конфиденциальности вашего приложения должно быть раскрыто, как собираются и используются данные о потреблении. Это позволяет пользователям ознакомиться с практиками конфиденциальности перед загрузкой приложения. Обратитесь к [App Privacy Details от Apple](https://developer.apple.com/app-store/app-privacy-details/) для получения рекомендаций. 2. **Получите согласие пользователей на передачу данных в вашем приложении:** Apple требует, чтобы вы получили явное согласие пользователя перед передачей его персональных данных в Apple. Как разработчик, вы несёте ответственность за получение этого согласия, поскольку именно вы будете передавать данные пользователей в Apple. Подробнее см. в [руководстве](https://developer.apple.com/documentation/appstoreserverapi/send-consumption-information) Apple. 3. **Включите Server Notifications V2:** Убедитесь, что Server Notifications V2 активированы в вашем аккаунте Apple Developer и правильно настроены в Adapty, поскольку V1-уведомления не поддерживаются. Если они ещё не активированы, следуйте шагам из гайда [Включение серверных уведомлений App Store](enable-app-store-server-notifications). ## Включение Refund Saver \{#turn-on-refund-saver\} 1. Откройте раздел [Refund Saver](https://app.adapty.io/refund-saver) в дашборде Adapty. 2. Нажмите **Turn on Refund Saver**, чтобы активировать функцию. ## Установите поведение по умолчанию для возвратов \{#set-a-default-refund-behavior\} Apple позволяет разработчикам указывать предпочтительный исход для каждого запроса на возврат средств при ответе на него. Цель этой настройки — найти правильный баланс между отклонением и одобрением запросов на возврат, чтобы возвраты выполнялись только в обоснованных случаях. Обратите внимание, что эта настройка лишь влияет на исход, но окончательное решение всё равно остаётся за Apple. Adapty поддерживает эту настройку, однако одно и то же значение будет применяться ко всем запросам на возврат средств. 1. Чтобы изменить настройку, нажмите **Edit refund preference**. 2. В окне **Edit refund preference** выберите нужный вариант **Default refund request preference**: | Опция | Описание | | -------------------------------------------------- | ------------------------------------------------------------ | | Always decline | (по умолчанию) Настройка по умолчанию, которая обычно даёт наилучшие результаты по минимизации возвратов. | | Decline first refund request, grant all next | При первом обращении за возвратом по любой транзакции Refund Saver просит Apple отказать. Если та же транзакция появляется снова, Refund Saver всегда рекомендует одобрить возврат. Это снижает недовольство пользователей от несправедливых отказов — они могут просто повторить запрос и, скорее всего, получат возврат. | | Always refund | Рекомендует Apple одобрять каждый запрос на возврат. | | No preference | Не передавать Apple никаких рекомендаций. В этом случае Apple самостоятельно принимает решение о возврате, опираясь на свои внутренние политики и историю пользователя. Наиболее нейтральный вариант. | ## Настройка поведения Refund Saver для конкретного пользователя в дашборде \{#set-refund-behavior-for-a-specific-user-in-the-dashboard\} Даже если вы настроили поведение Refund Saver по умолчанию для всего приложения, вы можете задать индивидуальные настройки для конкретных пользователей. В дашборде Adapty это можно сделать из профиля пользователя. Используйте раздел **Refund Saver Preferences**, расположенный в нижнем левом углу. :::note Настройки на уровне пользователя переопределяют стандартное поведение на уровне приложения — включая поведение «Отклонить первый запрос на возврат, удовлетворить все последующие». ::: ## Установите поведение возврата для конкретного пользователя в SDK \{#set-refund-behavior-for-a-specific-user-in-the-sdk\} Вы можете задать предпочтение по возврату в коде приложения индивидуально для каждой установки в зависимости от действий пользователя. Используйте фрагмент ниже, чтобы установить предпочтение: <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (3.4.1+)" default> ```swift showLineNumbers code do { try await Adapty.updateRefundPreference(<PREFERENCE_VALUE>) // possible values: .noPreference, .grant, .decline } catch { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter (3.4.0+)" default> ```javascript showLineNumbers code try { // possible values: AdaptyRefundPreference.noPreference, AdaptyRefundPreference.grant, AdaptyRefundPreference.decline await Adapty().updateRefundPreference(<PREFERENCE_VALUE>); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="rn" label="React Native (3.4.0+)" default> ```typescript showLineNumbers try { await adapty.updateRefundPreference(<PREFERENCE_VALUE>); // possible values: RefundPreference.NoPreference, RefundPreference.Grant, RefundPreference.Decline } catch (error) { // handle the `AdaptyError` } ``` </TabItem> <TabItem value="unity" label="Unity (3.3.0+)" default> ```csharp showLineNumbers Adapty.UpdateAppStoreRefundPreference(<PREFERENCE_VALUE>, (error) => { if (error != null) { // handle the error return; } }); ``` </TabItem> </Tabs> :::note Вы также можете использовать Server-side API, чтобы [задать индивидуальные настройки возврата средств](api-adapty/operations/setRefundSaverSettings): - Используйте SDK, когда настройка предпочтений напрямую связана с действиями пользователя на клиенте, например когда пользователь нажимает кнопку для их настройки. - Используйте API, когда необходима серверная обработка или это лучше соответствует архитектуре вашего приложения. ::: ## Получение согласия пользователя \{#obtain-user-consent\} Способ сбора согласия пользователя на передачу данных остаётся на ваше усмотрение, однако Apple требует действительного согласия пользователя до передачи каких-либо персональных данных. Apple рекомендует использовать **подход на основе явного согласия (opt-in)**: показывать в приложении запросы с объяснением того, как будут использоваться данные, и требовать явного действия пользователя для подтверждения согласия. Если пользователь игнорирует запрос или отказывается, он не считается давшим согласие. Подробнее см. в [руководстве](https://developer.apple.com/documentation/appstoreserverapi/send-consumption-information) Apple. Если явное согласие пользователей нецелесообразно для вашего приложения, можно рассмотреть **подход с отказом (opt-out)**. Он предполагает включение в Условия использования пункта об обмене данными, согласно которому пользователи соглашаются на передачу данных, принимая эти условия. Обязательно чётко укажите, как пользователи могут отозвать своё согласие. Ниже приведён пример формулировки для подхода с отказом, включающей перечень типов данных, которыми вы можете делиться. Это лишь образец, призванный помочь вам составить собственный текст. Вы несёте ответственность за то, чтобы итоговая версия соответствовала всем применимым законам и требованиям Apple. *«Если мы получаем запрос на возврат средств за встроенную покупку, мы можем предоставить Apple информацию об активности пользователя в приложении. Это может включать такие данные, как время с момента установки приложения, общее время использования приложения, анонимный идентификатор аккаунта, была ли встроенная покупка полностью использована, включала ли она пробный период, общая потраченная сумма и общая сумма возврата.»* В зависимости от выбранного подхода установите параметр **Default consent policy** в меню **Edit refund preferences**: <p> </p> | Опция | Описание | | ------- | ------------------------------------------------------------ | | Opt-out | (по умолчанию) Если Adapty не знает статус согласия пользователя, предполагается, что согласие **было дано**, и Refund Saver **передаст** данные о возвратах в Apple. | | Opt-in | Если Adapty не знает статус согласия пользователя, предполагается, что согласие **не было дано**, и Refund Saver **не передаст** никакие данные в Apple. Это рекомендованный Apple подход. | ## Обновление согласия пользователя в SDK \{#update-user-consent-in-the-sdk\} Чтобы сообщить Adapty, дал ли конкретный пользователь согласие, используйте метод `updateCollectingRefundDataConsent`. Значение сохраняется на сервере для каждого профиля, поэтому вызывать его нужно только при изменении согласия. <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS (3.4.1+)" default> ```swift showLineNumbers do { try await Adapty.updateCollectingRefundDataConsent(<CONSENT_VALUE>) // true = согласие явно предоставлено, false = согласие явно отозвано } catch { // handle the error } ``` </TabItem> <TabItem value="flutter" label="Flutter (3.4.0+)" default> ```dart showLineNumbers try { // true = user gave consent, false = user revoked consent await Adapty().updateCollectingRefundDataConsent(<CONSENT_VALUE>); } on AdaptyError catch (adaptyError) { // handle the error } catch (e) { // handle the error } ``` </TabItem> <TabItem value="rn" label="React Native (3.4.0+)" default> ```typescript showLineNumbers try { await adapty.updateCollectingRefundDataConsent(<CONSENT_VALUE>); // true = согласие явно предоставлено, false = согласие явно отозвано } catch (error) { // обработайте `AdaptyError` } ``` </TabItem> <TabItem value="unity" label="Unity (3.3.0+)" default> ```csharp showLineNumbers Adapty.UpdateAppStoreCollectingRefundDataConsent(<CONSENT_VALUE>, (error) => { if (error != null) { // обработайте ошибку return; } }); ``` </TabItem> </Tabs> :::note Вы также можете использовать Server-side API для [настройки индивидуальных параметров передачи данных](api-adapty/operations/setRefundSaverSettings): - Используйте SDK, когда настройка предпочтений напрямую связана с действиями пользователя, например когда пользователь нажимает кнопку для изменения своих настроек. - Используйте API, когда требуется серверная обработка или когда это лучше соответствует архитектуре вашего приложения. ::: ## Проверка согласия пользователя \{#check-user-consent\} Вы можете проверить текущий статус согласия пользователя в любой момент. В дашборде Adapty откройте профиль пользователя и найдите настройку **Allow data sharing** в разделе **Refund Saver Preferences** в левом нижнем углу. :::note Вы также можете использовать Server-side API, чтобы [получить индивидуальные настройки возврата и передачи данных](api-adapty/operations/getRefundSaverSettings). ::: ## Ограничения \{#limitations\} - **Только App Store:** Refund Saver доступен только для запросов на возврат средств в App Store. Google Play не предоставляет данных об использовании покупок для анализа возвратов. Решения о возврате в Google Play принимаются исключительно на основе политик Google и информации, предоставленной пользователем. - **Требуется Server Notifications V2:** Refund Saver несовместим с App Store Server Notifications V1. Если вы используете V1 в Adapty, необходимо перейти на V2 — см. гайд [Отправка серверных уведомлений App Store в Adapty](enable-app-store-server-notifications). Переход на V2 также улучшит аналитику в Adapty: данные станут более точными и полными. --- # File: meta-create-campaign --- --- title: "Реклама приложения в Meta Ads" --- В этом пошаговом гайде вы узнаете, как создать и настроить рекламу для вашего приложения в Meta, чтобы удобно оптимизировать её и отслеживать эффективность. ## Структура рекламы в Meta \{#how-ads-in-meta-are-structured\} При размещении рекламы в Meta Ads нужно настроить три иерархических уровня: - **Campaign**: Кампании определяют ваши рекламные цели. - **Ad set**: Группы объявлений задают целевую аудиторию и плейсменты — то, кому и где будут показываться ваши объявления. Одна кампания может содержать несколько групп объявлений. - **Ads**: Объявления — это сами креативы, которые видят и с которыми взаимодействуют пользователи. Каждая группа объявлений может содержать несколько объявлений, однако для оптимальной эффективности рекомендуется не превышать пяти объявлений на группу. ## Шаг 1. Создайте аккаунт в Meta Ads Manager \{#step-1-create-meta-ads-manager-account\} Чтобы начать работу с Meta Ads, вам понадобится бизнес-страница в Facebook — запускать рекламу с личной страницы не получится. Вам нужно привязать бизнес-страницу к бизнес-портфолио в Meta Ads: 1. Перейдите на [business.facebook.com](https://business.facebook.com/). Если в бизнес-портфолио ещё нет бизнес-страницы, её нужно добавить. Нажмите **Go to settings**. 2. Перейдите в **Account > Pages** на боковой панели. Нажмите **Add** и выберите **Add an existing Facebook page** или **Create a new Facebook page**. Если у вас ещё нет бизнес-страницы, воспользуйтесь [гайдом по её созданию](https://www.facebook.com/business/help/473994396650734). 3. При желании привяжите аккаунт Instagram на странице **Account > Instagram accounts** в настройках. После подключения бизнес-страницы можно двигаться дальше. ## Шаг 2. Добавьте Meta pixel \{#step-2-add-meta-pixel\} Чтобы связать данные кампаний с выручкой и получить более точные результаты, вам понадобится Meta pixel. Прежде чем подключить данные и создать пиксель, вам потребуется: - Бизнес-страница — добавьте её в бизнес-портфолио в [**Settings > Accounts > Pages**](https://business.facebook.com/latest/settings/pages) - Аккаунт Business Manager — у вас должен быть полный контроль над бизнес-портфолио - Бизнес-email — задаётся в [**Settings > Business info**](https://business.facebook.com/latest/settings/business_info) - Рекламный аккаунт — добавьте его в бизнес-портфолио в [**Settings > Accounts > Ad accounts**](https://business.facebook.com/latest/settings/ad_accounts) Когда будете готовы, создайте пиксель: 1. Перейдите в [**Events Manager**](https://www.facebook.com/events_manager2). Нажмите **Connect data**. 2. Выберите **Web** в качестве типа источника данных. 3. Дайте своему датасету имя и нажмите **Create**. 4. Для [атрибуции Adapty](adapty-user-acquisition) полная установка пикселя не нужна. Поэтому, когда появится вопрос об интеграции, просто нажмите **x** в окне настройки — пиксель всё равно появится в списке после обновления страницы. 5. Как только датасет появится в списке, можно приступать к созданию кампании. ## Шаг 3. Создайте кампанию \{#step-3-create-campaign\} Чтобы создать кампанию в Meta Ads Manager: 1. Перейдите в [Meta Ads Manager](https://adsmanager.facebook.com/adsmanager/manage). На вкладке **Campaign** нажмите **Create**. 2. Выберите **Sales** в качестве цели кампании и нажмите **Continue**. 3. Назовите кампанию в разделе **Campaign name**. 4. В разделе **Budget** в поле **Budget strategy** выберите, как хотите управлять бюджетом: - **Campaign budget**: Самый простой вариант, если вы не знаете, какие возможности сработают лучше. При его выборе Meta Ads автоматически определит наиболее эффективные объявления и перераспределит на них больший бюджет. Затем выберите, нужен ли вам бюджет **Daily** (ежедневный) или **Lifetime** (на весь срок), и укажите лимит в вашей валюте. **Daily** бюджет даёт больше гибкости, пока вы ещё разбираетесь: можно начать с небольших сумм и постепенно их корректировать. Или выберите **Schedule budget increase** и настройте правила автоматического увеличения бюджета на фиксированную сумму или процент. - **Ad set budget**: выберите этот вариант, если хотите вручную управлять тем, какие аудитории получат больше или меньше бюджета кампании. Если не уверены — можно выбрать **Share some of your budget with other ad sets**, чтобы Meta автоматически перераспределяла бюджеты между группами объявлений на 20%, если это улучшит результаты. 5. В разделе **Campaign bid strategy** выберите подходящий для ваших целей вариант: - **Highest volume (default)**: самый простой способ начать. При выборе этого варианта Meta самостоятельно оптимизирует стоимость клика для достижения наилучших результатов в рамках вашего бюджета. - **Cost per result goal**: задайте целевую стоимость результата, если знаете свои ориентиры. - **Bid cap**: установите максимальную ставку, которую вы готовы платить. 6. Adapty позволяет проводить полноценные [A/B-тесты](ab-tests). Тем не менее при необходимости вы можете также включить A/B-тесты в Meta Ads. Подробнее об A/B-тестах в Meta Ads Manager читайте [здесь](https://www.facebook.com/business/help/1159714227408868). 7. Теперь можно добавить первую группу объявлений в кампанию. Нажмите **Next**, чтобы продолжить. ## Шаг 4. Создайте группу объявлений \{#step-4-create-ad-set\} Чтобы создать группу объявлений: 1. Введите название группы объявлений в поле **Ad set name**. 2. В выпадающем списке **Conversion location** выберите **Website**. 3. В поле **Performance goal** выберите **Maximize number of landing page views**, если у вас есть лендинг, или **Maximize number of link clicks**, если вы используете смарт-ссылку, которая ведёт пользователей напрямую в стор. 4. В поле **Dataset** выберите датасет, созданный на [шаге 2](#step-2-add-meta-pixel). 5. Выберите **Conversion event**. В нашем случае это, скорее всего, будет **Purchase** или **Start trial**. Если вы видите предупреждение о том, что в датасете ещё нет событий — не беспокойтесь, это просто означает, что датасет новый. 6. Если при настройке кампании вы выбрали **Ad set budget**, укажите, нужен ли вам **Daily** или **Lifetime** бюджет, и введите лимит в вашей валюте. **Daily** бюджет даёт больше гибкости на этапе обучения: можно начать с небольших сумм и постепенно корректировать их по ходу. Задайте дату начала и, если нужно, окончания для группы объявлений. Например, если вы рекламируете promotional offer в приложении, важно синхронизировать временные рамки группы с периодом действия офера. 7. В разделе **Audience controls** задайте настройки аудитории: - **Location**: Локации могут быть как широкими, так и узкими — на ваш выбор. Вы можете ограничить **Locations** в наборе объявлений, чтобы учитывать региональную специфику ваших объявлений. - **Minimum age**: Укажите минимальный возраст пользователей, которые увидят вашу рекламу. Для некоторых объявлений это может быть обязательным требованием по закону. Нельзя установить минимальный возраст ниже 18 лет в большинстве стран или 20 лет в Таиланде. - **Language**: Указывайте **Language** только в том случае, если это не самый распространённый язык в выбранных странах. Например, в США не нужно выбирать **English**, но если вы хотите охватить испаноязычную аудиторию в этой стране, стоит выбрать **Spanish**. 8. По умолчанию Meta автоматически находит небольшие группы людей, которым ваше объявление может быть интересно. Однако если вы добавите подсказку об аудитории, вы можете направить Meta к людям, которые, по вашему мнению, с большей вероятностью отреагируют. В разделе **Advantage+ audience** вы можете настроить: - **Age**: Задайте определённый возрастной диапазон для таргетинга, чтобы лучше охватить конкретные возрастные группы. - **Gender**: Показывайте объявление всем пользователям или настройте таргетинг по полу. - **Detailed targeting**: Этот параметр даёт вам наиболее точный контроль над аудиторией для вашего объявления и/или приложения. Здесь можно формировать группы на основе **Demographics**, **Interests** или **Behaviors**. В зависимости от тематики вашего приложения можно, например, сфокусироваться на определённых профессиях, поклонниках конкретных музыкальных групп, родителях новорождённых или активных онлайн-покупателях. :::note Условия **Detailed targeting** применяются с оператором **Or**. Если вы хотите применить условия с оператором **And**, нажмите **Define further** и выберите новые условия. ::: 9. В разделе **Placements** вы можете выбрать, где будет показываться ваше объявление. По умолчанию выбрана настройка **Advantage+**, которая позволяет Meta распределять бюджет группы объявлений по нескольким плейсментам на основе того, где они, вероятнее всего, покажут наилучший результат. Рекомендуем использовать этот вариант, если вы не уверены, где размещать объявление. Если вы хотите выбрать конкретные плейсменты вручную, выберите **Manual placements** и настройте их. Подробнее читайте [здесь](https://www.facebook.com/business/help/965529646866485). 10. **Рекомендуется**: Таргетинг по устройствам помогает оптимизировать расходы. В разделе **Placements** нажмите **Show more settings**. В подразделе **Devices and operating system** выберите устройства, операционные системы и версии ОС, которые должны входить в вашу аудиторию. Это гарантирует, что реклама будет показана только релевантным пользователям. Например, пользователи на десктопе не увидят вашу рекламу, а пользователи со старыми версиями ОС, которые ваше приложение не поддерживает, будут исключены. 11. Когда всё готово, нажмите **Next**, чтобы продолжить. ## Шаг 5. Создайте рекламу \{#step-5-create-ads\} Чтобы создать рекламу в Meta Ads Manager: 1. Введите название объявления в поле **Ad name**. 2. В разделе **Identity** выберите страницу Facebook, которая будет использоваться для публикации рекламы. Если у вашего приложения есть отдельный аккаунт Instagram и вы подключили его в Meta Business Suite на [шаге 1](#step-1-create-meta-ads-manager-account), выберите его в выпадающем списке **Instagram account**. В противном случае выберите **Use Facebook page**, и реклама в Instagram будет публиковаться от имени страницы Facebook. 3. В разделе **Ad setup** выберите способ публикации объявления. При продвижении приложений рекомендуем выбирать **Create ad** — тогда публикация будет перенаправлять пользователей в приложение, а не на страницу Facebook. В поле **Format** выберите вариант в зависимости от количества креативов и желаемого способа их отображения. 4. В разделе **Destination** оставьте выбранным **Website** в качестве **Main destination**. В поле **Website URL** вставьте `https://api-ua.adapty.io/api/v1/attribution/click`. В [Adapty Attribution](adapty-user-acquisition) [создайте веб-кампанию](ua-facebook) и вставьте содержимое поля **Click link** после `https://api-ua.adapty.io/api/v1/attribution/click` в поле **URL parameters** в разделе **Tracking**. 5. В разделе **Ad creative** нажмите **Set up creative** и выберите **Image ad** или **Video ad**. Откроется новое окно, где можно загрузить медиафайлы, обрезать их и добавить тексты. 6. Если вы хотите автоматически переводить тексты объявления, в разделе **Languages** нажмите **Add languages**. Затем добавьте основной язык — тексты из креатива подтянутся автоматически. После этого добавьте языки для автоматического перевода. 7. Когда всё готово, нажмите **Publish**, чтобы запустить объявление. ## Что дальше \{#whats-next\} Чтобы активировать рекламу, добавьте способ оплаты, если вы ещё этого не сделали. После этого вы можете [изучить, как кампания влияет на доход приложения, в дашборде Adapty User Acquisition](adapty-user-acquisition). Ещё не используете Adapty User Acquisition? [Запишитесь на звонок с нами](https://calendly.com/tnurutdinov-adapty/30min), чтобы узнать, как он поможет отслеживать и оптимизировать рекламные кампании. --- # File: tiktok-create-campaign --- --- title: "Реклама приложения в TikTok for Business" --- В этом пошаговом гайде вы узнаете, как создать и настроить рекламу приложения в TikTok for Business, чтобы удобно отслеживать её эффективность и оптимизировать результаты. ## Шаг 1. Добавьте информацию о компании \{#step-1-add-business-info\} Если вы только начинаете работу с TikTok for Business, сначала нужно добавить данные о компании: 1. Перейдите на [https://ads.tiktok.com](https://ads.tiktok.com/business/) и нажмите **Get started**. 2. Зарегистрируйтесь через email или аккаунт TikTok. 3. Введите информацию о компании и следуйте инструкциям на экране. После одобрения бизнес-аккаунта вас перенаправят на страницу создания первой кампании. ## Шаг 2. Создайте пиксель \{#step-2-create-a-pixel\} Для связи данных кампании с доходами и улучшения результатов вам понадобится TikTok-пиксель: 1. Откройте [**Events Manager**](https://ads.tiktok.com/i18n/events_manager/home). Нажмите **Connect data source**. 2. Выберите **Web** в качестве типа источника данных. 3. В окне **Add your website** нажмите **Skip**. 4. Выберите **Manual setup** и нажмите **Next**. 5. Выберите **TikTok pixel + Events API** и нажмите **Next**. 6. Задайте название пикселя и нажмите **Create**. 7. Для [Атрибуции Adapty](adapty-user-acquisition) полная установка пикселя не требуется. Просто закройте окно настройки — ваш пиксель появится в списке. 8. Чтобы пиксель стал доступен для использования в кампаниях, нужно отправить на него тестовое событие из [Атрибуции Adapty](adapty-user-acquisition): 1. [Создайте новую кампанию TikTok](ua-tiktok). 2. Раскройте раздел для нужной платформы — например, iOS. 3. Выберите пиксель из выпадающего списка. 4. Нажмите **Send test event**. 5. В выпадающем списке выберите событие, которое будете использовать для оптимизации объявления. 6. В TikTok for Business откройте ваш пиксель и перейдите на вкладку **Test events**. Скопируйте `test_event_code`. 7. Вставьте его в поле **Test event code** в Adapty и нажмите **Send**. 9. Тестовое событие появится в TikTok через несколько минут. Как только оно отобразится в деталях пикселя, можно продолжить настройку кампании в TikTok Ads Manager. ## Шаг 3. Выберите цель кампании \{#step-3-select-the-campaign-objective\} :::important В этом руководстве используется режим Quick setup в TikTok Ads Manager. Некоторые рекомендуемые настройки доступны только в режиме Full view — мы укажем на это в соответствующих шагах. ::: Перейдите на [страницу создания объявления](https://ads.tiktok.com/i18n/nb_creation/create/objectives) в Ads Manager. На первом экране выберите цель рекламы и нажмите **Continue**. Выберите **Sales > Website conversion**. ## Шаг 4. Заполните информацию о кампании \{#step-4-fill-in-the-campaign-info\} Далее заполните информацию о кампании: 1. Введите название кампании в поле **Campaign name**. 2. В поле **Optimization goal** выберите **Conversion**. 3. Выберите активный пиксель из выпадающего списка и укажите **Optimization event**. Обратите внимание, что для выбора доступны только активные события. Если нужное событие недоступно, отправьте тестовое событие, следуя инструкциям из [Шага 2](#step-2-create-a-pixel). 4. Ваше объявление будет показываться в ленте TikTok и в поиске. Для дополнительной настройки нажмите **Advanced settings**. В разделе **Placements** настройте параметры размещения: - **User comment**: включите эту опцию, если хотите, чтобы объявление отображалось также в разделе комментариев. TikTok рекомендует оставлять комментарии пользователей включёнными — это помогает объявлениям набирать больше показов. - **Allow video download**: разрешить зрителям скачивать ваше объявление. - **Allow video sharing**: разрешить зрителям делиться вашим объявлением. 5. Нажмите **Continue**. ## Шаг 5. Добавьте рекламные материалы \{#step-5-add-ad-content\} Теперь настройте рекламные материалы и URL назначения: 1. В поле **TikTok account** выберите аккаунт, который будет использоваться для публикации. 2. В [Атрибуции Adapty](adapty-user-acquisition) [создайте веб-кампанию](ua-tiktok) и вставьте **Click link** в поле **Destination URL**. 3. В разделе **Creatives** нажмите **+ Videos and images**. 4. Если вы хотите использовать свои публикации в TikTok в качестве креативов, выберите их на вкладке **TikTok post**. В противном случае перейдите на вкладку **Creative library** и нажмите **Upload**. Загруженные файлы будут доступны на этой вкладке позже, так что вы сможете повторно использовать их в других кампаниях. 5. Обрежьте креативы под формат TikTok и укажите, будут ли они использоваться как отдельные объявления или как одна карусель. 6. Разверните загруженный креатив и нажмите **+** рядом с **No music selected**. Туда можно загрузить собственные mp3-файлы. Добавление музыки обязательно. 7. В поле **Add text** введите текст, который будет использоваться в качестве описания. 8. Выберите **Place the ads on this TikTok account as a post**, если хотите опубликовать это объявление в своём аккаунте TikTok. 9. В поле **Call to action** выберите или удалите призывы к действию, соответствующие вашему объявлению. TikTok автоматически добавит их к вашему объявлению. 10. Нажмите **Continue**. ## Шаг 6. Настройте таргетинг и бюджет \{#step-6-configure-targeting-and-budget\} Наконец, задайте, кто должен видеть вашу рекламу и сколько вы готовы за неё платить: 1. В разделе **Targeting** выберите **Automatic** или **Custom**. Вариант **Automatic** — самый простой, если вы ещё не до конца понимаете свою аудиторию. Если же выбрать **Custom**, можно оптимизировать расходы, указав группы пользователей, которые с большей вероятностью отреагируют на вашу рекламу. 2. Если вы выбрали **Custom**, настройте: - **Location**: по умолчанию используется местоположение вашего рекламного аккаунта. Если выбрать несколько стран или регионов, результаты проверки объявлений будут возвращаться отдельно для каждого из них. Фактическая доставка рекламы также может различаться в зависимости от поддерживаемых локаций разных плейсментов. - **Languages**: по умолчанию выбраны все языки. Укажите язык таргетинга исходя из того, какой язык чаще всего используется в выбранном регионе. - **Gender**: по умолчанию выбраны все гендеры. :::tip Если переключиться в режим Full, под разделом **Targeting** появится дополнительный раздел **Device**. В нём можно ограничить аудиторию по типу устройства, операционной системе и её версии — это удобно, если ваше приложение требует определённой минимальной версии ОС. ::: 3. В разделе **Budget** выберите один из предложенных вариантов или укажите **Custom**. 4. Если вы выбрали **Custom**, укажите тип бюджета — **Daily** или **Lifetime** — и введите лимит в вашей валюте. **Daily** даёт больше гибкости на этапе обучения: можно начать с небольших сумм и постепенно корректировать их по ходу. 5. В разделе **Schedule** выберите **Continue for at least 7 days** или **Custom**. Если реклама привязана к конкретному времени, рекомендуем выбрать **Custom**, чтобы не пропустить момент, когда её нужно остановить. 6. Если вы выбрали **Custom**, задайте дату и время начала или начала и окончания показа рекламы. Учтите, что будет использоваться часовой пояс вашего аккаунта. 7. Нажмите **Publish**. Когда всё будет готово, будет создана новая кампания с одной группой объявлений. В группу войдёт одно объявление, если вы настроили карусель, или несколько объявлений, если вы добавили креативы как отдельные объявления. ## Шаг 7. Введите платёжные данные \{#step-7-enter-payment-details\} Чтобы запустить рекламу, после настройки таргетинга и бюджета введите платёжные данные. После этого всё готово! ## Что дальше \{#whats-next\} Теперь можно [посмотреть, как кампания влияет на доход приложения, в дашборде Adapty User Acquisition](adapty-user-acquisition). Ещё не используете Adapty User Acquisition? [Запишитесь на звонок с нами](https://calendly.com/tnurutdinov-adapty/30min), чтобы узнать, как отслеживать и оптимизировать рекламные кампании. --- # File: getting-started-with-server-side-api --- --- title: "Server-side API" description: "Начните работу с серверным API Adapty для управления подписками." --- :::tip Используете AI-ассистент для разработки? Смотрите [Проверка и предоставление доступа к подписке через бэкенд](server-side-api-with-ai) — всё необходимое на одной странице. ::: С помощью API вы можете: 1. Проверить статус подписки пользователя. 2. Активировать подписку пользователя с уровнем доступа. 3. Получить атрибуты пользователя. 4. Задать атрибуты пользователя. 5. Получить и обновить конфигурации пейволов. <img src="/assets/shared/img/server.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> <p> </p> :::note Для отслеживания событий подписки используйте интеграцию [Webhook](webhook) в Adapty или интегрируйтесь напрямую с вашим существующим сервисом. ::: ::: ## Случай 1: синхронизация подписчиков между вебом и мобильным приложением \{#case-1-sync-subscribers-between-web-and-mobile\} Если вы используете веб-провайдеры платежей, например Stripe, ChargeBee или другие, вы можете легко синхронизировать своих подписчиков. Вот как это сделать: 1. <InlineTooltip tooltip="Назначьте уникальный ID каждому пользователю">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users) и [Unity](unity-identifying-users)</InlineTooltip>. 2. [Проверьте статус подписки](api-adapty/operations/getProfile) через API. 3. Если пользователь на бесплатном плане — покажите пейвол на вашем сайте. 4. После успешной оплаты [обновите статус подписки](api-adapty/operations/setTransaction) в Adapty через API. 5. Ваши подписчики автоматически останутся в синхронизации с мобильным приложением. ## Case 2: Выдать подписку \{#case-2-grant-a-subscription\} :::note По соображениям безопасности выдать подписку через SDK невозможно. ::: Если вы продаёте через собственный интернет-магазин, Amazon Appstore, Microsoft Store или любую другую платформу помимо Google Play и App Store, вам нужно синхронизировать эти транзакции с Adapty, чтобы предоставлять доступ и отслеживать транзакции в аналитике. 1. <InlineTooltip tooltip="Присвойте уникальный ID каждому пользователю">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users) и [Unity](unity-identifying-users)</InlineTooltip>. 2. [Настройте кастомный стор для ваших продуктов в дашборде Adapty](custom-store). 3. Синхронизируйте транзакцию с Adapty с помощью API-запроса [Set transaction](api-adapty/operations/setTransaction). ## Case 3: Предоставление уровня доступа \{#case-3-grant-an-access-level\} Допустим, вы запускаете акцию с 7-дневным бесплатным пробным периодом и хотите обеспечить единый пользовательский опыт на всех платформах. Чтобы синхронизировать это с мобильным приложением: 1. <InlineTooltip tooltip="Присвойте уникальный ID каждому пользователю">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users) и [Unity](unity-identifying-users)</InlineTooltip>. 2. Используйте API, чтобы [предоставить премиум-доступ](api-adapty/operations/grantAccessLevel) на 7 дней. После 7 дней пользователи, которые не оформили подписку, будут переведены на бесплатный уровень. ## Вариант 4: Синхронизация свойств и кастомных атрибутов пользователей \{#case-4-sync-users-properties-and-custom-attributes\} Если у ваших пользователей есть кастомные атрибуты — например, количество выученных слов в приложении для изучения языков — их тоже можно синхронизировать. 1. <InlineTooltip tooltip="Присвойте каждому пользователю уникальный ID">[iOS](identifying-users), [Android](android-identifying-users), [React Native](react-native-identifying-users), [Flutter](flutter-identifying-users), and [Unity](unity-identifying-users)</InlineTooltip>. 2. [Обновите атрибут](api-adapty/operations/updateProfile) через API или SDK. Эти пользовательские атрибуты можно использовать для создания сегментов и запуска A/B-тестов. ## Пример 5: Управление конфигурациями пейвола \{#case-5-manage-paywall-configurations\} Вы можете [обновлять Remote Config в пейволах](api-adapty/operations/updatePaywall), чтобы динамически менять внешний вид и поведение пейвола без повторного выпуска приложения. --- **Что дальше:** - Перейдите к [авторизации для серверного API](ss-authorization) - Запросы: - [Получить профиль](api-adapty/operations/getProfile) - [Создать профиль](api-adapty/operations/createProfile) - [Обновить профиль](api-adapty/operations/updateProfile) - [Удалить профиль](api-adapty/operations/deleteProfile) - [Выдать уровень доступа](api-adapty/operations/grantAccessLevel) - [Отозвать уровень доступа](api-adapty/operations/revokeAccessLevel) - [Задать транзакцию](api-adapty/operations/setTransaction) - [Валидировать покупку, предоставить уровень доступа пользователю и импортировать историю транзакций](api-adapty/operations/validateStripePurchase) - [Добавить идентификаторы интеграции](api-adapty/operations/setIntegrationIdentifiers) - [Получить пейвол](api-adapty/operations/getPaywall) - [Список пейволов](api-adapty/operations/listPaywalls) - [Обновить пейвол](api-adapty/operations/updatePaywall) --- # File: onboardings --- --- title: "Онбординги" --- :::warning Конструктор онбордингов без кода полностью функционален, однако Adapty больше не добавляет в него новые функции и не выпускает обновления. Для новых проектов рекомендуем [Adapty Flow Builder](adapty-flow-builder) — визуальный редактор без кода для создания одноэкранных пейволов и многоэкранных онбординг-флоу, которые рендерятся нативно на устройстве: - **Любой тип флоу**: создавайте одноэкранные пейволы, многошаговые онбординги с пейволом и всё, что между ними. - **Нативный рендеринг**: флоу рендерятся через Adapty SDK без использования веб-вью. - **Обновление без релиза**: меняйте тексты, дизайн или логику в любой момент — изменения доходят до пользователей без обновления приложения. ::: Онбординги в Adapty позволяют нетехническим командам создавать онбординг-флоу без написания кода. Конструктор без кода формирует последовательность экранов, знакомящих пользователей с вашим приложением. Вы можете персонализировать экраны с помощью интерактивных вопросов и переменных, а затем запускать A/B-тесты, чтобы найти наиболее эффективный флоу. Онбординги доступны для приложений, использующих Adapty SDK версии 3.8.0+ (iOS, Android, React Native, Flutter), v3.14.0+ (Unity) или v3.15.0+ (Kotlin Multiplatform, Capacitor). ## Как это работает \{#how-it-works\} 1. [Создайте онбординг в редакторе без кода.](design-onboarding) 2. [Создайте плейсмент для онбординга.](create-onboarding#step-2-create-a-placement-for-your-onboarding) 3. Интегрируйте онбординг в свой проект с помощью Adapty SDK: - [iOS](ios-onboardings) - [Android](android-onboardings) - [React Native](react-native-onboardings) - [Flutter](flutter-onboardings) - [Unity](unity-onboardings) - [Kotlin Multiplatform](kmp-onboardings) - [Capacitor](capacitor-onboardings) 4. Протестируйте онбординг и выпустите его для ваших пользователей. --- # File: create-onboarding --- --- title: "Создание онбординга" --- [Онбординги](onboardings) знакомят новых пользователей с ценностью, возможностями и особенностями вашего мобильного приложения. ## Шаг 1. Создайте онбординг \{#step-1-create-an-onboarding\} Чтобы создать новый онбординг в дашборде Adapty: 1. Перейдите в раздел **Onboardings** в главном меню Adapty. На этой странице отображаются все настроенные онбординги и их метрики. Нажмите **Create onboarding**. <img src="/assets/shared/img/create-onboarding1.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Введите понятное название онбординга и нажмите **Proceed to build onboarding**. <img src="/assets/shared/img/create-onboarding2.png" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Вы будете перенаправлены в конструктор онбординга. Он содержит демо-шаблон по умолчанию, на котором можно изучить, как онбординги собирают данные и как их можно персонализировать с помощью переменных и квизов. Удаляйте ненужные экраны и [создавайте собственный онбординг](design-onboarding) на своё усмотрение. <img src="/assets/shared/img/create-onboarding3.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Когда будете готовы, нажмите кнопку **Preview** в правом верхнем углу. Пройдите онбординг самостоятельно, чтобы убедиться, что всё работает как ожидается. <img src="/assets/shared/img/create-onboarding4.png" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Если всё работает корректно, нажмите **Publish** в правом верхнем углу. Дождитесь завершения публикации, прежде чем возвращаться в Adapty. Иначе ваши изменения будут потеряны. :::danger Если вы не нажмёте **Publish**, SDK не сможет получить созданный онбординг. ::: <img src="/assets/shared/img/create-onboarding5.png" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> После публикации онбординга нажмите **Back to Adapty**. Онбординг создан — теперь вы можете добавить его в плейсмент и начать использовать. ## Шаг 2. Создайте плейсмент для онбординга \{#step-2-create-a-placement-for-your-onboarding\} 1. Перейдите в **Placements** из главного меню и откройте вкладку **Onboardings**. Нажмите **Create placement**. <img src="/assets/shared/img/create-onboarding6.png" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Введите название и ID плейсмента. Затем нажмите **Run onboarding** и выберите онбординг, который будет показан всем пользователям. 3. Если у вас есть отдельный онбординг для определённой группы пользователей, [добавьте дополнительные аудитории](audience) и выберите для них другой онбординг. ## Шаг 3. Интегрируйте онбординг в своё приложение \{#step-3-integrate-the-onboarding-into-your-app\} :::important Онбординги доступны для приложений, использующих Adapty SDK v3.8.0+ (iOS, Android, React Native, Flutter), v3.14.0+ (Unity) или v3.15.0+ (Kotlin Multiplatform, Capacitor). ::: Чтобы начать показывать онбординги в своём приложении, интегрируйте их с помощью Adapty SDK: - [iOS](ios-onboardings) - [Android](android-onboardings) - [React Native](react-native-onboardings) - [Flutter](flutter-onboardings) - [Unity](unity-onboardings) - [Kotlin Multiplatform](kmp-onboardings) - [Capacitor](capacitor-onboardings) Чтобы понять, какой онбординг работает лучше, вы также можете запустить [A/B-тесты](ab-tests). --- # File: design-onboarding --- --- title: "Дизайн онбордингов" description: "Создавайте полноценные онбординги." --- No-code конструктор онбордингов для мобильных приложений — мощный и гибкий инструмент, который поможет вам создать лучший опыт онбординга для ваших пользователей. Быть разработчиком или дизайнером для этого не нужно. ## Экраны онбординга \{#onboarding-screens\} Онбординг состоит из нескольких экранов, которые вы добавляете и оформляете. Пользователи будут переходить между ними, нажимая кнопку. :::tip Если часть ваших пользователей нуждается в немного другом флоу (например, в фитнес-приложении вы хотите показывать разные изображения «цели» в зависимости от пола пользователя), создавать отдельные онбординги не нужно. Вместо этого можно сделать некоторые экраны скрытыми по умолчанию и отображать их только в определённых сценариях. ::: ## Элементы онбординга \{#onboarding-elements\} Элементы онбординга отображаются слева в том порядке, в котором они показываются. Нажмите **Add** в правом верхнем углу, чтобы добавить новый элемент. Доступны следующие группы элементов: - **Containers**: Контейнеры позволяют настроить гибкую раскладку. Например, если вы хотите добавить текст в две колонки, добавьте **Columns**, а затем перетащите два текстовых блока в **Columns** на левой панели. Или, если вы добавляете карусель, нужно добавить изображения в элементы **Media** внутри неё. - **Typography**: Добавляйте предварительно отформатированные текстовые блоки и настраивайте их внешний вид по мере необходимости. - **Media & Display**: Помимо изображений и видео, можно добавлять анимированные графики, демонстрирующие ценность вашего приложения и мотивирующие пользователей. **Поддерживаемые форматы видео**: MP4 и WebM. **Максимальный размер медиафайла** — 15 МБ. Если вы хотите добавить неподдерживаемый анимированный элемент (например, Lottie), можно конвертировать его в видео (например, с помощью [этого инструмента](https://www.lottielab.com/lottie/lottie-to-video)) и вставить как видео. - **Quiz**: Создавайте короткие опросы с текстовыми и графическими вариантами ответов, чтобы персонализировать онбординг и лучше узнать своих пользователей. - **Inputs**: Собирайте данные от пользователей. - **Buttons**: Кнопки позволяют пользователям переходить между экранами, закрывать онбординг или переходить к пейволу. Можно также добавлять глянцевые или анимированные кнопки, чтобы привлечь внимание пользователей и повысить конверсию из установки в покупку. - **Loaders**: Анимированные лоадеры удерживают внимание пользователей в процессе ожидания. - **User engagement**: Добавляйте отзывы, списки email-адресов пользователей и обратные отсчёты. :::note В рамках группы **Media & Display** можно также добавить пользовательский HTML-код, если встроенных возможностей кастомизации недостаточно. Однако пользовательские HTML-элементы не предзагружаются и не кешируются, поэтому **Raw HTML** рекомендуется использовать только для небольших и лёгких элементов. ::: <img src="/assets/shared/img/design-onboarding4.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### ID элемента и ID действия \{#element-id-and-action-id\} Если вы хотите использовать кнопку для пользовательских действий, назначьте ей **action ID** и используйте его в исходном коде. Action ID позволяет одинаково обрабатывать разные кнопки с одним и тем же action ID. <img src="/assets/shared/img/ios-events-1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Если вы хотите обрабатывать пользовательский ввод в конкретном поле (например, сохранять возраст или email), назначьте ему **element ID** и используйте его в исходном коде для связывания вопросов с ответами. Element ID можно использовать в онбординге только один раз. <img src="/assets/shared/img/design-onboarding5.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Параметры кастомизации \{#customization-options\} В конструкторе доступны следующие параметры кастомизации: - Вкладка **Styles**: Настройте внешний вид элемента. <img src="/assets/shared/img/design-onboarding1.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> - Вкладка **Element**: Задайте атрибуты элемента: видимость, действия при нажатии кнопок и другие свойства, не связанные с внешним видом элемента. <img src="/assets/shared/img/design-onboarding2.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> - Вкладка **Screen**: Настройте общую конфигурацию экрана: заголовок или отображение счётчика экранов. <img src="/assets/shared/img/design-onboarding3.png" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Копирование экранов и элементов \{#copy-screens-and-elements\} Если вы создали онбординг и хотите повторно использовать его части, или хотите внести небольшие изменения и запустить A/B-тесты, можно скопировать один или несколько экранов из одного онбординга в другой. Чтобы скопировать экраны, откройте конструктор онбордингов и выполните одно из следующих действий: - Кликните правой кнопкой мыши по отдельному экрану и выберите **Copy** - Выберите нужный экран и нажмите `Ctrl+C` (Windows) или `⌘+C` (Mac) Также можно копировать отдельные элементы или текстовые блоки — как в рамках одного онбординга, так и между разными онбордингами. ## Копирование экранов из web-to-app воронок \{#copy-screens-from-web-to-app-funnels\} Если вы используете web-to-app воронки, созданные в [FunnelFox](https://funnelfox.com/), и хотите использовать экраны из воронок в онбордингах, это можно быстро сделать, скопировав экраны в конструкторе воронок и вставив их в конструктор онбордингов: 1. В конструкторе воронок FunnelFox кликните правой кнопкой мыши по экрану и выберите **Copy**, или выберите экран и нажмите `Ctrl+C`/`⌘+C`. 2. Откройте конструктор онбордингов. 3. Кликните правой кнопкой мыши по экрану, ниже которого хотите вставить скопированный экран, и выберите **Paste**, или выберите его и нажмите `Ctrl+V`/`⌘+V`. Скопированный экран будет вставлен ниже выбранного. <img src="/assets/shared/img/funnel-to-onboarding.gif" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> --- # File: adapty-paywall-builder --- --- title: "Adapty Paywall Builder (Legacy)" description: "Создавайте пейволы и онбординги с помощью визуального редактора без кода." --- :::warning Paywall Builder полностью функционален, но Adapty больше не добавляет новые функции и не выпускает обновления для него. Для новых проектов рекомендуем рассмотреть [Adapty Flow Builder](adapty-flow-builder) — визуальный no-code редактор для одноэкранных пейволов и многоэкранных онбординг-флоу, которые рендерятся нативно на устройстве: - **Любой тип флоу**: создавайте одноэкранные пейволы, многошаговые онбординги с пейволом и всё, что между ними. - **Нативный рендеринг**: флоу отображаются через SDK Adapty, без веб-вью. - **Обновление без релиза**: меняйте тексты, дизайн или логику в любой момент — обновления доходят до пользователей без выпуска новой версии приложения. ::: **Paywall Builder** в Adapty — это визуальный no-code инструмент для создания кастомных пейволов. Вы можете начать с готового шаблона, настроить макет и добавить такие элементы, как карусели, карточки, списки продуктов и футеры. Также поддерживаются кастомные шрифты, теги продуктов и локализация. Paywall Builder требует Adapty SDK версии 3.0 или выше. После того как пейвол готов, [добавьте его в плейсмент](add-audience-paywall-ab-test) и отобразите в приложении: - [iOS](ios-quickstart-paywalls) - [Android](android-quickstart-paywalls) - [React Native](react-native-quickstart-paywalls) - [Flutter](flutter-quickstart-paywalls) - [Unity](unity-quickstart-paywalls) - [Capacitor](capacitor-quickstart-paywalls) - [Kotlin Multiplatform](kmp-quickstart-paywalls) --- # File: flutterflow --- --- title: "Плагин Adapty для FlutterFlow" description: "Интегрируйте FlutterFlow с Adapty для расширенного управления подписками." --- Adapty — универсальная платформа, созданная для роста мобильных приложений. Независимо от того, только ли вы начинаете или у вас уже тысячи пользователей, Adapty позволяет сэкономить месяцы на интеграции встроенных покупок и удвоить доход от подписок с помощью управления пейволами. Плагин Adapty для FlutterFlow позволяет использовать все возможности Adapty без написания кода. Вы можете проектировать страницы пейволов во FlutterFlow, подключать для них покупки, а затем удалённо управлять тем, какие продукты на них отображаются, — в том числе с таргетингом на конкретные группы пользователей или с помощью A/B-тестов. После выпуска приложения вы сразу получите доступ к подробной аналитике покупок ваших клиентов прямо в нашем дашборде. Хотите обновить продукты, доступные на вашем пейволе? Всё просто! Внесите изменения в несколько кликов в дашборде Adapty, и ваши клиенты сразу увидят новые продукты — без необходимости выпускать новую версию приложения! Что ещё предлагает Adapty: - **Подписки и встроенные покупки**: Adapty берёт на себя серверную валидацию чеков и синхронизирует ваших клиентов на всех платформах, включая веб. - **A/B-тесты для пейволов**: Тестируйте разные цены, длительности, пробные периоды и визуальные элементы, чтобы оптимизировать предложения по подпискам и разовым покупкам. - **Мощная аналитика**: Получайте подробные метрики, чтобы лучше понимать монетизацию приложения и улучшать её. - **Интеграции**: Adapty легко подключается к сторонним аналитическим инструментам — Amplitude, AppsFlyer, Adjust, Branch, Mixpanel, Facebook Ads, AppMetrica, пользовательским вебхукам и другим сервисам. --- # File: ff-getting-started --- --- title: "Начало работы" description: "Начните работу с Feature Flags в Adapty для персонализации подписочных потоков." --- С помощью Adapty вы можете создавать и запускать пейволы и A/B-тесты в разных точках пользовательского пути в мобильном приложении: например, в онбординге, настройках и так далее. Такие точки называются [плейсментами](placements). Один плейсмент может управлять несколькими пейволами или [A/B-тестами](ab-tests) одновременно — каждый из них предназначен для определённой группы пользователей, которую мы называем [аудиторией](audience). Кроме того, вы можете экспериментировать с пейволами, заменяя один на другой без выпуска новой версии приложения. Единственное, что жёстко прописывается в мобильном приложении — это идентификатор плейсмента. <img src="/assets/shared/img/audience.jpg" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Библиотека Adapty поддерживает актуальность вашего пейвола, синхронизируя его с последними продуктами из дашборда Adapty. Она [получает данные о продуктах](ff-action-flow) и [отображает их на пейволе](ff-add-variables-to-paywalls), [обрабатывает покупки](ff-make-purchase) и [проверяет уровень доступа пользователя](ff-check-subscription-status), чтобы определить, следует ли открыть ему платный контент. Для начала работы [добавьте библиотеку Adapty](ff-getting-started#add-the-adapty-library-as-a-dependency) в свой проект FlutterFlow и [инициализируйте её](ff-getting-started#initiate-adapty-plugin), как показано ниже. :::warning Перед началом работы обратите внимание на следующие ограничения: - Библиотека Adapty для FlutterFlow не поддерживает веб-приложения. Не компилируйте веб-приложения с её использованием. - Библиотека Adapty для FlutterFlow не поддерживает пейволы, созданные с помощью Paywall Builder. Вам нужно самостоятельно разработать пейвол во FlutterFlow, прежде чем подключать покупки через Adapty. ::: ## Добавление библиотеки Adapty как зависимости \{#add-the-adapty-library-as-a-dependency\} 1. В [FlutterFlow Dashboard](https://app.flutterflow.io/dashboard) откройте свой проект и нажмите **Settings and Integrations** в левом меню. В разделе **Project setup** слева выберите **Project dependencies**. <img src="/assets/shared/img/main_settings.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. В разделе **FlutterFlow Libraries** нажмите **Add Library** и введите `adapty-xtuel0`. Нажмите **Add**. 3. Теперь нужно привязать ваш SDK-ключ к библиотеке. Нажмите **View details** рядом с библиотекой. <img src="/assets/shared/img/ff_view_details.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Скопируйте **Public SDK key** со вкладки [**App Settings** -> **General**](https://app.adapty.io/settings/general) в дашборде Adapty. <img src="/assets/shared/FF_img/adaptyapikey.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Вставьте ключ в поле **AdaptyApiKey** во FlutterFlow. <img src="/assets/shared/img/ff_apikey.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Библиотека Adapty FF будет добавлена в ваш проект как зависимость. В окне библиотеки **Adapty** FF вы найдёте все ресурсы Adapty, импортированные в проект. ## Вызов действия активации при запуске приложения \{#call-the-new-activation-action-at-application-launch\} 1. Перейдите в раздел **Custom Code** в левом меню и откройте `main.dart`. <img src="/assets/shared/img/ff_dartmain.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите **+** и выберите `activate (Adapty)`. <img src="/assets/shared/img/ff_activate.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Нажмите **Save**. ## Инициализация плагина Adapty \{#initiate-adapty-plugin\} Чтобы дашборд Adapty распознал ваше приложение, нужно указать специальный ключ во FlutterFlow. 1. В своём проекте FlutterFlow перейдите в **Settings and Integrations > Permissions** через левое меню. 2. В открывшемся окне **Permissions** нажмите кнопку **Add Permission**. 3. В полях **iOS Permission Key** и **Android Permission Key** введите `AdaptyPublicSdkKey`. 4. В поле **Permission Message** вставьте **Public SDK key** со вкладки [**App Settings** -> **General**](https://app.adapty.io/settings/general) в дашборде Adapty. У каждого приложения свой SDK-ключ, поэтому если у вас несколько приложений, убедитесь, что выбрали нужный. <img src="/assets/shared/img/ff_permissions.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> После выполнения этих шагов вы сможете вызвать пейвол в своём FlutterFlow-приложении и настроить покупки через него. ## Что дальше? \{#whats-next\} 1. [Создайте action flow](ff-action-flow) для обработки продуктов пейвола Adapty и их данных во FlutterFlow. 2. [Привяжите полученные данные к пейволу](ff-add-variables-to-paywalls), который вы разработали во FlutterFlow. 3. [Настройте кнопку покупки](ff-make-purchase) на пейволе, чтобы транзакции обрабатывались через Adapty при нажатии. 4. Наконец, [добавьте проверку статуса подписки](ff-check-subscription-status), чтобы определять, нужно ли показывать пользователю платный контент. --- # File: ff-action-flow --- --- title: "Шаг 1. Создание флоу для отображения данных пейвола" description: "Настройте флоу действий для feature flag в Adapty, чтобы персонализировать подписки пользователей." --- :::important При использовании плагина FlutterFlow нельзя использовать пейволы, созданные в Adapty Paywall Builder. Необходимо реализовать собственную страницу пейвола в FlutterFlow и подключить её к Adapty. ::: После добавления библиотеки Adapty в качестве зависимости в ваш проект FlutterFlow, пора создать флоу, который **получает данные пейвола и продуктов Adapty и отображает их на пейволе, разработанном в FlutterFlow**. Сначала нужно получить данные пейвола от Adapty. Начнём с запроса пейвола Adapty, затем связанных с ним продуктов и проверки успешности получения данных. Если всё прошло успешно — отобразим название продукта и цену на странице пейвола. В противном случае покажем сообщение об ошибке. Прежде чем продолжить, убедитесь, что вы выполнили следующее: 1. [Создали хотя бы один пейвол и добавили в него хотя бы один продукт](create-paywall) в дашборде Adapty. 2. [Создали хотя бы один плейсмент](create-placement) и [добавили в него свой пейвол](add-audience-paywall-ab-test) в дашборде Adapty. Приступим! ## Шаг 1.1. Запрос пейвола Adapty \{#step-11-request-adapty-paywall\} Как уже упоминалось, для отображения данных на пейволе FlutterFlow сначала нужно получить их от Adapty. Первый шаг — получить сам пейвол Adapty. Вот как это сделать: 1. Откройте экран пейвола и перейдите в раздел **Actions** на правой панели. Там откройте **Action Flow Editor**. <img src="/assets/shared/img/ff_action_flow.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. В окне **Select Action Trigger** выберите **On Page Load**. <img src="/assets/shared/img/ff_action_trigger.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Нажмите **Add Action**. Затем найдите кастомное действие `getPaywall` и выберите его. <img src="/assets/shared/img/ff_getpaywall.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. В разделе **Set Actions Arguments** введите реальный ID [плейсмента, который вы создали](create-placement) в дашборде Adapty и который включает нужный пейвол. В примере это `monthly`. Обязательно используйте свой реальный ID плейсмента! <img src="/assets/shared/img/ff_placementid.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Если вы [локализовали](localizations-and-locale-codes) свой пейвол в дашборде Adapty, вы также можете задать аргумент **locale**. 6. В поле **Action Output Variable Name** создайте новую переменную и назовите её `getPaywallResult`. Она понадобится на следующем шаге для обращения к пейволу Adapty и запроса его продуктов. <img src="/assets/shared/img/ff_getpaywallresult.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 1.2. Запрос продуктов пейвола Adapty \{#step-12-request-adapty-paywall-products\} Отлично! Мы получили пейвол Adapty. Теперь запросим продукты, связанные с ним: 1. Нажмите **+** под созданным действием и выберите **Add Action**. Это действие получит продукты пейвола Adapty. Для этого найдите и выберите `getPaywallProducts`. 2. В разделе **Set Actions Arguments** выберите созданную ранее переменную `getPaywallResult`. <img src="/assets/shared/img/ff_getpaywallproduct.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Заполните остальные поля следующим образом: - **Available Options**: Data Structured Field - **Select Field**: value - **Available Options**: без изменений <img src="/assets/shared/img/ff_getpaywallresult2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Нажмите **Confirm**. 5. В поле **Action Output Variable Name** создайте новую переменную и назовите её `getPaywallProductsResult`. С её помощью мы свяжем разработанный в FlutterFlow пейвол с данными пейвола Adapty. <img src="/assets/shared/img/ff_getpaywallproductsresult.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 1.3. Добавление проверки успешной загрузки пейвола \{#step-13-add-check-if-the-paywall-uploaded-successfully\} Прежде чем двигаться дальше, проверим, что пейвол Adapty был получен успешно. Если да — обновим пейвол данными продуктов. Если нет — обработаем ошибку. Вот как добавить эту проверку: 1. Нажмите **+** и выберите **Add Conditional**. <img src="/assets/shared/img/ff-add-conditional.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. В разделе **Action Output** выберите созданную ранее переменную результата действия (`getPaywallResult` в нашем примере). <img src="/assets/shared/img/ff-getpaywallresult.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Чтобы проверить, что пейвол Adapty получен, убедитесь в наличии поля со значением. Заполните поля следующим образом: - **Available Options**: Has Field - **Field (AdaptyGetPaywallResult)**: value 4. Нажмите **Confirm**, чтобы завершить создание условия. ## Шаг 1.4. Логирование просмотра пейвола \{#step-14-log-the-paywall-review\} Чтобы аналитика Adapty фиксировала просмотры пейвола, нужно залогировать это событие. Без этого шага просмотр не будет учтён в аналитике. Вот как это сделать: 1. Нажмите **+** под меткой **TRUE** и выберите **Add Action**. 2. В поле **Select Action** найдите и выберите **logShowPaywall**. <img src="/assets/shared/img/ff-logshowpaywall.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Нажмите **Value** в области **Set Action Arguments** и выберите созданную переменную `getPaywallResult`. Эта переменная содержит данные пейвола. 4. Заполните поля следующим образом: - **Available Options**: Data Structured Field - **Select Field**: value 5. Нажмите **Confirm**. <img src="/assets/shared/img/ff-lohsgowpaywallresult.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 1.5. Отображение ошибки при неполучении пейвола \{#step-15-show-error-if-paywall-not-received\} Если пейвол Adapty не получен, необходимо [обработать ошибку](error-handling-on-flutter-react-native-unity#system-storekit-codes). В этом примере мы просто покажем диалоговое окно с сообщением. 1. Добавьте действие **Informational Dialog** к метке **FALSE**. 2. В поле **Title** введите текст, который хотите видеть в качестве заголовка диалога. В этом примере — **Error**. 3. Нажмите **Value** в поле **Message**. 4. Заполните поля следующим образом: - **Set Variable**: переменная `getPaywallProductResult`, которую мы создали - **Available Options**: Data Structure Field - **Select Field**: error - **Available Options**: Data Structure Field - **Select Field**: errorMessage <img src="/assets/shared/img/ff-error.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Нажмите **Confirm**. 6. Добавьте действие **Terminate action** во флоу **FALSE**. <img src="/assets/shared/img/ff-terminate.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Нажмите **Close** в правом верхнем углу. Поздравляем! Вы успешно получили данные продуктов. Теперь давайте [свяжем их с пейволом, разработанным в FlutterFlow](ff-add-variables-to-paywalls). --- # File: ff-add-variables-to-paywalls --- --- title: "Шаг 2. Добавление данных на страницу пейвола" description: "Добавьте переменные Feature Flag на пейволы в Adapty." --- После того как вы [получили все необходимые данные о продукте](ff-action-flow), самое время привязать их к красивому пейволу, который вы оформили в FlutterFlow. В этом примере мы привяжем название продукта и его цену. ## Шаг 2.1. Добавление названия продукта на страницу пейвола \{#step-21-add-product-title-to-paywall-page\} 1. Дважды кликните по тексту продукта на странице пейвола. В окне **Set from Variable** найдите переменную `getPaywallProductResult` и выберите её. <img src="/assets/shared/img/ff-paywall-text.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Заполните поля следующим образом: - **Available Options**: Data Structured Field - **Select Field**: value - **Available Options**: Item at Index - **List Index Options**: First - **Available Options**: Data Structured Field - **Select Field**: localizedTitle - **Default Variable Value**: null - **UI Builder Display Value**: Любое значение, в примере это `product.title` <img src="/assets/shared/img/ff-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Нажмите **Confirm**, чтобы сохранить изменения. ## Шаг 2.2. Добавление текста с ценой на страницу пейвола \{#step-22-add-price-text-to-paywall-page\} Повторите шаги из раздела 2.1 для текста с ценой, как показано ниже: 1. Дважды кликните по тексту цены на странице пейвола. В окне **Set from Variable** найдите переменную `getPaywallProductResult` и выберите её. <img src="/assets/shared/img/ff-price.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Заполните поля следующим образом: - **Available Options**: Data Structured Field - **Select Field**: value - **Available Options**: Item at Index - **List Index Options**: First - **Available Options**: Data Structured Field - **Select Field**: price - **Default Variable Value**: null - **UI Builder Display Value**: Любое значение, в примере это `product.price` 3. Нажмите кнопку **Confirm**, чтобы сохранить изменения. ### Добавление цены в местной валюте на страницу пейвола \{#add-price-in-local-currency-to-paywall-page\} 1. Дважды кликните по цене на странице пейвола. В окне **Set from Variable** найдите переменную `getPaywallProductResult` и выберите её. 2. Заполните поля следующим образом: - **Available Options**: Data Structured Field - **Select Field**: value - **Available Options**: Item at Index - **List Index Options**: First - **Available Options**: Data Structured Field - **Select Field**: price - **Available Options**: Data Structured Field - **Select Field**: amount - **Available Options**: Decimal - **Decimal Type**: Automatic - **Default Variable Value**: null - **UI Builder Display Value**: Любое значение, в примере это `price.amount` 3. Нажмите **Confirm**, чтобы сохранить изменения. Вуаля! Теперь при запуске приложения данные продукта из пейвола Adapty будут отображаться прямо на странице вашего пейвола! Пора [дать пользователям возможность купить этот продукт](ff-make-purchase). --- # File: ff-make-purchase --- --- title: "Шаг 3. Включение покупки" description: "Узнайте, как совершать покупки с помощью системы Feature Flags." --- Поздравляем! Вы успешно [настроили пейвол для отображения данных о продуктах из Adapty](ff-add-variables-to-paywalls), включая название и цену продукта. Теперь перейдём к финальному шагу — дадим пользователям возможность совершить покупку через пейвол. ## Шаг 3.1. Включите возможность покупки для пользователей \{#step-31-enable-users-to-make-purchases\} 1. Дважды кликните кнопку покупки на странице пейвола. В правой панели откройте раздел **Actions**, если он ещё не открыт. 2. Откройте **Action Flow Editor**. <img src="/assets/shared/img/ff-action-flow-editor.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. В окне **Select Action Trigger** выберите **On Tap**. 4. В окне **No Actions Created** нажмите **Add Action**. Найдите действие `makePurchase` и выберите его. <img src="/assets/shared/img/ff-makepurchase.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. В разделе **Set Actions Arguments** выберите переменную `getPaywallProductsResult`, созданную ранее. 6. Заполните поля следующим образом: - **Available Options**: Data Structure Field - **Select Field**: value - **Available Options**: Item at Index - **List Index Options**: First <img src="/assets/shared/img/ff-makepurchase-value.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 7. Нажмите `subscriptionUpdateParameters`, найдите `AdaptySubscriptionUpdateParameters` и выберите его. Нажмите **Confirm**. :::info По умолчанию все поля объекта можно оставить пустыми. Их нужно заполнять только при замене одной подписки другой в Android-приложениях. Подробнее читайте [здесь](https://android.adapty.io/adapty/com.adapty.models/-adapty-subscription-update-parameters/). ::: <img src="/assets/shared/img/ff-subupdate.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 8. Нажмите **Confirm**. 9. В поле **Action Output Variable Name** создайте новую переменную и назовите её `makePurchaseResult` — она понадобится позже для подтверждения успешной покупки. <img src="/assets/shared/img/ff-makepurchaseresult.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 3.2. Проверьте, прошла ли покупка успешно \{#step-32-check-if-the-purchase-was-successful\} Теперь настроим проверку того, что покупка была выполнена. 1. Нажмите **+** и выберите **Add Conditional**. 2. В разделе **Set Condition for Action** выберите переменную `makePurchaseResult`. 3. В окне **Set Variable** заполните поля следующим образом: - **Available Options**: Has Field - **Select Field**: profile <img src="/assets/shared/img/ff-makepurchaseresult-conditional.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Нажмите **Confirm**. ## Шаг 3.3. Откройте платный контент \{#step-33-open-paid-content\} Если покупка прошла успешно, можно открыть доступ к платному контенту. Вот как это настроить: 1. Нажмите **+** под меткой **TRUE** и выберите **Add Action**. 2. В поле **Define Action** найдите и выберите страницу, которую хотите открыть, из списка **Navigate To**. В данном примере это страница **Questions**. <img src="/assets/shared/img/ff-questions.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 3.4. Показ сообщения об ошибке при неудачной покупке \{#step-34-show-error-message-if-purchase-failed\} Если покупка не прошла, покажем пользователю соответствующее уведомление. 1. Добавьте действие **Informational Dialog** к метке **FALSE**. 2. В поле **Title** введите текст заголовка диалога, например **Purchase Failed**. <img src="/assets/shared/img/ff-purchase-fail.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. Нажмите **Value** в поле **Message**. В окне **Set from Variable** найдите `makePurchaseResult` и выберите его. Заполните поля следующим образом: - **Available Options**: Data Structure Field - **Select Field**: error - **Available Options**: Data Structure Field - **Select Field**: errorMessage <img src="/assets/shared/img/ff-fail-message.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Нажмите **Confirm**. 5. Добавьте действие **Terminate** в ветку **FALSE**. <img src="/assets/shared/img/ff-terminate-purchase.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. Наконец, нажмите **Close** в правом верхнем углу. Поздравляем! Теперь пользователи могут приобретать ваши продукты. В качестве дополнительного шага [настройте проверку доступа пользователей к платному контенту](ff-check-subscription-status) в других местах приложения, чтобы решить — показывать им платный контент или пейвол. --- # File: ff-check-subscription-status --- --- title: "Шаг 4. Проверка доступа к платному контенту" description: "Узнайте, как проверять статус подписки с помощью флагов функций Adapty для более точной сегментации пользователей." --- Чтобы определить, есть ли у пользователя доступ к определённому платному контенту, нужно проверить его уровень доступа. Это означает, что у пользователя должен быть хотя бы один уровень доступа, и он должен быть нужным. Это можно сделать, проверив профиль пользователя, который содержит все доступные уровни доступа. Теперь давайте разрешим пользователям покупать ваш продукт: 1. Дважды щёлкните по кнопке, которая должна открывать платный контент, и откройте раздел **Actions** на правой панели, если он ещё не открыт. 2. Откройте **Action Flow Editor**. <img src="/assets/shared/img/ff-open-paid-content.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 3. В окне **Select Action Trigger** выберите **On Tap**. 4. В окне **No Actions Created** нажмите кнопку **Add Conditional Action**. 5. Нажмите **UNSET**, чтобы задать аргументы действия, и выберите переменную `currentProfile`. Это переменная Adapty, которая хранит данные о профиле текущего пользователя. <img src="/assets/shared/img/ff-currentprofile.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '300px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 6. Заполните поля следующим образом: - **Available Options**: Data Structure Field - **Select Field**: accessLevels - **Available Options**: Filter List Items - **Filter Conditions**: 1. Выберите **Conditions -> Single Condition** и нажмите **UNSET**. 2. В поле **First value** выберите **Item in list** в качестве **Source** и заполните поля следующим образом: - **Available Options**: Data Structure Field - **Select Field**: accessLevelIdentifier 3. Установите оператор фильтра **Equal to**. 4. Нажмите **UNSET** рядом с **Second value** и в поле **Value** введите ID вашего уровня доступа; в нашем примере используется `premium`. <img src="/assets/shared/img/ff-filter.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '500px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Нажмите **Confirm** и продолжите заполнять остальные поля ниже. - **Available Options**: Item at Index - **List Index Options**: First - **Available Options**: Data Structure Field - **Select Field**: accessLevel - **Available Options**: Data Structure Field - **Select Field**: isActive 7. Нажмите **Confirm**. Теперь добавьте действия для дальнейших сценариев — есть ли у пользователя нужная подписка или нет. Либо перенаправьте его на страницу, доступную подписчикам премиум-уровня, либо откройте страницу с пейволом, чтобы он мог купить доступ. --- # File: ff-resources --- --- title: "Действия и типы данных плагина Adapty для FlutterFlow" description: "Используйте ресурсы функциональных флагов Adapty для упрощения работы с функциями на основе подписки." --- ## Кастомные действия \{#custom-actions\} Ниже перечислены методы Adapty, доступные во FlutterFlow через плагин Adapty. Их можно использовать как кастомные действия во FlutterFlow. | Пользовательское действие | Описание | Аргументы действия | Типы данных Adapty — переменная вывода действия | |---|----|--------|----| | activate | Инициализирует SDK Adapty | Нет || | <p id="getPaywall">getPaywall</p> | Получает пейвол. Не возвращает продукты пейвола. Используйте действие `getPaywallProducts`, чтобы получить актуальные продукты | <ul><li>[Placement_ID](placements)</li><li>[Locale](localizations-and-locale-codes)</li></ul> | [AdaptyGetPaywallResult](ff-resources#adaptygetpaywallresult)| | <p id="getPaywallProducts">getPaywallProducts</p> | Возвращает список актуальных продуктов пейвола | [AdaptyPaywall](ff-resources#adaptypaywall) | [AdaptyGetProductsResult](ff-resources#adaptygetproductsresult) | | <p id="getproductsintroductoryoffereligibility">getProductsIntroductoryOfferEligibility</p> | Проверяет, имеет ли пользователь право на introductory offer для iOS-подписки | [AdaptyPaywallProduct](product) | [AdaptyGetIntroEligibilitiesResult](ff-resources#adaptygetintroeligibilitiesresult) | | <p id="makePurchase">makePurchase</p> | Завершает покупку и открывает доступ к контенту. Если у пейвола есть promotional offer, Adapty автоматически применяет его при оформлении покупки | <ul><li> **product**: объект AdaptyPaywallProduct, полученный из пейвола.</li><li> **subscriptionUpdateParams**: объект [`AdaptySubscriptionUpdateParameters`](ff-resources#adaptysubscriptionupdateparameters), используемый для повышения или понижения уровня подписки (для Android).</li><li>**isOfferPersonalized**: указывает, является ли предложение персонализированным для покупателя (для Android).</li></ul> | [AdaptyMakePurchaseResult](ff-resources#adaptymakepurchaseresult) | | <p id="getprofile">getProfile</p> | <p>Получает профиль текущего пользователя приложения. Позволяет задавать уровни доступа и другие параметры</p><p>Если запрос завершается ошибкой (например, из-за отсутствия интернета), возвращаются кешированные данные. Adapty регулярно обновляет кеш профиля, чтобы информация оставалась как можно более актуальной</p> | Нет | [AdaptyGetProfileResult](ff-resources#adaptygetprofileresult) | | updateProfile | Изменяет необязательные атрибуты профиля текущего пользователя: email, номер телефона и т. д. Атрибуты можно использовать для создания [сегментов](segments) пользователей или просматривать их в CRM | ID и любые параметры, которые нужно обновить для [AdaptyProfile](ff-resources#adaptyprofile) | [AdaptyError](ff-resources#adaptyerror) (необязательно) | | restorePurchases | Восстанавливает все покупки пользователя | Нет | [AdaptyGetProfileResult](ff-resources#adaptygetprofileresult) | | logShowPaywall | Фиксирует показ конкретного пейвола пользователю | [AdaptyPaywall](ff-resources#adaptypaywall) | [AdaptyError](ff-resources#adaptyerror) (необязательно) | | identify | Идентифицирует пользователя с помощью `customerUserId` из вашей системы | customerUserId | [AdaptyError](ff-resources#adaptyerror) (необязательно) | | logout | Выполняет выход текущего пользователя из приложения | Нет | [AdaptyError](ff-resources#adaptyerror) (необязательно) | | presentCodeRedemptionSheet | Отображает окно для активации промокодов (только iOS) | Нет | Нет | ## Типы данных \{#data-types\} Типы данных Adapty (наборы значений данных), передаваемые в FlutterFlow через плагин Adapty. ### AdaptyAccessLevel Информация об [уровне доступа](access-level) пользователя. | Название поля | Тип | Описание | |--------------------------|----------|-------------| | activatedAt | DateTime | Время активации данного уровня доступа | | activeIntroductoryOfferType | String | Тип активного introductory offer. Если задано, значит в текущем расчётном периоде подписки применялось предложение | | activePromotionalOfferId | String | ID активного promotional offer (приобретённого через iOS) | | activePromotionalOfferType | String | Тип активного promotional offer (приобретённого через iOS). Если задано, значит в текущем расчётном периоде подписки применялось предложение | | billingIssueDetectedAt | DateTime | Время обнаружения проблемы с оплатой. Подписка при этом может оставаться активной. Устанавливается в null при успешной обработке платежа | | cancellationReason | String | Причина отмены подписки | | expiresAt | DateTime | Время истечения уровня доступа (может быть в прошлом или не задано для пожизненного доступа) | | id | String | Идентификатор уровня доступа | | isActive | Boolean | True, если данный уровень доступа активен. Как правило, именно это свойство используется для проверки наличия у пользователя доступа к премиум-функциям | | isInGracePeriod | Boolean | True, если данная автовозобновляемая подписка находится в [льготном периоде](https://developer.apple.com/help/app-store-connect/manage-subscriptions/enable-billing-grace-period-for-auto-renewable-subscriptions) | | isLifetime | Boolean | True, если данный уровень доступа активен на всё время (без даты истечения) | | isRefund | Boolean | True, если данная покупка была возвращена | | offerId | String | ID активного promotional offer (приобретённого через Android) | | renewedAt | DateTime | Время последнего продления уровня доступа | | startsAt | DateTime | Время начала действия данного уровня доступа (может быть в будущем) | | store | String | Стор, в котором была совершена покупка | | unsubscribedAt | DateTime | Время отключения автопродления подписки. Подписка при этом может оставаться активной. Если не задано, пользователь повторно активировал подписку | | vendorProductId | String | ID продукта в сторе, открывшего данный уровень доступа | | willRenew | Boolean | True, если данная автовозобновляемая подписка настроена на продление | ### AdaptyAccessLevelIdentifiers Эта структура предназначена для замены пары ключ-значение в `Map<String, AdaptyAccessLevel` [AdaptyAccessLevel](ff-resources#adaptyaccesslevel). | Название поля | Тип | Описание | |--------------|-----|---------| | accessLevelIdentifier | String | ID уровня доступа | | accessLevel | Data ([AdaptyAccessLevel](ff-resources#adaptyaccesslevel)) | Связанный [AdaptyAccessLevel](ff-resources#adaptyaccesslevel) | ### AdaptyCustomDoubleAttribute Информация о пользовательских атрибутах типа double, заданных для [пользователя](ff-resources#adaptyprofile). | Название поля | Тип | Описание | |---------------|-----|----------| | key | String | Идентификатор пользовательского атрибута типа double | | value | Double | Значение пользовательского атрибута типа double | ### AdaptyCustomStringAttribute Информация о пользовательских строковых атрибутах, заданных для [пользователя](ff-resources#adaptyprofile). | Название поля | Тип | Описание | |--------------|-----|---------| | key | String | Идентификатор пользовательского строкового атрибута | | value | String | Значение пользовательского строкового атрибута | ### AdaptyError Содержит подробности об ошибке. Полный список кодов ошибок приведён в разделе [React Native, Flutter, Unity — Обработка ошибок](error-handling-on-flutter-react-native-unity). | Название поля | Тип | Описание | |--------------------------|----------|-------------| | errorMessage | String | Человекочитаемое описание ошибки | | errorCode | Integer | Числовой код, идентифицирующий ошибку | ### AdaptyGetIntroEligibilitiesResult Содержит результат выполнения пользовательского действия `getProductsIntroductoryOfferEligibility`. | Название поля | Тип | Описание | |--------------------------|----------|-------------| | value | List < Data ([AdaptyProductIntroEligibility](ff-resources#adaptyproductintroeligibility)) > | Список статусов доступности introductory offer для пользователя | | error | Data ([AdaptyError](ff-resources#adaptyerror)) | Содержит детали ошибки через [`AdaptyError`](ff-resources#adaptyerror) | ### AdaptyGetPaywallResult Содержит результат пользовательского действия `getPaywall`. | Название поля | Тип | Описание | |--------------------------|----------|-------------| | value | Data ([AdaptyPaywall](ff-resources#adaptypaywall)) | Содержит список объектов [AdaptyPaywall](ff-resources#adaptypaywall) | | error | Data ([AdaptyError](ff-resources#adaptyerror)) | Содержит информацию об ошибке через [AdaptyError](ff-resources#adaptyerror) | ### AdaptyGetProductsResult Содержит результат выполнения пользовательского действия `getPaywallProducts`. | Название поля | Тип | Описание | |--------------------------|----------|-------------| | value | List < Data ([AdaptyPaywallProduct](product)) > | Содержит список [AdaptyPaywallProducts](product) | | error | Data ([AdaptyError](ff-resources#adaptyerror)) | Содержит информацию об ошибке через [AdaptyError](ff-resources#adaptyerror) | ### AdaptyGetProfileResult Содержит результат выполнения пользовательского действия `getProfile`. | Название поля | Тип | Описание | |--------------------------|----------|-------------| | value | Data ([AdaptyProfile](ff-resources#adaptyprofile)) | Содержит профиль пользователя в виде [AdaptyProfile](ff-resources#adaptyprofile) | | error | Data (AdaptyError) | Содержит информацию об ошибке через [AdaptyError](ff-resources#adaptyerror) | ### AdaptyMakePurchaseResult Содержит результат пользовательского действия `makePurchase`. | Название поля | Тип | Описание | |--------------------------|----------|-------------| | value | Data ([AdaptyProfile](ff-resources#adaptyprofile)) | Содержит профиль пользователя в виде [AdaptyProfile](ff-resources#adaptyprofile) | | error | Data ([AdaptyError](ff-resources#adaptyerror)) | Содержит информацию об ошибке через [AdaptyError](ff-resources#adaptyerror) | ### AdaptyNonSubscription Информация о покупках, не являющихся подписками. Это могут быть разовые (расходуемые) покупки, разблокировки (например, новая карта в игре) и т. д. | Название поля | Тип | Описание | |--------------------------|----------|-------------| | isConsumable | Boolean | Указывает, является ли продукт расходуемой покупкой | | isOneTime | Boolean | Указывает, является ли продукт разовой покупкой (например, если true, покупка обрабатывается только один раз) | | isRefund | Boolean | Указывает, был ли выполнен возврат средств за продукт | | isSandbox | Boolean | Указывает, была ли покупка совершена в среде песочницы | | purchasedAt | DateTime | Время, когда был куплен продукт | | purchaseId | String | ID покупки в Adapty. Можно использовать для отслеживания разовых покупок | | store | String | Стор, в котором был куплен продукт (например, App Store, Google Play) | | vendorProductId | String | ID продукта в системе поставщика | | vendorTransactionId | String | ID транзакции в системе поставщика | ### AdaptyPaywall Информация о [пейволе](paywalls). | Название поля | Тип | Описание | |----------------------|----------|-------------| | abTestName | String | Название родительского A/B-теста | | hasViewConfiguration | Boolean | Указывает, есть ли конфигурация отображения для пейвола | | locale | String | ID локали пейвола | | name | String | Название пейвола | | placement.id | String | ID родительского плейсмента | | remoteConfigString | String | Пользовательский словарь из дашборда Adapty, связанный с этим пейволом | | placement.revision | Integer | Текущая ревизия/версия пейвола. Каждое изменение создаёт новую ревизию | | variationId | String | ID варианта, используемый для атрибуции покупок этому пейволу | | vendorProductIds | String | Массив ID продуктов, связанных с пейволом | ### AdaptyPaywallProduct Информация о [продукте](product). | Название поля | Тип | Описание | | -------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | vendorProductId | String | Идентификатор продукта в сторе | | localizedDescription | String | Описание продукта на языке пользователя | | localizedTitle | String | Название продукта на языке пользователя | | regionCode | String | Код региона локали, используемый для форматирования цены продукта (применяется для iOS) | | isFamilyShareable | Boolean | Булево значение, указывающее, доступен ли продукт для семейного доступа в App Store Connect. Всегда возвращает FALSE для iOS ниже версии 14.0 и macOS ниже версии 11.0 (применяется для iOS) | | paywallVariationId | String | Идентификатор варианта, используемый для атрибуции покупок к данному пейволу | | paywallABTestName | String | Название родительского A/B-теста | | paywallName | String | Название родительского пейвола | | price | Data ([AdaptyPriceData](#adaptyprice)) | Цена продукта | | subscriptionDetails | Data ([AdaptySubscriptionDetails](#adaptysubscriptiondetails)) | Информация о подписке | ### AdaptyPrice Информация о цене продукта. | Название поля | Тип | Описание | | --------------- | ------ | ------------------------------------------ | | amount | Double | Числовое значение цены | | currencyCode | String | Код валюты цены | | currencySymbol | String | Символ валюты | | localizedString | String | Цена, отображаемая на языке пользователя | ### AdaptyProductIntroEligibility Определяет, имеет ли пользователь право на introductory offer для подписки iOS. | Название поля | Тип | Описание | | --------------- | ----------------------------------------------------------- | ------------------------------------------------------------ | | vendorProductId | String | Идентификатор продукта в сторе | | eligibility | [AdaptyEligibilityEnum](ff-resources#adaptyeligibilityenum) | Определяет, имеет ли пользователь право на introductory offer для подписки iOS | ### AdaptyProductNonsubscriptions Детали активной неподписочной покупки, связанной с этим продуктом. | Название поля | Тип | Описание | | ---------------- | ----------------------------------------------------------- | ------------------------------------------------------------ | | productId | String | Идентификатор продукта в сторе | | nonsubscriptions | [AdaptyNonSubscription](ff-resources#adaptynonsubscription) | Информация о покупках без подписки. Это могут быть разовые (расходуемые) покупки, разблокировки контента (например, новая карта в игре) и т. д. | ### AdaptyProductSubscriptions Детали активной подписки, привязанной к этому продукту. | Название поля | Тип | Описание | | ------------- | ----------------------------------------------------- | ----------------------------------------------- | | productId | String | ID продукта в сторе | | subscription | [AdaptySubscription](ff-resources#adaptysubscription) | Информация о покупках подписки | ### AdaptyProfile Информация о профиле пользователя | Название поля | Тип | Описание | | ---------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | accessLevels | List < Data ([AdaptyAccessLevelIdentifiers](ff-resources#adaptyaccesslevelidentifiers)) > | Список всех уровней доступа, принадлежащих пользователю | | profileId | String | ID профиля пользователя | | customerUserId | String | ID пользователя в системе вендора | | subscriptions | List < Data ([MapKeySubscriptions](#mapkeysubscriptions)) > | Список всех подписок, купленных пользователем | | nonSubscriptions | List < Data ([MapKeyNonSubscriptions](#mapkeynonsubscriptions)) > | Список всех продуктов без подписки, купленных пользователем | ### AdaptyProfileParameters Информация о пользователе. | Название поля | Тип | Описание | | ----------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | firstName | String | Имя пользователя | | lastName | String | Фамилия пользователя | | gender | [AdaptyGenderEnum](#adaptygenderenum) | Пол пользователя | | birthday | String | Дата рождения пользователя | | email | String | Email пользователя | | phoneNumber | String | Номер телефона пользователя | | facebookAnonymousId | String | ID пользователя в [интеграции Facebook Ads](facebook-ads) | | amplitudeUserId | String | ID пользователя в [интеграции Amplitude](amplitude) | | amplitudeDeviceId | String | ID устройства пользователя в [интеграции Amplitude](amplitude) | | mixpanelUserId | String | ID пользователя в [интеграции Mixpanel](mixpanel) | | appmetricaProfileId | String | ID пользователя в [интеграции AppMetrica](appmetrica) | | appmetricaDeviceId | String | ID устройства пользователя в [интеграции AppMetrica](appmetrica) | | oneSignalPlayerId | String | ID пользователя в [интеграции OneSignal](onesignal) | | pushwooshHWID | String | ID устройства пользователя в [интеграции Pushwoosh](pushwoosh) | | firebaseAppInstanceId | String | ID пользователя в [интеграции Firebase](firebase-and-google-analytics) | | airbridgeDeviceId | String | ID устройства пользователя в [интеграции Airbridge](airbridge) | | appTrackingTransparencyStatus | AdaptyATTStatus | Статус доступа к IDFA (используется для iOS) | | analyticsDisabled | Boolean | Признак того, что внешняя [аналитика отключена для пользователя](analytics-integration#disabling-external-analytics-for-a-specific-customer) | | customStringAttributes | List < Data ([AdaptyCustomStringAttribute](ff-resources#adaptycustomstringattribute)) > | Список пользовательских строковых атрибутов пользователя | | customDoubleAttributes | List < Data ([AdaptyCustomDoubleAttribute](ff-resources#adaptycustomdoubleattribute)) > | Список пользовательских числовых атрибутов пользователя | ### AdaptySubscription Информация о существующей подписке пользователя. | Название поля | Тип | Описание | | --------------------------- | -------- | ------------------------------------------------------------ | | activatedAt | DateTime | Время активации подписки | | activeIntroductoryOfferType | String | Тип активного introductory offer. Если задан, значит в течение данного периода подписки было применено предложение | | activePromotionalOfferId | String | ID активного promotional offer (используется для iOS) | | activePromotionalOfferType | String | Тип активного promotional offer (используется для iOS). Если задан, значит в течение данного периода подписки было применено предложение | | cancellationReason | String | Причина отмены подписки | | expiresAt | DateTime | Время истечения подписки | | renewedAt | DateTime | Время последнего продления подписки | | unsubscribedAt | DateTime | Время отключения автопродления подписки. Подписка при этом может оставаться активной. Если не задано — пользователь повторно активировал подписку | | billingIssueDetectedAt | DateTime | Время обнаружения проблемы с оплатой. Подписка при этом может оставаться активной. Сбрасывается в null при успешной обработке платежа | | isActive | Boolean | True, если подписка активна. Как правило, это поле используется для проверки доступа пользователя к премиум-функциям | | isInGracePeriod | Boolean | True, если автовозобновляемая подписка находится в [льготном периоде](https://developer.apple.com/help/app-store-connect/manage-subscriptions/enable-billing-grace-period-for-auto-renewable-subscriptions) | | isLifetime | Boolean | True, если подписка действует бессрочно (без даты истечения) | | isRefund | Boolean | True, если покупка была возвращена | | isSandbox | Boolean | Указывает, была ли покупка совершена в среде песочницы | | offerId | String | ID активного promotional offer (используется для Android) | | startsAt | DateTime | Время начала действия данного уровня доступа (может быть в будущем) | | store | String | Стор, в котором был куплен продукт (например, App Store, Google Play) | | vendorOriginalTransactionId | String | ID первоначальной подписки в системе вендора | | vendorProductId | String | ID продукта в системе вендора | | vendorTransactionId | String | ID транзакции в системе вендора | | willRenew | Boolean | True, если автовозобновляемая подписка настроена на продление | ### AdaptySubscriptionDetails Схема объекта Subscription, являющегося частью [AdaptyPaywallProduct](product). | Название поля | Тип | Описание | | ----------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | androidBasePlanId | String | [ID базового плана](https://support.google.com/googleplay/android-developer/answer/12154973) в Google Play Store или [ID цены](https://docs.stripe.com/products-prices/how-products-and-prices-work#use-products-and-prices) в Stripe. | | androidIntroductoryOfferEligibility | [AdaptyEligibilityEnum](ff-resources#adaptyeligibilityenum) | Определяет, имеет ли пользователь право на introductory offer для iOS-подписки | | androidOfferId | String | ID активного promotional offer (используется для Android) | | androidOfferTags | List < String > | Список [пользовательских тегов](https://developers.google.com/android-publisher/api-ref/rest/v3/OfferTag), заданных для базовых планов и предложений по подписке. | | introductoryOffer | List < Data ([AdaptySubscriptionPhase](ff-resources#adaptysubscriptionphase)) > | ID introductory offer (используется для iOS) | | localizedSubscriptionPeriod | String | Период подписки на языке пользователя | | promotionalOffer | Data ([AdaptySubscriptionPhase](ff-resources#adaptysubscriptionphase)) | Детали promotional offer (используется для iOS) | | promotionalOfferEligibility | Boolean | Определяет, имеет ли пользователь право на promotional offer для iOS-подписки | | promotionalOfferId | String | ID promotional offer (используется для iOS) | | renewalType | [AdaptyRenewalTypeEnum](#adaptyrenewaltypeenum) | Определяет, является ли подписка автовозобновляемой, через [AdaptyRenewalTypeEnum](ff-resources#adaptyrenewaltypeenum) | | subscriptionGroupIdentifier | String | ID группы продуктов, к которой относится продукт (используется для iOS) | | subscriptionPeriod | Data ([AdaptySubscriptionPeriod](#adaptysubscriptionperiod)) | Длительность подписки | ### AdaptySubscriptionPeriod Длительность подписки. | Название поля | Тип | Описание | | ------------- | --------------------------------------------- | ---------------------------------------------------------------------------- | | numberOfUnits | Integer | Количество дней/недель/месяцев/лет, на которое оформляется подписка. | | unit | [AdaptyPeriodUnitEnum](#adaptyperiodunitenum) | Единица измерения периода: дни, недели, месяцы, годы. | ### AdaptySubscriptionPhase Представляет фазу подписки, например бесплатный пробный период или introductory offer. | Название поля | Тип | Описание | | --------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | | identifier | String | ID фазы | | localizedNumberOfPeriods | String | Длительность фазы. Например, предложение на 6 месяцев отобразится как `6 months` на языке пользователя. | | localizedSubscriptionPeriod | String | Длительность подписки на языке пользователя, например `3 months`. | | numberOfPeriods | Integer | Количество периодов подписки в данной фазе. Например, предложение на 6 месяцев будет содержать два периода по 3 месяца. | | paymentMode | [AdaptyPaymentModeEnum](#adaptypaymentmodeenum) | Модель оплаты, используемая для данной фазы. | | price | Data ([AdaptyPrice](#adaptyprice)) | Цена данной фазы. | | subscriptionPeriod | Data ([AdaptySubscriptionPeriod](#adaptysubscriptionperiod)) | Период подписки, на котором основана данная фаза. | ### AdaptySubscriptionUpdateParameters (*Только для Android*) Параметры для замены одной подписки на другую. | Поле | Тип | Описание | | ---------- | ------------------------------------------------------------ | ---------- | | oldSubVendorProductId | String | ID текущей подписки в Play Store, которую вы хотите заменить. | | replacementMode | [AdaptySubscriptionUpdateReplacementMode](ff-resources#adaptysubscriptionupdatereplacementmode) | Enum, соответствующий значениям [`BillingFlowParams.ProrationMode`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode). | ### MapKeyNonSubscriptions Замена словаря для [AdaptyNonSubscription](ff-resources#adaptynonsubscription). | Название поля | Тип | | ------------- | --------------------------------------------------------------------------------------------------- | | key | String | | value | List < Data ([AdaptyNonSubscription](ff-resources#adaptynonsubscription)) > | ### MapKeySubscriptions Замена словаря для [AdaptySubscription](ff-resources#adaptysubscription). | Название поля | Тип | | ------------- | ------------------------------------------------------------ | | key | String | | value | List < Data ([AdaptySubscription](ff-resources#adaptysubscription)) > | ## Перечисления \{#enums\} Перечисления Adapty (переменные, представляющие собой наборы предопределённых констант), поставляемые во FlutterFlow с плагином Adapty. ### AdaptyEligibilityEnum Определяет, имеет ли пользователь право на introductory offer для подписки iOS. | Название поля | Описание | |--------------|----------| | eligible | Пользователь имеет право на introductory offer — можно безопасно отображать эту информацию в интерфейсе | | ineligible | Пользователь не имеет права на получение какого-либо предложения — не следует показывать его в интерфейсе | | notApplicable | Для этого продукта не настроено ни одного предложения | ### AdaptyGenderEnum Определяет пол пользователя. | Название поля | Описание | | ------------- | ----------------------------------------------- | | none | Пол не указан | | female | Пол пользователя — женский | | male | Пол пользователя — мужской | | Other | Пользователь указал пол как «другой» | ### AdaptyPaymentModeEnum Определяет модель оплаты. | Название поля | Описание | | ------------- | -------- | | payAsYouGo | Модель оплаты, при которой пользователь платит по факту использования продукта или сервиса, а не вносит фиксированную сумму заранее | | payUpFront | Модель оплаты, при которой пользователь платит до получения продукта или сервиса | | freeTrial | Пользователь находится на бесплатном пробном периоде | | unknown | Модель оплаты не определена | ### AdaptyPeriodUnitEnum Определяет единицы измерения периодов. | Название поля | Описание | | ------------- | ---------------- | | day | В днях | | week | В неделях | | month | В месяцах | | year | В годах | | unknown | Не определено | ### AdaptyRenewalTypeEnum Определяет, является ли подписка автоматически возобновляемой. | Field Name | Description | | ------------- | -------------------------------------------------------- | | prepaid | Подписка является предоплаченной и не возобновляется автоматически. | | autorenewable | Подписка является автоматически возобновляемой. | ### AdaptySubscriptionUpdateReplacementMode Определяет режим обновления подписки для Android. | Название поля | Описание | | ------------- | -------- | | withTimeProration | (по умолчанию) Новый план вступает в силу немедленно, оставшееся время пересчитывается и зачисляется на счёт пользователя. | | chargeProratedPrice | Новый план вступает в силу немедленно, расчётный период остаётся прежним. Списывается оплата за оставшийся период. Доступно только при повышении уровня подписки. | | withoutProration | Новый план вступает в силу немедленно, новая цена будет списана в следующую дату продления. Расчётный период остаётся прежним. | | deferred | Новая покупка оформляется немедленно, но новый план вступает в силу после истечения текущего. | | chargeFullPrice | Новый план вступает в силу немедленно, расчётный период остаётся прежним. Списывается полная стоимость за оставшийся период. Доступно только при повышении уровня подписки. | ### Состояния приложения \{#app-states\} Переменные состояния приложения — это специальные переменные, которые хранят текущее состояние приложения. Они доступны и могут изменяться в любом месте приложения: на всех страницах и во всех компонентах. Такой тип переменных удобен для хранения данных, которые нужно передавать между разными частями приложения, — например, пользовательских настроек или токенов аутентификации. | Название поля | Тип данных | Сохраняется | Описание | | -------------- | -------------------------------------------------- | ----------- | ------------------------------------------------------------ | | currentProfile | Data ([AdaptyProfile](ff-resources#adaptyprofile)) | False | Переменная, содержащая информацию о текущем профиле пользователя. Поддерживайте её в актуальном состоянии. | --- # End of Documentation _Generated on: 2026-07-24T13:01:12.958Z_ _Successfully processed: 277/277 files_ # UNITY - Adapty Documentation (Full Content) This file contains the complete content of all documentation pages for this platform. Locale: ru Generated on: 2026-07-24T13:01:12.968Z Total files: 41 --- # File: sdk-installation-unity --- --- title: "Установка и настройка Unity SDK" description: "Пошаговое руководство по установке Adapty SDK на Unity для приложений с подписками." --- SDK Adapty включает два ключевых модуля для интеграции в ваше Unity-приложение: - **Core Adapty**: основной SDK, необходимый для работы Adapty в вашем приложении. - **AdaptyUI**: этот модуль нужен, если вы используете [Adapty Paywall Builder](adapty-paywall-builder) — удобный no-code инструмент для создания кроссплатформенных пейволов. :::tip Хотите посмотреть на реальный пример интеграции Adapty SDK в мобильное приложение? Изучите наш [пример приложения](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Assets) — в нём показана полная настройка: отображение пейволов, совершение покупок и другая базовая функциональность. ::: ## Требования \{#requirements\} Adapty SDK поддерживает iOS 13.0+, однако для работы с пейволами, созданными в Paywall Builder, требуется iOS 15.0+. :::info Adapty совместима с Google Play Billing Library версий до 8.x включительно. По умолчанию Adapty использует Google Play Billing Library v7.0.0. Чтобы использовать более новую версию, [переопределите зависимость Billing](https://developer.android.com/google/play/billing/integrate#dependency) в вашей Android-сборке. ::: :::info Установка SDK — это шаг 5 настройки Adapty. Прежде чем покупки заработают в вашем приложении, вам также нужно подключить приложение к сторам, а затем создать продукты, пейвол и плейсмент в дашборде Adapty. [Гайд по быстрому старту](quickstart) описывает все необходимые шаги. ::: ## Установка Adapty SDK \{#install-adapty-sdk\} [![Release](https://img.shields.io/github/v/release/adaptyteam/AdaptySDK-Unity.svg?style=flat&logo=unity)](https://github.com/adaptyteam/AdaptySDK-Unity/releases) Выберите удобный способ установки: <Tabs groupId="unity-install-method"> <TabItem value="git-url" label="Git URL"> Установите Adapty SDK через Unity Package Manager с помощью Git URL: 1. В Unity откройте **Window → Package Manager**. 2. Нажмите **+** в верхнем левом углу и выберите **Add package from git URL...**. 3. Введите следующий URL и нажмите **Add**: ``` https://github.com/adaptyteam/AdaptySDK-Unity.git?path=Packages/com.adapty.unity-sdk#upm ``` Подробнее см. в руководстве Unity по [установке UPM-пакета из Git URL](https://docs.unity3d.com/Manual/upm-ui-giturl.html). </TabItem> <TabItem value="unity-package" label="Unity package" default> Скачайте [`adapty-unity-plugin-*.unitypackage`](https://github.com/adaptyteam/AdaptySDK-Unity/tree/main/Releases) с GitHub и импортируйте его в свой проект. <img src="/assets/shared/img/456bd98-adapty-unity-plugin.webp" style={{ border: 'none', /* border width and color */ width: '400px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </TabItem> </Tabs> После установки SDK выполните следующие шаги: 1. Установите [плагин External Dependency Manager (EDM)](https://github.com/googlesamples/unity-jar-resolver#getting-started). SDK использует его для управления зависимостями iOS Cocoapods и Android gradle. 2. После установки EDM может потребоваться запустить менеджер зависимостей: `Assets -> External Dependency Manager -> Android Resolver -> Force Resolve` и `Assets -> External Dependency Manager -> iOS Resolver -> Install Cocoapods` 3. При сборке Unity-проекта для iOS вы получите файл `Unity-iPhone.xcworkspace`, который необходимо открывать вместо `Unity-iPhone.xcodeproj`, иначе зависимости Cocoapods не будут использоваться. ## Активация модуля Adapty в SDK \{#activate-adapty-module-of-adapty-sdk\} Активируйте Adapty SDK в коде вашего приложения. :::note SDK Adapty нужно активировать только один раз в приложении. ::: Чтобы получить **Public SDK Key**: 1. Откройте дашборд Adapty и перейдите в [**App settings → General**](https://app.adapty.io/settings/general). 2. В разделе **Api keys** скопируйте **Public SDK Key** (НЕ Secret Key). 3. Замените `"YOUR_PUBLIC_SDK_KEY"` в коде. Или получите его программно с помощью [Adapty CLI](developer-cli): ``` npm install -g adapty adapty auth login adapty apps list ``` Или напрямую: ``` npx adapty auth login adapty apps list ``` - Убедитесь, что для инициализации Adapty вы используете **Public SDK key** — **Secret key** предназначен только для [серверного API](getting-started-with-server-side-api). - **SDK-ключи** уникальны для каждого приложения, поэтому если у вас несколько приложений, выберите нужный ключ. ```csharp showLineNumbers title="C#" using UnityEngine; using AdaptySDK; public class AdaptyListener : MonoBehaviour, AdaptyEventListener { void Start() { DontDestroyOnLoad(this.gameObject); Adapty.SetEventListener(this); var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY"); Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); } public void OnLoadLatestProfile(AdaptyProfile profile) { } public void OnInstallationDetailsSuccess(AdaptyInstallationDetails details) { } public void OnInstallationDetailsFail(AdaptyError error) { } } ``` :::important Дождитесь коллбэка завершения `Activate` перед вызовом любых других методов SDK. Полная последовательность описана в разделе [Порядок вызовов в Unity SDK](unity-sdk-call-order). ::: ## Настройка прослушивания событий \{#set-up-event-listening\} Создайте скрипт для прослушивания событий Adapty. Назовите его `AdaptyListener` в вашей сцене. Рекомендуем использовать метод `DontDestroyOnLoad` для этого объекта, чтобы он сохранялся на протяжении всего жизненного цикла приложения. <img src="/assets/shared/img/2ccd564-create_adapty_listener.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Adapty использует пространство имён `AdaptySDK`. В начале файлов скриптов, использующих Adapty SDK, можно добавить: ```csharp showLineNumbers title="C#" using AdaptySDK; ``` Подпишитесь на события Adapty: ```csharp showLineNumbers title="C#" using UnityEngine; using AdaptySDK; public class AdaptyListener : MonoBehaviour, AdaptyEventListener { public void OnLoadLatestProfile(AdaptyProfile profile) { // handle updated profile data } public void OnInstallationDetailsSuccess(AdaptyInstallationDetails details) { } public void OnInstallationDetailsFail(AdaptyError error) { } } ``` Мы рекомендуем настроить Script Execution Order так, чтобы AdaptyListener выполнялся раньше Default Time. Это обеспечит инициализацию Adapty как можно раньше. <img src="/assets/shared/img/activate_unity.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Теперь настройте пейволы в своём приложении: - Если вы используете [Adapty Paywall Builder](adapty-paywall-builder), сначала [активируйте модуль AdaptyUI](#activate-adaptyui-module-of-adapty-sdk) ниже, затем следуйте [быстрому старту с Paywall Builder](unity-quickstart-paywalls). - Если вы создаёте собственный интерфейс пейвола, смотрите [быстрый старт для кастомных пейволов](unity-quickstart-manual). ## Активация модуля AdaptyUI в Adapty SDK \{#activate-adaptyui-module-of-adapty-sdk\} Если вы планируете использовать [Paywall Builder](adapty-paywall-builder) и установили модуль AdaptyUI, его необходимо активировать. Это можно сделать при конфигурации: ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetActivateUI(true); ``` ## Дополнительная настройка \{#optional-setup\} ### Логирование \{#logging\} #### Настройка системы логирования \{#set-up-the-logging-system\} Adapty логирует ошибки и другую важную информацию, чтобы помочь вам разобраться в происходящем. Доступны следующие уровни логирования: | Level | Description | | ---------- | ------------------------------------------------------------ | | `error` | Будут записываться только ошибки | | `warn` | Будут записываться ошибки и сообщения от SDK, которые не вызывают критических ошибок, но заслуживают внимания | | `info` | Будут записываться ошибки, предупреждения и различные информационные сообщения | | `verbose` | Будет записываться любая дополнительная информация, которая может быть полезна при отладке: вызовы функций, запросы к API и т. д. | Уровень логирования можно задать при настройке Adapty в вашем приложении: ```csharp showLineNumbers title="C#" // 'verbose' is recommended for development and the first production release var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY"); builder.LogLevel = AdaptyLogLevel.Verbose; ``` Уровень логирования также можно изменить во время работы приложения: ```csharp showLineNumbers title="C#" Adapty.SetLogLevel(AdaptyLogLevel.Verbose, (error) => { // handle result }); ``` ### Политики обработки данных \{#data-policies\} Adapty не хранит персональные данные ваших пользователей, если вы явно их не передаёте. При этом вы можете настроить дополнительные политики безопасности данных в соответствии с требованиями стора или законодательства конкретной страны. #### Отключение сбора и передачи IP-адресов \{#disable-ip-address-collection-and-sharing\} При активации модуля Adapty установите `SetIPAddressCollectionDisabled` в значение `true`, чтобы отключить сбор и передачу IP-адресов пользователей. Значение по умолчанию — `false`. Используйте этот параметр для защиты конфиденциальности пользователей, соблюдения региональных требований по защите данных (например, GDPR или CCPA) или для сокращения избыточного сбора данных в случаях, когда функции на основе IP-адреса не нужны вашему приложению. ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetIPAddressCollectionDisabled(true); ``` #### Отключение сбора и передачи рекламного идентификатора \{#disable-advertising-id-collection-and-sharing\} При активации модуля Adapty установите `SetAppleIDFACollectionDisabled` и/или `SetGoogleAdvertisingIdCollectionDisabled` в значение `true`, чтобы отключить сбор рекламных идентификаторов. Значение по умолчанию — `false`. Используйте этот параметр для соблюдения политик App Store/Google Play, чтобы не вызывать запрос App Tracking Transparency, или если ваше приложение не требует рекламной атрибуции или аналитики на основе рекламных идентификаторов. ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetAppleIDFACollectionDisabled(true) .SetGoogleAdvertisingIdCollectionDisabled(true); ``` #### Настройка конфигурации медиакеша для AdaptyUI \{#set-up-media-cache-configuration-for-adaptyui\} По умолчанию AdaptyUI кеширует медиафайлы (изображения и видео) для повышения производительности и снижения потребления трафика. Вы можете настроить параметры кеша, передав собственную конфигурацию. Используйте `SetAdaptyUIMediaCache`, чтобы переопределить настройки кеша по умолчанию: ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetAdaptyUIMediaCache( 100 * 1024 * 1024, // MemoryStorageTotalCostLimit 100MB null, // MemoryStorageCountLimit 100 * 1024 * 1024 // DiskStorageSizeLimit 100MB ); ``` Параметры: | Параметр | Обязательный | Описание | |-----------------------------|----------|----------------------------------------------------------------------------------------| | memoryStorageTotalCostLimit | optional | Общий размер кэша в памяти в байтах. По умолчанию используется платформозависимое значение. | | memoryStorageCountLimit | optional | Максимальное количество элементов в памяти. По умолчанию используется платформозависимое значение. | | diskStorageSizeLimit | optional | Максимальный размер файлов на диске в байтах. По умолчанию используется платформозависимое значение. | ### Включение локальных уровней доступа (Android) \{#enable-local-access-levels-android\} По умолчанию [локальные уровни доступа](local-access-levels) включены на iOS и отключены на Android. Чтобы включить их и на Android, установите `SetGoogleLocalAccessLevelAllowed` в `true`: ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetGoogleLocalAccessLevelAllowed(true); ``` ### Очистка данных при восстановлении из резервной копии \{#clear-data-on-backup-restore\} Когда `SetAppleClearDataOnBackup` установлен в `true`, SDK обнаруживает восстановление приложения из резервной копии iCloud и удаляет все локально сохранённые данные SDK, включая кешированную информацию о профиле, данные о продуктах и пейволы. После этого SDK инициализируется с чистым состоянием. Значение по умолчанию — `false`. :::note Удаляется только локальный кеш SDK. История транзакций с Apple и пользовательские данные на серверах Adapty остаются неизменными. ::: ```csharp showLineNumbers title="C#" var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetAppleClearDataOnBackup(true); ``` ## Устранение неполадок \{#troubleshooting\} #### Правила резервного копирования Android (настройка Auto Backup) \{#android-backup-rules-auto-backup-configuration\} Некоторые SDK (включая Adapty) поставляются с собственной конфигурацией Android Auto Backup. Если вы используете несколько SDK, каждый из которых определяет правила резервного копирования, слияние манифестов Android может завершиться ошибкой, связанной с `android:fullBackupContent`, `android:dataExtractionRules` или `android:allowBackup`. Типичные симптомы ошибки: `Manifest merger failed: Attribute application@dataExtractionRules value=(@xml/your_data_extraction_rules) is also present at [com.other.sdk:library:1.0.0] value=(@xml/other_sdk_data_extraction_rules)` :::note Эти изменения нужно вносить в директорию Android-платформы (обычно находится в папке `android/` вашего проекта). ::: Чтобы решить проблему, необходимо: - Указать механизму слияния манифестов использовать значения вашего приложения для атрибутов, связанных с резервным копированием. - Создать файлы правил резервного копирования, объединяющие правила Adapty с правилами других SDK. #### 1. Добавьте пространство имён `tools` в манифест В файле `AndroidManifest.xml` убедитесь, что корневой тег `<manifest>` включает tools: ```xml <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="com.example.app"> ... </manifest> ``` #### 2. Переопределите атрибуты резервного копирования в `<application>` В том же файле `AndroidManifest.xml` обновите тег `<application>`, чтобы ваше приложение предоставляло итоговые значения и указывало механизму слияния манифестов заменять значения библиотек: ```xml <application android:name=".App" android:allowBackup="true" android:fullBackupContent="@xml/sample_backup_rules" android:dataExtractionRules="@xml/sample_data_extraction_rules" tools:replace="android:fullBackupContent,android:dataExtractionRules"> ... </application> ``` Если какой-либо SDK также задаёт `android:allowBackup`, включите его в `tools:replace`: ```xml tools:replace="android:allowBackup,android:fullBackupContent,android:dataExtractionRules" ``` #### 3. Создайте объединённые файлы правил резервного копирования Создайте XML-файлы в директории `res/xml/` вашего Android-проекта, объединяющие правила Adapty с правилами других SDK. Android использует разные форматы правил резервного копирования в зависимости от версии ОС, поэтому создание обоих файлов обеспечивает совместимость со всеми версиями Android, которые поддерживает ваше приложение. :::note В примерах ниже в качестве стороннего SDK используется AppsFlyer. Замените или добавьте правила для других SDK, которые используются в вашем приложении. ::: **Для Android 12 и выше** (используется новый формат правил извлечения данных): ```xml title="sample_data_extraction_rules.xml" <?xml version="1.0" encoding="utf-8"?> <data-extraction-rules> <cloud-backup> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </cloud-backup> <device-transfer> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="appsflyer-purchase-data"/> <exclude domain="database" path="afpurchases.db"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> </device-transfer> </data-extraction-rules> ``` **Для Android 11 и ниже** (используется устаревший формат полного резервного копирования): ```xml title="sample_backup_rules.xml" <?xml version="1.0" encoding="utf-8"?> <full-backup-content> <exclude domain="sharedpref" path="appsflyer-data"/> <exclude domain="sharedpref" path="AdaptySDKPrefs.xml"/> :::important В Unity применяйте эти изменения в `Assets/Plugins/Android/AndroidManifest.xml` и создавайте файлы правил резервного копирования в `Assets/Plugins/Android/res/xml/`. ::: #### Покупки завершаются с ошибкой после возврата из другого приложения в Android \{#purchases-fail-after-returning-from-another-app-in-android\} Если Activity, запускающий флоу покупки, использует нестандартный `launchMode`, Android может пересоздать или повторно использовать его некорректно при возврате пользователя из Google Play, банковского приложения или браузера. В результате результат покупки может быть потерян или расценён как отменённый. Чтобы покупки работали корректно, используйте для Activity, запускающей флоу покупки, только режимы запуска `standard` или `singleTop` — остальные режимы не поддерживаются. В файле `AndroidManifest.xml` убедитесь, что Activity, запускающая флоу покупки, настроена на `standard` или `singleTop`: ```xml <activity android:name=".MainActivity" android:launchMode="standard" /> ``` #### Приложение падает при отображении пейвола на Android \{#app-crashes-when-a-paywall-is-displayed-on-android\} Если приложение падает на Android при отображении пейвола, возможно, в конфигурации Gradle отсутствует плагин Kotlin. Чтобы добавить его: 1. В разделе **Player Settings** убедитесь, что выбраны опции **Custom Launcher Gradle Template** и **Custom Base Gradle Template**. <img src="/assets/shared/img/kotlin-plugin1.webp" style={{ border: 'none', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Добавьте следующую строку в `/Assets/Plugins/Android/launcherTemplate.gradle`: ```groovy showLineNumbers apply plugin: 'com.android.application' // highlight-next-line apply plugin: 'kotlin-android' apply from: 'setupSymbols.gradle' apply from: '../shared/keepUnitySymbols.gradle' ``` 3. Добавьте следующую строку в `/Assets/Plugins/Android/baseProjectTemplate.gradle`: ```groovy showLineNumbers plugins { // If you are changing the Android Gradle Plugin version, make sure it is compatible with the Gradle version preinstalled with Unity // See which Gradle version is preinstalled with Unity here https://docs.unity3d.com/Manual/android-gradle-overview.html // See official Gradle and Android Gradle Plugin compatibility table here https://developer.android.com/studio/releases/gradle-plugin#updating-gradle // To specify a custom Gradle version in Unity, go do "Preferences > External Tools", uncheck "Gradle Installed with Unity (recommended)" and specify a path to a custom Gradle version id 'com.android.application' version '8.3.0' apply false id 'com.android.library' version '8.3.0' apply false // highlight-next-line id 'org.jetbrains.kotlin.android' version '1.8.0' apply false **BUILD_SCRIPT_DEPS** } ``` --- # File: unity-quickstart-paywalls --- --- title: "Включение покупок с помощью пейволов в Unity SDK" description: "Узнайте, как отображать пейволы в вашем приложении на Unity с помощью Adapty SDK." --- Чтобы включить встроенные покупки, нужно понять три ключевых концепции: - [**Продукты**](product) — всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ) - [**Пейволы**](paywalls) — конфигурации, определяющие, какие продукты предлагать. В Adapty пейволы — единственный способ получить продукты, но благодаря этому вы можете менять предложения, цены и наборы продуктов, не трогая код приложения. - [**Плейсменты**](placements) — где и когда показывать пейволы в приложении (например, `main`, `onboarding`, `settings`). Вы настраиваете пейволы для плейсментов в дашборде, а затем запрашиваете их по ID плейсмента в коде. Это упрощает проведение A/B-тестов и показ разных пейволов разным пользователям. Adapty предлагает три способа включить покупки в приложении. Выберите подходящий в зависимости от требований вашего приложения: | Реализация | Сложность | Когда использовать | |---------------------------|------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Adapty Paywall Builder | ✅ Просто | Вы [создаёте готовый к покупкам пейвол в no-code конструкторе](quickstart-paywalls). Adapty автоматически его отображает и берёт на себя весь сложный процесс покупки, валидацию чеков и управление подписками. | | Пейволы, созданные вручную | 🟡 Средне | Вы реализуете UI пейвола в коде приложения, но всё равно получаете объект пейвола из Adapty для гибкости в управлении предложениями. Смотрите [гайд](unity-quickstart-manual). | | Observer mode | 🔴 Сложно | У вас уже есть собственная инфраструктура обработки покупок, и вы хотите её использовать. Учтите, что observer mode имеет ограничения в Adapty. Смотрите [статью](observer-vs-full-mode). | :::important **Описанные ниже шаги показывают, как реализовать пейвол, созданный в Adapty Paywall Builder.** Если вы не хотите использовать Paywall Builder, смотрите [гайд по обработке покупок в пейволах, созданных вручную](unity-making-purchases). ::: Чтобы отобразить пейвол, созданный в Adapty Paywall Builder, в коде приложения вам нужно только: 1. **Получить пейвол**: Получите пейвол из Adapty. 2. **Отобразить пейвол — Adapty возьмёт на себя покупки**: Покажите контейнер пейвола в вашем приложении. 3. **Обработать действия кнопок**: Свяжите взаимодействия пользователя с пейволом с реакцией приложения на них. Например, открывайте ссылки или закрывайте пейвол при нажатии кнопок. ## Прежде чем начать \{#before-you-start\} Перед началом выполните следующие шаги: 1. Подключите приложение к [App Store](initial_ios) и/или [Google Play](initial-android) в дашборде Adapty. 2. [Создайте продукты](create-product) в Adapty. 3. [Создайте пейвол и добавьте в него продукты](create-paywall). 4. [Создайте плейсмент и добавьте в него пейвол](create-placement). 5. [Установите и активируйте Adapty SDK](sdk-installation-unity) в коде приложения. :::tip Самый быстрый способ выполнить эти шаги — следовать [quickstart-гайду](quickstart) или создать пейволы и плейсменты с помощью [Developer CLI](developer-cli-quickstart). ::: ## 1. Получите пейвол \{#1-get-the-paywall\} Ваши пейволы привязаны к плейсментам, настроенным в дашборде. Плейсменты позволяют запускать разные пейволы для разных аудиторий или проводить [A/B-тесты](ab-tests). Чтобы получить пейвол, созданный в Adapty Paywall Builder, необходимо: 1. Получить объект `paywall` по ID [плейсмента](placements) с помощью метода `GetPaywall` и проверить, является ли это пейволом, созданным в конструкторе, с помощью свойства `HasViewConfiguration`. 2. Создать представление пейвола с помощью метода `CreatePaywallView`. Представление содержит элементы UI и стили, необходимые для отображения пейвола. :::important Чтобы получить конфигурацию представления, необходимо включить переключатель **Show on device** в Paywall Builder. В противном случае вы получите пустую конфигурацию представления, и пейвол не будет отображён. ::: ```csharp showLineNumbers Adapty.GetPaywall("YOUR_PLACEMENT_ID", (paywall, error) => { if(error != null) { // handle the error return; } // Create paywall view parameters var parameters = new AdaptyUICreatePaywallViewParameters(); // Create the paywall view AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { if(error != null) { // handle the error return; } // view - the paywall view ready to be presented }); }); ``` :::info Этот quickstart содержит минимальную конфигурацию, необходимую для отображения пейвола. Подробнее о расширенных настройках смотрите в [гайде по получению пейволов](unity-get-pb-paywalls). ::: ## 2. Отобразите пейвол \{#2-display-the-paywall\} Теперь, когда у вас есть конфигурация пейвола, достаточно добавить несколько строк для его отображения. Чтобы показать пейвол, используйте метод `view.Present()` на объекте `view`, созданном методом `CreatePaywallView`. Каждый объект `view` можно использовать только один раз. Если нужно показать пейвол снова, вызовите `CreatePaywallView` ещё раз, чтобы создать новый экземпляр `view`. ```csharp showLineNumbers title="Unity" view.Present((error) => { // handle the error }); ``` :::info Подробнее об отображении пейвола смотрите в нашем [гайде](unity-present-paywalls). ::: ## 3. Обработайте действия кнопок \{#3-handle-button-actions\} Когда пользователи нажимают кнопки в пейволе, Unity SDK автоматически обрабатывает покупки и восстановление. Однако другие кнопки имеют пользовательские или предопределённые идентификаторы и требуют обработки действий в вашем коде. Например, в вашем пейволе, скорее всего, есть кнопка закрытия и ссылки для открытия (например, условия использования и политика конфиденциальности). Чтобы обработать эти действия, ваш класс должен реализовывать интерфейс `AdaptyPaywallsEventsListener` и зарегистрироваться как слушатель. :::tip Читайте наши гайды о том, как обрабатывать [действия](unity-handle-paywall-actions) кнопок и [события](unity-handling-events). ::: ```csharp showLineNumbers title="Unity" public class YourClass : MonoBehaviour, AdaptyPaywallsEventsListener { void Start() { // Register this class as the paywall events listener Adapty.SetPaywallsEventsListener(this); } // AdaptyPaywallsEventsListener method - handles button actions public void PaywallViewDidPerformAction( AdaptyUIPaywallView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Close: view.Dismiss(null); break; case AdaptyUIUserActionType.OpenUrl: Application.OpenURL(action.Value); break; default: break; } } } ``` ## Следующие шаги \{#next-steps\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Ваш пейвол готов к отображению в приложении. Протестируйте покупки в [песочнице App Store](test-purchases-in-sandbox) или в [Google Play Store](testing-on-android), чтобы убедиться, что тестовая покупка через пейвол проходит успешно. Теперь вам нужно [проверить уровень доступа пользователей](unity-check-subscription-status), чтобы убедиться, что вы показываете пейвол или открываете доступ к платным функциям нужным пользователям. ## Полный пример \{#full-example\} Вот как все эти шаги можно интегрировать в приложение вместе. ```csharp showLineNumbers using System; using UnityEngine; using AdaptySDK; public class PaywallManager : MonoBehaviour, AdaptyPaywallsEventsListener { [SerializeField] private string placementId = "YOUR_PLACEMENT_ID"; private AdaptyUIPaywallView currentPaywallView; void Start() { // Register for paywall events Adapty.SetPaywallsEventsListener(this); GetAndDisplayPaywall(); } private void GetAndDisplayPaywall() { Adapty.GetPaywall(placementId, (paywall, error) => { if (error != null) { Debug.LogError("Error getting paywall: " + error.Message); return; } if (paywall.HasViewConfiguration) { CreateAndPresentPaywallView(paywall); } else { Debug.LogWarning("Paywall was not created using the builder"); } }); } private void CreateAndPresentPaywallView(AdaptyPaywall paywall) { var parameters = new AdaptyUICreatePaywallViewParameters(); AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { if (error != null) { Debug.LogError("Error creating paywall view: " + error.Message); return; } currentPaywallView = view; view.Present((presentError) => { if (presentError != null) { Debug.LogError("Error presenting paywall: " + presentError.Message); return; } Debug.Log("Paywall presented successfully"); }); }); } // AdaptyPaywallsEventsListener implementation public void PaywallViewDidPerformAction( AdaptyUIPaywallView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Close: Debug.Log("Close button pressed"); view.Dismiss(null); break; case AdaptyUIUserActionType.OpenUrl: Application.OpenURL(action.Value); break; default: break; } } // Required interface methods (implement as needed) public void PaywallViewDidAppear(AdaptyUIPaywallView view) { } public void PaywallViewDidDisappear(AdaptyUIPaywallView view) { } public void PaywallViewDidSelectProduct(AdaptyUIPaywallView view, string productId) { } public void PaywallViewDidStartPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product) { } public void PaywallViewDidFinishPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchasedResult) { } public void PaywallViewDidFailPurchase(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) { } public void PaywallViewDidStartRestore(AdaptyUIPaywallView view) { } public void PaywallViewDidFinishRestore(AdaptyUIPaywallView view, AdaptyProfile profile) { } public void PaywallViewDidFailRestore(AdaptyUIPaywallView view, AdaptyError error) { } public void PaywallViewDidFailRendering(AdaptyUIPaywallView view, AdaptyError error) { } public void PaywallViewDidFailLoadingProducts(AdaptyUIPaywallView view, AdaptyError error) { } public void PaywallViewDidFinishWebPaymentNavigation(AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error) { } public void ShowPaywall() { GetAndDisplayPaywall(); } void OnDestroy() { if (currentPaywallView != null) { currentPaywallView.Dismiss(null); } } } ``` --- # File: unity-check-subscription-status --- --- title: "Проверка статуса подписки в Unity SDK" description: "Узнайте, как проверить статус подписки в приложении Unity с помощью Adapty." --- Чтобы решить, может ли пользователь получить доступ к платному контенту или нужно показать ему пейвол, проверьте его [уровень доступа](access-level) в профиле. В этой статье рассказывается, как обращаться к состоянию профиля, чтобы понимать, что показывать пользователю — пейвол или платный контент. ## Получение статуса подписки \{#get-subscription-status\} Когда нужно решить, показать пользователю пейвол или платный контент, проверьте его [уровень доступа](access-level) в профиле. Есть два способа: - Вызвать `GetProfile`, если нужны актуальные данные профиля прямо сейчас (например, при запуске приложения) или требуется принудительное обновление. - Настроить **автоматические обновления профиля**, чтобы хранить локальную копию, которая автоматически обновляется при изменении статуса подписки. ### Получение профиля \{#get-profile\} Самый простой способ узнать статус подписки — использовать метод `GetProfile`: ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // check the access }); ``` ### Отслеживание обновлений подписки \{#listen-to-subscription-updates\} Чтобы автоматически получать обновления профиля в приложении: 1. Унаследуйте `AdaptyEventListener` и реализуйте метод `OnLoadLatestProfile` — Adapty будет автоматически вызывать его при каждом изменении статуса подписки пользователя. 2. Сохраняйте обновлённые данные профиля при вызове этого метода, чтобы использовать их в приложении без дополнительных сетевых запросов. ```csharp public class SubscriptionManager : MonoBehaviour, AdaptyEventListener { private AdaptyProfile currentProfile; void Start() { // Register this object as an Adapty event listener Adapty.SetEventListener(this); } // Store the profile when it updates public void OnLoadLatestProfile(AdaptyProfile profile) { currentProfile = profile; // Update UI, unlock content, etc. } public void OnInstallationDetailsSuccess(AdaptyInstallationDetails details) { } public void OnInstallationDetailsFail(AdaptyError error) { } // Use stored profile instead of calling getProfile() public bool HasAccess() { if (currentProfile?.AccessLevels != null && currentProfile.AccessLevels.ContainsKey("premium")) { return currentProfile.AccessLevels["premium"].IsActive; } return false; } } ``` :::note Adapty автоматически вызывает `OnLoadLatestProfile` при запуске приложения, предоставляя кешированные данные о подписке даже при отсутствии интернета. ::: ## Связь профиля с логикой пейвола \{#connect-profile-with-paywall-logic\} Когда нужно мгновенно решить, показывать пейвол или открывать доступ к платным функциям, можно проверить профиль пользователя напрямую. Это удобно при запуске приложения, входе в премиальные разделы или перед показом определённого контента. ```csharp private void CheckAccessLevel() { Adapty.GetProfile((profile, error) => { if (error != null) { Debug.LogError("Error checking access level: " + error.Message); // Show paywall if access check fails return; } var accessLevel = profile.AccessLevels["YOUR_ACCESS_LEVEL"]; if (accessLevel == null || !accessLevel.IsActive) { // Show paywall if no access } }); } private void InitializePaywall() { LoadPaywall(); CheckAccessLevel(); } ``` ## Дальнейшие шаги \{#next-steps\} Теперь, когда вы знаете, как отслеживать статус подписки, узнайте, как [работать с профилями пользователей](unity-quickstart-identify), чтобы они получали доступ к тому, за что заплатили. --- # File: unity-quickstart-identify --- --- title: "Идентификация пользователей в Unity SDK" description: "Быстрый старт по настройке Adapty для управления встроенными подписками в Unity." --- :::important Этот гайд для вас, если у вас есть собственная система аутентификации. Здесь вы узнаете, как работать с профилями пользователей в Adapty, чтобы они корректно интегрировались с вашей существующей системой аутентификации. ::: То, как вы управляете покупками пользователей, зависит от модели аутентификации в вашем приложении: - Если ваше приложение не использует серверную аутентификацию и не хранит данные пользователей, см. [раздел об анонимных пользователях](#anonymous-users). - Если в вашем приложении есть (или будет) серверная аутентификация, см. [раздел об идентифицированных пользователях](#identified-users). **Ключевые понятия**: - **Профили** — это сущности, необходимые для работы SDK. Adapty создаёт их автоматически. - Они могут быть анонимными **(без customer user ID)** или идентифицированными **(с customer user ID)**. - Вы передаёте **customer user ID**, чтобы связать профили в Adapty с вашей внутренней системой авторизации. Вот чем отличаются анонимные и идентифицированные пользователи: | | Анонимные пользователи | Идентифицированные пользователи | |-------------------------|---------------------------------------------------------------|-------------------------------------------------------------------------------------------------------| | **Управление покупками** | Восстановление покупок на уровне стора | История покупок сохраняется на всех устройствах через customer user ID | | **Управление профилем** | Новый профиль при каждой переустановке | Один и тот же профиль во всех сессиях и на всех устройствах | | **Сохранность данных** | Данные анонимных пользователей привязаны к установке приложения | Данные идентифицированных пользователей сохраняются между установками приложения | ## Анонимные пользователи \{#anonymous-users\} Если у вас нет серверной аутентификации, **вам не нужно реализовывать аутентификацию в коде приложения**: 1. Когда SDK активируется при первом запуске приложения, Adapty **создаёт новый профиль для пользователя**. 2. Когда пользователь совершает покупку в приложении, она **привязывается к его профилю Adapty и аккаунту в сторе**. 3. Когда пользователь **переустанавливает** приложение или устанавливает его на **новое устройство**, Adapty **создаёт новый анонимный профиль при активации**. 4. Если пользователь ранее совершал покупки в вашем приложении, по умолчанию они автоматически синхронизируются из App Store при активации SDK. Таким образом, для анонимных пользователей при каждой установке будет создаваться новый профиль — но это не проблема, так как в аналитике Adapty можно [настроить, что считать новой установкой](general#4-installs-definition-for-analytics). Для анонимных пользователей нужно считать установки по **device ID**. В этом случае каждая установка приложения на устройство считается отдельной установкой, включая переустановки. ## Идентификация пользователей \{#identified-users\} Есть два способа идентифицировать пользователей в приложении: - [**При входе/регистрации:**](#during-loginsignup) Если пользователи входят в систему после запуска приложения, вызовите `identify()` с customer user ID в момент аутентификации. - [**При активации SDK:**](#during-the-sdk-activation) Если customer user ID уже сохранён на момент запуска приложения, передайте его при вызове `activate()`. :::important По умолчанию, когда Adapty получает покупку от Customer User ID, который уже связан с другим Customer User ID, уровень доступа становится общим — оба профиля получают платный доступ. Вы можете настроить это поведение так, чтобы платный доступ переносился с одного профиля на другой, или полностью отключить общий доступ. Подробнее см. в [статье](general#6-sharing-paid-access-between-user-accounts). ::: <img src="/assets/shared/img/identify-diagram.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ### При входе или регистрации \{#during-loginsignup\} Если вы идентифицируете пользователей после запуска приложения (например, после входа или регистрации), используйте метод `identify`, чтобы задать их customer user ID. - Если вы **ещё не использовали этот customer user ID**, Adapty автоматически привяжет его к текущему профилю. - Если вы **уже использовали этот customer user ID для идентификации пользователя**, Adapty переключится на работу с профилем, связанным с этим customer user ID. :::important ID пользователя должен быть уникальным для каждого пользователя. Если вы укажете фиксированное значение параметра, все пользователи будут считаться одним. ::: Дождитесь выполнения коллбэка `Identify` перед вызовом других методов SDK. Параллельные вызовы приводят к ошибке `#3006 profileWasChanged` или попаданию в анонимный профиль. См. [Порядок вызовов в Unity SDK](unity-sdk-call-order). ```csharp showLineNumbers Adapty.Identify("YOUR_USER_ID", (error) => { // Уникальный для каждого пользователя if(error == null) { // successful identify } }); ``` ### Во время активации SDK \{#during-the-sdk-activation\} Если вы уже знаете customer user ID в момент активации SDK, можно передать его прямо в метод `activate` — вместо того чтобы вызывать `identify` отдельно. Если вы знаете customer user ID, но задаёте его только после активации, то при активации Adapty создаст новый анонимный профиль и переключится на существующий лишь после вызова `identify`. Вы можете передать как существующий customer user ID (тот, что использовали раньше), так и новый. Если передать новый — профиль, созданный при активации, будет автоматически привязан к этому customer user ID. :::note По умолчанию создание анонимных профилей не влияет на аналитические дашборды, поскольку установки считаются по идентификаторам устройств. Идентификатор устройства соответствует одной установке приложения из стора на устройстве и пересоздаётся только при переустановке приложения. Он не зависит от того, первая это установка или повторная, и от того, используется ли существующий пользовательский ID. Создание профиля (при активации SDK или выходе из аккаунта), вход в систему или обновление приложения без переустановки не генерируют дополнительных событий установки. Если вы хотите считать установки на основе уникальных пользователей, а не устройств, перейдите в **App settings** и настройте параметр [**Installs definition for analytics**](general#4-installs-definition-for-analytics). ::: ```csharp showLineNumbers using UnityEngine; using AdaptySDK; var builder = new AdaptyConfiguration.Builder("YOUR_API_KEY") .SetCustomerUserId("YOUR_USER_ID"); // Customer user IDs must be unique for each user. If you hardcode the parameter value, all users will be considered as one. Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); ``` ### Выход пользователей из системы \{#log-users-out\} Если у вас есть кнопка для выхода из аккаунта, используйте метод `logout`. :::important При выходе из аккаунта для пользователя создаётся новый анонимный профиль. ::: ```csharp showLineNumbers Adapty.Logout((error) => { if(error == null) { // successful logout } }); ``` :::info Чтобы снова войти в приложение, используйте метод `identify`. ::: ### Разрешите покупки без входа в систему \{#allow-purchases-without-login\} Если пользователи могут совершать покупки как до, так и после входа в приложение, нужно убедиться, что после авторизации они сохранят доступ к купленным возможностям: 1. Когда неавторизованный пользователь совершает покупку, Adapty привязывает её к анонимному идентификатору профиля. 2. Когда пользователь входит в аккаунт, Adapty переключается на работу с идентифицированным профилем. - Если это новый customer user ID (например, покупка была совершена до регистрации), Adapty присваивает customer user ID текущему профилю, сохраняя всю историю покупок. - Если это существующий customer user ID (customer user ID уже привязан к профилю), нужно получить актуальный уровень доступа после переключения профиля. Можно вызвать [`getProfile`](unity-check-subscription-status) сразу после идентификации или [подписаться на обновления профиля](unity-check-subscription-status), чтобы данные синхронизировались автоматически. ## Следующие шаги \{#next-steps\} Поздравляем! Вы реализовали логику встроенных покупок в своём приложении! Желаем вам успехов в монетизации! Чтобы получить от Adapty ещё больше, изучите эти темы: - [**Тестирование**](troubleshooting-test-purchases): Убедитесь, что всё работает как ожидается - [**Онбординги**](onboardings): Вовлекайте пользователей с помощью онбордингов и повышайте удержание - [**Интеграции**](configuration): Интегрируйтесь с сервисами маркетинговой атрибуции и аналитики буквально в одну строку кода - [**Установка пользовательских атрибутов профиля**](unity-setting-user-attributes): Добавляйте пользовательские атрибуты к профилям и создавайте сегменты, чтобы запускать A/B-тесты или показывать разные пейволы разным пользователям --- # File: adapty-sdk-integration-skill-unity --- --- title: "Интеграция Adapty в приложение Unity с помощью навыка SDK integration" description: "Используйте навык adapty-sdk-integration для сквозной интеграции Adapty SDK в приложение Unity с помощью AI-инструмента для написания кода." --- <AdaptySdkIntegrationSkill platform="Unity" /> :::important Навык находится в бета-версии. Если он зависнет или поведёт себя неожиданно, воспользуйтесь [пошаговым руководством по интеграции](adapty-cursor-unity) — в нём описан каждый этап с нужной документацией. ::: [Скилл adapty-sdk-integration](https://github.com/adaptyteam/adapty-sdk-integration-skill) автоматизирует интеграцию Adapty от начала до конца: настройку дашборда, установку SDK, пейвол и проверку на каждом этапе. Он автоматически определяет вашу платформу и подгружает нужную документацию Adapty на каждом шаге. **Поддерживаемые инструменты**: Claude Code, GitHub Copilot CLI, OpenAI Codex, Gemini CLI. Для установки выберите команду для своего инструмента. Полный список — в [README скилла](https://github.com/adaptyteam/adapty-sdk-integration-skill). **Claude Code** ``` claude plugin marketplace add adaptyteam/adapty-sdk-integration-skill claude plugin install adapty-sdk-integration@adapty ``` **GitHub Copilot CLI** ``` gh skill install adaptyteam/adapty-sdk-integration-skill ``` **Gemini CLI** ``` gemini skills install https://github.com/adaptyteam/adapty-sdk-integration-skill ``` **OpenAI Codex или любой другой инструмент** — используйте [skills CLI](https://skills.sh) (обратите внимание, что скиллы, установленные таким способом, не обновляются автоматически): ``` npx skills add adaptyteam/adapty-sdk-integration-skill ``` Либо клонируйте репозиторий и скопируйте `skills/adapty-sdk-integration/` в директорию скиллов вашего инструмента. После установки запустите скилл в вашем проекте: ``` /adapty-sdk-integration ``` Скилл задаст несколько вопросов по настройке, а затем проведёт через настройку дашборда, установку SDK, пейвол и проверку. --- # File: adapty-cursor-unity --- --- title: "Интеграция Adapty в приложение Unity с помощью ИИ" description: "Пошаговый гайд по интеграции Adapty в приложение Unity с использованием Cursor, Context7, ChatGPT, Claude и других ИИ-инструментов." --- Этот гайд поможет вам шаг за шагом интегрировать Adapty в ваше Unity-приложение с помощью AI-инструмента для написания кода — вы передаёте ему нужную документацию Adapty в правильном порядке. For a fully automated integration, use the [adapty-sdk-integration skill](https://github.com/adaptyteam/adapty-sdk-integration-skill): it runs the whole integration from your AI coding tool in one command. ## Перед началом работы: настройка дашборда \{#before-you-start-dashboard-setup\} Adapty требует определённой настройки дашборда до написания кода SDK. Это можно сделать с помощью интерактивного навыка LLM или вручную через дашборд. ### Подход с использованием skill (рекомендуется) \{#skill-approach-recommended\} Skill Adapty CLI позволяет вашей LLM настроить приложение, продукты, уровни доступа, пейволы и плейсменты напрямую — без необходимости открывать дашборд на каждом шаге. Вам нужно только [подключить сторы](integrate-payments) в дашборде. ``` npx skills add adaptyteam/adapty-cli --skill adapty-cli ``` После добавления skill запустите `/adapty-cli` в вашем агенте. Он проведёт вас через каждый шаг — включая момент, когда нужно открыть дашборд для подключения сторов. ### Настройка через дашборд Если вы предпочитаете настраивать всё вручную, вот что нужно сделать до написания кода. LLM не сможет найти значения из дашборда за вас — вам придётся предоставить их самостоятельно. 1. **Подключите сторы**: В дашборде Adapty перейдите в **App settings → General**. Подключите App Store и Google Play, если ваше Unity-приложение поддерживает обе платформы. Это обязательное условие для работы покупок. [Подключить сторы](integrate-payments) 2. **Скопируйте публичный SDK-ключ**: В дашборде Adapty перейдите в **App settings → General** и найдите раздел **API keys**. В коде это строка, которую вы передаёте в конфигуратор Adapty. 3. **Создайте хотя бы один продукт**: В дашборде Adapty перейдите на страницу **Products**. Ссылаться на продукты напрямую в коде не нужно — Adapty доставляет их через пейволы. [Добавить продукты](quickstart-products) 4. **Создайте пейвол и плейсмент**: В дашборде Adapty создайте пейвол на странице **Paywalls**, затем назначьте его на плейсмент на странице **Placements**. В коде идентификатор плейсмента — это строка, которую вы передаёте в `Adapty.GetPaywall("YOUR_PLACEMENT_ID")`. [Создать пейвол](quickstart-paywalls) 5. **Настройте уровни доступа**: в дашборде Adapty настройте каждый продукт на странице **Products**. В коде проверяйте строку `profile.AccessLevels["premium"]?.IsActive`. Стандартный уровень доступа `premium` подходит для большинства приложений. Если платящие пользователи получают доступ к разным функциям в зависимости от продукта (например, план `basic` и план `pro`), [создайте дополнительные уровни доступа](assigning-access-level-to-a-product) до начала разработки. :::tip Как только все пять элементов будут у вас на руках, можно приступать к написанию кода. Скажите своему LLM: «Мой публичный SDK-ключ — X, мой placement ID — Y», чтобы он сгенерировал корректный код инициализации и получения пейвола. ::: ### Настройка по мере готовности \{#set-up-when-ready\} Это не обязательно для начала разработки, но пригодится по мере развития интеграции: - **A/B-тесты**: настраиваются на странице **Placements**. Изменений в коде не требуется. [A/B-тесты](ab-tests) - **Дополнительные пейволы и плейсменты**: добавьте больше вызовов `GetPaywall` с разными идентификаторами плейсментов. - **Аналитические интеграции**: настраиваются на странице **Integrations**. Процесс настройки зависит от конкретной интеграции. См. [аналитические интеграции](analytics-integration) и [интеграции атрибуции](attribution-integration). ## Передайте документацию Adapty вашему LLM \{#feed-adapty-docs-to-your-llm\} ### Используйте Context7 (рекомендуется) \{#use-context7-recommended\} [Context7](https://context7.com) — это MCP-сервер, который даёт вашей LLM прямой доступ к актуальной документации Adapty. LLM автоматически находит нужные доки на основе вашего запроса — никакого ручного копирования URL. Context7 работает с **Cursor**, **Claude Code**, **Windsurf** и другими MCP-совместимыми инструментами. Для настройки выполните: ``` npx ctx7 setup ``` Команда определит ваш редактор и настроит сервер Context7. Для ручной настройки смотрите [репозиторий Context7 на GitHub](https://github.com/upstash/context7). После настройки ссылайтесь на библиотеку Adapty в своих запросах: ``` Use the adaptyteam/adapty-docs library to look up how to install the Unity SDK ``` :::warning Несмотря на то что Context7 избавляет от необходимости вручную вставлять ссылки на документацию, порядок реализации имеет значение. Следуйте [пошаговому руководству](#implementation-walkthrough) ниже строго по шагам, чтобы всё работало корректно. ::: ### Используйте документацию в формате обычного текста \{#use-plain-text-docs\} Любую статью Adapty можно получить в виде обычного текста Markdown. Для этого добавьте `.md` в конец её URL или нажмите **Copy for LLM** под заголовком статьи. Например: [adapty-cursor-unity.md](https://adapty.io/docs/ru/adapty-cursor-unity.md). Каждый шаг в [пошаговом руководстве по интеграции](#implementation-walkthrough) ниже содержит блок «Отправьте это вашему LLM» со ссылками `.md` для копирования. Чтобы получить сразу несколько статей, смотрите [индексные файлы и подборки по платформам](#plain-text-doc-index-files) ниже. ## Пошаговое руководство по внедрению \{#implementation-walkthrough\} В этом гайде мы разберём интеграцию Adapty в порядке реализации. Каждый этап включает документацию для передачи вашей LLM, ожидаемый результат и типичные проблемы. ### Планируйте интеграцию \{#plan-your-integration\} Прежде чем писать код, попросите LLM проанализировать ваш проект и составить план реализации. Если ваш AI-инструмент поддерживает режим планирования (как в Cursor или Claude Code), используйте его — тогда LLM сможет изучить структуру проекта и документацию Adapty до начала написания кода. Сообщите LLM, какой подход вы используете для покупок — это определяет, какие гайды ей нужно будет учитывать: - [**Adapty Paywall Builder**](adapty-paywall-builder): Вы создаёте пейволы в визуальном редакторе Adapty без кода, а SDK отображает их автоматически. - [**Паywalls, созданные вручную**](unity-making-purchases): Вы строите собственный интерфейс пейвола в коде, но используете Adapty для получения продуктов и обработки покупок. - [**Observer mode**](observer-vs-full-mode): Вы сохраняете существующую инфраструктуру покупок и используете Adapty только для аналитики и интеграций. Не знаете, что выбрать? Прочитайте [таблицу сравнения в разделе быстрого старта](unity-quickstart-paywalls). ### Установка и настройка SDK \{#install-and-configure-the-sdk\} Добавьте пакет Adapty SDK через Unity Package Manager и активируйте его с помощью вашего публичного ключа SDK. Это основа — без неё ничего не работает. **Гайд:** [Установка и настройка Adapty SDK](sdk-installation-unity) Отправьте это в ваш LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/sdk-installation-unity.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** Проект собирается и запускается. В консоли Unity отображается лог активации Adapty. - **Частая ошибка:** «Public API key is missing» → убедитесь, что вы заменили плейсхолдер на реальный ключ из **App settings**. ::: ### Показ пейволов и обработка покупок \{#show-paywalls-and-handle-purchases\} Получите пейвол по ID плейсмента, отобразите его и обработайте события покупки. Нужные вам гайды зависят от того, как вы обрабатываете покупки. Тестируйте каждую покупку в песочнице по мере работы — не откладывайте на конец. Инструкции по настройке см. в разделе [Тестирование покупок в песочнице](test-purchases-in-sandbox). <Tabs groupId="paywall-approach"> <TabItem value="builder" label="Paywall Builder" default> **Гайды:** - [Включение покупок с помощью пейволов (быстрый старт)](unity-quickstart-paywalls) - [Получение пейволов Paywall Builder и их конфигурации](unity-get-pb-paywalls) - [Отображение пейволов](unity-present-paywalls) - [Обработка событий пейвола](unity-handling-events) - [Реакция на действия кнопок](unity-handle-paywall-actions) Отправьте это своему LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/unity-quickstart-paywalls.md - https://adapty.io/docs/ru/unity-get-pb-paywalls.md - https://adapty.io/docs/ru/unity-present-paywalls.md - https://adapty.io/docs/ru/unity-handling-events.md - https://adapty.io/docs/ru/unity-handle-paywall-actions.md ``` :::tip[Checkpoint] - **Ожидается:** Пейвол отображается с вашими настроенными продуктами. Нажатие на продукт запускает диалог покупки в песочнице. - **Проблема:** Пустой пейвол или ошибка `GetPaywall` → проверьте, что ID плейсмента точно совпадает с указанным в дашборде и что плейсменту назначена аудитория. ::: </TabItem> <TabItem value="manual" label="Ручные пейволы"> **Гайды:** - [Включите покупки в своём кастомном пейволе (быстрый старт)](unity-quickstart-manual) - [Загрузите пейволы и продукты](fetch-paywalls-and-products-unity) - [Отобразите пейвол, созданный через Remote Config](present-remote-config-paywalls-unity) - [Совершайте покупки](unity-making-purchases) - [Восстановите покупки](unity-restore-purchase) Read these Adapty docs before writing code: - https://adapty.io/docs/ru/unity-quickstart-manual.md - https://adapty.io/docs/ru/fetch-paywalls-and-products-unity.md - https://adapty.io/docs/ru/present-remote-config-paywalls-unity.md - https://adapty.io/docs/ru/unity-making-purchases.md - https://adapty.io/docs/ru/unity-restore-purchase.md :::tip[Checkpoint] - **Ожидаемый результат:** Ваш кастомный пейвол отображает продукты, полученные из Adapty. Нажатие на продукт открывает диалог покупки в песочнице. - **Возможная проблема:** Пустой массив продуктов → убедитесь, что в дашборде пейволу назначены продукты и у плейсмента есть аудитория. ::: </TabItem> <TabItem value="observer" label="Observer mode"> **Гайды:** - [Обзор Observer mode](observer-vs-full-mode) - [Реализация Observer mode](implement-observer-mode-unity) - [Отправка транзакций в Observer mode](report-transactions-observer-mode-unity) Отправьте это своей LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/observer-vs-full-mode.md - https://adapty.io/docs/ru/implement-observer-mode-unity.md - https://adapty.io/docs/ru/report-transactions-observer-mode-unity.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После тестовой покупки в песочнице через ваш существующий флоу покупки транзакция появляется в **Event Feed** дашборда Adapty. - **Частая ошибка:** Нет событий → убедитесь, что вы передаёте транзакции в Adapty и серверные уведомления настроены для обоих сторов. ::: </TabItem> </Tabs> ### Проверка статуса подписки \{#check-subscription-status\} После покупки проверьте профиль пользователя на наличие активного уровня доступа, чтобы открыть доступ к премиум-контенту. **Гайд:** [Проверка статуса подписки](unity-check-subscription-status) Отправьте это в свой LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/unity-check-subscription-status.md ``` :::tip[Контрольная точка] - **Ожидаемый результат:** После покупки в песочнице `profile.AccessLevels["premium"]?.IsActive` возвращает `true`. - **Частая ошибка:** Пустой `AccessLevels` после покупки → проверьте, что продукту назначен уровень доступа в дашборде. ::: ### Идентификация пользователей \{#identify-users\} Привяжите аккаунты пользователей вашего приложения к профилям Adapty, чтобы покупки сохранялись на всех устройствах. :::important Пропустите этот шаг, если в вашем приложении нет аутентификации. ::: **Гайд:** [Идентификация пользователей](unity-quickstart-identify) Отправьте это в свой LLM: ``` Read these Adapty docs before writing code: - https://adapty.io/docs/ru/unity-quickstart-identify.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** После вызова `Adapty.Identify("your-user-id")` в разделе **Profiles** дашборда появится ваш пользовательский ID. - **Важно:** Вызывайте `Identify` после активации, но до загрузки пейволов, чтобы избежать анонимной атрибуции профиля. ::: ### Подготовка к релизу \{#prepare-for-release\} Когда интеграция заработает в песочнице, пройдитесь по чеклисту релиза и убедитесь, что всё готово к продакшену. **Гайд:** [Чеклист релиза](release-checklist) Отправьте это своему LLM: ``` Read these Adapty docs before releasing: - https://adapty.io/docs/ru/release-checklist.md ``` :::tip[Checkpoint] - **Ожидаемый результат:** Все пункты чеклиста подтверждены: подключения к сторам, серверные уведомления, флоу покупки, проверки уровней доступа и требования к конфиденциальности. - **Частая ошибка:** Отсутствие серверных уведомлений → настройте App Store Server Notifications в **App settings → iOS SDK** и Google Play Real-Time Developer Notifications в **App settings → Android SDK**. ::: ## Индексные файлы в виде обычного текста \{#plain-text-doc-index-files\} Если вы хотите дать вашему LLM более широкий контекст, выходящий за рамки отдельных страниц, мы предоставляем индексные файлы, которые перечисляют или объединяют всю документацию Adapty: - [`llms.txt`](https://adapty.io/docs/ru/llms.txt): Список всех страниц со ссылками в формате `.md`. [Формирующийся стандарт](https://llmstxt.org/) для обеспечения доступности сайтов языковым моделям. Обратите внимание: для некоторых AI-агентов (например, ChatGPT) потребуется скачать `llms.txt` и загрузить его в чат как файл. - [`llms-full.txt`](https://adapty.io/docs/ru/llms-full.txt): Вся документация Adapty, объединённая в один файл. Очень большой объём — используйте только тогда, когда нужна полная картина. - Специфичные для Unity файлы [`unity-llms.txt`](https://adapty.io/docs/ru/unity-llms.txt) и [`unity-llms-full.txt`](https://adapty.io/docs/ru/unity-llms-full.txt): Подмножества документации для конкретной платформы, позволяющие сэкономить токены по сравнению с полным сайтом. --- # File: unity-get-pb-paywalls --- --- title: "Получение пейволов Paywall Builder и их конфигурации в Unity SDK" description: "Узнайте, как получать пейволы PB в Adapty для более гибкого управления подписками в вашем приложении на Unity." --- После того как вы [разработали визуальную часть пейвола](adapty-paywall-builder) в новом Paywall Builder на дашборде Adapty, его можно отобразить в мобильном приложении. Первый шаг — получить пейвол, связанный с плейсментом, и его конфигурацию отображения, как описано ниже. :::warning Новый Paywall Builder работает с Unity SDK версии 3.3.0 и выше. ::: Пожалуйста, обратите внимание, что этот раздел относится к пейволам, настроенным через Paywall Builder. Если вы реализуете пейволы вручную, обратитесь к разделу [Получение пейволов и продуктов для пейволов с Remote Config в мобильном приложении](fetch-paywalls-and-products-unity). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Прежде чем начать отображать пейволы в мобильном приложении (нажмите, чтобы развернуть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте пейвол и добавьте в него продукты](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте в них пейвол](create-placement) в дашборде Adapty. 4. Установите [Adapty SDK](sdk-installation-unity) в своём мобильном приложении. </details> ## Получение пейвола, созданного в Paywall Builder \{#fetch-paywall-designed-with-paywall-builder\} Если вы [создали пейвол в Paywall Builder](adapty-paywall-builder), вам не нужно беспокоиться о его отображении в коде мобильного приложения — пейвол уже содержит всё необходимое: что и как должно быть показано. Тем не менее, вам нужно получить его ID через плейсмент, конфигурацию отображения, а затем показать пейвол в приложении. Для оптимальной производительности важно получать пейвол и его [конфигурацию отображения](unity-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder) как можно раньше, чтобы изображения успели загрузиться до того, как пользователь увидит пейвол. Чтобы получить пейвол, используйте метод `GetPaywall`: ```csharp showLineNumbers Adapty.GetPaywall("YOUR_PLACEMENT_ID", "en", (paywall, error) => { if(error != null) { // handle the error return; } // paywall - the resulting object }); ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-paywall-locale-in-adapty-paywall-builder). Ожидается языковой код, состоящий из одного или двух подтегов, разделённых символом минус (**-**). Первый подтег — язык, второй — регион.</p><p></p><p>Пример: `en` означает английский, `pt-br` — португальский (Бразилия).</p><p>Подробнее о кодах локалей и рекомендациях по их использованию — в разделе [Локализации и коды локалей](localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант, так как он гарантирует, что пользователи всегда получают самые актуальные данные.</p><p></p><p>Однако если ваши пользователи часто сталкиваются с нестабильным интернетом, рассмотрите вариант `.returnCacheDataElseLoad` — он возвращает кешированные данные, если они есть. В этом случае пользователи могут получить не самые свежие данные, зато загрузка будет быстрее вне зависимости от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или ручной очистке.</p><p></p><p>Adapty SDK хранит пейволы локально в двух слоях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](fallback-paywalls). Для ускорения загрузки пейволов мы также используем CDN и отдельный резервный сервер на случай недоступности CDN. Эта система гарантирует, что вы всегда получаете актуальную версию пейволов, сохраняя надёжность даже при слабом интернет-соединении.</p> | | **loadTimeout** | по умолчанию: 5 сек | <p>Ограничивает таймаут этого метода. При достижении таймаута будут возвращены кешированные данные или локальный резервный вариант.</p><p>Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой относительно значения, указанного в `loadTimeout`, так как операция может включать несколько запросов под капотом.</p> | Параметры ответа: | Параметр | Описание | | :-------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Объект [`AdaptyPaywall`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html) со списком ID продуктов, идентификатором пейвола, Remote Config и рядом других свойств. | ## Получение конфигурации отображения пейвола, созданного в Paywall Builder \{#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder\} :::important Убедитесь, что в Paywall Builder включён переключатель **Show on device**. Если этот параметр не активирован, конфигурация отображения не будет доступна для получения. ::: После загрузки пейвола проверьте, содержит ли он `ViewConfiguration` — это признак того, что пейвол создан в Paywall Builder. Это поможет вам понять, как отображать пейвол. Если `ViewConfiguration` присутствует, обрабатывайте его как пейвол Paywall Builder; если нет — [обработайте его как пейвол с Remote Config](present-remote-config-paywalls-unity). В Unity SDK напрямую вызывайте метод `CreatePaywallView`, не запрашивая конфигурацию вью вручную. :::warning Результат метода `CreatePaywallView` можно использовать только один раз. Если нужно использовать его повторно, вызовите `CreatePaywallView` заново. Повторный вызов без пересоздания может привести к ошибке `AdaptyUIError.viewAlreadyPresented`. ::: ```csharp showLineNumbers var parameters = new AdaptyUICreatePaywallViewParameters() .SetPreloadProducts(preloadProducts) .SetLoadTimeout(new TimeSpan(0, 0, 3)); AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { // handle the result }); ``` Параметры: | Параметр | Наличие | Описание | | :------------------ | :------------------ | :----------------------------------------------------------- | | **paywall** | обязательный | Объект `AdaptyPaywall` для получения контроллера нужного пейвола. | | **loadTimeout** | по умолчанию: 5 сек | Ограничивает таймаут для этого метода. Если таймаут истёк, будут возвращены кешированные данные или локальный резервный пейвол. Обратите внимание, что в редких случаях метод может завершиться с небольшой задержкой сверх значения `loadTimeout`, поскольку операция может включать несколько запросов под капотом. | | **PreloadProducts** | опциональный | Передайте массив `AdaptyPaywallProducts` для оптимизации времени отображения продуктов на экране. Если передать `nil`, AdaptyUI автоматически загрузит необходимые продукты. | | **CustomTags** | опциональный | Задайте словарь пользовательских тегов и их значений. Пользовательские теги служат плейсхолдерами в контенте пейвола и динамически заменяются конкретными строками для персонализации. Подробнее см. в разделе о пользовательских тегах в Paywall Builder. | | **CustomTimers** | опциональный | Задайте словарь пользовательских таймеров и дат их окончания. Пользовательские таймеры позволяют отображать обратный отсчёт на пейволе. | :::note Если вы используете несколько языков, узнайте, как добавить [локализацию Paywall Builder](add-paywall-locale-in-adapty-paywall-builder) и как правильно использовать коды локалей [здесь](localizations-and-locale-codes). ::: Как только у вас есть представление, [покажите пейвол](unity-present-paywalls). ## Настройка ассетов \{#customize-assets\} Чтобы настроить изображения и видео на пейволе, используйте кастомные ассеты. Hero-изображения и видео имеют предопределённые идентификаторы: `hero_image` и `hero_video`. В бандле кастомных ассетов вы обращаетесь к этим элементам по их идентификаторам и настраиваете их поведение. Для остальных изображений и видео нужно [задать кастомный идентификатор](custom-media) в дашборде Adapty. Например, вы можете: - Показывать разные изображения или видео разным пользователям. - Показывать локальное превью-изображение, пока загружается основное удалённое. - Показывать превью-изображение перед запуском видео. :::important Чтобы использовать эту функцию, обновите Adapty Unity SDK до версии 3.8.0 или выше. ::: Вот пример того, как можно передавать пользовательские ресурсы через простой словарь: ```csharp showLineNumbers var customAssets = new Dictionary<string, AdaptyCustomAsset> { { "custom_image", AdaptyCustomAsset.LocalImageFile("custom_assets/images/custom_image.png") }, { "hero_video", AdaptyCustomAsset.LocalVideoFile("custom_assets/videos/custom_video.mp4") } }; var parameters = new AdaptyUICreatePaywallViewParameters() .SetCustomAssets(customAssets) .SetLoadTimeout(new TimeSpan(0, 0, 3)); AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { // handle the result }); ``` :::note Если ресурс не найден, пейвол вернётся к внешнему виду по умолчанию. ::: ## Настройка таймеров, заданных разработчиком \{#set-up-developer-defined-timers\} Чтобы использовать кастомные таймеры в Unity-приложении, передайте словарь с ID таймеров и датами их окончания напрямую в метод `SetCustomTimers`. Пример: ```csharp showLineNumbers var customTimers = new Dictionary<string, DateTime> { { "CUSTOM_TIMER_6H", DateTime.Now.AddHours(6) }, { "CUSTOM_TIMER_NY", new DateTime(2025, 1, 1) } }; var parameters = new AdaptyUICreatePaywallViewParameters() .SetCustomTimers(customTimers) .SetLoadTimeout(new TimeSpan(0, 0, 3)); AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { // handle the result }); ``` В этом примере `CUSTOM_TIMER_NY` и `CUSTOM_TIMER_6H` — это **Timer ID** таймеров, заданных разработчиком в дашборде Adapty. Резолвер таймеров обеспечивает динамическое обновление каждого таймера с правильным значением. Например: - `CUSTOM_TIMER_NY`: время, оставшееся до окончания таймера, например до Нового года. - `CUSTOM_TIMER_6H`: время, оставшееся в 6-часовом периоде, который начался, когда пользователь открыл пейвол. ## Ускорьте загрузку пейвола с помощью пейвола аудитории по умолчанию \{#speed-up-paywall-fetching-with-default-audience-paywall\} Как правило, пейволы загружаются почти мгновенно, и беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и пейволов, а пользователи работают в условиях слабого интернет-соединения, загрузка пейвола может занять больше времени, чем хотелось бы. В таких ситуациях стоит показывать пейвол по умолчанию — это обеспечит плавный пользовательский опыт вместо полного отсутствия пейвола. Чтобы решить эту задачу, вы можете использовать метод `GetPaywallForDefaultAudience`, который получает пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать пейвол с помощью метода `getPaywall`, как описано в разделе [Получение пейвола](#fetch-paywall) выше. :::warning Рекомендуем использовать `GetPaywall` вместо `GetPaywallForDefaultAudience`, так как последний имеет важные ограничения: - **Проблемы совместимости**: Могут возникнуть трудности при поддержке нескольких версий приложения — придётся либо делать обратно совместимые дизайны, либо мириться с тем, что старые версии будут отображать пейвол некорректно. - **Без персонализации**: Показывает контент только для аудитории «Все пользователи», исключая таргетинг по стране, атрибуции или кастомным атрибутам. Если более быстрая загрузка перевешивает эти недостатки для вашего случая, используйте `GetPaywallForDefaultAudience`, как показано ниже. В противном случае используйте `GetPaywall`, как описано [выше](#fetch-paywall). ::: ```csharp showLineNumbers Adapty.GetPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", (paywall, error) => { if(error != null) { // handle the error return; } // paywall - the resulting object }); ``` Параметры: | Параметр | Наличие | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | <p>Идентификатор локализации пейвола. Ожидается языковой код, состоящий из одного или двух подтегов, разделённых символом минуса (**-**). Первый подтег — язык, второй — регион.</p><p></p><p>Пример: `en` означает английский, `pt-br` — бразильский португальский.</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае ошибки. Мы рекомендуем этот вариант: он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `.returnCacheDataElseLoad` — этот режим возвращает кешированные данные, если они есть. В таком случае данные могут быть не самыми свежими, зато загрузка будет быстрее независимо от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы сократить количество сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или принудительной очистке.</p><p></p><p>Adapty SDK хранит пейволы локально в двух слоях: регулярно обновляемый кеш, описанный выше, и резервные пейволы. Для более быстрой загрузки пейволов также используется CDN, а на случай его недоступности — отдельный резервный сервер. Такая система гарантирует, что вы всегда получаете актуальную версию пейволов, обеспечивая надёжность даже при плохом интернет-соединении.</p> | --- # File: unity-present-paywalls --- --- title: "Отображение пейволов" description: "Узнайте, как отображать пейволы в приложении Unity с помощью Adapty SDK." --- Если вы настроили пейвол с помощью Paywall Builder, вам не нужно беспокоиться о его рендеринге в коде мобильного приложения для отображения пользователю. Такой пейвол содержит как то, что должно быть показано, так и то, как именно это должно быть показано. :::warning Этот гайд охватывает **новый Paywall Builder**, который требует Adapty SDK версии 3.3.0 или выше. Чтобы отображать пейволы на Remote Config, см. [Рендеринг пейволов, созданных с помощью Remote Config](present-remote-config-paywalls). ::: Чтобы отобразить пейвол, используйте метод `view.Present()` для объекта `view`, созданного методом [`CreatePaywallView`](unity-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). Каждый объект `view` можно использовать только один раз. Если нужно показать пейвол снова, вызовите `CreatePaywallView` ещё раз, чтобы создать новый экземпляр `view`. :::warning Повторное использование одного и того же `view` без его пересоздания может привести к ошибке `AdaptyUIError.viewAlreadyPresented`. ::: ```csharp showLineNumbers title="Unity" view.Present((error) => { // handle the error }); ``` :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ## Показ диалога \{#show-dialog\} Используйте этот метод вместо стандартных диалоговых окон, когда на Android отображается пейвол. На Android обычные алерты появляются позади пейвола и становятся невидимы для пользователей. Этот метод гарантирует корректное отображение диалога поверх пейвола на всех платформах. ```csharp showLineNumbers title="Unity" var dialog = new AdaptyUIDialogConfiguration() .SetTitle("Close paywall?") .SetContent("You will lose access to exclusive offers.") .SetDefaultActionTitle("Stay") .SetSecondaryActionTitle("Close"); AdaptyUI.ShowDialog(view, dialog, (action, error) => { if (error == null) { if (action == AdaptyUIDialogActionType.Secondary) { // User confirmed - close the paywall view.Dismiss(); } // If primary - do nothing, user stays } }); ``` ## Настройка стиля презентации на iOS \{#configure-ios-presentation-style\} Настройте способ отображения пейвола на iOS, передав параметр `iosPresentationStyle` в метод `Present()`. Параметр принимает значения `AdaptyUIIOSPresentationStyle.FullScreen` (по умолчанию) или `AdaptyUIIOSPresentationStyle.PageSheet`. ```csharp showLineNumbers title="Unity" view.Present(AdaptyUIIOSPresentationStyle.PageSheet, (error) => { // handle the error }); ``` --- # File: unity-handle-paywall-actions --- --- title: "Реагирование на действия кнопок в Unity SDK" description: "Обрабатывайте действия кнопок пейвола в Unity с помощью Adapty для лучшей монетизации приложения." --- Если вы создаёте пейволы с помощью Paywall Builder, важно правильно настроить кнопки: 1. Добавьте [кнопку в Paywall Builder](paywall-buttons) и назначьте ей готовое действие или создайте собственный ID действия. 2. Напишите код в приложении для обработки каждого назначенного действия. В этом гайде показано, как обрабатывать пользовательские и стандартные действия в коде. :::warning **Только покупки и восстановления обрабатываются автоматически.** Все остальные действия кнопок — закрытие пейволов, открытие ссылок и т. д. — требуют реализации обработчиков в коде приложения. ::: ## Закрытие пейволов \{#close-paywalls\} Чтобы добавить кнопку, закрывающую пейвол: 1. В Paywall Builder добавьте кнопку и назначьте ей действие **Close**. 2. В коде приложения реализуйте обработчик действия `close`, который скрывает пейвол. ```csharp showLineNumbers title="Unity" public void PaywallViewDidPerformAction( AdaptyUIPaywallView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Close: view.Dismiss(null); break; default: // handle other events break; } } ``` ## Открытие URL из пейволов \{#open-urls-from-paywalls\} :::tip Если вы хотите добавить группу ссылок (например, пользовательское соглашение и восстановление покупок), добавьте элемент **Link** в Paywall Builder и обработайте его так же, как кнопки с действием **Open URL**. ::: Чтобы добавить кнопку, открывающую ссылку из пейвола (например, **Terms of use** или **Privacy policy**): 1. В Paywall Builder добавьте кнопку, назначьте ей действие **Open URL** и введите нужный URL. 2. В коде приложения реализуйте обработчик действия `openUrl`, который открывает полученный URL в браузере. ```csharp showLineNumbers title="Unity" public void PaywallViewDidPerformAction( AdaptyUIPaywallView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.OpenUrl: var urlString = action.Value; if(!string.IsNullOrWhiteSpace(urlString)) { Application.OpenURL(urlString); } break; default: // handle other events break; } } ``` ## Вход в приложение \{#log-into-the-app\} Чтобы добавить кнопку, которая выполняет вход пользователя в приложение: 1. В Paywall Builder добавьте кнопку и назначьте ей действие **Custom** с ID `login`. 2. В коде приложения реализуйте обработчик пользовательского действия `login`, который идентифицирует пользователя. ```csharp showLineNumbers title="Unity" public void PaywallViewDidPerformAction( AdaptyUIPaywallView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Custom: if (action.Value == "login") { // Navigate to login scene SceneManager.LoadScene("LoginScene"); } break; default: // handle other events break; } } ``` ## Обработка пользовательских действий \{#handle-custom-actions\} Чтобы добавить кнопку для обработки любых других действий: 1. В Paywall Builder добавьте кнопку, назначьте ей действие **Custom** и задайте ID. 2. В коде приложения реализуйте обработчик созданного ID действия. Например, если у вас есть другой набор предложений по подпискам или разовых покупок, можно добавить кнопку, которая будет отображать другой пейвол: ```csharp showLineNumbers title="Unity" public void PaywallViewDidPerformAction( AdaptyUIPaywallView view, AdaptyUIUserAction action ) { switch (action.Type) { case AdaptyUIUserActionType.Custom: if (action.Value == "openNewPaywall") { // Display another paywall ShowAlternativePaywall(); } break; default: // handle other events break; } } private void ShowAlternativePaywall() { // Implement your logic to show alternative paywall } ``` --- # File: unity-handling-events --- --- title: "Обработка событий пейвола" description: "Узнайте, как обрабатывать события пейвола в Unity-приложении с помощью Adapty SDK." --- :::important Этот гайд охватывает обработку событий для покупок, восстановлений, выбора продуктов и отрисовки пейвола. Также необходимо реализовать обработку кнопок (закрытие пейвола, открытие ссылок и т. д.). Подробнее см. в нашем [гайде по обработке действий с кнопками](unity-handle-paywall-actions). ::: Пейволы, настроенные с помощью [Paywall Builder](adapty-paywall-builder), не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют ряд событий, на которые ваше приложение может реагировать. Это нажатия кнопок (кнопки закрытия, URL-адреса, выбор продуктов и т. д.), а также уведомления о действиях, связанных с покупками на пейволе. Узнайте, как реагировать на эти события, ниже. :::warning Этот гайд предназначен **только для пейволов нового Paywall Builder**, которые требуют Adapty SDK версии 3.3.0 или выше. ::: :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ## Обработка событий \{#handling-events\} Чтобы контролировать или отслеживать процессы, происходящие на экране пейвола в вашем мобильном приложении, реализуйте интерфейс `AdaptyPaywallsEventsListener`: ```csharp showLineNumbers title="Unity" using UnityEngine; using AdaptySDK; public class PaywallEventsHandler : MonoBehaviour, AdaptyPaywallsEventsListener { void Start() { Adapty.SetPaywallsEventsListener(this); } // Implement all required interface methods below } ``` ### События, инициируемые пользователем \{#user-generated-events\} #### Пейвол появился \{#paywall-appeared\} Вызывается, когда экран пейвола отображается на экране. :::note На iOS также вызывается, когда пользователь нажимает [кнопку веб-пейвола](web-paywall#step-2a-add-a-web-purchase-button) внутри пейвола и веб-пейвол открывается во встроенном браузере. ::: ```csharp showLineNumbers title="Unity" public void PaywallViewDidAppear(AdaptyUIPaywallView view) { } ``` #### Пейвол исчез \{#paywall-disappeared\} Вызывается, когда экран пейвола закрывается. :::note На iOS также вызывается, когда [веб-пейвол](web-paywall#step-2a-add-a-web-purchase-button), открытый из пейвола во встроенном браузере, исчезает с экрана. ::: ```csharp showLineNumbers title="Unity" public void PaywallViewDidDisappear(AdaptyUIPaywallView view) { } ``` #### Выбор продукта \{#product-selection\} Вызывается, когда продукт выбран для покупки (пользователем или системой). ```csharp showLineNumbers title="Unity" public void PaywallViewDidSelectProduct( AdaptyUIPaywallView view, string productId ) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "productId": "premium_monthly" } ``` </Details> #### Покупка начата \{#started-purchase\} Вызывается, когда пользователь инициирует процесс покупки. ```csharp showLineNumbers title="Unity" public void PaywallViewDidStartPurchase( AdaptyUIPaywallView view, AdaptyPaywallProduct product ) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" } } ``` </Details> #### Успешная, отменённая или ожидающая покупка \{#successful-canceled-or-pending-purchase\} Этот метод вызывается, если покупка прошла успешно, пользователь отменил покупку или покупка находится в состоянии ожидания. Отмены пользователем и ожидающие платежи (например, требующие родительского одобрения) вызывают этот метод, а не `PaywallViewDidFailPurchase`. ```csharp showLineNumbers title="Unity" public void PaywallViewDidFinishPurchase( AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyPurchaseResult purchasedResult ) { } ``` <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```javascript // Successful purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "Success", "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } } } } } // Cancelled purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "UserCancelled" } } // Pending purchase { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "purchaseResult": { "type": "Pending" } } ``` </Details> В таких случаях рекомендуем закрывать экран. #### Покупка завершилась с ошибкой \{#failed-purchase\} Если покупка завершается с ошибкой, вызывается этот метод. Сюда входят ошибки StoreKit/Google Play Billing (ограничения платежей, недействительные продукты, сбои сети), ошибки проверки транзакций и системные ошибки. Обратите внимание: отмены пользователем вызывают `PaywallViewDidFinishPurchase` с результатом отмены, а ожидающие платежи этот метод не вызывают. ```csharp showLineNumbers title="Unity" public void PaywallViewDidFailPurchase( AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error ) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "purchase_failed", "message": "Purchase failed due to insufficient funds", "details": { "underlyingError": "Insufficient funds in account" } } } ``` </Details> #### Восстановление начато \{#started-restore\} Вызывается, когда пользователь инициирует процесс восстановления покупок: ```csharp showLineNumbers title="Unity" public void PaywallViewDidStartRestore(AdaptyUIPaywallView view) { } ``` #### Восстановление выполнено успешно \{#successful-restore\} Вызывается при успешном восстановлении покупок: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFinishRestore( AdaptyUIPaywallView view, AdaptyProfile profile ) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "profile": { "accessLevels": { "premium": { "id": "premium", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } }, "subscriptions": [ { "vendorProductId": "premium_monthly", "isActive": true, "expiresAt": "2024-02-15T10:30:00Z" } ] } } ``` </Details> Рекомендуем закрывать экран, если у пользователя есть требуемый `accessLevel`. Подробнее о том, как это проверить, см. в разделе [Статус подписки](unity-listen-subscription-changes). #### Восстановление завершилось с ошибкой \{#failed-restore\} Вызывается при ошибке восстановления покупок: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFailRestore( AdaptyUIPaywallView view, AdaptyError error ) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "error": { "code": "restore_failed", "message": "Purchase restoration failed", "details": { "underlyingError": "No previous purchases found" } } } ``` </Details> #### Завершена навигация к веб-оплате \{#finished-web-payment-navigation\} После попытки открыть [веб-пейвол](web-paywall) для покупки (успешной или неудачной) будет вызван этот метод: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFinishWebPaymentNavigation( AdaptyUIPaywallView view, AdaptyPaywallProduct product, AdaptyError error ) { } ``` **Параметры:** - `product`: продукт, для которого был открыт (или предпринята попытка открыть) веб-пейвол - `error`: `null`, если веб-пейвол успешно открылся, или `AdaptyError` при ошибке <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```javascript // Successful navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": null } // Failed navigation { "product": { "vendorProductId": "premium_monthly", "localizedTitle": "Premium Monthly", "localizedDescription": "Premium subscription for 1 month", "localizedPrice": "$9.99", "price": 9.99, "currencyCode": "USD" }, "error": { "code": "wrong_param", "message": "Current method is not available for this product", "details": { "underlyingError": "Product not configured for web purchases" } } } ``` </Details> ### Загрузка данных и отрисовка \{#data-fetching-and-rendering\} #### Ошибки загрузки продуктов \{#product-loading-errors\} Вызывается при ошибке загрузки продуктов и предоставляет `AdaptyError`. Если при инициализации массив продуктов не был передан, AdaptyUI самостоятельно получит необходимые объекты с сервера. Эта операция может завершиться с ошибкой, о которой AdaptyUI сообщит, вызвав этот метод: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFailLoadingProducts( AdaptyUIPaywallView view, AdaptyError error ) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "error": { "code": "products_loading_failed", "message": "Failed to load products from the server", "details": { "underlyingError": "Network timeout" } } } ``` </Details> #### Ошибки отрисовки \{#rendering-errors\} Вызывается при возникновении ошибки в процессе отрисовки интерфейса и предоставляет `AdaptyError`: ```csharp showLineNumbers title="Unity" public void PaywallViewDidFailRendering( AdaptyUIPaywallView view, AdaptyError error ) { } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```javascript { "error": { "code": "rendering_failed", "message": "Failed to render paywall interface", "details": { "underlyingError": "Invalid paywall configuration" } } } ``` </Details> В нормальных условиях такие ошибки не должны возникать, поэтому если вы с ними столкнётесь — пожалуйста, сообщите нам. --- # File: unity-web-paywalls --- --- title: "Реализация веб-пейволов в Unity SDK" description: "Настройте веб-пейвол, чтобы принимать платежи без комиссий и проверок App Store." --- :::important Прежде чем начать, убедитесь, что вы [настроили веб-пейвол в дашборде](web-paywall) и установили Adapty SDK версии 3.14 или выше. ::: ## Открытие веб-пейволов \{#open-web-paywalls\} Если вы работаете с пейволом, разработанным самостоятельно, для работы с веб-пейволами необходимо использовать метод SDK. Метод `Adapty.OpenWebPaywall`: 1. Генерирует уникальный URL, позволяющий Adapty связать конкретный показанный пейвол с конкретным пользователем и веб-страницей, на которую он перенаправляется. 2. Отслеживает возвращение пользователя в приложение, а затем с короткими интервалами вызывает `Adapty.GetProfile`, чтобы определить, обновились ли права доступа профиля. Таким образом, если платёж прошёл успешно и права доступа обновились, подписка активируется в приложении почти мгновенно. ```csharp showLineNumbers title="Unity" Adapty.OpenWebPaywall( product, (error) => { if (error != null) { Debug.LogError($"Failed to open web paywall: {error.Message}"); } else { Debug.Log("Web paywall opened successfully"); } } ); ``` :::note Существует две версии метода `OpenWebPaywall`: 1. `OpenWebPaywall(product)` — генерирует URL по пейволу и добавляет данные о продукте к URL. 2. `OpenWebPaywall(paywall)` — генерирует URL по пейволу без добавления данных о продукте к URL. Используйте её, когда продукты в пейволе Adapty отличаются от продуктов в веб-пейволе. ::: #### Обработка ошибок \{#handle-errors\} | Код ошибки | Описание | Рекомендуемые действия | |-----------|--------------------------------------------------------|---------------------------------------------------------------------------| | `AdaptyErrorCode.WrongParam` | У пейвола или продукта не настроен URL для веб-покупки, либо не удалось открыть URL в браузере | Проверьте сообщение об ошибке для получения подробностей. Убедитесь в правильности настройки пейвола/продукта в дашборде Adapty или проверьте настройки устройства. | | `AdaptyErrorCode.DecodingFailed` | Не удалось корректно закодировать параметры в URL | Убедитесь, что параметры URL корректны и правильно отформатированы | :::note Проверьте свойство `Message` ошибки, чтобы узнать подробности о причине сбоя: `WrongParam` может указывать на несколько разных проблем (отсутствующий URL покупки, ошибка открытия браузера и т. д.). ::: ## Открытие веб-пейволов во встроенном браузере \{#open-web-paywalls-in-an-in-app-browser\} :::important Открытие веб-пейволов во встроенном браузере поддерживается начиная с Adapty SDK v3.15. ::: По умолчанию веб-пейволы открываются во внешнем браузере, что уводит пользователей из вашего приложения. Для более комфортного пользовательского опыта можно открывать веб-пейволы во встроенном браузере. Это позволяет отображать страницу покупки прямо внутри приложения, так что пользователи завершают транзакцию, не переключаясь между приложениями. Чтобы включить эту возможность, передайте `AdaptyWebPresentation.InAppBrowser` в метод `OpenWebPaywall`: ```csharp showLineNumbers title="Unity" Adapty.OpenWebPaywall( product, AdaptyWebPresentation.InAppBrowser, // default — ExternalBrowser (error) => { if (error != null) { Debug.LogError($"Failed to open web paywall: {error.Message}"); } else { Debug.Log("Web paywall opened successfully"); } } ); ``` --- # File: unity-use-fallback-paywalls --- --- title: "Unity - Use fallback paywalls" description: "Обработка случаев, когда пользователи офлайн или серверы Adapty недоступны" --- :::warning Резервные пейволы поддерживаются начиная с Unity SDK v2.11. ::: Чтобы поддерживать бесперебойный пользовательский опыт, важно настроить [резервные пейволы](/fallback-paywalls) для флоу, [пейволов](paywalls) и [онбордингов](onboardings). Это позволит приложению продолжить работу при частичной или полной потере интернет-соединения. * **Если приложение не может обратиться к серверам Adapty:** Оно сможет отобразить резервный флоу или пейвол, а также использовать локальную конфигурацию онбординга. * **Если приложение не может подключиться к интернету:** Оно сможет отобразить резервный флоу или пейвол. Онбординги содержат удалённый контент и требуют интернет-соединения для работы. :::important Прежде чем следовать шагам этого гайда, [скачайте](/local-fallback-paywalls) файлы резервной конфигурации из Adapty. ::: ## Конфигурация \{#configuration\} 1. Добавьте файлы резервной конфигурации в общую директорию `Assets/StreamingAssets` вашего проекта. 2. Вызовите метод `.setFallback` **до** того, как запрашиваете целевой пейвол или онбординг. ```csharp using UnityEngine; using AdaptySDK; #if UNITY_IOS string fileName = "ios_fallback.json"; #elif UNITY_ANDROID string fileName = "android_fallback.json"; #else // Optional: handle Editor or other platforms string fileName = "fallback.json"; #endif Adapty.SetFallback(fileName, (error) => { if (error != null) { Debug.LogError($"Failed to set fallback: {error}"); return; } // Fallback set successfully }); ``` Параметры: | Параметр | Описание | |:-------------|:----------------------------------------------------------------------| | **fileName** | Строка с именем файла резервной конфигурации. | --- # File: unity-localizations-and-locale-codes --- --- title: "Использование локализаций и кодов языков в Unity SDK" description: "Узнайте, как локализовать пейволы в вашем Unity-приложении с помощью Adapty SDK." --- ## Почему это важно \{#why-this-is-important\} Коды языков (locale codes) задействованы в нескольких сценариях — например, когда вам нужно получить правильный пейвол для текущей локализации приложения. Коды языков могут быть сложными и различаться от платформы к платформе, поэтому мы используем внутренний стандарт для всех поддерживаемых платформ. Однако из-за этой сложности важно понимать, что именно вы отправляете на наш сервер для получения нужной локализации и что происходит дальше — чтобы всегда получать ожидаемый результат. ## Стандарт кодов языков в Adapty \{#locale-code-standard-at-adapty\} Adapty использует немного модифицированный стандарт [BCP 47](https://en.wikipedia.org/wiki/IETF_language_tag): каждый код состоит из подтегов в нижнем регистре, разделённых дефисами. Например: `en` (английский), `pt-br` (португальский (Бразилия)), `zh` (упрощённый китайский), `zh-hant` (традиционный китайский). ## Сопоставление кодов языков \{#locale-code-matching\} Когда Adapty получает запрос от клиентского SDK с кодом языка и начинает поиск соответствующей локализации пейвола, происходит следующее: 1. Входящая строка с кодом языка приводится к нижнему регистру, а все символы подчёркивания (`_`) заменяются дефисами (`-`). 2. Выполняется поиск локализации с полным совпадением кода языка. 3. Если совпадение не найдено, берётся подстрока до первого дефиса (`pt` для `pt-br`) и снова выполняется поиск. 4. Если совпадение снова не найдено, возвращается локализация по умолчанию — `en`. Таким образом, устройство на iOS, отправившее `'pt_BR'`, устройство на Android, отправившее `pt-BR`, и другое устройство, отправившее `pt-br`, получат одинаковый результат. ## Реализация локализаций: рекомендуемый способ \{#implementing-localizations-recommended-way\} Если вы занимаетесь локализациями, скорее всего, вы уже работаете с файлами локализованных строк в своём проекте. В этом случае мы рекомендуем добавить в каждый такой файл пару ключ-значение с нужным кодом языка для Adapty. Затем извлекайте значение этого ключа при обращении к нашему SDK вот так: ```csharp showLineNumbers // 1. Modify your localization files (e.g., using Unity's Localization package) /* en.json */ { "adapty_paywalls_locale": "en" } /* es.json */ { "adapty_paywalls_locale": "es" } /* pt-BR.json */ { "adapty_paywalls_locale": "pt-br" } // 2. Extract and use the locale code using UnityEngine; using UnityEngine.Localization; using UnityEngine.Localization.Settings; using AdaptySDK; public class PaywallManager : MonoBehaviour { public async void FetchPaywall() { // Get the current locale from Unity's Localization system var locale = LocalizationSettings.SelectedLocale; var localeCode = GetAdaptyLocaleCode(locale); // Pass locale code to Adapty.GetPaywall or Adapty.GetPaywallForDefaultAudience method Adapty.GetPaywall("placement_id", localeCode, (paywall, error) => { if (error != null) { // handle the error return; } // Use the paywall }); } private string GetAdaptyLocaleCode(Locale locale) { // Convert Unity locale to Adapty format var localeIdentifier = locale.Identifier.Code; return localeIdentifier.ToLower().Replace('_', '-'); } } ``` Такой подход даёт вам полный контроль над тем, какая локализация будет загружена для каждого пользователя вашего приложения. ## Реализация локализаций: альтернативный способ \{#implementing-localizations-the-other-way\} Похожего (но не идентичного) результата можно достичь без явного задания кодов языков для каждой локализации. Для этого нужно извлекать код языка из других объектов, которые предоставляет ваша платформа, например вот так: ```csharp showLineNumbers using UnityEngine; using System.Globalization; using AdaptySDK; public class PaywallManager : MonoBehaviour { public void FetchPaywall() { var localeCode = GetSystemLocaleCode(); // Pass locale code to Adapty.GetPaywall or Adapty.GetPaywallForDefaultAudience method Adapty.GetPaywall("placement_id", localeCode, (paywall, error) => { if (error != null) { // handle the error return; } // Use the paywall }); } private string GetSystemLocaleCode() { // Get the system's current culture var culture = CultureInfo.CurrentCulture; var languageCode = culture.TwoLetterISOLanguageName; var regionCode = culture.Name.Contains('-') ? culture.Name.Split('-')[1] : null; if (!string.IsNullOrEmpty(regionCode)) { return $"{languageCode}-{regionCode.ToLower()}"; } return languageCode; } } ``` Мы не рекомендуем этот подход по нескольким причинам: 1. На iOS предпочтительные языки и текущая локаль — это не одно и то же. Чтобы локализация определялась корректно, придётся либо положиться на логику Apple (которая работает автоматически при использовании рекомендованного подхода с файлами локализованных строк), либо воспроизвести её самостоятельно. 2. Сложно предсказать, что именно получит сервер Adapty. Например, на iOS устройство может вернуть локаль вида `ar_OM@numbers='latn'`, которая будет отправлена на сервер. В ответ вы получите не локализацию `ar-om`, которую ожидали, а `ar` — что, скорее всего, не то, что нужно. Если вы всё же решите использовать этот подход — убедитесь, что учли все актуальные сценарии использования. --- # File: unity-troubleshoot-paywall-builder --- --- title: "Устранение неполадок Paywall Builder в Unity SDK" description: "Устранение неполадок Paywall Builder в Unity SDK" --- Этот гайд поможет вам решить распространённые проблемы при использовании пейволов, созданных в Adapty Paywall Builder, в Unity SDK. ## Ошибка при получении конфигурации пейвола \{#getting-a-paywall-configuration-fails\} **Проблема**: Метод `CreateView` не может получить конфигурацию пейвола. **Причина**: Пейвол не включён для отображения на устройстве в Paywall Builder. **Решение**: Включите переключатель **Show on device** в Paywall Builder. <img src="/assets/shared/img/show-on-device.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Слишком большое число просмотров пейвола \{#the-paywall-view-number-is-too-big\} **Проблема**: Счётчик просмотров пейвола показывает вдвое больше ожидаемого значения. **Причина**: Возможно, в вашем коде вызывается `LogShowPaywall`, что дублирует счётчик просмотров при использовании Paywall Builder. Для пейволов, созданных в Paywall Builder, аналитика отслеживается автоматически, поэтому использовать этот метод не нужно. **Решение**: Убедитесь, что в вашем коде не вызывается `LogShowPaywall`, если вы используете Paywall Builder. ## Другие проблемы \{#other-issues\} **Проблема**: Вы столкнулись с другими проблемами, связанными с Paywall Builder, которые не описаны выше. **Решение**: При необходимости обновите SDK до последней версии с помощью [гайдов по миграции](unity-sdk-migration-guides). Многие проблемы устраняются в новых версиях SDK. --- # File: unity-quickstart-manual --- --- title: "Включение покупок в вашем кастомном пейволе в Unity SDK" description: "Интегрируйте Adapty SDK в ваши кастомные пейволы Unity для включения встроенных покупок." --- Этот гайд описывает, как интегрировать Adapty в ваши кастомные пейволы. Вы сохраняете полный контроль над реализацией пейвола, а SDK Adapty получает продукты, обрабатывает новые покупки и восстанавливает предыдущие. :::important **Этот гайд предназначен для разработчиков, которые реализуют кастомные пейволы.** Если вы хотите самый простой способ включить покупки, используйте [Adapty Paywall Builder](unity-quickstart-paywalls). С Paywall Builder вы создаёте пейволы в визуальном редакторе без написания кода, Adapty автоматически обрабатывает всю логику покупок, и вы можете тестировать разные дизайны без повторной публикации приложения. ::: ## Прежде чем начать \{#before-you-start\} ### Настройте продукты \{#set-up-products\} Для включения встроенных покупок вам нужно понять три ключевых концепции: - [**Продукты**](product) — всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ) - [**Пейволы**](paywalls) — конфигурации, определяющие, какие продукты предлагать. В Adapty пейволы — единственный способ получить продукты, но такой подход позволяет изменять продукты, цены и офферы без изменения кода приложения. - [**Плейсменты**](placements) — где и когда вы показываете пейволы в приложении (например, `main`, `onboarding`, `settings`). Вы настраиваете пейволы для плейсментов в дашборде, затем запрашиваете их по ID плейсмента в коде. Это упрощает проведение A/B-тестов и показ разных пейволов разным пользователям. Убедитесь, что вы понимаете эти концепции, даже если работаете с кастомным пейволом. По сути, это просто способ управлять продуктами, которые вы продаёте в приложении. Для реализации кастомного пейвола вам нужно создать **пейвол** и добавить его в **плейсмент**. Эта настройка позволяет получать ваши продукты. Чтобы понять, что нужно сделать в дашборде, следуйте [быстрому старту здесь](quickstart). ### Управление пользователями \{#manage-users\} Вы можете работать как с бэкенд-аутентификацией, так и без неё. SDK Adapty по-разному обрабатывает анонимных и идентифицированных пользователей. Прочитайте [гайд по идентификации](unity-quickstart-identify), чтобы разобраться в особенностях и убедиться, что вы правильно работаете с пользователями. ## Шаг 1. Получите продукты \{#step-1-get-products\} Чтобы получить продукты для вашего кастомного пейвола, необходимо: 1. Получить объект `paywall`, передав ID [плейсмента](placements) в метод `getPaywall`. 2. Получить массив продуктов для этого пейвола с помощью метода `getPaywallProducts`. ```csharp showLineNumbers using AdaptySDK; void LoadPaywall() { Adapty.GetPaywall("YOUR_PLACEMENT_ID", (paywall, error) => { if (error != null) { // Handle the error return; } Adapty.GetPaywallProducts(paywall, (products, productsError) => { if (productsError != null) { // Handle the error return; } // Use products to build your custom paywall UI }); }); } ``` ## Шаг 2. Принимайте покупки \{#step-2-accept-purchases\} Когда пользователь нажимает на продукт в вашем кастомном пейволе, вызовите метод `makePurchase` с выбранным продуктом. Это запустит процесс покупки и вернёт обновлённый профиль. ```csharp showLineNumbers using AdaptySDK; void PurchaseProduct(AdaptyPaywallProduct product) { Adapty.MakePurchase(product, (result, error) => { if (error != null) { // Handle the error return; } switch (result.Type) { case AdaptyPurchaseResultType.Success: var profile = result.Profile; // Purchase successful, profile updated break; case AdaptyPurchaseResultType.UserCancelled: // User canceled the purchase break; case AdaptyPurchaseResultType.Pending: // Purchase is pending (e.g., user will pay offline with cash) break; } }); } ``` ## Шаг 3. Восстановите покупки \{#step-3-restore-purchases\} Сторы требуют, чтобы все приложения с подписками предоставляли пользователям возможность восстановить покупки. Вызовите метод `restorePurchases`, когда пользователь нажимает кнопку восстановления. Это синхронизирует историю покупок с Adapty и вернёт обновлённый профиль. ```csharp showLineNumbers using AdaptySDK; void RestorePurchases() { Adapty.RestorePurchases((profile, error) => { if (error != null) { // Handle the error return; } // Restore successful, profile updated }); } ``` ## Следующие шаги \{#next-steps\} :::tip Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь! ::: Ваш пейвол готов к отображению в приложении. Протестируйте покупки в [песочнице App Store](test-purchases-in-sandbox) или в [Google Play Store](testing-on-android), чтобы убедиться, что вы можете совершить тестовую покупку через пейвол. Далее [проверьте, завершили ли пользователи покупку](unity-check-subscription-status), чтобы решить, показывать ли пейвол или предоставить доступ к платным функциям. --- # File: fetch-paywalls-and-products-unity --- --- title: "Получение пейволов и продуктов для пейволов с Remote Config в Unity SDK" description: "Получайте пейволы и продукты в Adapty Unity SDK для улучшения монетизации пользователей." --- Прежде чем отображать Remote Config и кастомные пейволы, нужно получить информацию о них. Обратите внимание, что этот раздел посвящён Remote Config и кастомным пейволам. Для получения пейволов, настроенных в Paywall Builder, смотрите [Получение пейволов Paywall Builder и их конфигурации](unity-get-pb-paywalls). :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: <details> <summary>Прежде чем начать получать пейволы и продукты в мобильном приложении (нажмите, чтобы развернуть)</summary> 1. [Создайте продукты](create-product) в дашборде Adapty. 2. [Создайте пейвол и добавьте продукты в него](create-paywall) в дашборде Adapty. 3. [Создайте плейсменты и добавьте пейвол в плейсмент](create-placement) в дашборде Adapty. 4. [Установите Adapty SDK](sdk-installation-unity) в своё мобильное приложение. </details> ## Получение информации о пейволе \{#fetch-paywall-information\} В Adapty [продукт](product) объединяет продукты из App Store и Google Play. Эти кросс-платформенные продукты встраиваются в пейволы, что позволяет показывать их в нужных плейсментах мобильного приложения. Чтобы отобразить продукты, нужно получить [пейвол](paywalls) из одного из ваших [плейсментов](placements) с помощью метода `getPaywall`. :::important **Не хардкодьте ID продуктов.** Единственный ID, который нужно хардкодить — это ID плейсмента. Пейволы настраиваются удалённо, поэтому количество продуктов и доступных офферов может меняться в любой момент. Приложение должно обрабатывать эти изменения динамически: если сегодня пейвол возвращает два продукта, а завтра три — отображайте все без изменений в коде. ::: ```csharp showLineNumbers Adapty.GetPaywall("YOUR_PLACEMENT_ID", "en", (paywall, error) => { if(error != null) { // handle the error return; } // paywall - the resulting object }); ``` | Параметр | Наличие | Описание | |---------|--------|-----------| | **placementId** | обязательный | Идентификатор [плейсмента](placements). Это значение, которое вы указали при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | <p>Идентификатор [локализации пейвола](add-remote-config-locale). Ожидается код языка, состоящий из одного или нескольких подтегов, разделённых символом минус (**-**). Первый подтег обозначает язык, второй — регион.</p><p></p><p>Например: `en` — английский, `pt-br` — бразильский португальский.</p><p></p><p>Подробнее о кодах локалей и рекомендуемых подходах к их использованию — в разделе [Локализации и коды локалей](unity-localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кешированные данные в случае сбоя. Мы рекомендуем этот вариант, поскольку он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если ваши пользователи часто сталкиваются с нестабильным интернет-соединением, рассмотрите использование `.returnCacheDataElseLoad` — этот режим возвращает кешированные данные, если они есть. В таком сценарии пользователи могут получать не самые свежие данные, зато загрузка будет быстрее независимо от качества соединения. Кеш регулярно обновляется, поэтому его безопасно использовать в рамках сессии для снижения количества сетевых запросов.</p><p></p><p>Обратите внимание: кеш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.</p><p></p><p>Adapty SDK хранит пейволы на двух уровнях: регулярно обновляемый кеш, описанный выше, и [резервные пейволы](unity-use-fallback-paywalls). Также используется CDN для более быстрой загрузки пейволов и отдельный резервный сервер на случай недоступности CDN. Такая система гарантирует, что вы всегда получаете актуальную версию пейволов, обеспечивая надёжность даже при слабом интернете.</p> | | **loadTimeout** | по умолчанию: 5 сек | <p>Ограничивает время ожидания для данного метода. По истечении таймаута будут возвращены кешированные данные или локальный резервный вариант.</p><p></p><p>Обратите внимание: в редких случаях метод может завершиться с небольшой задержкой сверх значения, указанного в `loadTimeout`, так как операция может включать несколько запросов под капотом.</p> | Не задавайте ID продуктов жёстко в коде! Поскольку пейволы настраиваются удалённо, набор доступных продуктов, их количество и специальные предложения (например, бесплатные пробные периоды) могут меняться со временем. Убедитесь, что ваш код учитывает эти сценарии. Например, если изначально вы получаете 2 продукта, приложение должно отображать именно 2 продукта. Но если позднее вы получите 3 продукта, приложение должно показать все 3 без каких-либо изменений в коде. Единственное, что нужно задать жёстко, — это ID плейсмента. Параметры ответа: | Параметр | Описание | | :-------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- | | Paywall | Объект [`AdaptyPaywall`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html) со списком идентификаторов продуктов, идентификатором пейвола, Remote Config и рядом других свойств. | ## Получение продуктов \{#fetch-products\} Получив пейвол, вы можете запросить массив продуктов, соответствующих ему: ```csharp showLineNumbers Adapty.GetPaywallProducts(paywall, (products, error) => { if(error != null) { // handle the error return; } // products - the requested products array }); ``` Параметры ответа: | Параметр | Описание | | :-------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Products | Список объектов [`AdaptyPaywallProduct`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall_product.html) с: идентификатором продукта, названием продукта, ценой, валютой, длительностью подписки и рядом других свойств. | При реализации собственного дизайна пейвола вам, скорее всего, понадобятся свойства объекта [`AdaptyPaywallProduct`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall_product.html). Ниже приведены наиболее часто используемые из них, полный список доступных свойств смотрите в документации по ссылке. | Свойство | Описание | |-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Title** | Чтобы отобразить название продукта, используйте `product.LocalizedTitle`. Обратите внимание: локализация зависит от выбранной пользователем страны стора, а не от локали устройства. | | **Price** | Чтобы отобразить цену в локализованном формате, используйте `product.Price.LocalizedString`. Локализация основана на локали устройства. Цену в числовом виде можно получить через `product.Price.Amount` — значение будет в местной валюте. Символ валюты доступен через `product.Price.CurrencySymbol`. | | **Subscription Period** | Чтобы отобразить период подписки (например, неделя, месяц, год и т. д.), используйте `product.Subscription?.LocalizedPeriod`. Локализация основана на локали устройства. Для программного получения периода подписки используйте `product.Subscription?.Period`. Через это свойство доступен enum `Unit` со значениями `AdaptySubscriptionPeriodUnit.Day`, `AdaptySubscriptionPeriodUnit.Week`, `AdaptySubscriptionPeriodUnit.Month`, `AdaptySubscriptionPeriodUnit.Year` и `AdaptySubscriptionPeriodUnit.Unknown`. Значение `NumberOfUnits` содержит количество единиц периода. Например, для квартальной подписки в свойстве `Unit` будет `AdaptySubscriptionPeriodUnit.Month`, а в `NumberOfUnits` — `3`. | | **Introductory Offer** | Чтобы отобразить бейдж или другой индикатор наличия introductory offer в подписке, проверьте свойство `product.Subscription?.Offer?.Phases`. Это список, который может содержать до двух фаз скидки: фазу бесплатного пробного периода и фазу вводной цены. Каждый объект фазы содержит следующие полезные свойства:<br/>• `PaymentMode`: enum со значениями `AdaptyPaymentMode.FreeTrial`, `AdaptyPaymentMode.PayAsYouGo`, `AdaptyPaymentMode.PayUpFront` и `AdaptyPaymentMode.Unknown`. Бесплатные пробные периоды имеют тип `AdaptyPaymentMode.FreeTrial`.<br/>• `Price`: цена со скидкой в числовом виде. Для бесплатных пробных периодов здесь будет `0`.<br/>• `LocalizedNumberOfPeriods`: строка, локализованная по локали устройства, описывающая длительность предложения. Например, для трёхдневного пробного периода в этом поле будет `"3 days"`.<br/>• `SubscriptionPeriod`: альтернативный способ получить детали периода предложения. Работает так же, как описано в предыдущем разделе.<br/>• `LocalizedSubscriptionPeriod`: форматированный период подписки скидки для локали пользователя. | ## Ускорьте загрузку пейвола с помощью пейвола для аудитории по умолчанию \{#speed-up-paywall-fetching-with-default-audience-paywall\} Как правило, пейволы загружаются почти мгновенно, поэтому беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и пейволов, а пользователи работают при слабом интернет-соединении, загрузка пейвола может занять больше времени, чем хотелось бы. В таких случаях имеет смысл показывать пейвол по умолчанию, чтобы пользователь не оставался без пейвола вовсе. Чтобы решить эту проблему, можно воспользоваться методом `GetPaywallForDefaultAudience`, который получает пейвол указанного плейсмента для аудитории **All Users**. Однако важно понимать, что рекомендуемый подход — получать пейвол с помощью метода `getPaywall`, как описано в разделе [Получение пейвола](#fetch-paywall) выше. :::warning Рекомендуем использовать `GetPaywall` вместо `GetPaywallForDefaultAudience`, так как последний имеет существенные ограничения: - **Проблемы совместимости**: могут возникнуть при поддержке нескольких версий приложения — придётся либо делать обратно совместимые дизайны, либо мириться с тем, что старые версии будут отображаться некорректно. - **Отсутствие персонализации**: показывает контент только для аудитории «All Users», исключая таргетинг по стране, атрибуции или пользовательским атрибутам. Если быстрая загрузка важнее этих недостатков для вашего случая, используйте `GetPaywallForDefaultAudience`, как показано ниже. В противном случае используйте `GetPaywall`, как описано [выше](#fetch-paywall). ::: ```csharp showLineNumbers Adapty.GetPaywallForDefaultAudience("YOUR_PLACEMENT_ID", "en", (paywall, error) => { if(error != null) { // handle the error return; } // paywall - the resulting object }); ``` Параметры: | Параметр | Обязательность | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указываете при создании плейсмента в дашборде Adapty. | | **locale** | <p>необязательный</p><p>по умолчанию: `en`</p> | <p>Идентификатор локализации пейвола. Ожидается языковой код, состоящий из одного или двух подтегов, разделённых символом минус (**-**). Первый подтег обозначает язык, второй — регион.</p><p></p><p>Например: `en` — английский, `pt-br` — бразильский португальский.</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кэшированные данные в случае сбоя. Мы рекомендуем этот вариант — он гарантирует, что пользователи всегда получают актуальные данные.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите использование `.returnCacheDataElseLoad`: оно возвращает кэшированные данные, если они есть. В этом случае пользователи могут не получить самые последние данные, но загрузка будет быстрее независимо от качества соединения. Кэш регулярно обновляется, поэтому его безопасно использовать в течение сессии, чтобы избежать лишних сетевых запросов.</p><p></p><p>Обратите внимание: кэш сохраняется при перезапуске приложения и очищается только при его переустановке или вручную.</p><p></p><p>Adapty SDK хранит пейволы локально на двух уровнях: регулярно обновляемый кэш, описанный выше, и резервные пейволы. Для более быстрой загрузки пейволов мы также используем CDN и отдельный резервный сервер на случай недоступности CDN. Такая система гарантирует, что вы всегда получаете последнюю версию пейволов, обеспечивая надёжность даже при нестабильном интернет-соединении.</p> | --- # File: present-remote-config-paywalls-unity --- --- title: "Отображение пейвола, настроенного через Remote Config, в Unity SDK" description: "Узнайте, как отображать пейволы на основе Remote Config в Adapty Unity SDK для персонализации пользовательского опыта." --- Если вы настроили пейвол с помощью Remote Config, вам потребуется реализовать его отображение в коде мобильного приложения. Поскольку Remote Config гибко адаптируется под ваши нужды, вы сами решаете, что включать в пейвол и как он будет выглядеть. Мы предоставляем метод для получения Remote Config, а дальнейшее отображение пейвола остаётся за вами. ## Получение Remote Config пейвола и его отображение \{#get-paywall-remote-config-and-present-it\} Чтобы получить Remote Config пейвола, обратитесь к свойству `remoteConfig` и извлеките нужные значения. ```csharp showLineNumbers Adapty.GetPaywall("YOUR_PLACEMENT_ID", (paywall, error) => { if (error != null) { // handle the error return; } // Access remote config dictionary var dictionary = paywall.RemoteConfig?.Dictionary; var headerText = dictionary?["header_text"] as string; // Or access raw JSON data var jsonData = paywall.RemoteConfig?.Data; }); ``` Получив все необходимые значения, приступайте к отрисовке и сборке пейвола. Убедитесь, что дизайн адаптируется под разные экраны и ориентации устройств — это обеспечит удобный пользовательский опыт на любом устройстве. :::warning Не забудьте [зафиксировать событие просмотра пейвола](present-remote-config-paywalls-unity#track-paywall-view-events), как описано ниже — это позволит аналитике Adapty собирать данные для воронок и A/B-тестов. ::: После отображения пейвола настройте процесс покупки. Когда пользователь совершает покупку, вызовите `.MakePurchase()` с продуктом из вашего пейвола. Подробнее о методе `.MakePurchase()` читайте в разделе [Совершение покупок](unity-making-purchases). Рекомендуем также [создать резервный пейвол (fallback paywall)](unity-use-fallback-paywalls). Он будет отображаться пользователю при отсутствии интернета или кеша, обеспечивая бесперебойную работу приложения в таких ситуациях. ## Отслеживание событий просмотра пейвола \{#track-paywall-view-events\} Adapty помогает измерять эффективность ваших пейволов. Данные о покупках мы собираем автоматически, а вот просмотры пейволов нужно логировать вручную — только вы знаете, когда пользователь видит пейвол. Чтобы зафиксировать событие просмотра, вызовите `.LogShowPaywall(paywall)` — это отразится в метриках пейвола в воронках и A/B-тестах. :::important Вызов `.LogShowPaywall(paywall)` не требуется, если вы отображаете пейволы, созданные в [Paywall Builder](adapty-paywall-builder). ::: ```csharp showLineNumbers Adapty.LogShowPaywall(paywall, (error) => { // handle the error }); ``` Параметры запроса: | Параметр | Обязательный | Описание | | :---------- | :----------- |:------------------------------------------------------------------| | **paywall** | да | Объект [`AdaptyPaywall`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html). | --- # File: unity-making-purchases --- --- title: "Совершение покупок в мобильном приложении с Unity SDK" description: "Гайд по обработке встроенных покупок и подписок с помощью Adapty." --- Отображение пейволов в мобильном приложении — обязательный шаг для предоставления пользователям доступа к премиум-контенту или сервисам. Однако просто показать пейвол достаточно для поддержки покупок только в том случае, если вы используете [Paywall Builder](adapty-paywall-builder) для его настройки. Если вы не используете Paywall Builder, для совершения покупки и открытия нужного контента необходимо вызвать отдельный метод `.makePurchase()`. Именно через него пользователи взаимодействуют с пейволами и выполняют нужные транзакции. Если на вашем пейволе настроен активный promotional offer для продукта, который пользователь хочет купить, Adapty автоматически применит его в момент покупки. :::warning Обратите внимание: introductory offer применяется автоматически только при использовании пейволов, созданных с помощью Paywall Builder. В других случаях вам потребуется [проверить право пользователя на introductory offer в iOS](fetch-paywalls-and-products#check-intro-offer-eligibility-on-ios). Пропуск этого шага может привести к отклонению приложения при релизе. Кроме того, пользователям, которым положен introductory offer, может быть выставлена полная цена. ::: Убедитесь, что вы [выполнили начальную настройку](quickstart), не пропустив ни одного шага. Без неё мы не сможем валидировать покупки. ## Совершение покупки \{#make-purchase\} :::note **Используете [Paywall Builder](adapty-paywall-builder)?** Покупки обрабатываются автоматически — этот шаг можно пропустить. **Нужна пошаговая инструкция?** Ознакомьтесь с [гайдом по быстрому старту](unity-implement-paywalls-manually) — там есть полное руководство по реализации с подробным контекстом. ::: ```csharp showLineNumbers using AdaptySDK; void MakePurchase(AdaptyPaywallProduct product) { Adapty.MakePurchase(product, (result, error) => { switch (result.Type) { case AdaptyPurchaseResultType.Pending: // handle pending purchase break; case AdaptyPurchaseResultType.UserCancelled: // handle purchase cancellation break; case AdaptyPurchaseResultType.Success: var profile = result.Profile; // handle successfull purchase break; default: break; } }); } ``` Параметры запроса: | Параметр | Наличие | Описание | | :---------- | :------- |:------------------------------------------------------------------------------------------------------| | **Product** | обязательный | Объект [`AdaptyPaywallProduct`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall_product.html), полученный из пейвола. | Параметры ответа: | Параметр | Описание | |---------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>При успешном запросе ответ содержит этот объект. Объект [AdaptyProfile](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_profile.html) предоставляет исчерпывающую информацию об уровнях доступа пользователя, подписках и разовых покупках в приложении.</p><p>Проверьте статус уровня доступа, чтобы убедиться, что у пользователя есть необходимый доступ к приложению.</p> | :::warning **Примечание:** если вы используете Apple StoreKit версии ниже 2.0 и Adapty SDK версии ниже v2.9.0, вам необходимо указать [общий секрет App Store](app-store-connection-configuration#step-5-enter-app-store-shared-secret). Этот метод в настоящее время устарел и не рекомендован Apple. ::: ## Смена подписки при покупке \{#change-subscription-when-making-a-purchase\} Когда пользователь выбирает новую подписку вместо продления текущей, поведение зависит от стора: - В App Store подписка обновляется автоматически в рамках группы подписок. Если пользователь покупает подписку из одной группы, уже имея активную из другой, обе подписки будут действовать одновременно. - В Google Play подписка не обновляется автоматически. Переключение нужно реализовать в коде приложения, как описано ниже. Чтобы заменить подписку на другую в Android, вызовите метод `.makePurchase()` с дополнительным параметром: ```csharp showLineNumbers // Create subscription update parameters var subscriptionUpdateParams = new AdaptySubscriptionUpdateParameters( "old_product_id", // Product ID of the current subscription AdaptySubscriptionUpdateReplacementMode.WithTimeProration ); Adapty.MakePurchase(product, subscriptionUpdateParams, (profile, error) => { if(error != null) { // Handle the error return; } // successful cross-grade }); ``` Дополнительный параметр запроса: | Параметр | Наличие | Описание | | :--------------------------- | :------- |:-------------------------------------------------------------------------------------------------------| | **subscriptionUpdateParams** | обязателен | объект [`AdaptySubscriptionUpdateParameters`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_subscription_update_parameters.html). | Подробнее о подписках и режимах замены читайте в документации Google Developer: - [О режимах замены](https://developer.android.com/google/play/billing/subscriptions#replacement-modes) - [Рекомендации Google по режимам замены](https://developer.android.com/google/play/billing/subscriptions#replacement-recommendations) - Режим замены [`CHARGE_PRORATED_PRICE`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#CHARGE_PRORATED_PRICE()). Примечание: этот метод доступен только для апгрейда подписки. Даунгрейд не поддерживается. - Режим замены [`DEFERRED`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode#DEFERRED()). Примечание: фактическая смена подписки произойдёт только по окончании текущего расчётного периода. ## Активация промокодов в iOS \{#redeem-offer-codes-in-ios\} <Details> <summary>Об офферных кодах</summary> Офферные коды позволяют предоставлять скидки или бесплатные пробные периоды конкретным пользователям. В отличие от обычных офферов, которые применяются автоматически, офферные коды распространяются за пределами приложения — через email-рассылки, социальные сети или печатные материалы. Пользователи активируют их, вводя код в App Store, переходя по ссылке для активации или через диалог внутри приложения. Чтобы настроить офферные коды, откройте подписку в App Store Connect и перейдите в раздел **Offer Codes**. Вы можете создать [три вида](https://developer.apple.com/help/app-store-connect/manage-subscriptions/set-up-subscription-offer-codes) офферных кодов: - **Free** — подписка бесплатна на заданный период, следующее продление — по полной цене. - **Pay as you go** — пользователь платит сниженную цену в каждом расчётном периоде на протяжении заданного срока, после чего подписка продлевается по полной цене. - **Pay up front** — пользователь единовременно платит сниженную цену за весь срок оффера, после чего подписка продлевается по полной цене. Добавлять офферные коды в Adapty не нужно. Apple помечает каждую транзакцию в период действия оффера категорией офферного кода. Это касается как первоначальной активации, так и всех последующих продлений со скидкой. Adapty обнаруживает метку и записывает каждую транзакцию с категорией оффера `offer_code`. Как только период оффера заканчивается и подписка продлевается по полной цене, метка исчезает. Вы можете фильтровать аналитику по типу оффера **Offer Code** в [дашборде Adapty](controls-filters-grouping-compare-proceeds). #### Устранение расхождений в выручке \{#revenue-discrepancy-troubleshooting\} Если транзакция по офферному коду отображается в Adapty по полной цене продукта вместо сниженной цены оффера, проверьте следующее в App Store Connect: - Для офферного кода настроены корректные цены для всех регионов, где пользователи могут его активировать. - Цена оффера задана для конкретной страны или региона пользователя. Apple передаёт региональную цену в транзакции. Если для оффера не настроена региональная цена, Apple может передать полную цену продукта. Вы можете фильтровать и проверять транзакции по офферным кодам в [дашборде Adapty](controls-filters-grouping-compare-proceeds) по фильтрам типа оффера **Offer Code** и **Offer Discount Type**. #### Устаревшие промокоды (deprecated) \{#legacy-promo-codes-deprecated\} :::warning Apple прекратила поддержку промокодов для встроенных покупок в марте 2026 года. Офферные коды заменяют их с расширенными возможностями: настраиваемые условия применения, сроки действия и до 1 миллиона кодов в квартал. Если вы ранее использовали промокоды для встроенных покупок, перейдите на офферные коды в App Store Connect. ::: Устаревшие промокоды (не более 100 на приложение на версию) предоставляли бесплатный доступ к подписке. В отличие от офферных кодов, Apple не включала информацию о скидке в транзакции по промокодам — в чеке указывалась полная цена продукта. В результате Adapty записывал эти транзакции по полной цене, что приводило к расхождениям в выручке между аналитикой Adapty и App Store Connect. Если вы видите исторические транзакции по полной цене, которые должны были быть бесплатными, скорее всего, они связаны с устаревшими промокодами. Поскольку эти коды больше не поддерживаются, перейдите на офферные коды для точного учёта выручки. </Details> Чтобы отобразить экран активации кода в приложении: ```csharp showLineNumbers Adapty.PresentCodeRedemptionSheet((error) => { // handle the error }); ``` :::danger По нашим наблюдениям, экран активации промокода (Offer Code Redemption sheet) в некоторых приложениях работает нестабильно. Мы рекомендуем перенаправлять пользователя напрямую в App Store. Для этого нужно открыть URL следующего формата: `https://apps.apple.com/redeem?ctx=offercodes&id={apple_app_id}&code={code}` ::: ## Управление предоплаченными планами (Android) \{#manage-prepaid-plans-android\} Если пользователи вашего приложения могут покупать [предоплаченные планы](https://developer.android.com/google/play/billing/subscriptions#prepaid-plans) (например, невозобновляемую подписку на несколько месяцев), вы можете включить [отложенные транзакции](https://developer.android.com/google/play/billing/subscriptions#pending) для предоплаченных планов. ```csharp showLineNumbers title="Unity" using UnityEngine; using AdaptySDK; var builder = new AdaptyConfiguration.Builder("YOUR_API_KEY") .SetGoogleEnablePendingPrepaidPlans(true); Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); ``` --- # File: unity-restore-purchase --- --- title: "Восстановление покупок в мобильном приложении через Unity SDK" description: "Узнайте, как восстановить покупки в Adapty для обеспечения бесперебойного пользовательского опыта." --- Восстановление покупок на iOS и Android позволяет пользователям снова получить доступ к ранее купленному контенту — подпискам или встроенным покупкам — без повторного списания средств. Это особенно удобно, если пользователь удалил и переустановил приложение или перешёл на новое устройство и хочет вернуть доступ к ранее приобретённому контенту. :::note В пейволах, созданных с помощью [Paywall Builder](adapty-paywall-builder), покупки восстанавливаются автоматически — дополнительный код писать не нужно. Если вы используете Paywall Builder, этот шаг можно пропустить. ::: Чтобы восстановить покупку без использования [Paywall Builder](adapty-paywall-builder) для настройки пейвола, вызовите метод `.restorePurchases()`: ```csharp showLineNumbers Adapty.RestorePurchases((profile, error) => { if (error != null) { // handle the error return; } var accessLevel = profile.AccessLevels["YOUR_ACCESS_LEVEL"]; if (accessLevel != null && accessLevel.IsActive) { // restore access } }); ``` Параметры ответа: | Параметр | Описание | |---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **Profile** | <p>Объект [`AdaptyProfile`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_profile.html). Модель содержит информацию об уровнях доступа, подписках и разовых покупках.</p><p>Проверьте **статус уровня доступа**, чтобы определить, есть ли у пользователя доступ к приложению.</p> | :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: --- # File: implement-observer-mode-unity --- --- title: "Реализация режима Observer в Unity SDK" description: "Реализуйте режим Observer в Adapty для отслеживания событий подписки пользователей в Unity SDK." --- Если у вас уже есть собственная инфраструктура покупок и вы не готовы полностью переходить на Adapty, вы можете воспользоваться [режимом Observer](observer-vs-full-mode). В базовом варианте режим Observer обеспечивает расширенную аналитику и бесшовную интеграцию с системами атрибуции и аналитики. Если это соответствует вашим потребностям, вам нужно только: 1. Включить его при настройке Adapty SDK, установив параметр `observerMode` в значение `true`. Следуйте инструкциям по настройке для [Unity](sdk-installation-unity#activate-adapty-module-of-adapty-sdk). 2. [Передавать транзакции](report-transactions-observer-mode-unity) из вашей существующей инфраструктуры покупок в Adapty. ### Настройка режима Observer \{#observer-mode-setup\} Включите режим Observer, если вы самостоятельно обрабатываете покупки и управляете статусом подписки, а Adapty используете для отправки событий подписки и аналитики. :::important В режиме Observer Adapty SDK не закрывает транзакции, поэтому убедитесь, что вы обрабатываете их самостоятельно. ::: ```csharp showLineNumbers title="C#" using UnityEngine; using AdaptySDK; public class AdaptyListener : MonoBehaviour, AdaptyEventListener { void Start() { DontDestroyOnLoad(this.gameObject); Adapty.SetEventListener(this); var builder = new AdaptyConfiguration.Builder("YOUR_PUBLIC_SDK_KEY") .SetObserverMode(true); // Enable observer mode Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); } public void OnLoadLatestProfile(AdaptyProfile profile) { } public void OnInstallationDetailsSuccess(AdaptyInstallationDetails details) { } public void OnInstallationDetailsFail(AdaptyError error) { } } ``` Параметры: | Параметр | Описание | |--------------|---------------------------------------------------------------------------------------------------------------| | observerMode | Булево значение, управляющее [режимом Observer](observer-vs-full-mode). Значение по умолчанию — `false`. | ## Использование пейволов Adapty в режиме Observer \{#using-adapty-paywalls-in-observer-mode\} Если вы также хотите использовать пейволы и функции A/B-тестирования Adapty — это возможно, но потребует дополнительной настройки в режиме Observer. Помимо шагов выше, вам нужно будет: 1. Отображать пейволы как обычно для [пейволов на Remote Config](present-remote-config-paywalls-unity). 3. [Связать пейволы](report-transactions-observer-mode-unity) с транзакциями покупок. --- # File: report-transactions-observer-mode-unity --- --- title: "Отчёт о транзакциях в Observer Mode в Unity SDK" description: "Сообщайте о транзакциях покупок в Observer Mode Adapty для отслеживания пользовательских данных и дохода в Unity SDK." --- <Tabs groupId="sdk-version" queryString> <TabItem value="current" label="Adapty SDK v3.4+ (current)" default> В Observer Mode SDK Adapty не может самостоятельно отслеживать покупки, совершённые через вашу существующую систему. Вам нужно передавать транзакции из вашего стора вручную. Важно настроить это **до** выпуска приложения, чтобы избежать ошибок в аналитике. Используйте `reportTransaction`, чтобы явно сообщать Adapty о каждой транзакции. :::warning **Не пропускайте отчёт о транзакциях!** Если вы не вызываете `ReportTransaction`, Adapty не распознает транзакцию: она не появится в аналитике и не будет отправлена в интеграции. ::: Если вы используете пейволы Adapty, передавайте `variationId` при отчёте о транзакции. Это связывает покупку с пейволом, который её инициировал, и обеспечивает точную аналитику пейволов. ```csharp showLineNumbers Adapty.ReportTransaction( "YOUR_TRANSACTION_ID", "PAYWALL_VARIATION_ID", // optional (error) => { // handle the error }); ``` Параметры: | Параметр | Обязательность | Описание | | ------------- | -------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | transactionId | обязательный | <ul><li> Для iOS: идентификатор транзакции.</li><li> Для Android: строковый идентификатор `purchase.getOrderId` покупки, где покупка — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) из библиотеки биллинга.</li></ul> | | variationId | необязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html). | </TabItem> <TabItem value="old" label="Adapty SDK 3.3.x (legacy)" default> В Observer Mode SDK Adapty не может самостоятельно отслеживать покупки, совершённые через вашу существующую систему. Вам нужно передавать транзакции из вашего стора вручную или восстанавливать их. Важно настроить это **до** выпуска приложения, чтобы избежать ошибок в аналитике. Используйте `reportTransaction` на обеих платформах, чтобы явно сообщать о каждой транзакции, а на Android дополнительно вызывайте `restorePurchases`, чтобы Adapty гарантированно её распознал. :::warning **Не пропускайте отчёт о транзакциях и восстановление покупок!** Если вы не вызываете эти методы, Adapty не распознает транзакцию: она не появится в аналитике и не будет отправлена в интеграции. ::: Если вы используете пейволы Adapty, передавайте `PAYWALL_VARIATION_ID` при отчёте о транзакции. Это связывает покупку с пейволом, который её инициировал, и обеспечивает точную аналитику пейволов. ```csharp showLineNumbers // every time when calling transasction.finish() #if UNITY_ANDROID && !UNITY_EDITOR Adapty.RestorePurchases((profile, error) => { // handle the error }); #endif Adapty.ReportTransaction( "YOUR_TRANSACTION_ID", "PAYWALL_VARIATION_ID", // optional (error) => { // handle the error }); ``` Параметры: | Параметр | Обязательность | Описание | | ------------- | -------------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | transactionId | обязательный | <ul><li> Для iOS, StoreKit 1: объект [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction).</li><li> Для iOS, StoreKit 2: объект [Transaction](https://developer.apple.com/documentation/storekit/transaction).</li><li> Для Android: строковый идентификатор (`purchase.getOrderId`) покупки, где покупка — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) из библиотеки биллинга.</li></ul> | | variationId | необязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html). | </TabItem> <TabItem value="old2" label="Adapty SDK up to 3.2.x (legacy)" default> <Tabs groupId="current-os" queryString> <TabItem value="swift" label="iOS" default> **Отчёт о транзакциях** - Версии до 3.1.x автоматически отслеживают транзакции в App Store, поэтому ручная передача данных не требуется. - Версия 3.2 не поддерживает Observer Mode. </TabItem> <TabItem value="kotlin" label="Android and Android-based cross-platforms" default> **Отчёт о транзакциях** Используйте `restorePurchases`, чтобы сообщить Adapty о транзакции в Observer Mode, как описано на странице [Восстановление покупок в мобильном коде](unity-restore-purchase). :::warning **Не пропускайте отчёт о транзакциях!** Если вы не вызываете `restorePurchases`, Adapty не распознает транзакцию: она не появится в аналитике и не будет отправлена в интеграции. ::: </TabItem> </Tabs> **Привязка пейволов к транзакциям** SDK Adapty не может определить источник покупок, так как их обрабатываете вы сами. Поэтому, если вы планируете использовать пейволы и/или A/B-тесты в Observer Mode, вам нужно связать транзакцию из вашего стора с соответствующим пейволом в коде мобильного приложения. Важно сделать это правильно до выпуска приложения, иначе это приведёт к ошибкам в аналитике. ```csharp Adapty.SetVariationForTransaction("<variationId>", "<transactionId>", (error) => { if(error != null) { // handle the error return; } // successful binding }); ``` | Параметр | Обязательность | Описание | | ------------- | -------------- |-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | transactionId | обязательный | <p>Для iOS, StoreKit 1: объект [SKPaymentTransaction](https://developer.apple.com/documentation/storekit/skpaymenttransaction).</p><p>Для iOS, StoreKit 2: объект [Transaction](https://developer.apple.com/documentation/storekit/transaction).</p><p>Для Android: строковый идентификатор (purchase.getOrderId покупки, где покупка — экземпляр класса [Purchase](https://developer.android.com/reference/com/android/billingclient/api/Purchase) из библиотеки биллинга.</p> | | variationId | обязательный | Строковый идентификатор варианта. Его можно получить через свойство `variationId` объекта [AdaptyPaywall](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_paywall.html). | </TabItem> </Tabs> --- # File: unity-troubleshoot-purchases --- --- title: "Устранение проблем с покупками в Unity SDK" description: "Устранение проблем с покупками в Unity SDK" --- Этот гайд поможет решить распространённые проблемы при ручной реализации покупок в Unity SDK. ## makePurchase вызывается успешно, но профиль не обновляется \{#makepurchase-is-called-successfully-but-the-profile-is-not-being-updated\} **Проблема**: Метод `makePurchase` завершается успешно, но профиль пользователя и статус подписки в Adapty не обновляются. **Причина**: Как правило, это указывает на неполную настройку Google Play Store или проблемы с конфигурацией. **Решение**: Убедитесь, что вы выполнили все [шаги настройки Google Play](initial-android). ## makePurchase вызывается дважды \{#makepurchase-is-invoked-twice\} **Проблема**: Метод `makePurchase` вызывается несколько раз для одной и той же покупки. **Причина**: Обычно это происходит, когда процесс покупки запускается несколько раз из-за проблем с управлением состоянием UI или быстрых повторных действий пользователя. **Решение**: Убедитесь, что вы выполнили все [шаги настройки Google Play](initial-android). ## AdaptyError.cantMakePayments в режиме наблюдателя \{#adaptyerror-cantmakepayments-in-observer-mode\} **Проблема**: Вы получаете `AdaptyError.cantMakePayments` при использовании `makePurchase` в режиме наблюдателя. **Причина**: В режиме наблюдателя покупки должны обрабатываться на вашей стороне — использовать метод `makePurchase` от Adapty не следует. **Решение**: Если вы используете `makePurchase` для покупок, отключите режим наблюдателя. Нужно либо использовать `makePurchase`, либо обрабатывать покупки самостоятельно в режиме наблюдателя. Подробнее см. в разделе [Реализация режима наблюдателя](implement-observer-mode-unity). ## Ошибка Adapty: (code: 103, message: Play Market request failed on purchases updated: responseCode=3, debugMessage=Billing Unavailable, detail: null) \{#adapty-error-code-103-message-play-market-request-failed-on-purchases-updated-responsecode3-debugmessagebilling-unavailable-detail-null\} **Проблема**: Вы получаете ошибку недоступности биллинга от Google Play Store. **Причина**: Эта ошибка не связана с Adapty. Это ошибка Google Play Billing Library, означающая, что биллинг недоступен на устройстве. **Решение**: Ошибка не связана с Adapty. Подробнее о ней можно узнать в документации Play Store: [Handle BillingResult response codes](https://developer.android.com/google/play/billing/errors#billing_unavailable_error_code_3) | Play Billing | Android Developers. ## makePurchasesCompletionHandlers не найден \{#not-found-makepurchasescompletionhandlers\} **Проблема**: Возникают проблемы с тем, что `makePurchasesCompletionHandlers` не найден. **Причина**: Как правило, это связано с проблемами при тестировании в песочнице. **Решение**: Создайте нового пользователя песочницы и попробуйте снова. Обычно это решает проблемы с обработчиком завершения покупки в песочнице. ## Другие проблемы \{#other-issues\} **Проблема**: Вы столкнулись с другими проблемами, связанными с покупками, которые не описаны выше. **Решение**: При необходимости обновите SDK до последней версии с помощью [гайдов по миграции](unity-sdk-migration-guides). Многие проблемы устранены в новых версиях SDK. --- # File: unity-identifying-users --- --- title: "Идентификация пользователей в Unity SDK" description: "Узнайте, как идентифицировать пользователей в вашем Unity-приложении с помощью Adapty SDK." --- Adapty создаёт внутренний ID профиля для каждого пользователя. Однако если у вас есть собственная система аутентификации, вам следует задать свой Customer User ID. Вы можете находить пользователей по Customer User ID в разделе [Профили](profiles-crm), а также использовать его в [серверном API](getting-started-with-server-side-api) — он будет передаваться во все интеграции. ### Указание идентификатора пользователя при конфигурации \{#setting-customer-user-id-on-configuration\} Если у вас есть ID пользователя в момент конфигурации, просто передайте его как параметр `customerUserId` в метод `.activate()`: ```csharp showLineNumbers using UnityEngine; using AdaptySDK; var builder = new AdaptyConfiguration.Builder("YOUR_API_KEY") .SetCustomerUserId("YOUR_USER_ID"); Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); ``` :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Задание customer user ID после инициализации \{#setting-customer-user-id-after-configuration\} Если при настройке SDK у вас не было ID пользователя, его можно задать позже в любой момент с помощью метода `.identify()`. Чаще всего этот метод используется после регистрации или авторизации, когда пользователь переходит из анонимного состояния в аутентифицированное. ```csharp showLineNumbers Adapty.Identify("YOUR_USER_ID", (error) => { if(error == null) { // successful identify } }); ``` Параметры запроса: - **Customer User ID** (обязательный): строковый идентификатор пользователя. :::warning Повторная отправка важных данных пользователя В некоторых случаях, например когда пользователь повторно входит в свой аккаунт, серверы Adapty уже располагают информацией об этом пользователе. В таких сценариях Adapty SDK автоматически переключится на работу с новым пользователем. Если вы передавали какие-либо данные анонимному пользователю — например, пользовательские атрибуты или атрибуцию из сторонних сетей — необходимо повторно отправить эти данные для идентифицированного пользователя. Также важно учитывать, что после идентификации пользователя следует заново запросить все пейволы и продукты, поскольку данные нового пользователя могут отличаться. ::: ### Выход и вход пользователя \{#logging-out-and-logging-in\} Вы можете выйти из аккаунта пользователя в любой момент, вызвав метод `.logout()`: ```csharp showLineNumbers Adapty.Logout((error) => { if(error == null) { // successful logout } }); ``` После этого можно снова войти, используя метод `.identify()`. ## Назначение `appAccountToken` (iOS) \{#assign-appaccounttoken-ios\} [`appAccountToken`](https://developer.apple.com/documentation/storekit/product/purchaseoption/appaccounttoken(_:)) — это **UUID**, который позволяет связать транзакции App Store с внутренним идентификатором пользователя. StoreKit привязывает этот токен к каждой транзакции, поэтому ваш бэкенд может сопоставить данные App Store с конкретными пользователями. Используйте стабильный UUID, сгенерированный для каждого пользователя, и применяйте его для одного и того же аккаунта на всех устройствах. Это гарантирует, что покупки и уведомления App Store будут правильно привязаны к нужному пользователю. Токен можно задать двумя способами — при активации SDK или при идентификации пользователя. :::important `appAccountToken` необходимо всегда передавать вместе с `customerUserId`. Если передать только токен, он не будет включён в транзакцию. ::: ```csharp showLineNumbers title="Unity" using UnityEngine; using AdaptySDK; using System; // During configuration: var appAccountToken = new Guid("YOUR_APP_ACCOUNT_TOKEN"); var builder = new AdaptyConfiguration.Builder("YOUR_API_KEY") .SetCustomerUserId("YOUR_USER_ID", appAccountToken); Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); // Or when identifying users Adapty.Identify("YOUR_USER_ID", appAccountToken, (error) => { if (error == null) { // successful identify } }); ``` ## Установка обфусцированных идентификаторов аккаунта (Android) \{#set-obfuscated-account-ids-android\} Google Play требует обфусцированные идентификаторы аккаунта в ряде случаев — для защиты конфиденциальности и безопасности пользователей. Эти идентификаторы позволяют Google Play отслеживать покупки, не раскрывая личные данные пользователей, что особенно важно для предотвращения мошенничества и аналитики. Они могут понадобиться, если приложение работает с чувствительными пользовательскими данными или если вы обязаны соблюдать определённые требования по конфиденциальности. Обфусцированные идентификаторы позволяют Google Play отслеживать покупки, не раскрывая реальные пользовательские идентификаторы. ```csharp showLineNumbers title="Unity" using UnityEngine; using AdaptySDK; // During configuration: var builder = new AdaptyConfiguration.Builder("YOUR_API_KEY") .SetCustomerUserId("YOUR_USER_ID", null, "YOUR_OBFUSCATED_ACCOUNT_ID"); Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); // Or when identifying users Adapty.Identify("YOUR_USER_ID", null, "YOUR_OBFUSCATED_ACCOUNT_ID", (error) => { if (error == null) { // successful identify } }); ``` ## Обнаружение пользователей на разных устройствах \{#detect-users-across-devices\} При активации SDK он автоматически считывает существующие права пользователя из StoreKit (iOS) или Google Play Billing (Android) и синхронизирует их с бэкендом Adapty. Активная подписка появляется в профиле Adapty без вызова `restorePurchases` со стороны приложения. Что **не** происходит автоматически — так это распознавание того, что профиль на новом устройстве принадлежит тому же пользователю, что и профиль на исходном устройстве. Adapty сопоставляет профили по Customer User ID, поэтому непрерывность идентификации зависит от того, что вы используете в качестве CUID. **Что Adapty может определить между устройствами** | Ваша настройка | Что Adapty определяет | Что нужно сделать | | --- | --- | --- | | Customer User ID = `device_id` (без входа в аккаунт) | Новое устройство получает другой CUID и, следовательно, другой профиль. Подписка синхронизируется с новым профилем через событие **Access level updated**, но `subscription_started` не срабатывает — новый профиль считается наследником исходной покупки. Аналитика, основанная на `subscription_started`, будет занижать количество возвращающихся пользователей. | Используйте стабильный идентификатор аккаунта в качестве Customer User ID, чтобы вернувшийся пользователь соответствовал существующему профилю на разных устройствах. | | Customer User ID = стабильный идентификатор аккаунта (вход на каждом устройстве) | SDK автоматически синхронизирует подписку при `activate()`, а `identify()` сопоставляет существующий профиль по CUID. | Никаких дополнительных действий не требуется — и идентификация, и подписка разрешаются автоматически. | | Наследник Apple Family Sharing | Член семьи получает подписку только через событие **Access level updated** — `subscription_started` не срабатывает. | Отслеживайте событие **Access level updated**. Полную матрицу событий см. в разделе [Apple Family Sharing](apple-family-sharing). | | Один аккаунт Apple/Google, разные пользователи внутри приложения | Первый профиль, зафиксировавший покупку, становится родительским. Последующие профили видят подписку через цепочку наследования с одним событием **Access level updated**. | Требуйте входа в аккаунт, затем выберите [режим совместного использования](sharing-paid-access-between-user-accounts), подходящий для вашей модели. | **Восстановление покупок на новом устройстве** Добавьте кнопку «Восстановить покупки», инициируемую пользователем, на свой пейвол. Apple App Review (руководство 3.1.1) её требует, и она служит запасным вариантом на случай, если автоматическая синхронизация пропустит граничный случай. Кнопка должна вызывать `restorePurchases` в вашем SDK. Программный вызов `restorePurchases` при первом запуске не нужен для обычного использования — SDK уже выполняет аналогичную операцию при `activate()`. Программные вызовы стоит использовать только для принудительной проверки чека, например при отладке отсутствующего доступа после завершения `activate()`. --- # File: unity-setting-user-attributes --- --- title: "Установка атрибутов пользователя в Unity SDK" description: "Узнайте, как обновлять атрибуты пользователя и данные профиля в приложении Unity с помощью Adapty SDK." --- Вы можете задавать пользователям приложения дополнительные атрибуты: email, номер телефона и т. д. Атрибуты можно использовать для создания пользовательских [сегментов](segments) или просто просматривать их в CRM. ### Установка атрибутов пользователя \{#setting-user-attributes\} Чтобы задать атрибуты пользователя, вызовите метод `.updateProfile()`: ```csharp showLineNumbers var builder = new Adapty.ProfileParameters.Builder() .SetFirstName("John") .SetLastName("Appleseed") .SetBirthday(new DateTime(1970, 1, 3)) .SetGender(ProfileGender.Female) .SetEmail("example@adapty.io"); Adapty.UpdateProfile(builder.Build(), (error) => { if(error != nil) { // handle the error } }); ``` Обратите внимание: атрибуты, ранее установленные с помощью метода `updateProfile`, не сбрасываются. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ### Список допустимых ключей \{#the-allowed-keys-list\} Допустимые ключи `<Key>` для `AdaptyProfileParameters.Builder` и соответствующие значения `<Value>` перечислены ниже: | Ключ | Значение | |---|-----| | <p>email</p><p>phoneNumber</p><p>firstName</p><p>lastName</p> | String | | gender | Enum, допустимые значения: `female`, `male`, `other` | | birthday | Date | ### Пользовательские атрибуты \{#custom-user-attributes\} Вы можете задавать собственные атрибуты, связанные с использованием приложения. Например, для фитнес-приложений это может быть количество тренировок в неделю, для приложений по изучению языков — уровень знаний пользователя и т. д. Атрибуты можно применять в сегментах для создания персонализированных пейволов и предложений, а также в аналитике — чтобы понять, какие продуктовые метрики сильнее всего влияют на выручку. ```csharp showLineNumbers try { builder = builder.SetCustomStringAttribute("string_key", "string_value"); builder = builder.SetCustomDoubleAttribute("double_key", 123.0f); } catch (Exception e) { // handle the exception } ``` Чтобы удалить существующий ключ, используйте метод `.withRemoved(customAttributeForKey:)`: ```csharp showLineNumbers try { builder = builder.RemoveCustomAttribute("key_to_remove"); } catch (Exception e) { // handle the exception } ``` Иногда нужно узнать, какие пользовательские атрибуты уже установлены. Для этого используйте поле `customAttributes` объекта `AdaptyProfile`. :::warning Имейте в виду, что значение `customAttributes` может быть устаревшим: атрибуты пользователя могут отправляться с разных устройств в любое время, поэтому данные на сервере могут измениться после последней синхронизации. ::: ### Ограничения \{#limits\} - До 30 пользовательских атрибутов на одного пользователя. - Длина имени ключа — до 30 символов. Допустимые символы: буквы, цифры, а также `_`, `-`, `.`. - Значение может быть строкой или числом с плавающей точкой длиной не более 50 символов. --- # File: unity-listen-subscription-changes --- --- title: "Проверка статуса подписки в Unity SDK" description: "Отслеживайте и управляйте статусом подписки пользователей в Adapty для повышения удержания клиентов в вашем Unity-приложении." --- С Adapty отслеживать статус подписки очень просто. Не нужно вручную прописывать идентификаторы продуктов в коде — достаточно проверить наличие активного [уровня доступа](access-level), чтобы узнать, есть ли у пользователя активная подписка. <details> <summary>Перед тем как проверять статус подписки (нажмите, чтобы раскрыть)</summary> - Для iOS настройте [App Store Server Notifications](enable-app-store-server-notifications) - Для Android настройте [Real-time Developer Notifications (RTDN)](enable-real-time-developer-notifications-rtdn) </details> ## Уровень доступа и объект AdaptyProfile \{#access-level-and-the-adaptyprofile-object\} Уровни доступа — это свойства объекта [AdaptyProfile](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_profile.html). Рекомендуем получать профиль при запуске приложения, например, когда вы [идентифицируете пользователя](unity-identifying-users#setting-customer-user-id-on-configuration), и обновлять его при любых изменениях. Так вы сможете использовать объект профиля, не запрашивая его каждый раз заново. Чтобы получать уведомления об обновлениях профиля, подпишитесь на изменения профиля, как описано в разделе [Отслеживание обновлений статуса подписки](#listening-for-subscription-status-updates) ниже. :::tip Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши [примеры приложений](sample-apps) — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции. ::: ## Получение уровня доступа с сервера \{#retrieving-the-access-level-from-the-server\} Чтобы получить уровень доступа с сервера, используйте метод `.GetProfile()`: ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // check the access }); ``` Параметры ответа: | Параметр | Описание | | --------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Profile | <p>Объект [AdaptyProfile](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_profile.html). Как правило, для определения доступа к премиум-функциям достаточно проверить статус уровня доступа профиля.</p><p></p><p>Метод `.getProfile` всегда пытается обратиться к API и возвращает наиболее актуальные данные. Если по какой-то причине (например, при отсутствии интернета) Adapty SDK не может получить данные с сервера, возвращаются данные из кэша. Важно учитывать, что Adapty SDK регулярно обновляет кэш `AdaptyProfile`, чтобы поддерживать информацию в актуальном состоянии.</p> | Метод `.getProfile()` возвращает профиль пользователя, из которого можно получить статус уровня доступа. В приложении может быть несколько уровней доступа. Например, в газетном приложении с независимыми подписками на разные темы можно создать уровни доступа «sports» и «science». Но в большинстве случаев достаточно одного уровня доступа — тогда можно просто использовать уровень доступа "premium" по умолчанию. Пример проверки уровня доступа "premium" по умолчанию: ```csharp showLineNumbers Adapty.GetProfile((profile, error) => { if (error != null) { // handle the error return; } // "premium" is an identifier of default access level var accessLevel = profile.AccessLevels["premium"]; if (accessLevel != null && accessLevel.IsActive) { // grant access to premium features } }); ``` ### Отслеживание обновлений статуса подписки \{#listening-for-subscription-status-updates\} При каждом изменении подписки пользователя Adapty генерирует событие. Чтобы получать сообщения от Adapty, необходимо выполнить дополнительную настройку: ```csharp showLineNumbers // Extend `AdaptyEventListener ` with `OnLoadLatestProfile ` method: public class AdaptyListener : MonoBehaviour, AdaptyEventListener { public void OnLoadLatestProfile(AdaptyProfile profile) { // handle any changes to subscription state } } ``` Adapty также генерирует событие при запуске приложения. В этом случае передаётся кэшированный статус подписки. ### Кэш статуса подписки \{#subscription-status-cache\} Кэш, реализованный в Adapty SDK, хранит статус подписки профиля. Это означает, что даже при недоступности сервера из кэша можно получить информацию о статусе подписки профиля. Однако важно учитывать, что напрямую запрашивать данные из кэша невозможно. SDK периодически обращается к серверу каждую минуту, чтобы проверить наличие обновлений профиля. При наличии изменений — например, новых транзакций или других обновлений — они записываются в кэш, чтобы он оставался синхронизированным с сервером. --- # File: unity-deal-with-att --- --- title: "Работа с ATT в Unity SDK" description: "Начните работу с Adapty на Unity для удобной настройки и управления подписками." --- Если ваше приложение использует фреймворк AppTrackingTransparency и запрашивает у пользователя разрешение на отслеживание, необходимо передать [статус авторизации](https://developer.apple.com/documentation/apptrackingtransparency/attrackingmanager/authorizationstatus/) в Adapty. ```csharp showLineNumbers var builder = new Adapty.ProfileParameters.Builder() .SetAppTrackingTransparencyStatus(IOSAppTrackingTransparencyStatus.Authorized); Adapty.UpdateProfile(builder.Build(), (error) => { if(error != null) { // handle the error } }); ``` :::warning Настоятельно рекомендуем передавать это значение как можно раньше при каждом его изменении — только тогда данные будут своевременно отправлены в настроенные вами интеграции. ::: --- # File: kids-mode-unity --- --- title: "Режим для детей в Unity SDK" description: "Легко включите режим для детей, чтобы соответствовать политикам Apple и Google. В Unity SDK не собираются IDFA, GAID или рекламные данные." --- Если ваше Unity-приложение предназначено для детей, вы обязаны соблюдать политики [Apple](https://developer.apple.com/kids/) и [Google](https://support.google.com/googleplay/android-developer/answer/9893335). Если вы используете Adapty SDK, несколько простых шагов помогут настроить его в соответствии с этими политиками и пройти проверку в сторах. ## Что нужно сделать? \{#whats-required\} Необходимо настроить Adapty SDK так, чтобы отключить сбор следующих данных: - [IDFA (Identifier for Advertisers)](https://en.wikipedia.org/wiki/Identifier_for_Advertisers) (iOS) - [Android Advertising ID (AAID/GAID)](https://support.google.com/googleplay/android-developer/answer/6048248) (Android) - [IP-адрес](https://www.ftc.gov/system/files/ftc_gov/pdf/p235402_coppa_application.pdf) Кроме того, рекомендуем осторожно использовать пользовательский ID. Идентификатор в формате `<FirstName.LastName>` однозначно будет расценён как сбор персональных данных — как и использование email. Для режима для детей лучшая практика — применять рандомизированные или анонимизированные идентификаторы (например, хешированные ID или UUID, сгенерированные на устройстве), чтобы обеспечить соответствие требованиям. ## Включение режима для детей \{#enabling-kids-mode\} ### Изменения в дашборде Adapty \{#updates-in-the-adapty-dashboard\} В дашборде Adapty необходимо отключить сбор IP-адресов. Для этого перейдите в [App settings](https://app.adapty.io/settings/general) и нажмите **Disable IP address collection** в разделе **Collect users' IP address**. ### Изменения в коде мобильного приложения \{#updates-in-your-mobile-app-code\} Поддержка режима для детей в Unity появится в ближайшее время! Пока вы можете воспользоваться гайдами для нативных платформ: - [Режим для детей в iOS SDK](kids-mode) — настройка для iOS - [Режим для детей в Android SDK](kids-mode-android) — настройка для Android --- # File: unity-get-onboardings --- --- title: "Получение онбордингов в Unity SDK" description: "Узнайте, как получать онбординги в Adapty для Unity." --- После того как вы [создали визуальную часть онбординга](design-onboarding) с помощью билдера в дашборде Adapty, его можно отобразить в вашем Unity-приложении. Первый шаг — получить онбординг, связанный с плейсментом, и его конфигурацию отображения, как описано ниже. Прежде чем начать, убедитесь, что: 1. Установлен [Adapty Unity SDK](sdk-installation-unity) версии 3.14.0 или выше. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). ## Получение онбординга и создание представления \{#fetch-onboarding-and-create-view\} Когда вы создаёте [онбординг](onboardings) с помощью нашего no-code билдера, он сохраняется как контейнер с конфигурацией, которую приложение должно получить и отобразить. Этот контейнер управляет всем процессом: какой контент отображается, как он представлен и как обрабатываются действия пользователя (например, ответы на тесты или ввод данных в формы). Контейнер также автоматически отслеживает аналитические события, поэтому отдельно реализовывать отслеживание просмотров не нужно. Для лучшей производительности загружайте конфигурацию онбординга заранее, чтобы изображения успели скачаться до показа пользователям. Чтобы получить онбординг, используйте метод `GetOnboarding`: ```csharp showLineNumbers Adapty.GetOnboarding("YOUR_PLACEMENT_ID", (onboarding, error) => { if (error != null) { // handle the error return; } // the requested onboarding }); ``` Параметры: | Параметр | Обязательность | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указывали при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | <p>Идентификатор локализации онбординга. Ожидается языковой код, состоящий из одного или двух подтегов, разделённых символом минус (**-**). Первый подтег — язык, второй — регион.</p><p></p><p>Пример: `en` — английский, `pt-br` — бразильский португальский.</p><p>Подробнее о кодах локалей и рекомендациях по их использованию см. в разделе [Локализации и коды локалей](flutter-localizations-and-locale-codes).</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кэшированные данные в случае ошибки. Мы рекомендуем этот вариант, так как он гарантирует актуальность данных для пользователей.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите `.returnCacheDataElseLoad` — он возвращает кэш, если он есть. В этом случае данные могут быть не самыми свежими, зато загрузка будет быстрее при любом качестве соединения. Кэш регулярно обновляется, поэтому использовать его в течение сессии для сокращения сетевых запросов безопасно.</p><p></p><p>Обратите внимание: кэш сохраняется при перезапуске приложения и очищается только при переустановке или ручной очистке.</p><p></p><p>Adapty SDK хранит онбординги локально в двух слоях: регулярно обновляемый кэш (описан выше) и резервные онбординги. Также используется CDN для ускорения загрузки и отдельный резервный сервер на случай недоступности CDN. Эта система обеспечивает актуальность онбордингов и надёжность даже при нестабильном интернет-соединении.</p> | | **loadTimeout** | по умолчанию: 5 сек | <p>Ограничивает таймаут выполнения метода. По истечении таймаута возвращаются кэшированные данные или локальный резервный вариант.</p><p>Обратите внимание: в редких случаях метод может превысить таймаут, указанный в `loadTimeout`, поскольку операция может включать несколько запросов под капотом.</p> | Параметры ответа: | Параметр | Описание | |:----------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Onboarding | Объект [`AdaptyOnboarding`](https://unity.adapty.io/class_adapty_s_d_k_1_1_adapty_onboarding.html), содержащий: идентификатор и конфигурацию онбординга, Remote Config и ряд других свойств. | После получения онбординга вызовите метод `CreateOnboardingView`. :::warning Результат метода `CreateOnboardingView` можно использовать только один раз. Если нужно использовать его повторно, вызовите `CreateOnboardingView` заново. Повторный вызов без пересоздания может привести к ошибке `AdaptyUIError.viewAlreadyPresented`. ::: ```csharp showLineNumbers AdaptyUI.CreateOnboardingView(onboarding, (view, error) => { // handle the result }); ``` Параметры: | Параметр | Обязательность | Описание | |:---------------| :------------- |:-----------------------------------------------------------------------------| | **onboarding** | обязательный | Объект `AdaptyOnboarding` для получения представления нужного онбординга. | | **externalUrlsPresentation** | <p>опциональный</p><p>по умолчанию: `InAppBrowser`</p> | <p>Управляет тем, как открываются ссылки в онбординге. Доступные варианты:</p><p>- `AdaptyWebPresentation.InAppBrowser` — открывает ссылки во встроенном браузере (по умолчанию)</p><p>- `AdaptyWebPresentation.ExternalBrowser` — открывает ссылки во внешнем браузере устройства</p><p>Примеры использования см. в разделе [Настройка открытия ссылок в онбордингах](unity-present-onboardings#customize-how-links-open-in-onboardings).</p> | После успешной загрузки онбординга и его конфигурации отображения вы можете [показать его в мобильном приложении](unity-present-onboardings). ## Ускорение загрузки онбординга с помощью онбординга для аудитории по умолчанию \{#speed-up-onboarding-fetching-with-default-audience-onboarding\} Как правило, онбординги загружаются практически мгновенно, и беспокоиться об ускорении этого процесса не нужно. Однако если у вас много аудиторий и онбордингов, а у пользователей слабое интернет-соединение, загрузка онбординга может занять больше времени, чем хотелось бы. В таких случаях можно отображать онбординг по умолчанию, чтобы обеспечить плавный пользовательский опыт, а не показывать пустой экран. Для этого используйте метод `GetOnboardingForDefaultAudience`, который загружает онбординг указанного плейсмента для аудитории **All Users**. Важно понимать, что рекомендуемый подход — получать онбординг методом `getOnboarding`, как описано в разделе [Получение онбординга](#fetch-onboarding) выше. :::warning По возможности используйте `GetOnboarding` вместо `GetOnboardingForDefaultAudience`, поскольку у последнего есть существенные ограничения: - **Проблемы совместимости**: могут возникнуть сложности при поддержке нескольких версий приложения — придётся либо делать обратно совместимый дизайн, либо мириться с тем, что старые версии могут отображаться некорректно. - **Отсутствие персонализации**: отображается только контент для аудитории «All Users» без таргетинга по стране, атрибуции или пользовательским атрибутам. Если скорость загрузки важнее этих ограничений для вашего случая, используйте `GetOnboardingForDefaultAudience`, как показано ниже. В остальных случаях используйте `GetOnboarding`, как описано [выше](#fetch-onboarding). ::: ```csharp showLineNumbers Adapty.GetOnboardingForDefaultAudience("YOUR_PLACEMENT_ID", (onboarding, error) => { if (error != null) { // handle the error return; } // the requested onboarding }); ``` Параметры: | Параметр | Обязательность | Описание | |---------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **placementId** | обязательный | Идентификатор нужного [плейсмента](placements). Это значение вы указывали при создании плейсмента в дашборде Adapty. | | **locale** | <p>опциональный</p><p>по умолчанию: `en`</p> | <p>Идентификатор локализации онбординга. Ожидается языковой код, состоящий из одного или двух подтегов, разделённых символом минус (**-**). Первый подтег — язык, второй — регион.</p><p></p><p>Пример: `en` — английский, `pt-br` — бразильский португальский.</p> | | **fetchPolicy** | по умолчанию: `.reloadRevalidatingCacheData` | <p>По умолчанию SDK пытается загрузить данные с сервера и возвращает кэшированные данные в случае ошибки. Мы рекомендуем этот вариант, так как он гарантирует актуальность данных для пользователей.</p><p></p><p>Однако если у ваших пользователей нестабильное интернет-соединение, рассмотрите `.returnCacheDataElseLoad` — он возвращает кэш, если он есть. В этом случае данные могут быть не самыми свежими, зато загрузка будет быстрее при любом качестве соединения. Кэш регулярно обновляется, поэтому использовать его в течение сессии для сокращения сетевых запросов безопасно.</p><p></p><p>Обратите внимание: кэш сохраняется при перезапуске приложения и очищается только при переустановке или ручной очистке.</p><p></p><p>Adapty SDK хранит онбординги локально в двух слоях: регулярно обновляемый кэш (описан выше) и резервные онбординги. Также используется CDN для ускорения загрузки и отдельный резервный сервер на случай недоступности CDN. Эта система обеспечивает актуальность онбордингов и надёжность даже при нестабильном интернет-соединении.</p> | --- # File: unity-present-onboardings --- --- title: "Показ онбординга в Unity SDK" description: "Узнайте, как эффективно показывать онбординги для повышения конверсии." --- Если вы настроили онбординг с помощью билдера, вам не нужно беспокоиться о его отрисовке в коде Unity-приложения для показа пользователю. Такой онбординг содержит как то, что должно отображаться внутри него, так и то, как именно это должно отображаться. Прежде чем начать, убедитесь, что: 1. Вы установили [Adapty Unity SDK](sdk-installation-unity) версии 3.14.0 или новее. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). Чтобы отобразить онбординг, вызовите метод `view.Present()` на объекте `view`, созданном методом `CreateOnboardingView`. Каждый `view` можно использовать только один раз. Если нужно показать пейвол повторно, вызовите `CreateOnboardingView` ещё раз, чтобы создать новый экземпляр `view`. :::warning Повторное использование одного и того же `view` без его пересоздания может привести к ошибке `AdaptyUIError.viewAlreadyPresented`. ::: ```csharp showLineNumbers title="Unity" view.Present((presentError) => { if (presentError != null) { // handle the error } }; ``` ## Настройка стиля презентации на iOS \{#configure-ios-presentation-style\} Настройте способ отображения онбординга на iOS, передав параметр `iosPresentationStyle` в метод `Present()`. Параметр принимает значения `AdaptyUIIOSPresentationStyle.FullScreen` (по умолчанию) или `AdaptyUIIOSPresentationStyle.PageSheet`. ```csharp showLineNumbers title="Unity" view.Present(AdaptyUIIOSPresentationStyle.PageSheet, (error) => { // handle the error }); ``` ## Настройка открытия ссылок в онбординге \{#customize-how-links-open-in-onboardings\} :::important Настройка способа открытия ссылок в онбординге поддерживается начиная с Adapty SDK v3.15. ::: По умолчанию ссылки в онбординге открываются во встроенном браузере — это обеспечивает удобство работы, поскольку веб-страницы отображаются прямо внутри приложения без переключения между приложениями. Чтобы ссылки открывались во внешнем браузере, передайте `AdaptyWebPresentation.ExternalBrowser` в метод `CreateOnboardingView`: ```csharp showLineNumbers title="Unity" AdaptyUI.CreateOnboardingView( onboarding, AdaptyWebPresentation.ExternalBrowser, // default — InAppBrowser (view, error) => { if (error != null) { // handle the error return; } // present the onboarding view view.Present((presentError) => { if (presentError != null) { // handle the error } }); } ); ``` Доступные варианты: - `AdaptyWebPresentation.InAppBrowser` — открывает ссылки во встроенном браузере (по умолчанию) - `AdaptyWebPresentation.ExternalBrowser` — открывает ссылки во внешнем браузере устройства --- # File: unity-handling-onboarding-events --- --- title: "Обработка событий онбординга в Unity SDK" description: "Обрабатывайте события онбординга в Unity с помощью Adapty." --- Прежде чем начать, убедитесь, что: 1. Вы установили [Adapty Unity SDK](sdk-installation-unity) версии 3.14.0 или новее. 2. Вы [создали онбординг](create-onboarding). 3. Вы добавили онбординг в [плейсмент](placements). Онбординги, настроенные с помощью конструктора, генерируют события, на которые может реагировать ваше приложение. Ниже описано, как с ними работать. Чтобы управлять процессами на экране онбординга в Unity-приложении или отслеживать их, реализуйте интерфейс `AdaptyOnboardingsEventsListener`. ## Пользовательские действия \{#custom-actions\} В конструкторе можно добавить к кнопке действие **custom** и назначить ему ID. <img src="/assets/shared/img/ios-events-1.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> Затем вы можете использовать этот ID в своём коде и обрабатывать его как пользовательское действие. Например, если пользователь нажимает на кнопку, такую как **Login** или **Allow notifications**, будет вызван метод `OnboardingViewOnCustomAction` с параметром `actionId`, соответствующим **Action ID** из билдера. Вы можете задавать собственные ID, например "allowNotifications". Чтобы обрабатывать события онбординга, реализуйте интерфейс `AdaptyOnboardingsEventsListener`: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { void Start() { Adapty.SetOnboardingsEventsListener(this); } public void OnboardingViewOnCustomAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, string actionId ) { if (actionId == "allowNotifications") { // request notification permissions } } public void OnboardingViewDidFailWithError( AdaptyUIOnboardingView view, AdaptyError error ) { // handle errors } // Implement other required interface methods (see examples below) } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "actionId": "allowNotifications", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 } } ``` </Details> ## Закрытие онбординга \{#closing-onboarding\} Онбординг считается закрытым, когда пользователь нажимает кнопку с назначенным действием **Close**. <img src="/assets/shared/img/ios-events-2.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> :::important Обратите внимание: вам нужно самостоятельно обработать закрытие онбординга. Например, скрыть экран онбординга. ::: Реализуйте метод `OnboardingViewOnCloseAction` в своём классе: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewOnCloseAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, string actionId ) { view.Dismiss((error) => { if (error != null) { // handle the error } }); } // ... other interface methods } ``` <Details> <summary>Пример события (нажмите, чтобы раскрыть)</summary> ```json { "action_id": "close_button", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> ## Открытие пейвола \{#opening-a-paywall\} :::tip Обрабатывайте это событие, чтобы открыть пейвол внутри онбординга. Если вы хотите открыть пейвол после его закрытия, есть более простой способ — обработайте [`OnboardingViewOnCloseAction`](#closing-onboarding) и откройте пейвол, не опираясь на данные события. ::: Самый удобный подход — сделать ID действия равным ID плейсмента пейвола. Тогда после получения события `OnboardingViewOnPaywallAction` можно сразу использовать этот ID, чтобы получить и открыть нужный пейвол. Обратите внимание, что в iOS одновременно на экране может отображаться только одно представление (пейвол или онбординг). Если вы показываете пейвол поверх онбординга, вы не можете программно управлять онбордингом в фоне. Попытка закрыть онбординг закроет пейвол вместо него, и онбординг останется видимым. Чтобы избежать этого, всегда закрывайте представление онбординга перед показом пейвола. ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewOnPaywallAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, string actionId ) { // Dismiss onboarding before presenting paywall view.Dismiss((dismissError) => { if (dismissError != null) { // handle the error return; } Adapty.GetPaywall(actionId, (paywall, error) => { if (error != null) { // handle the error return; } AdaptyUI.CreatePaywallView(paywall, (paywallView, createError) => { if (createError != null) { // handle the error return; } paywallView.Present((presentError) => { if (presentError != null) { // handle the error } }); }); }); }); } // ... other interface methods } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "action_id": "premium_offer_1", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "pricing_screen", "screen_index": 2, "total_screens": 4 } } ``` </Details> ## Завершение загрузки онбординга \{#finishing-loading-onboarding\} Когда онбординг завершает загрузку, реализуйте метод `OnboardingViewDidFinishLoading`: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewDidFinishLoading( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta ) { // handle loading completion } // ... other interface methods } ``` <Details> <summary>Пример события (нажмите, чтобы развернуть)</summary> ```json { "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } ``` </Details> ## Отслеживание навигации \{#tracking-navigation\} Метод `OnboardingViewOnAnalyticsEvent` вызывается при возникновении различных аналитических событий в ходе флоу онбординга. Объект `analyticsEvent` может быть одного из следующих типов: |Тип | Описание | |------------|-------------| | `AdaptyOnboardingsAnalyticsEventOnboardingStarted` | Когда онбординг загружен | | `AdaptyOnboardingsAnalyticsEventScreenPresented` | Когда отображается любой экран | | `AdaptyOnboardingsAnalyticsEventScreenCompleted` | Когда экран завершён. Включает необязательный `ElementId` (идентификатор завершённого элемента) и необязательный `Reply` (ответ пользователя). Срабатывает, когда пользователь выполняет любое действие для выхода с экрана. | | `AdaptyOnboardingsAnalyticsEventSecondScreenPresented` | Когда отображается второй экран | | `AdaptyOnboardingsAnalyticsEventUserEmailCollected` | Срабатывает, когда email пользователя собирается через поле ввода | | `AdaptyOnboardingsAnalyticsEventOnboardingCompleted` | Срабатывает, когда пользователь достигает экрана с идентификатором `final`. Если вам нужно это событие, [назначьте идентификатор `final` последнему экрану](design-onboarding). | | `AdaptyOnboardingsAnalyticsEventUnknown` | Для любого нераспознанного типа события. Включает `Name` (название неизвестного события) и `meta` (дополнительные метаданные) | Каждое событие содержит мета-информацию (`meta`) со следующими полями: | Поле | Описание | |------------|-------------| | `OnboardingId` | Уникальный идентификатор онбординга | | `ScreenClientId` | Идентификатор текущего экрана | | `ScreenIndex` | Позиция текущего экрана в флоу | | `ScreensTotal` | Общее количество экранов в флоу | Пример использования аналитических событий для отслеживания: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewOnAnalyticsEvent( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, AdaptyOnboardingsAnalyticsEvent analyticsEvent ) { switch (analyticsEvent) { case AdaptyOnboardingsAnalyticsEventOnboardingStarted: // track onboarding start TrackEvent("onboarding_started", meta); break; case AdaptyOnboardingsAnalyticsEventScreenPresented: // track screen presentation TrackEvent("screen_presented", meta); break; case AdaptyOnboardingsAnalyticsEventScreenCompleted screenCompleted: // track screen completion with user response TrackEvent("screen_completed", meta, screenCompleted.ElementId, screenCompleted.Reply); break; case AdaptyOnboardingsAnalyticsEventOnboardingCompleted: // track successful onboarding completion TrackEvent("onboarding_completed", meta); break; case AdaptyOnboardingsAnalyticsEventUnknown unknownEvent: // handle unknown events TrackEvent(unknownEvent.Name, meta); break; // handle other cases as needed } } // ... other interface methods } ``` :::note Метод `TrackEvent` — это заглушка, которую нужно реализовать самостоятельно для отправки аналитики в выбранный вами сервис. ::: <Details> <summary>Примеры событий (нажмите, чтобы развернуть)</summary> ```javascript // onboardingStarted { "name": "onboarding_started", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "welcome_screen", "screen_index": 0, "total_screens": 4 } } // screenPresented { "name": "screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "interests_screen", "screen_index": 2, "total_screens": 4 } } // screenCompleted { "name": "screen_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 }, "params": { "element_id": "profile_form", "reply": "success" } } // secondScreenPresented { "name": "second_screen_presented", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // userEmailCollected { "name": "user_email_collected", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "profile_screen", "screen_index": 1, "total_screens": 4 } } // onboardingCompleted { "name": "onboarding_completed", "meta": { "onboarding_id": "onboarding_123", "screen_cid": "final_screen", "screen_index": 3, "total_screens": 4 } } ``` </Details> --- # File: unity-onboarding-input --- --- title: "Обработка данных из онбординга в Unity SDK" description: "Сохраняйте и используйте данные из онбординга в вашем Unity-приложении с помощью Adapty SDK." --- Когда пользователи отвечают на вопрос викторины или вводят данные в поле ввода, вызывается метод `OnboardingViewOnStateUpdatedAction`. Вы можете сохранить или обработать тип поля в своём коде. Реализуйте метод `OnboardingViewOnStateUpdatedAction` в вашем классе: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, string elementId, AdaptyOnboardingsStateUpdatedParams @params ) { switch (@params) { case AdaptyOnboardingsSelectParams selectParams: // handle single selection break; case AdaptyOnboardingsMultiSelectParams multiSelectParams: // handle multiple selections break; case AdaptyOnboardingsInputParams inputParams: // handle text input break; case AdaptyOnboardingsDatePickerParams datePickerParams: // handle date selection break; } } // ... other interface methods } ``` Параметры включают: | Параметр | Описание | |---|---| | `elementId` | Уникальный идентификатор элемента ввода. Используйте его, чтобы связать вопросы с ответами при их сохранении. | | `@params` | Объект с данными, введёнными пользователем. Может быть одного из следующих типов. | | `AdaptyOnboardingsSelectParams` | Одиночный выбор из вариантов. Содержит `Id`, `Value`, `Label` | | `AdaptyOnboardingsMultiSelectParams` | Множественный выбор из вариантов. Содержит список `Params` (каждый с `Id`, `Value`, `Label`)<br/>• `input`: объект с `type`, `value`<br/>• `datePicker`: объект с `day`, `month`, `year` | | `AdaptyOnboardingsInputParams` | Текстовое поле ввода. Содержит `Input`, который может быть `AdaptyOnboardingsTextInput`, `AdaptyOnboardingsEmailInput` или `AdaptyOnboardingsNumberInput` | | `AdaptyOnboardingsDatePickerParams` | Выбор даты. Содержит nullable-поля `Day`, `Month`, `Year` | <Details> <summary>Примеры сохранённых данных (в вашей реализации могут отличаться)</summary> ```javascript // Example of a saved select action { "elementId": "preference_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "preferences_screen", "screenIndex": 1, "screensTotal": 3 }, "params": { "type": "select", "value": { "id": "option_1", "value": "premium", "label": "Premium Plan" } } } // Example of a saved multi-select action { "elementId": "interests_selector", "meta": { "onboardingId": "onboarding_123", "screenClientId": "interests_screen", "screenIndex": 2, "screensTotal": 3 }, "params": { "type": "multiSelect", "value": [ { "id": "interest_1", "value": "sports", "label": "Sports" }, { "id": "interest_2", "value": "music", "label": "Music" } ] } } // Example of a saved input action { "elementId": "name_input", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "input", "value": { "type": "text", "value": "John Doe" } } } // Example of a saved date picker action { "elementId": "birthday_picker", "meta": { "onboardingId": "onboarding_123", "screenClientId": "profile_screen", "screenIndex": 0, "screensTotal": 3 }, "params": { "type": "datePicker", "value": { "day": 15, "month": 6, "year": 1990 } } } ``` </Details> ## Варианты использования \{#use-cases\} ### Обогащение профилей пользователей данными \{#enrich-user-profiles-with-data\} Если вы хотите сразу связать введённые данные с профилем пользователя и не спрашивать его дважды об одном и том же, [обновите профиль пользователя](unity-setting-user-attributes) с этими данными при обработке действия. Например, вы просите пользователей ввести имя в текстовое поле с ID `name` и хотите установить значение этого поля в качестве имени пользователя. Кроме того, вы просите их ввести email в поле `email`. В коде приложения это может выглядеть так: ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, string elementId, AdaptyOnboardingsStateUpdatedParams @params ) { if (@params is AdaptyOnboardingsInputParams inputParams) { var builder = new AdaptyProfileParameters.Builder(); switch (elementId) { case "name": if (inputParams.Input is AdaptyOnboardingsTextInput textInput) { builder.SetFirstName(textInput.Value); } break; case "email": if (inputParams.Input is AdaptyOnboardingsEmailInput emailInput) { builder.SetEmail(emailInput.Value); } break; } Adapty.UpdateProfile(builder.Build(), (error) => { if (error != null) { // handle the error } }); } } // ... other interface methods } ``` ### Кастомизация пейволов на основе ответов \{#customize-paywalls-based-on-answers\} С помощью викторин в онбординге вы также можете настраивать пейволы, которые показываются пользователям после его прохождения. Например, можно спросить пользователей об их опыте в спорте и показывать разные призывы к действию и продукты разным группам пользователей. 1. [Добавьте викторину](onboarding-quizzes) в конструктор онбординга и задайте значимые ID её вариантам ответов. 2. Обрабатывайте ответы викторины по их ID и [устанавливайте пользовательские атрибуты](unity-setting-user-attributes) для пользователей. ```csharp showLineNumbers title="Unity" public class OnboardingManager : MonoBehaviour, AdaptyOnboardingsEventsListener { public void OnboardingViewOnStateUpdatedAction( AdaptyUIOnboardingView view, AdaptyUIOnboardingMeta meta, string elementId, AdaptyOnboardingsStateUpdatedParams @params ) { if (@params is AdaptyOnboardingsSelectParams selectParams) { var builder = new AdaptyProfileParameters.Builder(); switch (elementId) { case "experience": // set custom attribute 'experience' with the selected value (beginner, amateur, pro) builder.SetCustomStringAttribute("experience", selectParams.Value); break; } Adapty.UpdateProfile(builder.Build(), (error) => { if (error != null) { // handle the error } }); } } // ... other interface methods } ``` 3. [Создайте сегменты](segments) для каждого значения пользовательского атрибута. 4. Создайте [плейсмент](placements) и добавьте [аудитории](audience) для каждого созданного сегмента. 5. [Отобразите пейвол](unity-paywalls) для плейсмента в коде приложения. Если в вашем онбординге есть кнопка, открывающая пейвол, реализуйте код пейвола как [реакцию на действие этой кнопки](unity-handling-onboarding-events#opening-a-paywall). --- # File: unity-sdk-call-order --- --- title: "Порядок вызовов в Unity SDK" description: "Избегайте потери premium-доступа, отсутствия атрибуции и периодических ошибок #2002, вызывая методы Adapty SDK в правильном порядке." --- `Adapty.Activate()` должен завершиться до вызова любого другого метода Adapty SDK. Пока не сработает callback завершения, SDK не имеет состояния. Любой вызов, сделанный до или параллельно с `Activate()`, завершится ошибкой [`#2002 notActivated`](unity-handle-errors#custom-network-codes). Если ваше приложение аутентифицирует пользователей и вы получаете customer user ID после запуска, вызовите `Adapty.Identify()` в этот момент. Не вызывайте методы, требующие действий пользователя, пока не сработает колбэк `Identify`. Вызовы, конкурирующие с ним, либо завершаются ошибкой [`#3006 profileWasChanged`](unity-handle-errors#custom-network-codes), либо применяются к анонимному профилю, созданному при активации. В таком случае атрибуция, MMP ID (например, `appsflyer_id`) и принадлежность установки не всегда переносятся на идентифицированный профиль. Если ваше приложение не аутентифицирует пользователей, пропустите `Identify` и продолжайте работу с анонимным профилем. MMP и аналитические SDK (AppsFlyer, Adjust, Branch, PostHog) подчиняются тому же правилу. Сначала инициализируйте их и дождитесь коллбэков с UID, а затем вызывайте `Adapty.Activate`. Иначе MMP ID попадёт в кратковременный анонимный профиль и не всегда переносится в идентифицированный. Подробнее об особенностях AppsFlyer см. в разделе [AppsFlyer](appsflyer). ## Правильный порядок \{#the-correct-order\} Ваш путь зависит от двух вещей: когда вы узнаёте customer user ID и используете ли вы MMP или аналитический SDK. - **Шаги 2 и 5**: Обязательны для каждого приложения. Активируйте SDK, затем вызывайте методы SDK. - **Шаги 1 и 3**: Нужны только если вы интегрируете MMP или аналитический SDK (AppsFlyer, Adjust, Branch, PostHog). - **Шаг 4**: Нужен только если ваше приложение аутентифицирует пользователей и получает customer user ID после запуска. Если у вас есть customer user ID при запуске приложения, передайте его напрямую в `Activate()` (шаг 2a). В этом случае анонимный профиль не создаётся, поэтому шаг 4 не нужен. | Шаг | Вызов | Когда | Примечания | |------|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------| | 1 | Инициализируйте MMP или аналитический SDK (AppsFlyer, Adjust, PostHog, Branch) | При запуске приложения, первым делом | Дождитесь коллбэка с UID от MMP, например `getAppsFlyerId`. | | 2a | `Adapty.Activate(builder.Build(), ...)` с `SetCustomerUserId` на билдере | При запуске приложения, после шага 1, если у вас есть customer user ID | Рекомендуется. Анонимный профиль не создаётся. | | 2b | `Adapty.Activate(builder.Build(), ...)` без `SetCustomerUserId` | При запуске приложения, после шага 1, если customer user ID нет (или вы его не собираете) | Adapty создаёт анонимный профиль. | | 3 | `Adapty.SetIntegrationIdentifier(key, value, callback)` для каждого MMP | После шага 2, до любых вызовов, связанных с действиями пользователя | Обязательно, чтобы идентификаторы MMP попали в правильный профиль. | | 4 | `Adapty.Identify("YOUR_USER_ID", callback)` | После шага 3 (или шага 2, если MMP нет), до шага 5 — только на пути 2b с аутентификацией | Дождитесь коллбэка завершения. Параллельные вызовы во время `Identify` приводят к `#3006 profileWasChanged`. | | 5 | `GetPaywall`, `GetPaywallProducts`, `RestorePurchases`, `MakePurchase`, `UpdateAttribution`, `UpdateProfile` | После шага 4, если вы вызываете `Identify`; иначе после шага 3 (или шага 2, если MMP нет) | Этим вызовам нужен стабильный профиль. | :::important Пропуск этих шагов приведёт к потере уровня доступа у вернувшихся пользователей, отсутствию `appsflyer_id` в профилях и отображению пейволов для неправильной аудитории. ::: ## Установки через web2app и веб-воронки \{#web2app-and-web-funnel-installs\} Если пользователь совершает покупку через веб-чекаут (Stripe, Paddle) и затем устанавливает нативное приложение, первый вызов `Activate()` на устройстве создаёт новый анонимный профиль. Этот профиль не связан с веб-профилем. Если вы можете получить customer user ID до запуска приложения (из потока аутентификации или реферера установки) — передайте его напрямую в `Activate()`. В противном случае веб-покупка останется невидимой на устройстве до тех пор, пока вы не вызовете `Identify("YOUR_USER_ID")`, а затем `RestorePurchases`. Подробнее о метаданных, которые нужно передавать при каждом веб-чекауте, читайте здесь: - [Stripe](stripe) - [Paddle](paddle) --- # File: unity-optimize-paywall-fetching --- --- title: "Оптимизация загрузки пейволов в Unity SDK" description: "Надёжная загрузка пейволов Adapty: тайминг, кеширование и резервные варианты для Unity." --- Надёжная загрузка пейвола в Unity решает три задачи: быстрый рендеринг, возврат пейвола, нацеленного на нужную аудиторию, и корректная работа в резервном режиме при медленной сети. Правила ниже охватывают тайминг, кеширование и резервные паттерны для достижения этих целей. :::tip Правила предполагают, что `Adapty.Activate()` и `Adapty.Identify()` уже выполнены. См. [Порядок вызовов в Unity SDK](unity-sdk-call-order). ::: ## Правила и подводные камни \{#rules-and-pitfalls\} | Делайте так | Не делайте так | Почему | |---|---|---| | Загружайте тот плейсмент, который собираетесь показать. | Не загружайте все плейсменты одновременно при запуске. | Массовая предзагрузка блокирует главный поток и вызывает чёрный экран во время пика нагрузки. | | Вызывайте `GetPaywall` после того, как атрибуция успела разрешиться — например, через 1–2 секунды после `Activate` или после срабатывания `OnLoadLatestProfile`. | Не вызывайте `GetPaywall` в `Awake()`. | Атрибуция ещё не получена. Пейвол разрешается относительно аудитории по умолчанию и незаметно игнорирует сегменты и персонализацию ASA. | | Задайте `loadTimeout` и настройте [резервный пейвол](fallback-paywalls) для каждого плейсмента. | Не ждите ответа `GetPaywall` бесконечно. | Без таймаута пользователи с плохим соединением видят пустой экран, пока сеть не ответит — или просто закрывают приложение. | Подробнее о параметрах `fetchPolicy` и `loadTimeout` см. в [Получение пейволов и продуктов](fetch-paywalls-and-products-unity), а о выборе нужного плейсмента — в [Плейсменты](placements). ## Настройка для нестабильного соединения \{#tune-for-poor-connectivity\} Для рынков с постоянно плохим качеством связи (сельская местность, транспорт, регионы с проблемной маршрутизацией): - Устанавливайте `fetchPolicy` в `AdaptyPlacementFetchPolicy.ReturnCacheDataElseLoad` при каждом запросе, кроме самого первого. - Настройте [резервный пейвол](fallback-paywalls) для каждого плейсмента в дашборде Adapty. - Задайте `loadTimeout` в диапазоне 3–5 секунд и принимайте резервный пейвол при срабатывании таймаута. - Не блокируйте отображение пейвола ожиданием `GetProfile`. Вызывайте `GetPaywall` независимо, чтобы медленная загрузка профиля не задерживала интерфейс. --- # File: unity-test --- --- title: "Тестирование и релиз в Unity SDK" description: "Узнайте, как тестировать и публиковать приложение на Unity с помощью Adapty SDK." --- Если вы уже интегрировали Adapty SDK в своё Unity-приложение, следующий шаг — убедиться, что всё настроено правильно и покупки работают корректно на платформах iOS и Android. Это включает тестирование как самой интеграции SDK, так и реального процесса покупки в песочнице Apple и тестовой среде Google Play. ## Тестирование приложения \{#test-your-app\} Для полноценного тестирования встроенных покупок обратитесь к нашим платформенным гайдам: [Тестирование на iOS](test-purchases-in-sandbox) и [Тестирование на Android](testing-on-android). ## Подготовка к релизу \{#prepare-for-release\} Перед отправкой приложения в стор пройдитесь по [Чеклисту для релиза](release-checklist) и убедитесь, что: - Подключение к стору и серверные уведомления настроены - Покупки проходят и передаются в Adapty - Уровни доступа корректно выдаются и восстанавливаются - Выполнены требования к конфиденциальности и прохождению ревью --- # File: InvalidProductIdentifiers-unity --- --- title: "Исправление ошибки Code-1000 noProductIDsFound в Unity SDK" description: "Устраните ошибки неверных идентификаторов продуктов при управлении подписками в Adapty." --- Ошибка с кодом 1000 — `noProductIDsFound` — означает, что ни один из продуктов, запрошенных на пейволе, недоступен для покупки в App Store, хотя они там указаны. Иногда эта ошибка сопровождается предупреждением `InvalidProductIdentifiers`. Если предупреждение появляется без ошибки, просто проигнорируйте его. Если вы столкнулись с ошибкой `noProductIDsFound`, выполните следующие шаги для её устранения: ## Шаг 1. Проверьте Bundle ID \{#step-2-check-bundle-id\} 1. Откройте [App Store Connect](https://appstoreconnect.apple.com/apps). Выберите своё приложение и перейдите в раздел **General** → **App Information**. 2. Скопируйте **Bundle ID** в подразделе **General Information**. <Zoom> <img src="/docs/img/afd5012-bundle_id_apple.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 3. Откройте вкладку [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) в верхнем меню Adapty и вставьте скопированное значение в поле **Bundle ID**. <Zoom> <img src="/docs/img/2d64163-bundle_id.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> </Zoom> 4. Вернитесь на страницу **App information** в App Store Connect и скопируйте **Apple ID**. 5. На странице [**App settings** -> **iOS SDK**](https://app.adapty.io/settings/ios-sdk) в дашборде Adapty вставьте этот ID в поле **Apple app ID**. ## Шаг 2. Проверьте продукты \{#step-3-check-products\} 1. Перейдите в **App Store Connect** и откройте раздел [**Monetization** → **Subscriptions**](https://appstoreconnect.apple.com/apps/6477523342/distribution/subscriptions) в левом меню. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите на название группы подписок. Вы увидите продукты в разделе **Subscriptions**. 3. Убедитесь, что тестируемый продукт отмечен как **Ready to Submit**. <img src="/assets/shared/img/ready-to-submit.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Сравните идентификатор продукта из таблицы с тем, что указан на вкладке [**Products**](https://app.adapty.io/products) в дашборде Adapty. Если идентификаторы не совпадают, скопируйте идентификатор продукта из таблицы и [создайте продукт](create-product) с ним в дашборде Adapty. <img src="/assets/shared/img/product-id-copy.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 3. Проверьте доступность продуктов \{#step-4-check-product-availability\} 1. Вернитесь в **App Store Connect** и откройте раздел **Subscriptions**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите на название группы подписок, чтобы посмотреть продукты. 3. Выберите продукт, который хотите протестировать. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Прокрутите до раздела **Availability** и убедитесь, что все необходимые страны и регионы указаны. <img src="/assets/shared/img/product-availability.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 4. Проверьте цены продуктов \{#step-5-check-product-prices\} 1. Снова перейдите в раздел **Monetization** → **Subscriptions** в **App Store Connect**. <img src="/assets/shared/img/subscription_group_open.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Нажмите на название группы подписок. 3. Выберите продукт, который хотите протестировать. <img src="/assets/shared/img/click-product.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 4. Прокрутите вниз до раздела **Subscription Pricing** и разверните секцию **Current Pricing for New Subscribers**. <img src="/assets/shared/img/check-prices.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 5. Убедитесь, что все необходимые цены указаны. <img src="/assets/shared/img/product-pricing.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> ## Шаг 5. Убедитесь, что статус приложения, банковский счёт и налоговые формы активны \{#step-5-check-app-paid-status-bank-account-and-tax-forms-are-active\} 1. На главной странице [**App Store Connect**](https://appstoreconnect.apple.com/) нажмите **Business**. <img src="/assets/shared/img/business.webp" style={{ border: '1px solid #727272', /* border width and color */ width: '700px', /* image width */ display: 'block', /* for alignment */ margin: '0 auto' /* center alignment */ }} /> 2. Выберите название вашей компании. 3. Прокрутите вниз и убедитесь, что **Paid Apps Agreement**, **Bank Account** и **Tax forms** отображаются как **Active**. Следуя этим шагам, вы сможете устранить предупреждение `InvalidProductIdentifiers` и опубликовать свои продукты в сторе. ## Шаг 6. Пересоздайте продукт, если он завис \{#step-6-recreate-the-product-if-its-stuck\} Шаги 1–5 могут пройти успешно — статус `Approved`, совпадающий Bundle ID, валидный API-ключ — но SDK всё равно возвращает `1000 noProductIDsFound`. В таком случае продукт может быть завис в реестре Apple. Иногда реестр продуктов Apple переходит в состояние, при котором продукт существует в UI App Store Connect, но недоступен для StoreKit при поиске. Удалите продукт в App Store Connect и пересоздайте его с тем же идентификатором. После пересоздания подождите до 24 часов — столько может занять распространение изменений. --- # File: cantMakePayments-unity --- --- title: "Исправление ошибки Code-1003 cantMakePayment в Unity SDK" description: "Устранение ошибки при проведении платежей и управлении подписками в Adapty." --- Ошибка 1003, `cantMakePayments`, означает, что на этом устройстве нельзя совершать встроенные покупки. Если вы столкнулись с ошибкой `cantMakePayments`, обычно это происходит по одной из следующих причин: - Ограничения устройства: ошибка не связана с Adapty. Способы решения описаны ниже. - Настройка Observer mode: метод `makePurchase` и Observer mode нельзя использовать одновременно. Подробнее — в соответствующем разделе ниже. ## Проблема: ограничения устройства \{#issue-device-restrictions\} | Проблема | Решение | |---------------------------------|-------------------------------------------------------------------------------------------------------------------| | Ограничения Screen Time | Отключите ограничения встроенных покупок в [Screen Time](https://support.apple.com/en-us/102470) | | Аккаунт заблокирован | Обратитесь в службу поддержки Apple для решения проблем с аккаунтом | | Региональные ограничения | Используйте аккаунт App Store из поддерживаемого региона | ## Проблема: одновременное использование Observer mode и makePurchase \{#issue-using-both-observer-mode-and-makepurchase\} Если вы используете `makePurchase` для обработки покупок, Observer mode не нужен. [Observer mode](observer-vs-full-mode) требуется только в том случае, если логику покупок вы реализуете самостоятельно. Таким образом, если вы используете `makePurchase`, можно смело убрать активацию Observer mode из кода инициализации SDK. --- # File: migration-to-unity-sdk-314 --- --- title: "Миграция Adapty Unity SDK на v3.14" description: "Перейдите на Adapty Unity SDK v3.14 для повышения производительности и новых функций монетизации." --- Adapty SDK 3.14.0 — это крупный релиз, который принёс ряд улучшений, однако для перехода на него могут потребоваться дополнительные шаги с вашей стороны: 1. Отдельный обработчик событий пейвола. 2. Переименование `AdaptyUI.CreateView` в `AdaptyUI.CreatePaywallView` и связанных методов. 3. Обновление метода `MakePurchase` для использования `AdaptyPurchaseParameters` вместо отдельных параметров. 4. Замена `SetFallbackPaywalls` на метод `SetFallback`. 5. Обновление доступа к свойствам пейвола через `AdaptyPlacement`. 6. Обновление доступа к Remote Config через объект `AdaptyRemoteConfig`. 7. Замена `VendorProductIds` на `ProductIdentifiers` в модели `AdaptyPaywall`. 8. Обновление политики получения пейвола в `GetPaywall` для использования `AdaptyFetchPolicy`. ## Отдельный слушатель событий для пейвола \{#separate-event-listener-for-paywall-events\} Если вы отображаете пейволы, созданные с помощью [Paywall Builder](adapty-paywall-builder), события пейвола теперь используют специальный интерфейс `AdaptyPaywallsEventsListener` и метод `SetPaywallsEventsListener`. Основной интерфейс `AdaptyEventListener` по-прежнему используется для обновлений профиля и данных об установке. ```diff showLineNumbers using UnityEngine; using AdaptySDK; public class AdaptyListener : MonoBehaviour, - AdaptyEventListener { + AdaptyEventListener, + AdaptyPaywallsEventsListener { void Start() { Adapty.SetEventListener(this); + Adapty.SetPaywallsEventsListener(this); } // AdaptyEventListener methods public void OnLoadLatestProfile(AdaptyProfile profile) { } public void OnInstallationDetailsSuccess(AdaptyInstallationDetails details) { } public void OnInstallationDetailsFail(AdaptyError error) { } + // AdaptyPaywallsEventsListener methods + // Implement paywall event handlers here } ``` [Подробнее об обработке событий пейвола](unity-handling-events). ## Переименование методов создания и отображения представления \{#rename-view-creation-and-presentation-methods\} Методы создания и отображения представления были переименованы: ```diff showLineNumbers using AdaptySDK; - AdaptyUI.CreateView(paywall, parameters, (view, error) => { + AdaptyUI.CreatePaywallView(paywall, parameters, (view, error) => { if (error != null) { // handle the error return; } - AdaptyUI.PresentView(view, (error) => { + AdaptyUI.PresentPaywallView(view, (error) => { // handle the error }); }); } ``` Аналогично был переименован метод закрытия: ```diff showLineNumbers - AdaptyUI.DismissView(view, (error) => { + AdaptyUI.DismissPaywallView(view, (error) => { // handle the error }); ``` ## Обновление метода MakePurchase \{#update-makepurchase-method\} Метод `MakePurchase` теперь принимает `AdaptyPurchaseParameters` вместо отдельных аргументов `subscriptionUpdateParams` и `isOfferPersonalized`. Это обеспечивает более строгую типизацию и упрощает добавление новых параметров покупки в будущем. ```diff showLineNumbers using AdaptySDK; void MakePurchase( AdaptyPaywallProduct product, AdaptySubscriptionUpdateParameters subscriptionUpdate, bool? isOfferPersonalized ) { - Adapty.MakePurchase(product, subscriptionUpdate, isOfferPersonalized, (result, error) => { + var parameters = new AdaptyPurchaseParametersBuilder() + .SetSubscriptionUpdateParams(subscriptionUpdate) + .SetIsOfferPersonalized(isOfferPersonalized) + .Build(); + + Adapty.MakePurchase(product, parameters, (result, error) => { switch (result.Type) { case AdaptyPurchaseResultType.Pending: // handle pending purchase break; case AdaptyPurchaseResultType.UserCancelled: // handle purchase cancellation break; case AdaptyPurchaseResultType.Success: var profile = result.Profile; // handle successful purchase break; default: break; } }); } ``` Если дополнительные параметры не нужны, можно использовать упрощённый вариант: ```csharp showLineNumbers using AdaptySDK; void MakePurchase(AdaptyPaywallProduct product) { Adapty.MakePurchase(product, (result, error) => { // handle purchase result }); } ``` ## Обновление метода для резервных пейволов \{#update-fallback-method\} :::important При обновлении до Unity SDK 3.14 вам потребуется загрузить новые резервные файлы из дашборда Adapty и заменить существующие в вашем проекте. ::: Метод для настройки резервных пейволов был обновлён. Метод `SetFallbackPaywalls` переименован в `SetFallback`: ```diff showLineNumbers using AdaptySDK; void SetFallBackPaywalls() { #if UNITY_IOS var assetId = "adapty_fallback_ios.json"; #elif UNITY_ANDROID var assetId = "adapty_fallback_android.json"; #else var assetId = ""; #endif - Adapty.SetFallbackPaywalls(assetId, (error) => { + Adapty.SetFallback(assetId, (error) => { // handle the error }); } ``` Ознакомьтесь с полным примером кода на странице [Использование резервных пейволов в Unity](unity-use-fallback-paywalls). ## Обновление доступа к свойствам пейвола \{#update-paywall-property-access\} Следующие свойства перенесены из `AdaptyPaywall` в `AdaptyPlacement`: ```diff showLineNumbers using AdaptySDK; void ProcessPaywall(AdaptyPaywall paywall) { - var abTestName = paywall.ABTestName; - var audienceName = paywall.AudienceName; - var revision = paywall.Revision; - var placementId = paywall.PlacementId; + var abTestName = paywall.Placement.ABTestName; + var audienceName = paywall.Placement.AudienceName; + var revision = paywall.Placement.Revision; + var placementId = paywall.Placement.Id; } ``` ## Обновление доступа к Remote Config \{#update-remote-config-access\} Свойства Remote Config были реструктурированы в объект `AdaptyRemoteConfig` для лучшей организации: ```diff showLineNumbers using AdaptySDK; void ProcessRemoteConfig(AdaptyPaywall paywall) { - var remoteConfigString = paywall.RemoteConfigString; - var locale = paywall.Locale; - var remoteConfigDict = paywall.RemoteConfig; + var remoteConfigString = paywall.RemoteConfig.Data; + var locale = paywall.RemoteConfig.Locale; + var remoteConfigDict = paywall.RemoteConfig.Dictionary; } ``` ## Обновление использования модели AdaptyPaywall \{#update-adaptypaywall-model-usage\} Свойство `VendorProductIds` устарело и заменено на `ProductIdentifiers`. Новое свойство возвращает объекты `AdaptyProductIdentifier` вместо обычных строк, предоставляя более структурированную информацию о продуктах. ```diff showLineNumbers using AdaptySDK; void ProcessPaywallProducts(AdaptyPaywall paywall) { - var productIds = paywall.VendorProductIds; - foreach (var vendorId in productIds) { - // use vendorId - } + var productIdentifiers = paywall.ProductIdentifiers; + foreach (var productId in productIdentifiers) { + var vendorId = productId.VendorProductId; + // use vendorId + } } ``` Объект `AdaptyProductIdentifier` предоставляет доступ к идентификатору продукта вендора через свойство `VendorProductId`, сохраняя ту же функциональность и обеспечивая лучшую структуру для будущих улучшений. ## Обновление политики загрузки в GetPaywall \{#update-getpaywall-fetch-policy\} Тип параметра `fetchPolicy` в методе `GetPaywall` изменён с `AdaptyPaywallFetchPolicy` на `AdaptyPlacementFetchPolicy`. Это изменение унифицирует использование политики загрузки во всём SDK. ```diff showLineNumbers using AdaptySDK; void GetPaywall(string placementId) { - Adapty.GetPaywall(placementId, AdaptyPaywallFetchPolicy.ReloadRevalidatingCacheData, null, (paywall, error) => { + Adapty.GetPaywall(placementId, AdaptyPlacementFetchPolicy.ReloadRevalidatingCacheData, null, (paywall, error) => { // handle the result }); } ``` --- # File: migration-to-unity-sdk-34 --- --- title: "Migrate Adapty Unity SDK to v. 3.4" description: "Перейдите на Adapty Unity SDK v3.4 для повышения производительности и доступа к новым функциям монетизации." --- Adapty SDK 3.4.0 — это мажорный релиз, который включает улучшения, требующие выполнения шагов миграции с вашей стороны. ## Обновите файлы резервного пейвола \{#update-fallback-paywall-files\} Обновите файлы резервного пейвола, чтобы обеспечить совместимость с новой версией SDK: 1. [Скачайте обновлённые файлы резервного пейвола](fallback-paywalls) из дашборда Adapty. 2. [Замените существующие резервные пейволы в своём мобильном приложении](unity-use-fallback-paywalls) на новые файлы. ## Обновите реализацию Observer Mode \{#update-implementation-of-observer-mode\} Если вы используете Observer Mode, убедитесь, что его реализация обновлена. Раньше для передачи транзакций в Adapty использовались разные методы. В новой версии для этого нужно использовать метод `reportTransaction` — он работает одинаково на Android и iOS. Метод явно сообщает Adapty о каждой транзакции, гарантируя её распознавание. Если при покупке использовался пейвол, передайте variation ID, чтобы связать транзакцию с ним. :::warning **Не пропускайте отчёт о транзакции!** Если не вызвать `reportTransaction`, Adapty не распознает транзакцию — она не появится в аналитике и не будет отправлена в интеграции. ::: ```diff showLineNumbers - #if UNITY_ANDROID && !UNITY_EDITOR - Adapty.RestorePurchases((profile, error) => { - // handle the error - }); - #endif Adapty.ReportTransaction( "YOUR_TRANSACTION_ID", "PAYWALL_VARIATION_ID", // optional (error) => { // handle the error }); ``` --- # File: migration-to-unity330 --- --- title: "Миграция Adapty Unity SDK на v3.3" description: "Выполните миграцию на Adapty Unity SDK v3.3 для повышения производительности и новых функций монетизации." --- Adapty SDK 3.3.0 — это мажорный релиз, который принёс ряд улучшений, однако для перехода на него может потребоваться выполнить несколько шагов миграции. 1. Обновитесь до Adapty SDK v3.3.x. 2. Переименованы несколько классов, свойств и методов в модулях Adapty и AdaptyUI Adapty SDK. 3. Теперь метод `SetLogLevel` принимает callback в качестве аргумента. 4. Теперь метод `PresentCodeRedemptionSheet` принимает callback в качестве аргумента. 5. Изменён способ создания представления пейвола. 6. Метод `GetProductsIntroductoryOfferEligibility` удалён. 7. Сохраняйте резервные пейволы в отдельные файлы (по одному на платформу) в `Assets/StreamingAssets/` и передавайте имена файлов в метод `SetFallbackPaywalls`. 8. Обновлено выполнение покупки. 9. Обновлена обработка событий Paywall Builder. 10. Обновлена обработка ошибок пейвола Paywall Builder. 11. Обновлены конфигурации интеграций для Adjust, Amplitude, AppMetrica, Appsflyer, Branch, Firebase и Google Analytics, Mixpanel, OneSignal, Pushwoosh. 13. Обновлена реализация режима Observer. 14. Обновлена инициализация плагина Unity с явным вызовом `Activate`. ## Обновление Adapty Unity SDK до версии 3.3.x \{#upgrade-adapty-unity-sdk-to-33x\} До этой версии Adapty SDK был основным и обязательным SDK для корректной работы Adapty в вашем приложении, а AdaptyUI SDK — опциональным, который требовался только при использовании Adapty Paywall Builder. Начиная с версии 3.3.0, AdaptyUI SDK объявлен устаревшим, а AdaptyUI объединён с Adapty SDK в виде модуля. В связи с этими изменениями вам нужно удалить AdaptyUI SDK и переустановить Adapty SDK. 1. Удалите зависимости пакетов **AdaptySDK** и **AdaptyUISDK** из вашего проекта. 2. Удалите папки **AdaptySDK** и **AdaptyUISDK**. 3. Повторно импортируйте пакет AdaptySDK, как описано на странице [Установка и настройка Adapty SDK для Unity](sdk-installation-unity). ## Переименования \{#renamings\} 1. Переименования в модуле Adapty: | Старая версия | Новая версия | | ------------------------- | ------------------------ | | Adapty.sdkVersion | Adapty.SDKVersion | | Adapty.LogLevel | AdaptyLogLevel | | Adapty.Paywall | AdaptyPaywall | | Adapty.PaywallFetchPolicy | AdaptyPaywallFetchPolicy | | PaywallProduct | AdaptyPaywallProduct | | Adapty.Profile | AdaptyProfile | | Adapty.ProfileParameters | AdaptyProfileParameters | | ProfileGender | AdaptyProfileGender | | Error | AdaptyError | 2. Переименования в модуле AdaptyUI: | Старая версия | Новая версия | | ------------------ | ------------------ | | CreatePaywallView | CreateView | | PresentPaywallView | PresentView | | DismissPaywallView | DismissView | | AdaptyUI.View | AdaptyUIView | | AdaptyUI.Action | AdaptyUIUserAction | ## Изменение метода SetLogLevel \{#change-the-setloglevel-method\} Теперь метод `SetLogLevel` принимает callback в качестве аргумента. ```diff showLineNumbers - Adapty.SetLogLevel(Adapty.LogLevel.Verbose); + Adapty.SetLogLevel(Adapty.LogLevel.Verbose, null); // or you can pass the callback to handle the possible error ``` ## Изменение метода PresentCodeRedemptionSheet \{#change-the-presentcoderedemptionsheet-method\} Теперь метод `PresentCodeRedemptionSheet` принимает callback в качестве аргумента. ```diff showLineNumbers - Adapty.PresentCodeRedemptionSheet(); + Adapty.PresentCodeRedemptionSheet(null); // or you can pass the callback to handle the possible error ``` ## Изменение способа создания вида пейвола \{#change-how-the-paywall-view-is-created\} Полный пример кода см. в разделе [Получение конфигурации вида пейвола, созданного с помощью Paywall Builder](unity-get-pb-paywalls#fetch-the-view-configuration-of-paywall-designed-using-paywall-builder). ```diff showLineNumbers + var parameters = new AdaptyUICreateViewParameters() + .SetPreloadProducts(true); - AdaptyUI.CreatePaywallView( + AdaptyUI.CreateView( paywall, - preloadProducts: true, + parameters, (view, error) => { // use the view }); ``` ## Удаление метода GetProductsIntroductoryOfferEligibility \{#remove-the-getproductsintroductoryoffereligibility-method\} До Adapty iOS SDK 3.3.0 объект продукта всегда содержал офферы, независимо от того, имел ли пользователь право на их получение. Вам приходилось вручную проверять это право перед использованием оффера. Теперь объект продукта содержит оффер только в том случае, если пользователь имеет право на его получение. Это означает, что проверка права больше не нужна — если оффер присутствует, пользователь имеет на него право. ## Обновлённый метод передачи резервных пейволов \{#update-method-for-providing-fallback-paywalls\} До этой версии резервные пейволы передавались в виде сериализованного JSON. Начиная с версии 3.3.0, механизм изменился: 1. Сохраните резервные пейволы в файлы в `/Assets/StreamingAssets/` — один файл для Android и один для iOS. 2. Передайте имена файлов в метод `SetFallbackPaywalls`. Ваш код изменится следующим образом: ```diff showLineNumbers using AdaptySDK; void SetFallBackPaywalls() { + #if UNITY_IOS + var assetId = "adapty_fallback_ios.json"; + #elif UNITY_ANDROID + var assetId = "adapty_fallback_android.json"; + #else + var assetId = ""; + #endif - Adapty.SetFallbackPaywalls("FALLBACK_PAYWALLS_JSON_STRING", (error) => { + Adapty.SetFallbackPaywalls(assetId, (error) => { // handle the error }); } ``` Полный пример кода смотрите на странице [Использование резервных пейволов в Unity](unity-use-fallback-paywalls). ## Обновление процесса покупки \{#update-making-purchase\} Ранее отменённые и ожидающие покупки считались ошибками и возвращали коды `PaymentCancelled` и `PendingPurchase` соответственно. Теперь для обработки отменённых, успешных и ожидающих покупок используется новый класс `AdaptyPurchaseResultType`. Обновите код покупки следующим образом: ```diff showLineNumbers using AdaptySDK; void MakePurchase(AdaptyPaywallProduct product) { - Adapty.MakePurchase(product, (profile, error) => { - // handle successfull purchase + Adapty.MakePurchase(product, (result, error) => { + switch (result.Type) { + case AdaptyPurchaseResultType.Pending: + // handle pending purchase + break; + case AdaptyPurchaseResultType.UserCancelled: + // handle purchase cancellation + break; + case AdaptyPurchaseResultType.Success: + var profile = result.Profile; + // handle successful purchase + break; + default: + break; } }); } ``` Ознакомьтесь с финальным примером кода на странице [Совершение покупок в мобильном приложении](unity-making-purchases). ## Обновите обработку событий Paywall Builder \{#update-handling-of-paywall-builder-events\} Отменённые и ожидающие покупки больше не считаются ошибками — все эти случаи обрабатываются методом `PaywallViewDidFinishPurchase`. 1. Удалите обработку события отменённой покупки. 2. Обновите обработку события успешной покупки следующим образом: ```diff showLineNumbers - public void OnFinishPurchase( - AdaptyUI.View view, - Adapty.PaywallProduct product, - Adapty.Profile profile - ) { } + public void PaywallViewDidFinishPurchase( + AdaptyUIView view, + AdaptyPaywallProduct product, + AdaptyPurchaseResult purchasedResult + ) { } ``` 3. Обновите обработку действий: ```diff showLineNumbers - public void OnPerformAction( - AdaptyUI.View view, - AdaptyUI.Action action - ) { + public void PaywallViewDidPerformAction( + AdaptyUIView view, + AdaptyUIUserAction action + ) { switch (action.Type) { - case AdaptyUI.ActionType.Close: + case AdaptyUIUserActionType.Close: view.Dismiss(null); break; - case AdaptyUI.ActionType.OpenUrl: + case AdaptyUIUserActionType.OpenUrl: var urlString = action.Value; if (urlString != null { Application.OpenURL(urlString); } default: // handle other events break; } } ``` 4. Обновите обработку начала покупки: ```diff showLineNumbers - public void OnSelectProduct( - AdaptyUI.View view, - Adapty.PaywallProduct product - ) { } + public void PaywallViewDidSelectProduct( + AdaptyUIView view, + string productId + ) { } ``` 5. Обновите обработку ошибки покупки: ```diff showLineNumbers - public void OnFailPurchase( - AdaptyUI.View view, - Adapty.PaywallProduct product, - Adapty.Error error - ) { } + public void PaywallViewDidFailPurchase( + AdaptyUIView view, + AdaptyPaywallProduct product, + AdaptyError error + ) { } ``` 6. Обновите обработку события успешного восстановления покупок: Ознакомьтесь с финальным примером кода на странице [Обработка событий пейвола](unity-handling-events). ## Обновление обработки ошибок пейвола в Paywall Builder \{#update-handling-of-paywall-builder-paywall-errors\} Обработка ошибок также изменилась — обновите свой код согласно инструкциям ниже. 1. Обновите обработку ошибок загрузки продуктов: ```diff showLineNumbers - public void OnFailLoadingProducts( - AdaptyUI.View view, - Adapty.Error error - ) { } + public void PaywallViewDidFailLoadingProducts( + AdaptyUIView view, + AdaptyError error + ) { } ``` 2. Обновите обработку ошибок рендеринга: ```diff showLineNumbers - public void OnFailRendering( - AdaptyUI.View view, - Adapty.Error error - ) { } + public void PaywallViewDidFailRendering( + AdaptyUIView view, + AdaptyError error + ) { } ``` ## Обновление конфигурации SDK сторонних интеграций \{#update-third-party-integration-sdk-configuration\} Начиная с Adapty Unity SDK 3.3.0, публичный API метода `updateAttribution` обновлён. Ранее он принимал словарь `[AnyHashable: Any]`, позволяя передавать объекты атрибуции напрямую из различных сервисов. Теперь он требует `[String: any Sendable]`, поэтому перед передачей объекты атрибуции необходимо конвертировать. Чтобы интеграции корректно работали с Adapty Unity SDK 3.3.0 и выше, обновите конфигурации SDK для следующих интеграций, как описано в разделах ниже. ### Adjust Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с Adjust](adjust#connect-your-app-to-adjust). ```diff showLineNumbers - using static AdaptySDK.Adapty; using AdaptySDK; Adjust.GetAdid((adid) => { - Adjust.GetAttribution((attribution) => { - Dictionary<String, object> data = new Dictionary<String, object>(); - - data["network"] = attribution.Network; - data["campaign"] = attribution.Campaign; - data["adgroup"] = attribution.Adgroup; - data["creative"] = attribution.Creative; - - String attributionString = JsonUtility.ToJson(data); - Adapty.UpdateAttribution(attributionString, AttributionSource.Adjust, adid, (error) => { - // handle the error - }); + if (adid != null) { + Adapty.SetIntegrationIdentifier( + "adjust_device_id", + adid, + (error) => { + // handle the error + }); } }); Adjust.GetAttribution((attribution) => { Dictionary<String, object> data = new Dictionary<String, object>(); data["network"] = attribution.Network; data["campaign"] = attribution.Campaign; data["adgroup"] = attribution.Adgroup; data["creative"] = attribution.Creative; String attributionString = JsonUtility.ToJson(data); - Adapty.UpdateAttribution(attributionString, AttributionSource.Adjust, adid, (error) => { + Adapty.UpdateAttribution(attributionString, "adjust", (error) => { // handle the error }); }); ``` ### Amplitude Обновите код своего мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с Amplitude](amplitude#sdk-configuration). ```diff showLineNumbers using AdaptySDK; - var builder = new Adapty.ProfileParameters.Builder(); - builder.SetAmplitudeUserId("YOUR_AMPLITUDE_USER_ID"); - builder.SetAmplitudeDeviceId(amplitude.getDeviceId()); - Adapty.UpdateProfile(builder.Build(), (error) => { - // handle error - }); + Adapty.SetIntegrationIdentifier( + "amplitude_user_id", + "YOUR_AMPLITUDE_USER_ID", + (error) => { + // handle the error + }); + Adapty.SetIntegrationIdentifier( + "amplitude_device_id", + amplitude.getDeviceId(), + (error) => { + // handle the error + }); ``` ### AppMetrica Обновите код мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [Настройка SDK для интеграции с AppMetrica](appmetrica#sdk-configuration). ```diff showLineNumbers using AdaptySDK; - var deviceId = AppMetrica.GetDeviceId(); - if (deviceId != null { - var builder = new Adapty.ProfileParameters.Builder(); - builder.SetAppmetricaProfileId("YOUR_ADAPTY_CUSTOMER_USER_ID"); - builder.SetAppmetricaDeviceId(deviceId); - Adapty.UpdateProfile(builder.Build(), (error) => { - // handle error - }); - } + var deviceId = AppMetrica.GetDeviceId(); + if (deviceId != null { + Adapty.SetIntegrationIdentifier( + "appmetrica_device_id", + deviceId, + (error) => { + // handle the error + }); + + Adapty.SetIntegrationIdentifier( + "appmetrica_profile_id", + "YOUR_ADAPTY_CUSTOMER_USER_ID", + (error) => { + // handle the error + }); + } ``` ### AppsFlyer Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с AppsFlyer](appsflyer#connect-your-app-to-appsflyer). ```diff showLineNumbers using AppsFlyerSDK; using AdaptySDK; // before SDK initialization AppsFlyer.getConversionData(this.name); // in your IAppsFlyerConversionData void onConversionDataSuccess(string conversionData) { // It's important to include the network user ID - string appsFlyerId = AppsFlyer.getAppsFlyerId(); - Adapty.UpdateAttribution(conversionData, AttributionSource.Appsflyer, appsFlyerId, (error) => { + string appsFlyerId = AppsFlyer.getAppsFlyerId(); + + Adapty.SetIntegrationIdentifier( + "appsflyer_id", + appsFlyerId, + (error) => { // handle the error }); + + Adapty.UpdateAttribution( + conversionData, + "appsflyer", + (error) => { + // handle the error + }); } ``` ### Branch Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [настройки SDK для интеграции Branch](branch#connect-your-app-to-branch). ```diff showLineNumbers using AdaptySDK; - class YourBranchImplementation { - func initializeBranch() { - Branch.getInstance().initSession(launchOptions: launchOptions) { (data, error) in - if let data { - Adapty.updateAttribution(data, source: .branch) - } - } - } - } + Branch.initSession(delegate(Dictionary<string, object> parameters, string error) { + string attributionString = JsonUtility.ToJson(parameters); + + Adapty.UpdateAttribution( + attributionString, + "branch", + (error) => { + // handle the error + }); + }); ``` ### Firebase и Google Analytics \{#firebase-and-google-analytics\} Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с Firebase и Google Analytics](firebase-and-google-analytics). ```diff showLineNumbers // We suppose FirebaseAnalytics Unity Plugin is already installed using AdaptySDK; Firebase.Analytics .FirebaseAnalytics .GetAnalyticsInstanceIdAsync() .ContinueWithOnMainThread((task) => { if (!task.IsCompletedSuccessfully) { // handle error return; } var firebaseId = task.Result var builder = new Adapty.ProfileParameters.Builder(); - builder.SetFirebaseAppInstanceId(firebaseId); - - Adapty.UpdateProfile(builder.Build(), (error) => { - // handle error + Adapty.SetIntegrationIdentifier( + "firebase_app_instance_id", + firebaseId, + (error) => { + // handle the error }); }); ``` ### Mixpanel Обновите код мобильного приложения, как показано ниже. Полный пример кода можно найти в разделе [настройка SDK для интеграции с Mixpanel](mixpanel#sdk-configuration). ```diff showLineNumbers using AdaptySDK; - var builder = new Adapty.ProfileParameters.Builder(); - builder.SetMixpanelUserId(Mixpanel.DistinctId); - Adapty.UpdateProfile(builder.Build(), (error) => { - // handle error - }); + var distinctId = Mixpanel.DistinctId; + if (distinctId != null) { + Adapty.SetIntegrationIdentifier( + "mixpanel_user_id", + distinctId, + (error) => { + // handle the error + }); + } ``` ### OneSignal Обновите код мобильного приложения, как показано ниже. Полный пример кода смотрите в разделе [Настройка SDK для интеграции с OneSignal](onesignal#sdk-configuration). ```diff showLineNumbers using AdaptySDK; - using OneSignalSDK; - var pushUserId = OneSignal.Default.PushSubscriptionState.userId; - var builder = new Adapty.ProfileParameters.Builder(); - builder.SetOneSignalPlayerId(pushUserId); - Adapty.UpdateProfile(builder.Build(), (error) => { - // handle error - }); + var distinctId = Mixpanel.DistinctId; + if (distinctId != null) { + Adapty.SetIntegrationIdentifier( + "mixpanel_user_id", + distinctId, + (error) => { + // handle the error + }); + } ``` ### Pushwoosh \{#pushwoosh\} Обновите код вашего мобильного приложения, как показано ниже. Полный пример кода см. в разделе [Настройка SDK для интеграции с Pushwoosh](pushwoosh#sdk-configuration). ```diff showLineNumbers using AdaptySDK; - var builder = new Adapty.ProfileParameters.Builder(); - builder.SetPushwooshHWID(Pushwoosh.Instance.HWID); - Adapty.UpdateProfile(builder.Build(), (error) => { - // handle error - }); + Adapty.SetIntegrationIdentifier( + "pushwoosh_hwid", + Pushwoosh.Instance.HWID, + (error) => { + // handle the error + }); ``` ## Обновите реализацию режима Observer \{#update-observer-mode-implementation\} Обновите способ привязки пейволов к транзакциям. Раньше для назначения `variationId` использовался метод `setVariationId`. Теперь `variationId` можно передавать напрямую при регистрации транзакции через новый метод `reportTransaction`. Смотрите итоговый пример кода в разделе [Привязка пейволов к транзакциям покупки в режиме Observer](report-transactions-observer-mode-unity). ```diff showLineNumbers // every time when calling transaction.finish() - Adapty.SetVariationForTransaction("<variationId>", "<transactionId>", (error) => { - if(error != null) { - // handle the error - return; - } - - // successful binding - }); + Adapty.ReportTransaction( + "YOUR_TRANSACTION_ID", + "PAYWALL_VARIATION_ID", // optional + (error) => { + // handle the error + }); ``` ## Обновление инициализации плагина Unity \{#update-the-unity-plugin-initialization\} Начиная с Adapty Unity SDK 3.3.0, при инициализации плагина необходимо явно вызывать метод `Activate`: ```csharp showLineNumbers Adapty.Activate(builder.Build(), (error) => { if (error != null) { // handle the error return; } }); ``` --- # File: migration-to-unity-sdk-v3 --- --- title: "Миграция Adapty Unity SDK на v3.0" description: "Мигрируйте на Adapty Unity SDK v3.0 для улучшенной производительности и новых функций монетизации." --- Adapty SDK v3.0 включает поддержку нового [Adapty Paywall Builder](adapty-paywall-builder) — обновлённой версии no-code инструмента для создания пейволов. Благодаря широким возможностям настройки и богатому набору дизайн-инструментов ваши пейволы станут максимально эффективными и прибыльными. ## Процесс обновления \{#upgrade-process\} Процесс обновления для Unity включает те же шаги, что и для других платформ: 1. Обновитесь до Adapty SDK v3.x 2. Перенесите существующие пейволы в новый Paywall Builder Подробные инструкции по миграции для Unity см. в [гайде по установке Unity SDK](sdk-installation-unity) и следуйте общим шагам миграции, описанным в основном руководстве по миграции. --- # File: unity-migration-guide --- --- title: "Гайд по миграции SDK" description: "Гайды по миграции для Unity Adapty SDK." --- ## Гайды по миграции \{#migration-guides\} ### [Гайд по миграции на Unity Adapty SDK 3.x](unity-sdk-migration-guides) Узнайте, как перейти со старых версий на Unity Adapty SDK 3.x. ## Что нового \{#whats-new\} ### Версия 3.x \{#version-3x\} - Улучшенное отображение пейволов - Улучшенная обработка ошибок - Улучшенная поддержка C# - Оптимизация производительности ### Версия 2.x \{#version-2x\} - Новые функции онбординга - Улучшенная аналитика - Улучшенный процесс покупки - Исправления ошибок и улучшение стабильности ## Критические изменения \{#breaking-changes\} ### Версия 3.x \{#version-3x-breaking\} - Обновлённый API наблюдателя - Изменённые методы отображения пейволов - Изменённая структура обработки ошибок ### Версия 2.x \{#version-2x-breaking\} - Обновлённый API онбординга - Изменённая структура профиля - Изменённый процесс покупки ## Чеклист миграции \{#migration-checklist\} При переходе на новую версию: - [ ] Изучите критические изменения - [ ] Обновите вызовы API - [ ] Протестируйте весь функционал - [ ] Обновите обработку ошибок - [ ] Проверьте отслеживание аналитики - [ ] Протестируйте на всех платформах --- # End of Documentation _Generated on: 2026-07-24T13:01:12.986Z_ _Successfully processed: 41/41 files_