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

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

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

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

Обработка событий в SwiftUI

Для управления и отслеживания процессов на экране флоу или пейвола в вашем мобильном приложении используйте модификатор .flow в SwiftUI:

@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 */ },
            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, содержащий визуальные данные флоу или пейвола. Подробнее см. в разделе Получение флоу и пейволов.
didFailPurchaseобязательныйВызывается при ошибке Adapty.makePurchase().
didFinishRestoreобязательныйВызывается при успешном завершении Adapty.restorePurchases().
didFailRestoreобязательныйВызывается при ошибке Adapty.restorePurchases().
didReceiveErrorобязательныйВызывается, когда флоу сталкивается с ошибкой рендеринга или ошибкой времени выполнения из скрипта флоу (например, исключение JavaScript, код AdaptyUIError 4105). В случае ошибки рендеринга обратитесь в поддержку Adapty.
placeholderBuilderнеобязательныйФункция для рендеринга заглушки, пока флоу или пейвол загружается. По умолчанию используется ProgressView.
fullScreenнеобязательныйОпределяет, отображается ли флоу или пейвол в полноэкранном режиме или как шит. По умолчанию true.
didAppearнеобязательныйВызывается, когда экран флоу или пейвола появляется на экране.
didDisappearнеобязательныйВызывается, когда экран флоу или пейвола закрывается.
didPerformActionнеобязательныйВызывается при нажатии кнопки пользователем. Предопределены два ID действий: close и openURL; остальные задаются в билдере как пользовательские.
didSelectProductнеобязательныйВызывается, когда продукт выбирается для покупки пользователем или системой.
didStartPurchaseнеобязательныйВызывается, когда пользователь начинает процесс покупки.
didFinishPurchaseнеобязательныйВызывается при успешном завершении Adapty.makePurchase().
didFinishWebPaymentNavigationнеобязательныйВызывается по завершении навигации веб-оплаты.
didStartRestoreнеобязательныйВызывается, когда пользователь начинает процесс восстановления покупок.
didFailLoadingProductsнеобязательныйВызывается при ошибках загрузки продуктов. Верните true, чтобы повторить загрузку.
didPartiallyLoadProductsнеобязательныйВызывается при частичной загрузке продуктов.
showAlertItemнеобязательныйПривязка, управляющая отображением элементов оповещения поверх флоу или пейвола.
showAlertBuilderнеобязательныйФункция для рендеринга представления оповещения.

Обработка событий в UIKit

Для UIKit-приложений события обрабатываются через протокол AdaptyFlowControllerDelegate. Подробнее о настройке AdaptyFlowController с AdaptyFlowControllerDelegate см. в разделе Отображение флоу и пейволов — iOS.

Протокол объявляет 13 методов. Три из них не имеют реализации по умолчанию и должны быть реализованы при соответствии протоколу: didFailPurchase, didFinishRestoreWith и didFailRestoreWith. Остальные методы имеют реализацию по умолчанию (ничего не делающую) и могут быть переопределены при необходимости кастомного поведения. Методы ниже сгруппированы по назначению.

Жизненный цикл

func flowControllerDidAppear(_ controller: AdaptyFlowController) { }

func flowControllerDidDisappear(_ controller: AdaptyFlowController) { }

Эти методы срабатывают при появлении и закрытии флоу или пейвола.

Действия пользователя

func flowController(
    _ controller: AdaptyFlowController,
    didPerform action: AdaptyUI.Action
) { }

Варианты AdaptyUI.Action:

  • .close — по умолчанию закрывает контроллер. Переопределите, если нужно оставить контроллер на экране или выполнить дополнительную очистку.
  • .openURL(url:) — по умолчанию открывает URL через UIApplication.shared.open(...).
  • .custom(id:) — срабатывает для кнопок, которым в билдере назначен пользовательский идентификатор действия.

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

func flowController(
    _ controller: AdaptyFlowController,
    didSelectProduct product: AdaptyPaywallProduct
) { }

Вызывается, когда пользователь или система выбирают продукт для покупки. Продукт содержит полную информацию об офере (в v4 совместимость определяется автоматически — отдельного типа AdaptyPaywallProductWithoutDeterminingOffer больше нет).

События покупки

func flowController(
    _ controller: AdaptyFlowController,
    didStartPurchase product: AdaptyPaywallProduct
) { }

func flowController(
    _ controller: AdaptyFlowController,
    didFinishPurchase product: AdaptyPaywallProduct,
    purchaseResult: AdaptyPurchaseResult
) {
    // Default: dismiss the controller unless the purchase was cancelled.
}

func flowController(
    _ controller: AdaptyFlowController,
    didFailPurchase product: AdaptyPaywallProduct,
    error: AdaptyError
) { }

didFailPurchase — единственное событие покупки без реализации по умолчанию. didFinishPurchase по умолчанию закрывает контроллер при успешной покупке; переопределяйте только если нужна собственная логика после покупки.

События восстановления покупок

func flowControllerDidStartRestore(_ controller: AdaptyFlowController) { }

func flowController(
    _ controller: AdaptyFlowController,
    didFinishRestoreWith profile: AdaptyProfile
) { }

func flowController(
    _ controller: AdaptyFlowController,
    didFailRestoreWith error: AdaptyError
) { }

didFinishRestoreWith и didFailRestoreWith не имеют реализаций по умолчанию. Перед закрытием контроллера убедитесь, что возвращённый AdaptyProfile содержит нужный уровень доступа.

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

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. При ошибках загрузки верните true из didFailLoadingProductsWith, чтобы повторить попытку — это удобно при временных сбоях сети.

Навигация при веб-оплате

func flowController(
    _ controller: AdaptyFlowController,
    didFinishWebPaymentNavigation product: AdaptyPaywallProduct?,
    error: AdaptyError?
) { }

Вызывается после завершения навигации при веб-оплате — как успешной, так и неуспешной.

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

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

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

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

Обработка событий в SwiftUI

Для управления и отслеживания процессов на экране пейвола в вашем мобильном приложении используйте модификатор .paywall в SwiftUI:

@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 и их конфигурации.
didFailPurchaseобязательныйВызывается при ошибке покупки (например, платёж не разрешён, проблемы с сетью, неверный продукт). Не вызывается при отмене пользователем или ожидающих платежах.
didFinishRestoreобязательныйВызывается при успешном завершении покупки.
didFailRestoreобязательныйВызывается при ошибке восстановления покупки.
didFailRenderingобязательныйВызывается при ошибке рендеринга интерфейса. В этом случае обратитесь в поддержку Adapty.
fullScreenнеобязательныйОпределяет, отображается ли пейвол в полноэкранном режиме или как модальное окно. По умолчанию true.
didAppearнеобязательныйВызывается, когда экран пейвола появляется на экране. Также вызывается, когда пользователь нажимает кнопку веб-пейвола внутри пейвола и веб-пейвол открывается во встроенном браузере.
didDisappearнеобязательныйВызывается, когда экран пейвола закрывается. Также вызывается, когда веб-пейвол, открытый из пейвола во встроенном браузере, исчезает с экрана.
didPerformActionнеобязательныйВызывается при нажатии пользователем кнопки. У разных кнопок разные идентификаторы действий. Два идентификатора заданы заранее: close и openURL, остальные — пользовательские и задаются в билдере.
didSelectProductнеобязательныйВызывается, если продукт выбран для покупки — пользователем или системой.
didStartPurchaseнеобязательныйВызывается, когда пользователь начинает процесс покупки.
didFinishPurchaseнеобязательныйВызывается при успешном завершении покупки.
didFinishWebPaymentNavigationнеобязательныйВызывается после попытки открыть веб-пейвол для покупки — успешной или нет.
didStartRestoreнеобязательныйВызывается, когда пользователь начинает процесс восстановления покупок.
didFailLoadingProductsнеобязательныйВызывается при ошибках загрузки продуктов. Верните true, чтобы повторить загрузку.
didPartiallyLoadProductsнеобязательныйВызывается, когда продукты загружены частично.
showAlertItemнеобязательныйБиндинг, управляющий отображением элементов алерта поверх пейвола.
showAlertBuilderнеобязательныйФункция для рендеринга представления алерта.
placeholderBuilderнеобязательныйФункция для рендеринга заглушки во время загрузки пейвола.

Обработка событий в UIKit

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

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

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

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

    func paywallController(
        _ controller: AdaptyPaywallController,
        didSelectProduct product: AdaptyPaywallProductWithoutDeterminingOffer
    ) { }
Пример события (нажмите, чтобы раскрыть)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

Покупка начата

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

func paywallController(_ controller: AdaptyPaywallController,
                       didStartPurchase product: AdaptyPaywallProduct) {
}
Пример события (нажмите, чтобы развернуть)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

Не вызывается в режиме Observer. Подробнее см. в разделе iOS — отображение пейволов Paywall Builder в режиме Observer.

Покупка через веб-пейвол

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

func paywallController(
        _ controller: AdaptyPaywallController,
        shouldContinueWebPaymentNavigation product: AdaptyPaywallProduct
    ) {
    }
Пример события (нажмите, чтобы развернуть)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

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

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

func paywallController(
    _ controller: AdaptyPaywallController,
    didFinishPurchase product: AdaptyPaywallProductWithoutDeterminingOffer,
    purchaseResult: AdaptyPurchaseResult
) { }
}
Примеры событий (нажмите, чтобы развернуть)
// 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"
  }
}

Мы рекомендуем в этом случае закрывать экран пейвола.

В режиме Observer mode это не вызывается. Подробнее см. в разделе iOS — отображение пейволов Paywall Builder в режиме Observer mode.

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

Если покупка завершается с ошибкой, вызывается этот метод. Сюда входят ошибки StoreKit (ограничения платежей, неверные продукты, сетевые сбои), сбои верификации транзакций и системные ошибки. Обратите внимание: отмена пользователем вызывает didFinishPurchase с результатом «отменено», а ожидающие платежи этот метод не вызывают.

func paywallController(
    _ controller: AdaptyPaywallController,
    didFailPurchase product: AdaptyPaywallProduct,
    error: AdaptyError
) { }
Пример события (нажмите, чтобы развернуть)
{
  "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"
    }
  }
}

Не вызывается в режиме Observer mode. Подробнее см. в разделе iOS — Отображение пейволов Paywall Builder в режиме Observer mode.

Ошибка покупки через веб-пейвол

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

func paywallController(
        _ controller: AdaptyPaywallController,
        didFailWebPaymentNavigation product: AdaptyPaywallProduct,
        error: AdaptyError
    ) { }
Пример события (нажмите, чтобы развернуть)
{
  "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"
    }
  }
}

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

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

func paywallController(
    _ controller: AdaptyPaywallController, 
    didFinishRestoreWith profile: AdaptyProfile
) { }
Пример события (нажмите, чтобы развернуть)
{
  "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. Обратитесь к разделу Статус подписки, чтобы узнать, как его проверить.

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

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

public func paywallController(
    _ controller: AdaptyPaywallController, 
    didFailRestoreWith error: AdaptyError
) { }
Пример события (нажмите, чтобы развернуть)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

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

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

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

public func paywallController(
    _ controller: AdaptyPaywallController,
    didFailLoadingProductsWith error: AdaptyError
) -> Bool {
    return true
}
Пример события (нажмите, чтобы развернуть)
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

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

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

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

public func paywallController(
    _ controller: AdaptyPaywallController,
    didFailRenderingWith error: AdaptyError
) { }
Пример события (нажмите, чтобы развернуть)
{
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render paywall interface",
    "details": {
      "underlyingError": "Invalid paywall configuration"
    }
  }
}

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