Gérer les événements de flow et de paywall - Kotlin Multiplatform

Ce guide couvre la gestion des événements liés aux achats, aux restaurations, à la sélection de produits et au rendu des flows. Vous devez également implémenter la gestion des boutons (fermeture du flow, ouverture de liens, etc.). Consultez notre guide sur la gestion des actions de flow pour plus de détails.

Les flows et paywalls configurés avec le Flow Builder ou le Paywall Builder n’ont pas besoin de code supplémentaire pour effectuer et restaurer des achats. Cependant, ils génèrent certains événements auxquels votre application peut réagir. Ces événements comprennent les pressions sur des boutons (boutons de fermeture, URLs, sélections de produits, etc.) ainsi que des notifications sur les actions liées aux achats. Découvrez comment répondre à ces événements ci-dessous.

Pour contrôler ou surveiller les processus qui se déroulent sur l’écran de flow dans votre application mobile, implémentez les méthodes de l’interface AdaptyUIFlowsEventsObserver et enregistrez votre observateur avec AdaptyUI.setFlowsEventsObserver(). Certaines méthodes ont des implémentations par défaut qui gèrent automatiquement les scénarios courants, donc ne surchargez que les méthodes que vous souhaitez modifier :

AdaptyUI.setFlowsEventsObserver(object : AdaptyUIFlowsEventsObserver {
    // override only the methods you want to change
})

Ces méthodes sont l’endroit où vous ajoutez votre logique personnalisée pour répondre aux événements de flow. Vous pouvez utiliser view.dismiss() pour fermer le flow, ou implémenter tout autre comportement personnalisé dont vous avez besoin. Notez que dismiss() est une fonction suspend — à l’intérieur d’un callback, lancez-la sur le mainUiScope de l’observateur : mainUiScope.launch { view.dismiss() }.

Événements générés par l’utilisateur

Apparition et disparition du flow

Lorsqu’un flow apparaît ou disparaît, ces méthodes seront invoquées :

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
}
  • Sur iOS, flowViewDidAppear est également invoqué quand un utilisateur appuie sur le bouton de paywall web dans un flow, et qu’un paywall web s’ouvre dans un navigateur intégré.
  • Sur iOS, flowViewDidDisappear est également invoqué quand un paywall web ouvert depuis un flow dans un navigateur intégré disparaît de l’écran.
Exemples d’événements (cliquez pour développer)
// Flow appeared
{
  // No additional data
}

// Flow disappeared
{
  // No additional data
}

Sélection de produit

Si un utilisateur sélectionne un produit à acheter, cette méthode sera invoquée :

override fun flowViewDidSelectProduct(view: AdaptyUIFlowView, productId: String) {
    // Handle product selection
    // You can update UI or track analytics here
}
Exemple d’événement (cliquez pour développer)
{
  "productId": "premium_monthly"
}

Achat démarré

Si un utilisateur initie le processus d’achat, cette méthode sera invoquée :

override fun flowViewDidStartPurchase(view: AdaptyUIFlowView, product: AdaptyPaywallProduct) {
    // Handle purchase start
    // You can show loading indicators or track analytics here
}

En mode Observateur, les achats démarrés depuis un flow sont transmis à votre AdaptyUIObserverModeResolver à la place.

Exemple d’événement (cliquez pour développer)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

Achat réussi, annulé ou en attente

Si un achat se termine, cette méthode sera invoquée. Par défaut, elle ne fait rien — le flow reste ouvert après l’achat jusqu’à ce que vous le fermiez, alors appelez view.dismiss() vous-même dès que l’utilisateur obtient l’accès :

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
        }
    }
}
Exemples d’événements (cliquez pour développer)
// 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"
  }
}

Nous recommandons de fermer l’écran du flow en cas d’achat réussi.

Achat échoué

Si un achat échoue en raison d’une erreur, cette méthode sera invoquée. Cela inclut les erreurs StoreKit/Google Play Billing (restrictions de paiement, produits invalides, échecs réseau), les échecs de vérification de transaction et les erreurs système. Notez que les annulations par l’utilisateur déclenchent flowViewDidFinishPurchase avec un résultat annulé à la place, et les paiements en attente ne déclenchent pas cette méthode.

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
}
Exemple d’événement (cliquez pour développer)
{
  "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"
    }
  }
}

Restauration démarrée

Si un utilisateur initie le processus de restauration, cette méthode sera invoquée :

override fun flowViewDidStartRestore(view: AdaptyUIFlowView) {
    // Handle restore start
    // You can show loading indicators or track analytics here
}

Restauration réussie

Si la restauration d’un achat réussit, cette méthode sera invoquée. Par défaut, elle ne fait rien — le flow reste ouvert après la restauration jusqu’à ce que vous le fermiez :

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() }
    }
}
Exemple d’événement (cliquez pour développer)
{
  "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"
      }
    ]
  }
}

Nous recommandons de fermer l’écran si l’utilisateur dispose du accessLevel requis. Consultez la rubrique Statut d’abonnement pour savoir comment le vérifier.

Restauration échouée

Si Adapty.restorePurchases() échoue, cette méthode sera invoquée :

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
}
Exemple d’événement (cliquez pour développer)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

Fin de la navigation de paiement web

Si un utilisateur initie le processus d’achat via un paywall web, cette méthode sera invoquée :

override fun flowViewDidFinishWebPaymentNavigation(
    view: AdaptyUIFlowView,
    product: AdaptyPaywallProduct?,
    error: AdaptyError?
) {
    if (error != null) {
        // Handle web payment navigation error
    } else {
        // Handle successful web payment navigation
    }
}
Exemples d’événements (cliquez pour développer)
// 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"
    }
  }
}

Chargement des données et rendu

Erreurs de chargement des produits

Si vous ne transmettez pas les produits lors de l’initialisation, AdaptyUI récupérera lui-même les objets nécessaires depuis le serveur. Si cette opération échoue, AdaptyUI signalera l’erreur en appelant cette méthode :

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
}
Exemple d’événement (cliquez pour développer)
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

Erreurs de rendu et d’exécution

Si une erreur survient pendant le rendu de l’interface, ou si une autre erreur d’exécution non liée à un achat se produit, elle sera signalée par cette méthode. Par défaut, le flow est fermé en cas d’erreur — surchargez la méthode pour le maintenir ouvert ou ajouter votre propre gestion :

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
}
Exemple d’événement (cliquez pour développer)
{
    "error": {
        "code": "rendering_failed",
            "message": "Failed to render flow interface",
            "details": {
            "underlyingError": "Invalid flow configuration"
        }
    }
}

Dans une situation normale, de telles erreurs ne devraient pas se produire. Si vous en rencontrez une, merci de nous en informer.

Événements d’analyse

Le callback flowViewDidReceiveAnalyticEvent est réservé aux événements d’analyse personnalisés d’un flow. Les flows n’émettent pas encore ces événements vers votre code, vous n’avez donc pas besoin de l’implémenter :

override fun flowViewDidReceiveAnalyticEvent(
    view: AdaptyUIFlowView,
    name: String,
    paramsJsonString: String
) {
    // Reserved for custom analytic events from a flow
}

Bouton retour système Android

Par défaut, un flow ne peut pas être fermé avec le bouton retour système Android ou le geste de retour — l’implémentation par défaut de flowViewDidPerformAction ferme le flow uniquement sur CloseAction et ignore AndroidSystemBackAction, de sorte que l’utilisateur quitte le flow via un chemin que vous définissez, comme un bouton Fermer ou une action on_device_back dans le builder. Si vous souhaitez que le bouton retour système ferme le flow, gérez l’action vous-même :

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

Consultez le guide sur la gestion des actions de flow pour la liste complète des actions.

Les paywalls configurés avec le Paywall Builder n’ont pas besoin de code supplémentaire pour effectuer et restaurer des achats. Cependant, ils génèrent certains événements auxquels votre application peut réagir. Ces événements comprennent les pressions sur des boutons (boutons de fermeture, URLs, sélections de produits, etc.) ainsi que des notifications sur les actions liées aux achats effectuées sur le paywall. Découvrez comment répondre à ces événements ci-dessous.

Ce guide est réservé aux paywalls du nouveau Paywall Builder uniquement.

Pour contrôler ou surveiller les processus qui se déroulent sur l’écran de paywall dans votre application mobile, implémentez les méthodes de l’interface AdaptyUIPaywallsEventsObserver. Certaines méthodes ont des implémentations par défaut qui gèrent automatiquement les scénarios courants.

Ces méthodes sont l’endroit où vous ajoutez votre logique personnalisée pour répondre aux événements de paywall. Vous pouvez utiliser view.dismiss() pour fermer le paywall, ou implémenter tout autre comportement personnalisé dont vous avez besoin.

Événements générés par l’utilisateur

Apparition et disparition du paywall

Lorsqu’un paywall apparaît ou disparaît, ces méthodes seront invoquées :

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
}
  • Sur iOS, paywallViewDidAppear est également invoqué quand un utilisateur appuie sur le bouton de paywall web dans un paywall, et qu’un paywall web s’ouvre dans un navigateur intégré.
  • Sur iOS, paywallViewDidDisappear est également invoqué quand un paywall web ouvert depuis un paywall dans un navigateur intégré disparaît de l’écran.
Exemples d’événements (cliquez pour développer)
// Paywall appeared
{
  // No additional data
}

// Paywall disappeared
{
  // No additional data
}

Sélection de produit

Si un utilisateur sélectionne un produit à acheter, cette méthode sera invoquée :

override fun paywallViewDidSelectProduct(view: AdaptyUIPaywallView, productId: String) {
    // Handle product selection
    // You can update UI or track analytics here
}
Exemple d’événement (cliquez pour développer)
{
  "productId": "premium_monthly"
}

Achat démarré

Si un utilisateur initie le processus d’achat, cette méthode sera invoquée :

override fun paywallViewDidStartPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct) {
    // Handle purchase start
    // You can show loading indicators or track analytics here
}
Exemple d’événement (cliquez pour développer)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

Achat réussi, annulé ou en attente

Si un achat réussit, cette méthode sera invoquée. Par défaut, elle ferme automatiquement le paywall sauf si l’achat a été annulé par l’utilisateur :

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
        }
    }
}
Exemples d’événements (cliquez pour développer)
// 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"
  }
}

Nous recommandons de fermer l’écran du paywall en cas d’achat réussi.

Achat échoué

Si un achat échoue en raison d’une erreur, cette méthode sera invoquée. Cela inclut les erreurs StoreKit/Google Play Billing (restrictions de paiement, produits invalides, échecs réseau), les échecs de vérification de transaction et les erreurs système. Notez que les annulations par l’utilisateur déclenchent paywallViewDidFinishPurchase avec un résultat annulé à la place, et les paiements en attente ne déclenchent pas cette méthode.

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
}
Exemple d’événement (cliquez pour développer)
{
  "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"
    }
  }
}

Restauration démarrée

Si un utilisateur initie le processus de restauration, cette méthode sera invoquée :

override fun paywallViewDidStartRestore(view: AdaptyUIPaywallView) {
    // Handle restore start
    // You can show loading indicators or track analytics here
}

Restauration réussie

Si la restauration d’un achat réussit, cette méthode sera invoquée :

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()
    }
}
Exemple d’événement (cliquez pour développer)
{
  "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"
      }
    ]
  }
}

Nous recommandons de fermer l’écran si l’utilisateur dispose du accessLevel requis. Consultez la rubrique Statut d’abonnement pour savoir comment le vérifier.

Restauration échouée

Si Adapty.restorePurchases() échoue, cette méthode sera invoquée :

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
}
Exemple d’événement (cliquez pour développer)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

Fin de la navigation de paiement web

Si un utilisateur initie le processus d’achat via un paywall web, cette méthode sera invoquée :

override fun paywallViewDidFinishWebPaymentNavigation(
    view: AdaptyUIPaywallView,
    product: AdaptyPaywallProduct?,
    error: AdaptyError?
) {
    if (error != null) {
        // Handle web payment navigation error
    } else {
        // Handle successful web payment navigation
    }
}
Exemples d’événements (cliquez pour développer)
// 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"
    }
  }
}

Chargement des données et rendu

Erreurs de chargement des produits

Si vous ne transmettez pas les produits lors de l’initialisation, AdaptyUI récupérera lui-même les objets nécessaires depuis le serveur. Si cette opération échoue, AdaptyUI signalera l’erreur en appelant cette méthode :

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
}
Exemple d’événement (cliquez pour développer)
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

Erreurs de rendu

Si une erreur survient pendant le rendu de l’interface, elle sera signalée par cette méthode :

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
}
Exemple d’événement (cliquez pour développer)
{
    "error": {
        "code": "rendering_failed",
            "message": "Failed to render paywall interface",
            "details": {
            "underlyingError": "Invalid paywall configuration"
        }
    }
}

Dans une situation normale, de telles erreurs ne devraient pas se produire. Si vous en rencontrez une, merci de nous en informer.