Manejar eventos de flow y paywall - Android

Esta guía cubre el manejo de eventos para compras, restauraciones, selección de productos y renderizado de flows. También debes implementar el manejo de botones (cerrar el flow, abrir enlaces, etc.). Consulta nuestra guía sobre el manejo de acciones de botones para más detalles.

Los flows y paywalls configurados con el Flow Builder o el Paywall Builder no necesitan código adicional para realizar y restaurar compras. Sin embargo, generan algunos eventos a los que tu app puede responder. Estos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selecciones de productos, etc.) y notificaciones sobre acciones relacionadas con compras. A continuación aprenderás cómo responder a estos eventos.

¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras apps de ejemplo, que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas.

Si necesitas controlar o monitorizar los procesos que ocurren en la pantalla de compra, implementa los métodos de AdaptyFlowEventListener. Si quieres mantener el comportamiento predeterminado en algunos casos, puedes extender AdaptyFlowDefaultEventListener y sobreescribir solo los métodos que quieras cambiar.

A continuación se muestran los valores predeterminados de AdaptyFlowDefaultEventListener.

Eventos generados por el usuario

Selección de producto

Si se selecciona un producto para su compra (por el usuario o por el sistema), se invocará este método:

public override fun onProductSelected(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
Ejemplo de evento (haz clic para expandir)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

Compra iniciada

Si un usuario inicia el proceso de compra, se invocará este método:

public override fun onPurchaseStarted(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
Ejemplo de evento (Haz clic para expandir)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

El método no se invocará en el modo Observer. Consulta el tema Android - Mostrar paywalls del Paywall Builder en modo Observer para más detalles.

Compra exitosa, cancelada o pendiente

Si la compra se realiza correctamente, se invocará este método:

public override fun onPurchaseFinished(
    purchaseResult: AdaptyPurchaseResult,
    product: AdaptyPaywallProduct,
    context: Context,
) {
    if (purchaseResult !is AdaptyPurchaseResult.UserCanceled)
        context.getActivityOrNull()?.onBackPressed()
}
Ejemplos de eventos (Haz clic para expandir)
// 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"
  }
}

Recomendamos cerrar la pantalla en ese caso.

El método no se invocará en el modo Observer. Consulta el tema Android - Presentar paywalls del Paywall Builder en modo Observer para más detalles.

Compra fallida

Si una compra falla debido a un error, se invocará este método. Esto incluye errores de Google Play Billing (restricciones de pago, productos no válidos, fallos de red), fallos en la verificación de transacciones y errores del sistema. Ten en cuenta que las cancelaciones del usuario activan onPurchaseFinished con un resultado cancelado en su lugar, y los pagos pendientes no activan este método.

public override fun onPurchaseFailure(
    error: AdaptyError,
    product: AdaptyPaywallProduct,
    context: Context,
) {}
Ejemplo de evento (haz clic para expandir)
{
  "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"
  }
}

El método no se invocará en el modo Observer. Consulta el tema Android - Mostrar paywalls del Paywall Builder en modo Observer para más detalles.

Navegación de pago web finalizada

Este método se invoca tras intentar abrir un web paywall para un producto específico. Esto incluye tanto los intentos de navegación exitosos como los fallidos:

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

Parámetros:

ParámetroDescripción
productUn AdaptyPaywallProduct para el cual se abrió el paywall web. Puede ser null.
errorUn objeto AdaptyError si la navegación del paywall web falló; null si la navegación fue exitosa.
Ejemplos de eventos (Haz clic para expandir)
// 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"
    }
  }
}

Restauración exitosa

Si la restauración de una compra se realiza correctamente, se invocará este método:

public override fun onRestoreSuccess(
    profile: AdaptyProfile,
    context: Context,
) {}
Ejemplo de evento (clic para expandir)
{
  "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"
      }
    ]
  }
}

Recomendamos cerrar la pantalla si el usuario tiene el accessLevel requerido. Consulta el tema Estado de suscripción para saber cómo comprobarlo.

Restauración fallida

Si Adapty.restorePurchases() falla, se invocará este método:

public override fun onRestoreFailure(
    error: AdaptyError,
    context: Context,
) {}
Ejemplo de evento (haz clic para expandir)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

Actualizar suscripción

Cuando un usuario intenta comprar una nueva suscripción mientras hay otra activa, puedes controlar cómo se debe gestionar la nueva compra sobreescribiendo este método. Tienes dos opciones:

  1. Reemplaza la suscripción actual con la nueva:
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. Mantener ambas suscripciones (agregar la nueva por separado):
public override fun onAwaitingPurchaseParams(
    product: AdaptyPaywallProduct,
    context: Context,
    onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback,
): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked {
    onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty)
    return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked
}

Si no sobreescribes este método, el comportamiento predeterminado es mantener ambas suscripciones activas (equivalente a usar AdaptyPurchaseParameters.Empty).

También puedes establecer parámetros de compra adicionales si es necesario:

AdaptyPurchaseParameters.Builder()
    .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription
    .withOfferPersonalized(true) // optional - if using personalized pricing
    .build()
Ejemplo de evento (haz clic para expandir)
{
  "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"
  }
}

Obtención de datos y renderizado

Errores al cargar productos

Si no pasas los productos durante la inicialización, AdaptyUI recuperará los objetos necesarios del servidor por sí solo. Si esta operación falla, AdaptyUI notificará el error invocando este método:

public override fun onLoadingProductsFailure(
    error: AdaptyError,
    context: Context,
): Boolean = false
Ejemplo de evento (Haz clic para expandir)
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

Si devuelves true, AdaptyUI repetirá la solicitud en 2 segundos.

Errores de renderizado

Si ocurre un error durante el renderizado de la interfaz, se notificará mediante este método:

public override fun onError(
    error: AdaptyError,
    context: Context,
) {}
Ejemplo de evento (Haz clic para expandir)
{
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render flow interface",
    "details": {
      "underlyingError": "Invalid flow configuration"
    }
  }
}

En una situación normal, estos errores no deberían ocurrir, así que si te encuentras con alguno, por favor comunícanoslo.

Botón de retroceso del sistema

Por defecto, un flow no se puede cerrar con el botón de retroceso del sistema ni con el gesto de deslizamiento — el usuario lo abandona a través de un camino que tú defines, como un botón Close o una acción on_device_back en el builder. Si quieres que el botón de retroceso del sistema cierre el flow, sobreescribe onBackPressed y devuelve false para que tu actividad o fragmento anfitrión gestione la pulsación:

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

Este callback se invoca solo cuando no hay ninguna acción on_device_back configurada para la pantalla actual — una acción configurada tiene precedencia y se gestiona internamente. Devuelve true para consumir la pulsación (comportamiento por defecto), o false para que se ejecute el propio manejo de retroceso del host.

Eventos reservados

AdaptyFlowEventListener declara algunos callbacks para funcionalidades que los flows aún no usan. No es necesario implementarlos — AdaptyFlowDefaultEventListener ya proporciona implementaciones vacías por defecto.

MétodoDescripción
onAnalyticEventReservado para eventos de análisis personalizados de un flow. Los flows aún no emiten estos eventos en tu código, por lo que no necesitas implementarlo.
onShowAppRateReservado para solicitudes de valoración de la app desde un flow. Los flows aún no activan solicitudes de valoración, por lo que no necesitas implementarlo.
onShowRequestPermissionReservado para solicitudes de permisos del sistema (como notificaciones push o acceso a la cámara) desde un flow. Los flows aún no activan solicitudes de permisos, por lo que no necesitas implementarlo.

Esta guía cubre el manejo de eventos para compras, restauraciones, selección de productos y renderizado de paywall. También debes implementar el manejo de botones (cerrar el paywall, abrir enlaces, etc.). Consulta nuestra guía sobre el manejo de acciones de botones para más detalles.

Los paywall configurados con el Paywall Builder no necesitan código adicional para realizar y restaurar compras. Sin embargo, generan ciertos eventos a los que tu app puede responder. Estos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selecciones de productos, etc.), así como notificaciones sobre acciones relacionadas con compras realizadas en el paywall. A continuación se explica cómo responder a estos eventos.

Esta guía es exclusivamente para paywall del nuevo Paywall Builder, que requieren Adapty SDK v3.0 o posterior.

¿Quieres ver un ejemplo real de cómo se integra el SDK de Adapty en una app móvil? Echa un vistazo a nuestras apps de ejemplo, que muestran la configuración completa, incluyendo la visualización de paywalls, la realización de compras y otras funcionalidades básicas.

Si necesitas controlar o monitorizar los procesos que ocurren en la pantalla de compra, implementa los métodos de AdaptyUiEventListener.

Si quieres mantener el comportamiento predeterminado en algunos casos, puedes extender AdaptyUiDefaultEventListener y sobreescribir solo los métodos que desees cambiar.

A continuación se muestran los valores predeterminados de AdaptyUiDefaultEventListener.

Eventos generados por el usuario

Selección de producto

Si se selecciona un producto para la compra (por un usuario o por el sistema), se invocará este método:

public override fun onProductSelected(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
Ejemplo de evento (haz clic para expandir)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

Compra iniciada

Si un usuario inicia el proceso de compra, se invocará este método:

public override fun onPurchaseStarted(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
Ejemplo de evento (haz clic para expandir)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

El método no se invocará en el modo Observer. Consulta el tema Android - Presentar paywalls del Paywall Builder en modo Observer para más detalles.

Compra exitosa, cancelada o pendiente

Si la compra se realiza correctamente, se invocará este método:

public override fun onPurchaseFinished(
    purchaseResult: AdaptyPurchaseResult,
    product: AdaptyPaywallProduct,
    context: Context,
) {
    if (purchaseResult !is AdaptyPurchaseResult.UserCanceled)
        context.getActivityOrNull()?.onBackPressed()
}
Ejemplos de eventos (Haz clic para expandir)
// 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"
  }
}

Recomendamos cerrar la pantalla en ese caso.

El método no se invocará en el modo Observer. Consulta el tema Android - Presentar paywalls del Paywall Builder en modo Observer para más detalles.

Compra fallida

Si una compra falla debido a un error, se invocará este método. Esto incluye errores de Google Play Billing (restricciones de pago, productos no válidos, fallos de red), fallos en la verificación de transacciones y errores del sistema. Ten en cuenta que las cancelaciones por parte del usuario activan onPurchaseFinished con un resultado de cancelado, y los pagos pendientes no activan este método.

public override fun onPurchaseFailure(
    error: AdaptyError,
    product: AdaptyPaywallProduct,
    context: Context,
) {}
Ejemplo de evento (haz clic para expandir)
{
  "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"
  }
}

El método no se invocará en el modo Observer. Consulta el tema Android - Mostrar paywalls de Paywall Builder en modo Observer para más detalles.

Navegación web de pago finalizada

Este método se invoca tras un intento de abrir un paywall web para un producto específico. Esto incluye tanto los intentos de navegación exitosos como los fallidos:

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

Parámetros:

ParámetroDescripción
productUn AdaptyPaywallProduct para el cual se abrió el paywall web. Puede ser null.
errorUn objeto AdaptyError si la navegación del paywall web falló; null si la navegación fue correcta.
Ejemplos de eventos (Haz clic para expandir)
// 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"
    }
  }
}

Restaurar con éxito

Si la restauración de una compra se realiza con éxito, se invocará este método:

public override fun onRestoreSuccess(
    profile: AdaptyProfile,
    context: Context,
) {}
Ejemplo de evento (haz clic para expandir)
{
  "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"
      }
    ]
  }
}

Te recomendamos cerrar la pantalla si el usuario tiene el accessLevel requerido. Consulta el tema Estado de la suscripción para aprender a comprobarlo.

Restauración fallida

Si Adapty.restorePurchases() falla, se invocará este método:

public override fun onRestoreFailure(
    error: AdaptyError,
    context: Context,
) {}
Ejemplo de evento (haz clic para expandir)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

Actualizar suscripción

Ejemplo de evento (haz clic para expandir)
{
  "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"
  }
}

Obtención y renderizado de datos

Errores al cargar productos

Si no pasas los productos durante la inicialización, AdaptyUI los obtendrá del servidor por su cuenta. Si esta operación falla, AdaptyUI notificará el error invocando este método:

public override fun onLoadingProductsFailure(
    error: AdaptyError,
    context: Context,
): Boolean = false
Ejemplo de evento (Haz clic para expandir)
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

Si devuelves true, AdaptyUI repetirá la solicitud en 2 segundos.

Errores de renderizado

Si se produce un error durante el renderizado de la interfaz, se notificará llamando a este método:

public override fun onRenderingError(
    error: AdaptyError,
    context: Context,
) {}
Ejemplo de evento (haz clic para expandir)
{
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render paywall interface",
    "details": {
      "underlyingError": "Invalid paywall configuration"
    }
  }
}

En condiciones normales, estos errores no deberían producirse, así que si encuentras alguno, por favor comunícanoslo.