---
title: "Activer les achats avec Flow Builder dans le SDK iOS"
description: "Guide de démarrage rapide pour activer les achats intégrés avec Adapty Flow Builder."
---

Pour activer les achats intégrés, vous devez comprendre trois concepts clés :

- [**Produits**](product) – tout ce que les utilisateurs peuvent acheter (abonnements, consommables, accès à vie)
- [**Flows**](adapty-flow-builder) – séquences d'écrans qui présentent des produits aux utilisateurs, créées dans le Flow Builder sans code. Le SDK les récupère via `getFlow`. Si vous préférez construire l'interface dans votre propre code, utilisez plutôt un paywall — voir [Implémenter des paywalls manuellement](ios-quickstart-manual).
- [**Placements**](placements) – où et quand afficher les flows dans votre app (par exemple `main`, `onboarding`, `settings`). Vous associez des flows aux placements dans le tableau de bord, puis vous les demandez par ID de placement dans votre code. Cela facilite l'exécution de tests A/B et l'affichage de flows différents selon les utilisateurs.

Adapty vous propose trois façons d'activer les achats dans votre app. Choisissez celle qui correspond à vos besoins :

| Implémentation | Complexité | Quand l'utiliser |
|---|---|---|
| Adapty Flow Builder | ✅ Facile | Vous [créez un flow complet et prêt à l'achat dans le builder sans code](quickstart-paywalls). Adapty le rend automatiquement et gère l'intégralité du flux d'achat, la validation des reçus et la gestion des abonnements en coulisses. |
| Paywalls créés manuellement | 🟡 Moyen | Vous implémentez l'interface de votre paywall dans le code de votre app, mais récupérez quand même l'objet flow depuis Adapty pour conserver de la flexibilité dans les offres de produits. Voir le [guide](ios-quickstart-manual). |
| Mode observateur | 🔴 Difficile | Vous disposez déjà de votre propre infrastructure de gestion des achats et souhaitez continuer à l'utiliser. Notez que le mode observateur a ses limitations dans Adapty. Voir l'[article](observer-vs-full-mode). |

:::important
**Les étapes ci-dessous montrent comment implémenter un flow créé dans Adapty Flow Builder.**

Si vous préférez construire l'interface du paywall vous-même, voir [Implémenter des paywalls manuellement](ios-quickstart-manual).
:::

Pour afficher un flow créé dans Adapty Flow Builder, vous n'avez besoin que de :

1. **Récupérer le flow** : Obtenez-le depuis Adapty.
2. **L'afficher et laisser Adapty gérer les achats** : Affichez la vue dans votre app.
3. **Gérer les actions des boutons** : Associez les interactions utilisateur aux réponses de votre app. Par exemple, ouvrez des liens ou fermez le flow lorsque les utilisateurs cliquent sur des boutons.

## Avant de commencer \{#before-you-start\}

Avant de commencer, effectuez ces étapes :

1. [Connectez votre app à l'App Store](initial_ios) dans l'Adapty Dashboard.
2. [Créez vos produits](create-product) dans Adapty.
3. [Créez un flow et ajoutez-y des produits](create-paywall).
4. [Créez un placement et ajoutez votre flow](create-placement).
5. [Installez et activez le SDK Adapty](sdk-installation-ios) dans votre code. Ce guide utilise les APIs du SDK Adapty iOS v4.

## 1. Récupérer le flow \{#1-get-the-flow\}

Vos flows sont associés à des placements configurés dans le tableau de bord. Les placements vous permettent d'exécuter des flows différents pour différentes audiences ou de lancer des [tests A/B](ab-tests).

Pour obtenir un flow créé dans Adapty Flow Builder, vous devez :

1. Récupérer l'objet `flow` par l'ID du [placement](placements) via la méthode `getFlow` et vérifier qu'il dispose d'une configuration de vue.
2. Obtenir la configuration de vue via la méthode `getFlowConfiguration`. Elle contient les éléments d'interface et le style nécessaires pour afficher le flow.

```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. Afficher le flow \{#2-display-the-flow\}

Maintenant que vous avez la configuration du flow, quelques lignes suffisent pour l'afficher.

<Tabs groupId="current-os" queryString>

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

En SwiftUI, lors de l'affichage du flow, vous devez également gérer les événements. `didFinishPurchase`, `didFailPurchase`, `didFinishRestore`, `didFailRestore` et `didReceiveError` sont obligatoires. Pour les tests, vous pouvez simplement copier le code du snippet ci-dessous pour journaliser ces événements.

:::tip
Le flow ne se ferme pas automatiquement après un achat réussi. Dans `didFinishPurchase`, passez votre binding de présentation à `false` pour le fermer, ou ne faites rien pour laisser le flow continuer vers les écrans suivants.
:::

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

Implémentez `AdaptyFlowControllerDelegate` pour gérer les événements. Au minimum, implémentez les quatre méthodes sans implémentation par défaut. Notez que le contrôleur ne se ferme pas tout seul après un achat réussi — fermez-le dans `didFinishPurchase`, ou ne faites rien pour laisser le flow continuer vers les écrans suivants :

```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
Pour plus de détails sur l'affichage d'un flow, consultez notre [guide](ios-present-paywalls).
:::

## 3. Gérer les actions des boutons \{#3-handle-button-actions\}

Lorsque les utilisateurs cliquent sur des boutons, le SDK iOS gère automatiquement les achats, la restauration, la fermeture du flow et l'ouverture des liens.

Cependant, d'autres boutons ont des identifiants personnalisés ou prédéfinis et nécessitent une gestion dans votre code. Vous pouvez également vouloir remplacer leur comportement par défaut.

Par exemple, voici comment gérer le bouton de fermeture. En UIKit, le SDK ferme le contrôleur automatiquement lorsque `.close` se déclenche — ne surchargez que si vous souhaitez un comportement personnalisé. En SwiftUI, vous devez passer votre binding `isPresented` à `false` vous-même.

:::tip
Consultez nos guides sur la gestion des [actions](handle-paywall-actions) et des [événements](ios-handling-events) des boutons.
:::

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

## Étapes suivantes \{#next-steps\}

:::tip
Des questions ou des problèmes ? Consultez notre [forum d'assistance](https://adapty.featurebase.app/) où vous trouverez des réponses aux questions fréquentes ou pourrez poser les vôtres. Notre équipe et notre communauté sont là pour vous aider !
:::

Votre flow est prêt à être affiché dans l'app. [Testez vos achats en mode sandbox](test-purchases-in-sandbox) pour vous assurer de pouvoir effectuer un achat de test.

Vous devez ensuite [vérifier le niveau d'accès des utilisateurs](ios-check-subscription-status) pour vous assurer d'afficher un flow ou de donner accès aux fonctionnalités payantes aux bons utilisateurs.

## Exemple complet \{#full-example\}

Voici comment toutes les étapes de ce guide peuvent être intégrées ensemble dans votre app.

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