---
title: "Включение покупок с Flow Builder в iOS SDK"
description: "Быстрый старт по включению встроенных покупок с Adapty Flow Builder."
---

Чтобы включить встроенные покупки, нужно разобраться в трёх ключевых концепциях:
- [**Продукты**](product) – всё, что пользователи могут купить (подписки, расходуемые покупки, пожизненный доступ)
- [**Флоу**](adapty-flow-builder) – последовательности экранов, которые показывают продукты пользователям, созданные в no-code Flow Builder. SDK получает их через `getFlow`. Если вы предпочитаете строить UI в собственном коде, используйте пейвол — см. [Реализация пейволов вручную](ios-quickstart-manual).
- [**Плейсменты**](placements) – где и когда в приложении показываются флоу (например, `main`, `onboarding`, `settings`). Вы привязываете флоу к плейсментам в дашборде, а затем запрашиваете их по ID плейсмента в коде. Это упрощает проведение A/B-тестов и показ разных флоу разным пользователям.
Adapty предлагает три способа подключить покупки в вашем приложении. Выберите подходящий в зависимости от требований:
| Реализация | Сложность | Когда использовать |
|---|---|---|
| Adapty Flow Builder | ✅ Легко | Вы [создаёте полноценный флоу, готовый к покупкам, в no-code конструкторе](quickstart-paywalls). Adapty автоматически его отображает и берёт на себя весь процесс покупки, валидацию чеков и управление подписками. |
| Пейволы, созданные вручную | 🟡 Средне | Вы реализуете UI пейвола в коде приложения, но всё равно получаете объект флоу от Adapty, сохраняя гибкость в управлении продуктами. См. [гайд](ios-quickstart-manual). |
| Observer mode | 🔴 Сложно | У вас уже есть собственная инфраструктура обработки покупок, и вы хотите продолжать ею пользоваться. Обратите внимание, что observer mode имеет ряд ограничений в Adapty. См. [статью](observer-vs-full-mode). |
:::important
**Шаги ниже показывают, как реализовать флоу, созданный в Adapty Flow Builder.**

Если вы предпочитаете создавать UI пейвола самостоятельно, см. [Реализация пейволов вручную](ios-quickstart-manual).
:::

Чтобы отобразить флоу, созданный в Adapty Flow Builder, в коде вашего приложения нужно всего лишь:

1. **Получить флоу**: Запросить его из Adapty.
2. **Отобразить его — Adapty сам обработает покупки**: Показать представление в вашем приложении.
3. **Обработать действия кнопок**: Связать взаимодействия пользователя с реакцией приложения на них. Например, открывать ссылки или закрывать флоу при нажатии кнопок.
## Прежде чем начать \{#before-you-start\}

Прежде чем начать, выполните следующие шаги:

1. [Подключите приложение к App Store](initial_ios) в дашборде Adapty.
2. [Создайте продукты](create-product) в Adapty.
3. [Создайте флоу и добавьте в него продукты](create-paywall).
4. [Создайте плейсмент и добавьте в него флоу](create-placement).
5. [Установите и активируйте SDK](sdk-installation-ios) в коде приложения. В этом гайде используются API Adapty iOS SDK v4.

## 1. Получите флоу \{#1-get-the-flow\}

Ваши флоу привязаны к плейсментам, настроенным в дашборде. Плейсменты позволяют показывать разные флоу разным аудиториям или запускать [A/B-тесты](ab-tests).

Чтобы получить флоу, созданный в Adapty Flow Builder, нужно:

1. Получить объект `flow` по ID [плейсмента](placements) с помощью метода `getFlow` и проверить, есть ли в нём конфигурация представления.
2. Получить конфигурацию представления с помощью метода `getFlowConfiguration`. Она содержит элементы UI и стили, необходимые для отображения флоу.
```swift

func loadFlow() async {
    let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID")

    guard flow.hasViewConfiguration else {
        print("Flow doesn't have a view configuration")
        return
    }

    flowConfiguration = try await AdaptyUI.getFlowConfiguration(forFlow: flow)
}
```
## 2. Отобразите флоу \{#2-display-the-flow\}

Теперь, когда у вас есть конфигурация флоу, достаточно добавить несколько строк, чтобы отобразить его.

<Tabs groupId="current-os" queryString>

<TabItem value="swiftui" label="SwiftUI" default>

В SwiftUI при отображении флоу также нужно обрабатывать события. `didFinishPurchase`, `didFailPurchase`, `didFinishRestore`, `didFailRestore` и `didReceiveError` обязательны. При тестировании можно просто скопировать код из фрагмента ниже, чтобы логировать эти события.
:::tip
Флоу не закрывается автоматически после успешной покупки. В `didFinishPurchase` установите привязку отображения в `false`, чтобы закрыть его, или ничего не делайте, чтобы флоу перешёл к следующим экранам.
:::
```swift
.flow(
    isPresented: $flowPresented,
    flowConfiguration: flowConfiguration,
    didFinishPurchase: { product, purchaseResult in
        print("Purchase finished successfully")
        flowPresented = false // or do nothing to let the flow continue
    },
    didFailPurchase: { product, error in
        print("Purchase failed: \(error)")
    },
    didFinishRestore: { profile in
        print("Restore finished successfully")
    },
    didFailRestore: { error in
        print("Restore failed: \(error)")
    },
    didReceiveError: { error in
        flowPresented = false
        print("Flow error: \(error)")
    }
)
```
</TabItem>
<TabItem value="uikit" label="UIKit" default>

```swift

func presentFlow(with config: AdaptyUI.FlowConfiguration) {
    let flowController = try AdaptyUI.flowController(
        with: config,
        delegate: self
    )
    present(flowController, animated: true)
}
```
Реализуйте `AdaptyFlowControllerDelegate` для обработки событий. Как минимум, реализуйте четыре метода, у которых нет реализации по умолчанию. Обратите внимание: контроллер не закрывается сам после успешной покупки — закройте его в `didFinishPurchase` или ничего не делайте, чтобы флоу перешёл к следующим экранам:
```swift
extension YourViewController: AdaptyFlowControllerDelegate {
    func flowController(_ controller: AdaptyFlowController,
                        didFinishPurchase product: AdaptyPaywallProduct,
                        purchaseResult: AdaptyPurchaseResult) {
        if !purchaseResult.isPurchaseCancelled {
            controller.dismiss(animated: true) // or do nothing to let the flow continue
        }
    }

    func flowController(_ controller: AdaptyFlowController,
                        didFailPurchase product: AdaptyPaywallProduct,
                        error: AdaptyError) {
        print("Purchase failed: \(error)")
    }

    func flowController(_ controller: AdaptyFlowController,
                        didFinishRestoreWith profile: AdaptyProfile) {
        print("Restore finished successfully")
    }

    func flowController(_ controller: AdaptyFlowController,
                        didFailRestoreWith error: AdaptyError) {
        print("Restore failed: \(error)")
    }
}
```
</TabItem>
</Tabs>
:::info
Подробнее о том, как отобразить флоу, см. наш [гайд](ios-present-paywalls).
:::
## 3. Обработка действий кнопок \{#handle-button-actions\}

Когда пользователи нажимают кнопки, iOS SDK автоматически обрабатывает покупки, восстановление, закрытие флоу и переходы по ссылкам.

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

Например, вот как обрабатывать кнопку закрытия. В UIKit SDK автоматически закрывает контроллер, когда срабатывает `.close` — переопределяйте это только если нужно нестандартное поведение. В SwiftUI вам нужно самостоятельно установить привязку `isPresented` в `false`.
:::tip
Читайте наши гайды о том, как обрабатывать [действия](handle-paywall-actions) кнопок и [события](ios-handling-events).
:::

<Tabs groupId="current-os" queryString>

<TabItem value="swiftui" label="SwiftUI" default>
```swift
.flow(
    isPresented: $flowPresented,
    flowConfiguration: flowConfiguration,
    didPerformAction: { action in
        switch action {
            case .close:
                flowPresented = false // dismiss the flow when the user taps close
            default:
                break
        }
    },
    didFinishPurchase: { product, purchaseResult in flowPresented = false },
    didFailPurchase: { product, error in /* handle the error */ },
    didFinishRestore: { profile in /* check access level and dismiss */ },
    didFailRestore: { error in /* handle the error */ },
    didReceiveError: { error in flowPresented = false }
)
```
</TabItem>
<TabItem value="uikit" label="UIKit" default>

```swift
extension YourViewController: AdaptyFlowControllerDelegate {
    func flowController(_ controller: AdaptyFlowController,
                        didPerform action: AdaptyUI.Action) {
        switch action {
            case .close:
                controller.dismiss(animated: true) // default behavior — override only if needed
            default:
                break
        }
    }
}
```
</TabItem>
</Tabs>
## Следующие шаги \{#next-steps\}

:::tip
Есть вопросы или возникли проблемы? Загляните на наш [форум поддержки](https://adapty.featurebase.app/), где можно найти ответы на распространённые вопросы или задать свой. Наша команда и сообщество всегда готовы помочь!
:::

Ваш пейвол готов к отображению в приложении. [Протестируйте покупки в режиме песочницы](test-purchases-in-sandbox), чтобы убедиться, что тестовая покупка через пейвол проходит успешно.

Теперь нужно [проверить уровень доступа пользователей](ios-check-subscription-status), чтобы показывать пейвол или предоставлять доступ к платным функциям только нужным пользователям.

## Полный пример \{#full-example\}

Вот как все шаги из этого гайда можно объединить в вашем приложении.

<Tabs groupId="current-os" queryString>

<TabItem value="swiftui" label="SwiftUI" default>
```swift

struct ContentView: View {
    @State private var flowPresented = false
    @State private var flowConfiguration: AdaptyUI.FlowConfiguration?
    @State private var isLoading = false
    @State private var hasInitialized = false

    var body: some View {
        VStack {
            if isLoading {
                ProgressView("Loading...")
            } else {
                Text("Your App Content")
            }
        }
        .task {
            guard !hasInitialized else { return }
            await initializeFlow()
            hasInitialized = true
        }
        .flow(
            isPresented: $flowPresented,
            flowConfiguration: flowConfiguration,
            didPerformAction: { action in
                switch action {
                case .close:
                    flowPresented = false
                default:
                    break
                }
            },
            didFinishPurchase: { product, purchaseResult in
                print("Purchase finished successfully")
                flowPresented = false // or do nothing to let the flow continue
            },
            didFailPurchase: { product, error in
                print("Purchase failed: \(error)")
            },
            didFinishRestore: { profile in
                print("Restore finished successfully")
            },
            didFailRestore: { error in
                print("Restore failed: \(error)")
            },
            didReceiveError: { error in
                print("Flow error: \(error)")
                flowPresented = false
            }
        )
    }

    private func initializeFlow() async {
        isLoading = true
        defer { isLoading = false }

        await loadFlow()
        flowPresented = true
    }

    private func loadFlow() async {
        do {
            let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID")
            guard flow.hasViewConfiguration else {
                print("Flow doesn't have a view configuration")
                return
            }
            flowConfiguration = try await AdaptyUI.getFlowConfiguration(forFlow: flow)
        } catch {
            print("Failed to load: \(error)")
        }
    }
}
```
</TabItem>
<TabItem value="uikit" label="UIKit" default>
```swift

class ViewController: UIViewController {
    private var flowConfiguration: AdaptyUI.FlowConfiguration?

    override func viewDidLoad() {
        super.viewDidLoad()

        Task {
            await initializeFlow()
        }
    }

    private func initializeFlow() async {
        do {
            flowConfiguration = try await loadFlow()

            if let flowConfiguration {
                await MainActor.run {
                    presentFlow(with: flowConfiguration)
                }
            }
        } catch {
            print("Error initializing: \(error)")
        }
    }

    private func loadFlow() async throws -> AdaptyUI.FlowConfiguration? {
        let flow = try await Adapty.getFlow(placementId: "YOUR_PLACEMENT_ID")

        guard flow.hasViewConfiguration else {
            print("Flow doesn't have a view configuration")
            return nil
        }

        return try await AdaptyUI.getFlowConfiguration(forFlow: flow)
    }

    private func presentFlow(with config: AdaptyUI.FlowConfiguration) {
        guard let flowController = try? AdaptyUI.flowController(
            with: config,
            delegate: self
        ) else { return }

        present(flowController, animated: true)
    }
}

extension ViewController: AdaptyFlowControllerDelegate {
    func flowController(_ controller: AdaptyFlowController,
                        didFinishPurchase product: AdaptyPaywallProduct,
                        purchaseResult: AdaptyPurchaseResult) {
        if !purchaseResult.isPurchaseCancelled {
            controller.dismiss(animated: true) // or do nothing to let the flow continue
        }
    }

    func flowController(_ controller: AdaptyFlowController,
                        didFailPurchase product: AdaptyPaywallProduct,
                        error: AdaptyError) {
        print("Purchase failed for \(product.vendorProductId): \(error)")

        guard error.adaptyErrorCode != .paymentCancelled else { return }

        let message = switch error.adaptyErrorCode {
        case .paymentNotAllowed:
            "Purchases are not allowed on this device."
        default:
            "Purchase failed. Please try again."
        }

        let alert = UIAlertController(title: "Purchase Error", message: message, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default))
        present(alert, animated: true)
    }

    func flowController(_ controller: AdaptyFlowController,
                        didFinishRestoreWith profile: AdaptyProfile) {
        print("Restore finished successfully")
        controller.dismiss(animated: true)
    }

    func flowController(_ controller: AdaptyFlowController,
                        didFailRestoreWith error: AdaptyError) {
        print("Restore failed: \(error)")
    }

    func flowController(_ controller: AdaptyFlowController,
                        didReceiveError error: AdaptyUIError) {
        print("Flow error: \(error)")
        controller.dismiss(animated: true)
    }
}
```
</TabItem>
</Tabs>