Обработка событий флоу и пейвола — Android

Этот гайд охватывает обработку событий для покупок, восстановлений, выбора продукта и рендеринга флоу. Вам также необходимо реализовать обработку кнопок (закрытие флоу, открытие ссылок и т. д.). Подробнее — в нашем гайде по обработке действий кнопок.

Флоу и пейволы, настроенные с помощью Flow Builder или Paywall Builder, не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют события, на которые ваше приложение может реагировать. К таким событиям относятся нажатия кнопок (кнопки закрытия, URL-ссылки, выбор продуктов и т. д.), а также уведомления о действиях, связанных с покупками. Ниже описано, как обрабатывать эти события.

Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши примеры приложений — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции.

Если вам нужно контролировать или отслеживать процессы на экране покупки, реализуйте методы AdaptyFlowEventListener. Если вы хотите сохранить поведение по умолчанию в некоторых случаях, вы можете расширить AdaptyFlowDefaultEventListener и переопределить только те методы, которые нужно изменить.

Ниже приведены значения по умолчанию из AdaptyFlowDefaultEventListener.

События, генерируемые пользователем

Выбор продукта

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

public override fun onProductSelected(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
Пример события (нажмите, чтобы развернуть)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

Начало покупки

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

public override fun onPurchaseStarted(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
Пример события (нажмите, чтобы развернуть)
{
  "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.

Успешная, отменённая или отложенная покупка

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

public override fun onPurchaseFinished(
    purchaseResult: AdaptyPurchaseResult,
    product: AdaptyPaywallProduct,
    context: Context,
) {
    if (purchaseResult !is AdaptyPurchaseResult.UserCanceled)
        context.getActivityOrNull()?.onBackPressed()
}
Примеры событий (нажмите, чтобы развернуть)
// 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.

Неудачная покупка

Если покупка завершается с ошибкой, вызывается этот метод. Это включает ошибки Google Play Billing (ограничения платежей, некорректные продукты, сбои сети), ошибки верификации транзакции и системные ошибки. Обратите внимание: отмена покупки пользователем вызывает onPurchaseFinished с результатом отмены, а ожидающие платежи этот метод не вызывают.

public override fun onPurchaseFailure(
    error: AdaptyError,
    product: AdaptyPaywallProduct,
    context: Context,
) {}
Пример события (нажмите, чтобы развернуть)
{
  "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.

Завершение навигации веб-оплаты

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

public override fun onFinishWebPaymentNavigation(
    product: AdaptyPaywallProduct?,
    error: AdaptyError?,
    context: Context,
) {}

Параметры:

ПараметрОписание
productОбъект AdaptyPaywallProduct, для которого был открыт веб-пейвол. Может быть null.
errorОбъект AdaptyError, если навигация в веб-пейволе завершилась с ошибкой; null, если навигация успешна.
Примеры событий (нажмите, чтобы раскрыть)
// 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"
    }
  }
}

Успешное восстановление покупки

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

public override fun onRestoreSuccess(
    profile: AdaptyProfile,
    context: Context,
) {}
Пример события (нажмите, чтобы раскрыть)
{
  "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. Как его проверить — читайте в разделе Статус подписки.

Неудачное восстановление

Если Adapty.restorePurchases() завершится с ошибкой, будет вызван следующий метод:

public override fun onRestoreFailure(
    error: AdaptyError,
    context: Context,
) {}
Пример события (нажмите, чтобы развернуть)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

Обновление подписки

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

  1. Замените текущую подписку на новую:
public override fun onAwaitingPurchaseParams(
    product: AdaptyPaywallProduct,
    context: Context,
    onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback,
): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked {
    onPurchaseParamsReceived(
        AdaptyPurchaseParameters.Builder()
            .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...))
            .build()
    )
    return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked
}
  1. Сохранить обе подписки (добавить новую отдельно):
public override fun onAwaitingPurchaseParams(
    product: AdaptyPaywallProduct,
    context: Context,
    onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback,
): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked {
    onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty)
    return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked
}

Если вы не переопределяете этот метод, поведение по умолчанию — сохранить обе подписки активными (эквивалентно использованию AdaptyPurchaseParameters.Empty).

Вы также можете задать дополнительные параметры покупки при необходимости:

AdaptyPurchaseParameters.Builder()
    .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription
    .withOfferPersonalized(true) // optional - if using personalized pricing
    .build()
Пример события (нажмите, чтобы развернуть)
{
  "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"
  }
}

Загрузка данных и рендеринг

Ошибки загрузки продуктов

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

public override fun onLoadingProductsFailure(
    error: AdaptyError,
    context: Context,
): Boolean = false
Пример события (нажмите, чтобы развернуть)
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

Если вы вернёте true, AdaptyUI повторит запрос через 2 секунды.

Ошибки рендеринга

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

public override fun onError(
    error: AdaptyError,
    context: Context,
) {}
Пример события (нажмите, чтобы развернуть)
{
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render flow interface",
    "details": {
      "underlyingError": "Invalid flow configuration"
    }
  }
}

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

Системная кнопка «Назад»

По умолчанию флоу нельзя закрыть системной кнопкой «Назад» или жестом — пользователь выходит из него только через путь, который вы задаёте: кнопку Close или действие on_device_back в билдере. Если вы хотите, чтобы системная кнопка «Назад» закрывала флоу, переопределите onBackPressed и верните false, чтобы хост-активити или фрагмент обработал нажатие:

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, чтобы передать его хосту для самостоятельной обработки.

Зарезервированные события

AdaptyFlowEventListener объявляет несколько колбэков для функциональности, которую флоу пока не используют. Реализовывать их не нужно — AdaptyFlowDefaultEventListener уже предоставляет пустые реализации по умолчанию.

МетодОписание
onAnalyticEventЗарезервирован для пользовательских аналитических событий из флоу. Флоу пока не отправляют эти события в ваш код, поэтому реализовывать его не нужно.
onShowAppRateЗарезервирован для запросов на оценку приложения из флоу. Флоу пока не инициируют запросы на оценку приложения, поэтому реализовывать его не нужно.
onShowRequestPermissionЗарезервирован для запросов системных разрешений (например, push-уведомлений или доступа к камере) из флоу. Флоу пока не инициируют запросы на разрешения, поэтому реализовывать его не нужно.

Этот гайд охватывает обработку событий для покупок, восстановлений, выбора продуктов и отображения пейвола. Вам также необходимо реализовать обработку кнопок (закрытие пейвола, открытие ссылок и т. д.). Подробности смотрите в нашем гайде по обработке действий кнопок.

Пейволы, настроенные с помощью Paywall Builder, не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют события, на которые ваше приложение может реагировать. Среди них — нажатия кнопок (закрытия, URL, выбора продуктов и т. д.), а также уведомления о действиях, связанных с покупками на пейволе. Узнайте ниже, как реагировать на эти события.

Это руководство предназначено только для пейволов на новом Paywall Builder, которые требуют Adapty SDK v3.0 или выше.

Хотите увидеть реальный пример интеграции Adapty SDK в мобильное приложение? Посмотрите наши примеры приложений — они демонстрируют полную настройку: отображение пейволов, совершение покупок и другие базовые функции.

Если вам нужно управлять процессами на экране покупки или отслеживать их, реализуйте методы AdaptyUiEventListener.

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

Ниже приведены значения по умолчанию из AdaptyUiDefaultEventListener.

Пользовательские события

Выбор продукта

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

public override fun onProductSelected(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
Пример события (нажмите, чтобы раскрыть)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

Начало покупки

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

public override fun onPurchaseStarted(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
Пример события (нажмите, чтобы развернуть)
{
  "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.

Успешная, отменённая или отложенная покупка

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

public override fun onPurchaseFinished(
    purchaseResult: AdaptyPurchaseResult,
    product: AdaptyPaywallProduct,
    context: Context,
) {
    if (purchaseResult !is AdaptyPurchaseResult.UserCanceled)
        context.getActivityOrNull()?.onBackPressed()
}
Примеры событий (нажмите, чтобы раскрыть)
// 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.

Неудачная покупка

Если покупка завершается ошибкой, этот метод будет вызван. Это включает ошибки Google Play Billing (ограничения платежей, недействительные продукты, сбои сети), ошибки проверки транзакций и системные ошибки. Обратите внимание, что отмена покупки пользователем вызывает onPurchaseFinished с результатом отмены, а ожидающие платежи не вызывают этот метод.

public override fun onPurchaseFailure(
    error: AdaptyError,
    product: AdaptyPaywallProduct,
    context: Context,
) {}
Пример события (нажмите, чтобы развернуть)
{
  "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.

Завершение навигации веб-платежа

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

public override fun onFinishWebPaymentNavigation(
    product: AdaptyPaywallProduct?,
    error: AdaptyError?,
    context: Context,
) {}

Параметры:

ПараметрОписание
productОбъект AdaptyPaywallProduct, для которого был открыт веб-пейвол. Может быть null.
errorОбъект AdaptyError, если при навигации на веб-пейвол произошла ошибка; null, если навигация успешна.
Примеры событий (нажмите, чтобы развернуть)
// 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"
    }
  }
}

Успешное восстановление покупки

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

public override fun onRestoreSuccess(
    profile: AdaptyProfile,
    context: Context,
) {}
Пример события (нажмите, чтобы раскрыть)
{
  "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. Подробнее о том, как это проверить, читайте в разделе Статус подписки.

Ошибка восстановления

Если Adapty.restorePurchases() завершится с ошибкой, будет вызван этот метод:

public override fun onRestoreFailure(
    error: AdaptyError,
    context: Context,
) {}
Пример события (нажмите, чтобы развернуть)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

Обновление подписки

Пример события (нажмите, чтобы развернуть)
{
  "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"
  }
}

Загрузка данных и рендеринг

Ошибки загрузки продуктов

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

public override fun onLoadingProductsFailure(
    error: AdaptyError,
    context: Context,
): Boolean = false
Пример события (нажмите, чтобы развернуть)
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

Если вы вернёте true, AdaptyUI повторит запрос через 2 секунды.

Ошибки рендеринга

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

public override fun onRenderingError(
    error: AdaptyError,
    context: Context,
) {}
Пример события (нажмите, чтобы развернуть)
{
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render paywall interface",
    "details": {
      "underlyingError": "Invalid paywall configuration"
    }
  }
}

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