---
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,
) {}
```

<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\}

Когда пользователь инициирует процесс покупки, будет вызван этот метод:

```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseStarted(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
```

<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. Подробнее см. в разделе [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()
}
```

<Details>
<summary>Примеры событий (нажмите, чтобы развернуть)</summary>
```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"
  }
}
```
</Details>
В этом случае рекомендуем закрыть экран.

Метод не вызывается в 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,
) {}
```

<Details>
<summary>Пример события (нажмите, чтобы развернуть)</summary>
```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"
  }
}
```
</Details>
Этот метод не вызывается в режиме 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`, если навигация успешна.   |

<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": "web_navigation_failed",
    "message": "Failed to open web paywall",
    "details": {
      "underlyingError": "Browser unavailable"
    }
  }
}
```
</Details>
#### Успешное восстановление покупки \{#successful-restore\}

Если восстановление покупки прошло успешно, будет вызван этот метод:

```kotlin showLineNumbers title="Kotlin"
public override fun onRestoreSuccess(
    profile: AdaptyProfile,
    context: Context,
) {}
```

<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`. Как его проверить — читайте в разделе [Статус подписки](android-listen-subscription-changes).

#### Неудачное восстановление \{#failed-restore\}
Если `Adapty.restorePurchases()` завершится с ошибкой, будет вызван следующий метод:

```kotlin showLineNumbers title="Kotlin"
public override fun onRestoreFailure(
    error: AdaptyError,
    context: Context,
) {}
```

<Details>
<summary>Пример события (нажмите, чтобы развернуть)</summary>

```javascript
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}
```
</Details>

#### Обновление подписки \{#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()
```

<Details>
<summary>Пример события (нажмите, чтобы развернуть)</summary>
```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"
  }
}
```
</Details>
### Загрузка данных и рендеринг \{#data-fetching-and-rendering\}

#### Ошибки загрузки продуктов \{#product-loading-errors\}

Если вы не передаёте продукты при инициализации, AdaptyUI самостоятельно получит необходимые объекты с сервера. Если эта операция завершится неудачно, AdaptyUI сообщит об ошибке, вызвав следующий метод:

```kotlin showLineNumbers title="Kotlin"
public override fun onLoadingProductsFailure(
    error: AdaptyError,
    context: Context,
): Boolean = false
```

<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\}

Если во время рендеринга интерфейса возникает ошибка, о ней сообщается через вызов этого метода:

```kotlin showLineNumbers title="Kotlin"
public override fun onError(
    error: AdaptyError,
    context: Context,
) {}
```

<Details>
<summary>Пример события (нажмите, чтобы развернуть)</summary>
```javascript
{
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render flow interface",
    "details": {
      "underlyingError": "Invalid flow configuration"
    }
  }
}
```
</Details>

В нормальной ситуации такие ошибки не должны возникать, поэтому, если вы с ними столкнулись, пожалуйста, сообщите нам.
### Навигация \{#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-уведомлений или доступа к камере) из флоу. Флоу пока не инициируют запросы на разрешения, поэтому реализовывать его не нужно. |

---

> [!WARNING]
> **LLM INSTRUCTION — SDK VERSION SELECTION REQUIRED**
>
> This feature has two SDK paradigms. Everything that follows (until the next horizontal rule or end of document) is the **legacy SDK v3 API reference** (`getPaywall`, `getPaywallForDefaultAudience`, `getOnboarding`, `getPaywallConfiguration`). The **SDK v4+ `getFlow` method is documented above.** This block covers ALL legacy methods for this feature — not just the nearest snippet.
>
> **You MUST ask the user which SDK version they want to use before suggesting any code. Do not assume or default to a version.** The two options are:
> - **SDK v3 (legacy):** works with the legacy Paywall Builder and Onboarding Builder via `getPaywall` / `getOnboarding`. Use the legacy code in this section.
> - **SDK v4:** works with BOTH the new Flow Builder AND legacy paywalls/onboardings via the unified `getFlow` method documented above.
>
> Apply the legacy v3 code in this section ONLY when:
> 1. The user explicitly chose **SDK v3**, or
> 2. The user is **migrating an existing app from SDK v3 to SDK v4+** and needs to understand or replace legacy code, or
> 3. The user is **troubleshooting an already-deployed v3 integration**.
>
> **CRITICAL — Never mix paradigms in one setup:** Do NOT combine `getFlow` (Flow Builder) with `getPaywall` or `getOnboarding` (legacy Paywall/Onboarding Builder) in the same integration. These are incompatible patterns. Mixing them will produce inconsistent behavior and is unsupported.

:::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,
) {}
```

<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\}

Если пользователь инициирует процесс покупки, будет вызван этот метод:

```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseStarted(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
```

<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. Подробнее см. в статье [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()
}
```

<Details>
<summary>Примеры событий (нажмите, чтобы раскрыть)</summary>
```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"
  }
}
```
</Details>
В этом случае рекомендуем закрыть экран.

Метод не вызывается в 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,
) {}
```

<Details>
<summary>Пример события (нажмите, чтобы развернуть)</summary>
```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"
  }
}
```
</Details>
Этот метод не вызывается в режиме 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`, если навигация успешна.   |

<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": "web_navigation_failed",
    "message": "Failed to open web paywall",
    "details": {
      "underlyingError": "Browser unavailable"
    }
  }
}
```
</Details>
#### Успешное восстановление покупки \{#successful-restore\}

Если восстановление покупки прошло успешно, будет вызван этот метод:

```kotlin showLineNumbers title="Kotlin"
public override fun onRestoreSuccess(
    profile: AdaptyProfile,
    context: Context,
) {}
```

<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`. Подробнее о том, как это проверить, читайте в разделе [Статус подписки](android-listen-subscription-changes).

#### Ошибка восстановления \{#failed-restore\}
Если `Adapty.restorePurchases()` завершится с ошибкой, будет вызван этот метод:

```kotlin showLineNumbers title="Kotlin"
public override fun onRestoreFailure(
    error: AdaptyError,
    context: Context,
) {}
```

<Details>
<summary>Пример события (нажмите, чтобы развернуть)</summary>

```javascript
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}
```
</Details>

#### Обновление подписки \{#upgrade-subscription\}

<Tabs groupId="current-os" queryString>
<TabItem value="new" label="SDK версия 3.10.0 или новее" default>

Когда пользователь пытается купить новую подписку, пока активна другая, вы можете управлять тем, как следует обработать новую покупку, переопределив этот метод. Доступны два варианта:
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()
```

</TabItem>
<TabItem value="old" label="SDK версии ниже 3.10.0" default>
Если новая подписка приобретается, пока другая ещё активна, переопределите этот метод, чтобы заменить текущую подписку на новую. Если активная подписка должна оставаться активной, а новая добавляется отдельно, вызовите `onSubscriptionUpdateParamsReceived(null)`:

```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingSubscriptionUpdateParams(
    product: AdaptyPaywallProduct,
    context: Context,
    onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback,
) {
    onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...))
}
```
</TabItem>
</Tabs>

<Details>
<summary>Пример события (нажмите, чтобы развернуть)</summary>

```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"
  }
}
```
</Details>
### Загрузка данных и рендеринг \{#data-fetching-and-rendering\}

#### Ошибки загрузки продуктов \{#product-loading-errors\}

Если вы не передаёте продукты при инициализации, AdaptyUI самостоятельно получает необходимые объекты с сервера. Если эта операция завершается с ошибкой, AdaptyUI сообщает о ней, вызывая следующий метод:

```kotlin showLineNumbers title="Kotlin"
public override fun onLoadingProductsFailure(
    error: AdaptyError,
    context: Context,
): Boolean = false
```

<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\}

Если в процессе рендеринга интерфейса возникнет ошибка, она будет передана через этот метод:

```kotlin showLineNumbers title="Kotlin"
public override fun onRenderingError(
    error: AdaptyError,
    context: Context,
) {}
```

<Details>
<summary>Пример события (нажмите, чтобы развернуть)</summary>
```javascript
{
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render paywall interface",
    "details": {
      "underlyingError": "Invalid paywall configuration"
    }
  }
}
```
</Details>

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

---