Gérer les événements de flow et de paywall - Android

Important

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

Les flows et les 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 incluent 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 ci-dessous comment répondre à ces événements.

Vous souhaitez voir un exemple concret d’intégration du SDK Adapty dans une application mobile ? Consultez nos exemples d’applications, qui illustrent la configuration complète, notamment l’affichage des paywalls, les achats et d’autres fonctionnalités de base.

Si vous devez contrôler ou surveiller les processus qui se déroulent sur l’écran d’achat, implémentez les méthodes AdaptyFlowEventListener.

Si vous souhaitez conserver le comportement par défaut dans certains cas, vous pouvez étendre AdaptyFlowDefaultEventListener et ne redéfinir que les méthodes que vous souhaitez modifier.

Voici les comportements par défaut issus de AdaptyFlowDefaultEventListener.

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

Sélection de produit

Si un produit est sélectionné pour achat (par un utilisateur ou par le système), cette méthode sera invoquée :

public override fun onProductSelected(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
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 initié

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

public override fun onPurchaseStarted(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
Exemple d’événement (Cliquez pour agrandir)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

La méthode ne sera pas appelée en mode Observer. Consultez la rubrique Android - Présenter les paywalls Paywall Builder en mode Observer pour plus de détails.

Achat réussi, annulé ou en attente

Si l’achat réussit, cette méthode sera appelée :

public override fun onPurchaseFinished(
    purchaseResult: AdaptyPurchaseResult,
    product: AdaptyPaywallProduct,
    context: Context,
) {
    if (purchaseResult !is AdaptyPurchaseResult.UserCanceled)
        context.getActivityOrNull()?.onBackPressed()
}
Exemples d’événements (Cliquer pour développer)
// Successful purchase
{
  "purchaseResult": {
    "type": "Success",
    "profile": {
      "accessLevels": {
        "premium": {
          "id": "premium",
          "isActive": true,
          "expiresAt": "2024-02-15T10:30:00Z"
        }
      }
    }
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

// Cancelled purchase
{
  "purchaseResult": {
    "type": "UserCanceled"
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

// Pending purchase
{
  "purchaseResult": {
    "type": "Pending"
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

Nous recommandons de fermer l’écran dans ce cas.

La méthode ne sera pas invoquée en mode Observer. Consultez la rubrique Android - Présenter les paywalls du Paywall Builder en mode Observer pour plus de détails.

Échec d’achat

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

public override fun onPurchaseFailure(
    error: AdaptyError,
    product: AdaptyPaywallProduct,
    context: Context,
) {}
Exemple d’événement (cliquer pour développer)
{
  "error": {
    "code": "purchase_failed",
    "message": "Purchase failed due to insufficient funds",
    "details": {
      "underlyingError": "Insufficient funds in account"
    }
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

Cette méthode ne sera pas invoquée en mode Observer. Consultez la rubrique Android - Présenter les paywalls Paywall Builder en mode Observer pour plus de détails.

Navigation web de paiement terminée

Cette méthode est invoquée après une tentative d’ouverture d’un paywall web pour un produit spécifique. Cela inclut les tentatives de navigation réussies et échouées :

public override fun onFinishWebPaymentNavigation(
    product: AdaptyPaywallProduct?,
    error: AdaptyError?,
    context: Context,
) {}

Paramètres :

ParamètreDescription
productUn AdaptyPaywallProduct pour lequel le paywall web a été ouvert. Peut être null.
errorUn objet AdaptyError si la navigation vers le paywall web a échoué ; null si la navigation a réussi.
Exemples d’événements (Cliquer pour développer)
// Successful 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 navigation
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "error": {
    "code": "web_navigation_failed",
    "message": "Failed to open web paywall",
    "details": {
      "underlyingError": "Browser unavailable"
    }
  }
}

Restauration réussie

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

public override fun onRestoreSuccess(
    profile: AdaptyProfile,
    context: Context,
) {}
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 de l’abonnement pour savoir comment le vérifier.

Échec de la restauration

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

public override fun onRestoreFailure(
    error: AdaptyError,
    context: Context,
) {}
Exemple d’événement (Cliquez pour développer)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

Mettre à niveau l’abonnement

Lorsqu’un utilisateur tente d’acheter un nouvel abonnement alors qu’un autre est déjà actif, vous pouvez contrôler la façon dont ce nouvel achat est traité en remplaçant cette méthode. Deux options s’offrent à vous :

  1. Remplacez l’abonnement actuel par le nouveau :
public override fun onAwaitingPurchaseParams(
    product: AdaptyPaywallProduct,
    context: Context,
    onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback,
): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked {
    onPurchaseParamsReceived(
        AdaptyPurchaseParameters.Builder()
            .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...))
            .build()
    )
    return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked
}
  1. Conserver les deux abonnements (ajouter le nouveau séparément) :
public override fun onAwaitingPurchaseParams(
    product: AdaptyPaywallProduct,
    context: Context,
    onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback,
): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked {
    onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty)
    return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked
}
Note

Si vous ne substituez pas cette méthode, le comportement par défaut est de conserver les deux abonnements actifs (équivalent à l’utilisation de AdaptyPurchaseParameters.Empty).

Vous pouvez également définir des paramètres d’achat supplémentaires si nécessaire :

AdaptyPurchaseParameters.Builder()
    .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription
    .withOfferPersonalized(true) // optional - if using personalized pricing
    .build()
Exemple d’événement (Cliquez pour développer)
{
  "product": {
    "vendorProductId": "premium_yearly",
    "localizedTitle": "Premium Yearly",
    "localizedDescription": "Premium subscription for 1 year",
    "localizedPrice": "$99.99",
    "price": 99.99,
    "currencyCode": "USD"
  },
  "subscriptionUpdateParams": {
    "replacementMode": "with_time_proration"
  }
}

Récupération et rendu des données

Erreurs de chargement des produits

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

public override fun onLoadingProductsFailure(
    error: AdaptyError,
    context: Context,
): Boolean = false
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"
    }
  }
}

Si vous retournez true, AdaptyUI répétera la requête dans 2 secondes.

Erreurs de rendu

Si une erreur survient lors du rendu de l’interface, elle sera signalée en appelant cette méthode :

public override fun onError(
    error: AdaptyError,
    context: Context,
) {}
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, ces erreurs ne devraient pas se produire. Si vous en rencontrez une, merci de nous le signaler.

Bouton retour système

Par défaut, un flow ne peut pas être fermé avec le bouton retour système ou le geste de retour — l’utilisateur le quitte via un chemin que vous définissez, comme un bouton Close ou une action on_device_back dans le builder. Si vous souhaitez que le bouton retour système ferme le flow, surchargez onBackPressed et retournez false pour laisser votre activité ou fragment hôte gérer l’appui :

public override fun onBackPressed(context: Context): Boolean {
    return false // let the host handle the back press (e.g. finish the activity or pop the fragment)
}

Ce callback est invoqué uniquement lorsqu’aucune action on_device_back n’est configurée pour l’écran en cours — une action configurée est prioritaire et gérée en interne. Retournez true pour consommer l’appui (comportement par défaut), ou false pour laisser la gestion du retour native s’exécuter.

Événements analytiques

public override fun onAnalyticEvent(
    name: String,
    params: Map<String, Any?>,
    context: Context,
) {}

Un flow envoie flow_screen_showed chaque fois qu’un utilisateur ouvre l’un de ses écrans. Adapty comptabilise ces événements dans ses propres analytics de flow et les transmet également à votre application, afin que vous puissiez reconstituer le même funnel dans vos propres analytics.

ParamètreDescription
instanceIdL’ID de l’écran que l’utilisateur a ouvert.
screen_orderLa position de l’écran dans le flow.
is_last_screentrue quand l’écran n’a nulle part où aller ensuite. Un flow avec des embranchements peut se terminer sur plusieurs écrans différents, et chacun d’eux renvoie true.

isBackendEvent et isCustomerEvent sont tous les deux true pour cet événement : Adapty continue de le comptabiliser, et votre application le reçoit également.

Consultez Suivre les vues d’écran de flow pour savoir quoi en faire.

Événements réservés

AdaptyFlowEventListener déclare quelques callbacks pour des fonctionnalités que les flows n’utilisent pas encore. Vous n’avez pas besoin de les implémenter — AdaptyFlowDefaultEventListener fournit déjà des implémentations vides par défaut.

MéthodeDescription
onShowAppRateRéservé aux demandes d’évaluation de l’application depuis un flow. Les flows ne déclenchent pas encore de demandes d’évaluation, donc vous n’avez pas besoin de l’implémenter.
onShowRequestPermissionRéservé aux demandes d’autorisation système (telles que les notifications push ou l’accès à la caméra) depuis un flow. Les flows ne déclenchent pas encore de demandes d’autorisation, donc vous n’avez pas besoin de l’implémenter.
Important

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

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 incluent 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 ci-dessous comment réagir à ces événements.

Warning

Ce guide concerne uniquement les paywalls du nouveau Paywall Builder qui nécessitent le SDK Adapty v3.0 ou une version ultérieure.

Vous souhaitez voir un exemple concret d’intégration du SDK Adapty dans une application mobile ? Consultez nos exemples d’applications, qui illustrent la configuration complète, notamment l’affichage des paywalls, les achats et d’autres fonctionnalités de base.

Si vous souhaitez contrôler ou surveiller les processus qui se déroulent sur l’écran d’achat, implémentez les méthodes AdaptyUiEventListener.

Si vous souhaitez conserver le comportement par défaut dans certains cas, vous pouvez étendre AdaptyUiDefaultEventListener et ne remplacer que les méthodes que vous souhaitez modifier.

Voici les valeurs par défaut de AdaptyUiDefaultEventListener.

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

Sélection d’un produit

Si un produit est sélectionné pour l’achat (par un utilisateur ou par le système), cette méthode sera invoquée :

public override fun onProductSelected(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
Exemple d’événement (cliquer 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 démarré

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

public override fun onPurchaseStarted(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
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"
  }
}

La méthode ne sera pas invoquée en mode Observer. Consultez la rubrique Android - Présenter les paywalls du Paywall Builder en mode Observer pour plus de détails.

Achat réussi, annulé ou en attente

Si l’achat réussit, cette méthode sera invoquée :

public override fun onPurchaseFinished(
    purchaseResult: AdaptyPurchaseResult,
    product: AdaptyPaywallProduct,
    context: Context,
) {
    if (purchaseResult !is AdaptyPurchaseResult.UserCanceled)
        context.getActivityOrNull()?.onBackPressed()
}
Exemples d’événements (Cliquer pour développer)
// Successful purchase
{
  "purchaseResult": {
    "type": "Success",
    "profile": {
      "accessLevels": {
        "premium": {
          "id": "premium",
          "isActive": true,
          "expiresAt": "2024-02-15T10:30:00Z"
        }
      }
    }
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

// Cancelled purchase
{
  "purchaseResult": {
    "type": "UserCanceled"
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

// Pending purchase
{
  "purchaseResult": {
    "type": "Pending"
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

Nous vous recommandons de fermer l’écran dans ce cas.

La méthode ne sera pas invoquée en mode Observer. Consultez la rubrique Android - Afficher les paywalls du Paywall Builder en mode Observer pour plus de détails.

Échec d’achat

Si un achat échoue en raison d’une erreur, cette méthode est appelée. Cela inclut les erreurs 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 onPurchaseFinished avec un résultat annulé, et les paiements en attente ne déclenchent pas cette méthode.

public override fun onPurchaseFailure(
    error: AdaptyError,
    product: AdaptyPaywallProduct,
    context: Context,
) {}
Exemple d’événement (cliquez pour développer)
{
  "error": {
    "code": "purchase_failed",
    "message": "Purchase failed due to insufficient funds",
    "details": {
      "underlyingError": "Insufficient funds in account"
    }
  },
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

La méthode ne sera pas invoquée en mode Observer. Consultez la rubrique Android - Présenter les paywalls Paywall Builder en mode Observer pour plus de détails.

Navigation de paiement web terminée

Cette méthode est invoquée après une tentative d’ouverture d’un paywall web pour un produit spécifique. Cela inclut les tentatives de navigation réussies et échouées :

public override fun onFinishWebPaymentNavigation(
    product: AdaptyPaywallProduct?,
    error: AdaptyError?,
    context: Context,
) {}

Paramètres :

ParamètreDescription
productUn AdaptyPaywallProduct pour lequel le paywall web a été ouvert. Peut être null.
errorUn objet AdaptyError si la navigation vers le paywall web a échoué ; null si la navigation a réussi.
Exemples d’événements (cliquer pour développer)
// Successful 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 navigation
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "error": {
    "code": "web_navigation_failed",
    "message": "Failed to open web paywall",
    "details": {
      "underlyingError": "Browser unavailable"
    }
  }
}

Successful restore

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

public override fun onRestoreSuccess(
    profile: AdaptyProfile,
    context: Context,
) {}
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 de l’abonnement pour savoir comment le vérifier.

Échec de la restauration

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

public override fun onRestoreFailure(
    error: AdaptyError,
    context: Context,
) {}
Exemple d’événement (Cliquez pour développer)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

Mettre à niveau l’abonnement

Exemple d’événement (Cliquez pour développer)
{
  "product": {
    "vendorProductId": "premium_yearly",
    "localizedTitle": "Premium Yearly",
    "localizedDescription": "Premium subscription for 1 year",
    "localizedPrice": "$99.99",
    "price": 99.99,
    "currencyCode": "USD"
  },
  "subscriptionUpdateParams": {
    "replacementMode": "with_time_proration"
  }
}

Récupération et rendu des données

Erreurs de chargement des produits

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

public override fun onLoadingProductsFailure(
    error: AdaptyError,
    context: Context,
): Boolean = false
Exemple d’événement (Cliquer pour développer)
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

Si vous retournez true, AdaptyUI répétera la requête dans 2 secondes.

Erreurs de rendu

Si une erreur survient lors du rendu de l’interface, elle sera signalée en appelant cette méthode :

public override fun onRenderingError(
    error: AdaptyError,
    context: Context,
) {}
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.