Обработка событий флоу и пейвола - Kotlin Multiplatform
Этот гайд охватывает обработку событий покупок, восстановлений, выбора продуктов и рендеринга флоу. Вам также необходимо реализовать обработку кнопок (закрытие флоу, открытие ссылок и т.д.). Подробнее смотрите в нашем гайде по обработке действий флоу.
Флоу и пейволы, настроенные с помощью Flow Builder или Paywall Builder, не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют события, на которые ваше приложение может реагировать. К таким событиям относятся нажатия кнопок (кнопки закрытия, URL-адреса, выбор продуктов и т. д.), а также уведомления о действиях, связанных с покупками. Узнайте, как реагировать на эти события, ниже.
Чтобы управлять процессами на экране флоу или отслеживать их в своём мобильном приложении, реализуйте методы интерфейса AdaptyUIFlowsEventsObserver и зарегистрируйте наблюдатель через AdaptyUI.setFlowsEventsObserver(). Некоторые методы имеют реализации по умолчанию, которые автоматически обрабатывают типичные сценарии, — переопределяйте только те из них, которые нужно изменить:
AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver {
// override only the methods you want to change
})Здесь вы добавляете собственную логику для обработки событий флоу. Используйте view.dismiss(), чтобы закрыть флоу, или реализуйте любое другое нужное поведение. Обратите внимание: dismiss() — это suspend-функция, поэтому внутри колбэка запускайте её через mainUiScope обозревателя: mainUiScope.launch { view.dismiss() }.
События, сгенерированные пользователем
Появление и исчезновение флоу
Когда флоу появляется или исчезает, будут вызваны следующие методы:
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
}- На iOS
flowViewDidAppearтакже вызывается, когда пользователь нажимает кнопку веб-пейвола внутри флоу, и веб-пейвол открывается во встроенном браузере. - На iOS
flowViewDidDisappearтакже вызывается, когда веб-пейвол, открытый из флоу во встроенном браузере, исчезает с экрана.
Примеры событий (нажмите, чтобы развернуть)
// Flow appeared
{
// No additional data
}
// Flow disappeared
{
// No additional data
}Выбор продукта
If a user selects a product for purchase, this method will be invoked:
override fun flowViewDidSelectProduct(view: AdaptyUIFlowView, productId: String) {
// Handle product selection
// You can update UI or track analytics here
}Пример события (нажмите, чтобы развернуть)
{
"productId": "premium_monthly"
}Покупка начата
If a user initiates the purchase process, this method will be invoked:
override fun flowViewDidStartPurchase(view: AdaptyUIFlowView, product: AdaptyPaywallProduct) {
// Handle purchase start
// You can show loading indicators or track analytics here
}В режиме Observer, покупки, инициированные из флоу, передаются в ваш AdaptyUIObserverModeResolver.
Пример события (нажмите, чтобы развернуть)
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}Успешная, отменённая или ожидающая покупка
Этот метод вызывается после завершения покупки. По умолчанию он ничего не делает — флоу остаётся открытым после покупки, пока вы его явно не закроете, поэтому вызывайте view.dismiss() самостоятельно, как только пользователь получает доступ:
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
}
}
}Примеры событий (нажмите, чтобы развернуть)
// 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"
}
}Рекомендуем закрывать экран флоу при успешной покупке.
Неудачная покупка
Этот метод вызывается, если покупка завершается с ошибкой. Сюда входят ошибки StoreKit/Google Play Billing (ограничения платежей, недействительные продукты, сетевые сбои), ошибки верификации транзакций и системные ошибки. Обратите внимание: отмена покупки пользователем вызывает flowViewDidFinishPurchase с результатом отмены, а ожидающие платежи этот метод не вызывают.
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
}Пример события (нажмите, чтобы развернуть)
{
"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"
}
}
}Восстановление начато
Если пользователь инициирует процесс восстановления покупок, будет вызван этот метод:
override fun flowViewDidStartRestore(view: AdaptyUIFlowView) {
// Handle restore start
// You can show loading indicators or track analytics here
}Успешное восстановление
Если восстановление покупки прошло успешно, будет вызван этот метод. По умолчанию он ничего не делает — флоу остаётся открытым после восстановления, пока вы его не закроете:
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() }
}
}Пример события (нажмите, чтобы раскрыть)
{
"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() завершится с ошибкой, будет вызван этот метод:
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
}Пример события (нажмите, чтобы развернуть)
{
"error": {
"code": "restore_failed",
"message": "Purchase restoration failed",
"details": {
"underlyingError": "No previous purchases found"
}
}
}Завершение навигации веб-оплаты
Если пользователь инициирует процесс покупки через веб-пейвол, будет вызван этот метод:
override fun flowViewDidFinishWebPaymentNavigation(
view: AdaptyUIFlowView,
product: AdaptyPaywallProduct?,
error: AdaptyError?
) {
if (error != null) {
// Handle web payment navigation error
} else {
// Handle successful web payment navigation
}
}Примеры событий (нажмите, чтобы развернуть)
// 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"
}
}
}Загрузка данных и рендеринг
Ошибки загрузки продуктов
Если вы не передаёте продукты при инициализации, AdaptyUI самостоятельно получит необходимые объекты с сервера. Если эта операция завершится неудачей, AdaptyUI сообщит об ошибке, вызвав следующий метод:
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
}Пример события (нажмите, чтобы развернуть)
{
"error": {
"code": "products_loading_failed",
"message": "Failed to load products from the server",
"details": {
"underlyingError": "Network timeout"
}
}
}Ошибки рендеринга и ошибки во время выполнения
Если в процессе рендеринга интерфейса или во время выполнения возникает ошибка (не связанная с покупкой), она будет передана через этот метод. По умолчанию флоу закрывается при ошибке — переопределите метод, чтобы оставить его открытым или добавить собственную обработку:
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
}Пример события (нажмите, чтобы развернуть)
{
"error": {
"code": "rendering_failed",
"message": "Failed to render flow interface",
"details": {
"underlyingError": "Invalid flow configuration"
}
}
}В штатной ситуации такие ошибки возникать не должны, поэтому, если вы с ними столкнулись, сообщите нам.
События аналитики
Коллбэк flowViewDidReceiveAnalyticEvent зарезервирован для пользовательских событий аналитики из флоу. Флоу пока не отправляют такие события в ваш код, поэтому реализовывать его не нужно:
override fun flowViewDidReceiveAnalyticEvent(
view: AdaptyUIFlowView,
name: String,
paramsJsonString: String
) {
// Reserved for custom analytic events from a flow
}Навигация
Кнопка «Назад» на Android
По умолчанию флоу нельзя закрыть системной кнопкой «Назад» или жестом назад на Android — стандартная реализация flowViewDidPerformAction закрывает флоу только по CloseAction и игнорирует AndroidSystemBackAction, поэтому пользователь покидает флоу тем путём, который вы определили: через кнопку Close или действие on_device_back в билдере. Если вы хотите, чтобы системная кнопка «Назад» закрывала флоу, обработайте это действие самостоятельно:
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
}
}См. гайд по обработке действий флоу для полного списка действий.
Пейволы, настроенные с помощью Paywall Builder, не требуют дополнительного кода для совершения и восстановления покупок. Однако они генерируют события, на которые ваше приложение может реагировать. К ним относятся нажатия кнопок (кнопки закрытия, URL-адреса, выбор продуктов и т. д.), а также уведомления о действиях, связанных с покупками, на пейволе. Узнайте, как реагировать на эти события, ниже.
Этот гайд предназначен только для пейволов нового Paywall Builder.
Для управления или отслеживания событий на экране пейвола в вашем мобильном приложении реализуйте методы интерфейса AdaptyUIPaywallsEventsObserver. Некоторые методы имеют реализации по умолчанию, которые автоматически обрабатывают типичные сценарии.
В этих методах вы добавляете собственную логику для реакции на события пейвола. Чтобы закрыть пейвол, используйте view.dismiss(), либо реализуйте любое другое необходимое поведение.
События, генерируемые пользователями
Появление и исчезновение пейвола
Когда пейвол появляется или исчезает, вызываются следующие методы:
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
}- На iOS
paywallViewDidAppearтакже вызывается, когда пользователь нажимает на кнопку веб-пейвола внутри пейвола, и веб-пейвол открывается во встроенном браузере. - На iOS
paywallViewDidDisappearтакже вызывается, когда веб-пейвол, открытый из пейвола во встроенном браузере, исчезает с экрана.
Примеры событий (нажмите, чтобы развернуть)
// Paywall appeared
{
// No additional data
}
// Paywall disappeared
{
// No additional data
}Выбор продукта
Если пользователь выбирает продукт для покупки, будет вызван этот метод:
override fun paywallViewDidSelectProduct(view: AdaptyUIPaywallView, productId: String) {
// Handle product selection
// You can update UI or track analytics here
}Пример события (нажмите, чтобы развернуть)
{
"productId": "premium_monthly"
}Начало покупки
Если пользователь начинает процесс покупки, будет вызван этот метод:
override fun paywallViewDidStartPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct) {
// Handle purchase start
// You can show loading indicators or track analytics here
}Пример события (нажмите, чтобы развернуть)
{
"product": {
"vendorProductId": "premium_monthly",
"localizedTitle": "Premium Monthly",
"localizedDescription": "Premium subscription for 1 month",
"localizedPrice": "$9.99",
"price": 9.99,
"currencyCode": "USD"
}
}Успешная, отменённая или ожидающая покупка
Если покупка прошла успешно, будет вызван этот метод. По умолчанию он автоматически закрывает пейвол, если только покупка не была отменена пользователем:
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
}
}
}Примеры событий (нажмите, чтобы развернуть)
// 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"
}
}Рекомендуем закрывать экран пейвола после успешной покупки.
Неудачная покупка
Если покупка завершилась ошибкой, будет вызван этот метод. Сюда входят ошибки StoreKit/Google Play Billing (ограничения оплаты, недействительные продукты, сбои сети), ошибки верификации транзакций и системные ошибки. Обратите внимание, что отмена покупки пользователем вызывает paywallViewDidFinishPurchase с результатом отмены, а ожидающие платежи этот метод не вызывают.
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
}Пример события (нажмите, чтобы развернуть)
{
"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"
}
}
}Начало восстановления
Если пользователь инициирует процесс восстановления, будет вызван этот метод:
override fun paywallViewDidStartRestore(view: AdaptyUIPaywallView) {
// Handle restore start
// You can show loading indicators or track analytics here
}Успешное восстановление покупки
Если восстановление покупки прошло успешно, будет вызван следующий метод:
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()
}
}Пример события (нажмите, чтобы развернуть)
{
"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() завершается с ошибкой, будет вызван этот метод:
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
}Пример события (нажмите, чтобы развернуть)
{
"error": {
"code": "restore_failed",
"message": "Purchase restoration failed",
"details": {
"underlyingError": "No previous purchases found"
}
}
}Завершение навигации веб-оплаты
Если пользователь инициирует процесс покупки через веб-пейвол, будет вызван этот метод:
override fun paywallViewDidFinishWebPaymentNavigation(
view: AdaptyUIPaywallView,
product: AdaptyPaywallProduct?,
error: AdaptyError?
) {
if (error != null) {
// Handle web payment navigation error
} else {
// Handle successful web payment navigation
}
}Примеры событий (нажмите, чтобы развернуть)
// 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"
}
}
}Получение и отображение данных
Ошибки загрузки продуктов
Если вы не передаёте продукты при инициализации, AdaptyUI самостоятельно получит нужные объекты с сервера. Если эта операция завершится неудачей, AdaptyUI сообщит об ошибке, вызвав следующий метод:
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
}Пример события (нажмите, чтобы развернуть)
{
"error": {
"code": "products_loading_failed",
"message": "Failed to load products from the server",
"details": {
"underlyingError": "Network timeout"
}
}
}Ошибки рендеринга
Если в процессе рендеринга интерфейса возникнет ошибка, она будет сообщена этим методом:
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
}Пример события (нажмите, чтобы развернуть)
{
"error": {
"code": "rendering_failed",
"message": "Failed to render paywall interface",
"details": {
"underlyingError": "Invalid paywall configuration"
}
}
}В штатной ситуации такие ошибки возникать не должны, поэтому если вы столкнулись с одной из них, пожалуйста, сообщите нам.