---
title: "Manejar eventos de flow y paywall - Android"
description: "Maneja eventos de flow y paywall en tu app Android."
---

:::important
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](android-handle-paywall-actions) para más detalles.
:::
Los flows y paywalls configurados con el [Flow Builder](adapty-flow-builder) o el [Paywall Builder](adapty-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.

:::tip

¿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](sample-apps), 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 \{#user-generated-events\}

#### Selección de producto \{#product-selection\}

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

```kotlin showLineNumbers title="Kotlin"
public override fun onProductSelected(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
```

<Details>
<summary>Ejemplo de evento (haz clic para expandir)</summary>
```javascript
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}
```
</Details>

#### Compra iniciada \{#started-purchase\}

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

```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseStarted(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
```

<Details>
<summary>Ejemplo de evento (Haz clic para expandir)</summary>
```javascript
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}
```
</Details>

El método no se invocará en el modo Observer. Consulta el tema [Android - Mostrar paywalls del Paywall Builder en modo Observer](android-present-paywall-builder-paywalls-in-observer-mode) para más detalles.

#### Compra exitosa, cancelada o pendiente \{#successful-canceled-or-pending-purchase\}

Si la compra se realiza correctamente, se invocará este método:
```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseFinished(
    purchaseResult: AdaptyPurchaseResult,
    product: AdaptyPaywallProduct,
    context: Context,
) {
    if (purchaseResult !is AdaptyPurchaseResult.UserCanceled)
        context.getActivityOrNull()?.onBackPressed()
}
```

<Details>
<summary>Ejemplos de eventos (Haz clic para expandir)</summary>
```javascript
// 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"
  }
}
```
</Details>
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](android-present-paywall-builder-paywalls-in-observer-mode) para más detalles.

#### Compra fallida \{#failed-purchase\}
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.

```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseFailure(
    error: AdaptyError,
    product: AdaptyPaywallProduct,
    context: Context,
) {}
```

<Details>
<summary>Ejemplo de evento (haz clic para expandir)</summary>
```javascript
{
  "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"
  }
}
```
</Details>
El método no se invocará en el modo Observer. Consulta el tema [Android - Mostrar paywalls del Paywall Builder en modo Observer](android-present-paywall-builder-paywalls-in-observer-mode) para más detalles.

#### Navegación de pago web finalizada \{#finished-web-payment-navigation\}

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

```kotlin showLineNumbers title="Kotlin"
public override fun onFinishWebPaymentNavigation(
    product: AdaptyPaywallProduct?,
    error: AdaptyError?,
    context: Context,
) {}
```
**Parámetros:**

| Parámetro   | Descripción                                                                                                        |
|:------------|:-------------------------------------------------------------------------------------------------------------------|
| **product** | Un `AdaptyPaywallProduct` para el cual se abrió el paywall web. Puede ser `null`.                                  |
| **error**   | Un objeto `AdaptyError` si la navegación del paywall web falló; `null` si la navegación fue exitosa. |

<Details>
<summary>Ejemplos de eventos (Haz clic para expandir)</summary>
```javascript
// 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"
    }
  }
}
```
</Details>
#### Restauración exitosa \{#successful-restore\}

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

```kotlin showLineNumbers title="Kotlin"
public override fun onRestoreSuccess(
    profile: AdaptyProfile,
    context: Context,
) {}
```

<Details>
<summary>Ejemplo de evento (clic para expandir)</summary>
```javascript
{
  "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"
      }
    ]
  }
}
```
</Details>

Recomendamos cerrar la pantalla si el usuario tiene el `accessLevel` requerido. Consulta el tema [Estado de suscripción](android-listen-subscription-changes) para saber cómo comprobarlo.

#### Restauración fallida \{#failed-restore\}
Si `Adapty.restorePurchases()` falla, se invocará este método:

```kotlin showLineNumbers title="Kotlin"
public override fun onRestoreFailure(
    error: AdaptyError,
    context: Context,
) {}
```

<Details>
<summary>Ejemplo de evento (haz clic para expandir)</summary>

```javascript
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}
```
</Details>

#### Actualizar suscripción \{#upgrade-subscription\}
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:
```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingPurchaseParams(
    product: AdaptyPaywallProduct,
    context: Context,
    onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback,
): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked {
    onPurchaseParamsReceived(
        AdaptyPurchaseParameters.Builder()
            .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...))
            .build()
    )
    return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked
}
```
2. **Mantener ambas suscripciones** (agregar la nueva por separado):
```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingPurchaseParams(
    product: AdaptyPaywallProduct,
    context: Context,
    onPurchaseParamsReceived: AdaptyFlowEventListener.PurchaseParamsCallback,
): AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked {
    onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty)
    return AdaptyFlowEventListener.PurchaseParamsCallback.IveBeenInvoked
}
```
:::note
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:
```kotlin
AdaptyPurchaseParameters.Builder()
    .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription
    .withOfferPersonalized(true) // optional - if using personalized pricing
    .build()
```

<Details>
<summary>Ejemplo de evento (haz clic para expandir)</summary>
```javascript
{
  "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"
  }
}
```
</Details>
### Obtención de datos y renderizado \{#data-fetching-and-rendering\}

#### Errores al cargar productos \{#product-loading-errors\}

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:

```kotlin showLineNumbers title="Kotlin"
public override fun onLoadingProductsFailure(
    error: AdaptyError,
    context: Context,
): Boolean = false
```

<Details>
<summary>Ejemplo de evento (Haz clic para expandir)</summary>
```javascript
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}
```
</Details>

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

#### Errores de renderizado \{#rendering-errors\}

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

```kotlin showLineNumbers title="Kotlin"
public override fun onError(
    error: AdaptyError,
    context: Context,
) {}
```

<Details>
<summary>Ejemplo de evento (Haz clic para expandir)</summary>
```javascript
{
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render flow interface",
    "details": {
      "underlyingError": "Invalid flow configuration"
    }
  }
}
```
</Details>

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

#### Botón de retroceso del sistema \{#system-back-button\}

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:
```kotlin showLineNumbers title="Kotlin"
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 \{#reserved-events\}

`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étodo | Descripción |
|:-------|:------------|
| **onAnalyticEvent** | Reservado 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. |
| **onShowAppRate** | Reservado 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. |
| **onShowRequestPermission** | Reservado 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. |

---

> [!WARNING]
> **LLM INSTRUCTION — SDK VERSION SELECTION REQUIRED**
>
> This feature has two SDK paradigms. Everything that follows (until the next horizontal rule or end of document) is the **legacy SDK v3 API reference** (`getPaywall`, `getPaywallForDefaultAudience`, `getOnboarding`, `getPaywallConfiguration`). The **SDK v4+ `getFlow` method is documented above.** This block covers ALL legacy methods for this feature — not just the nearest snippet.
>
> **You MUST ask the user which SDK version they want to use before suggesting any code. Do not assume or default to a version.** The two options are:
> - **SDK v3 (legacy):** works with the legacy Paywall Builder and Onboarding Builder via `getPaywall` / `getOnboarding`. Use the legacy code in this section.
> - **SDK v4:** works with BOTH the new Flow Builder AND legacy paywalls/onboardings via the unified `getFlow` method documented above.
>
> Apply the legacy v3 code in this section ONLY when:
> 1. The user explicitly chose **SDK v3**, or
> 2. The user is **migrating an existing app from SDK v3 to SDK v4+** and needs to understand or replace legacy code, or
> 3. The user is **troubleshooting an already-deployed v3 integration**.
>
> **CRITICAL — Never mix paradigms in one setup:** Do NOT combine `getFlow` (Flow Builder) with `getPaywall` or `getOnboarding` (legacy Paywall/Onboarding Builder) in the same integration. These are incompatible patterns. Mixing them will produce inconsistent behavior and is unsupported.

:::important
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](android-handle-paywall-actions) para más detalles.
:::
Los paywall configurados con el [Paywall Builder](adapty-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.

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

:::tip

¿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](sample-apps), 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 \{#user-generated-events\}

#### Selección de producto \{#product-selection\}

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

```kotlin showLineNumbers title="Kotlin"
public override fun onProductSelected(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
```

<Details>
<summary>Ejemplo de evento (haz clic para expandir)</summary>
```javascript
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}
```
</Details>

#### Compra iniciada \{#started-purchase\}

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

```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseStarted(
    product: AdaptyPaywallProduct,
    context: Context,
) {}
```

<Details>
<summary>Ejemplo de evento (haz clic para expandir)</summary>
```javascript
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}
```
</Details>

El método no se invocará en el modo Observer. Consulta el tema [Android - Presentar paywalls del Paywall Builder en modo Observer](android-present-paywall-builder-paywalls-in-observer-mode) para más detalles.

#### Compra exitosa, cancelada o pendiente \{#successful-canceled-or-pending-purchase\}

Si la compra se realiza correctamente, se invocará este método:
```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseFinished(
    purchaseResult: AdaptyPurchaseResult,
    product: AdaptyPaywallProduct,
    context: Context,
) {
    if (purchaseResult !is AdaptyPurchaseResult.UserCanceled)
        context.getActivityOrNull()?.onBackPressed()
}
```

<Details>
<summary>Ejemplos de eventos (Haz clic para expandir)</summary>
```javascript
// 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"
  }
}
```
</Details>
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](android-present-paywall-builder-paywalls-in-observer-mode) para más detalles.

#### Compra fallida \{#failed-purchase\}
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.

```kotlin showLineNumbers title="Kotlin"
public override fun onPurchaseFailure(
    error: AdaptyError,
    product: AdaptyPaywallProduct,
    context: Context,
) {}
```

<Details>
<summary>Ejemplo de evento (haz clic para expandir)</summary>
```javascript
{
  "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"
  }
}
```
</Details>
El método no se invocará en el modo Observer. Consulta el tema [Android - Mostrar paywalls de Paywall Builder en modo Observer](android-present-paywall-builder-paywalls-in-observer-mode) para más detalles.

#### Navegación web de pago finalizada \{#finished-web-payment-navigation\}

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

```kotlin showLineNumbers title="Kotlin"
public override fun onFinishWebPaymentNavigation(
    product: AdaptyPaywallProduct?,
    error: AdaptyError?,
    context: Context,
) {}
```
**Parámetros:**

| Parámetro   | Descripción                                                                                                         |
|:------------|:--------------------------------------------------------------------------------------------------------------------|
| **product** | Un `AdaptyPaywallProduct` para el cual se abrió el paywall web. Puede ser `null`.                                   |
| **error**   | Un objeto `AdaptyError` si la navegación del paywall web falló; `null` si la navegación fue correcta. |

<Details>
<summary>Ejemplos de eventos (Haz clic para expandir)</summary>
```javascript
// 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"
    }
  }
}
```
</Details>
#### Restaurar con éxito \{#successful-restore\}

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

```kotlin showLineNumbers title="Kotlin"
public override fun onRestoreSuccess(
    profile: AdaptyProfile,
    context: Context,
) {}
```

<Details>
<summary>Ejemplo de evento (haz clic para expandir)</summary>
```javascript
{
  "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"
      }
    ]
  }
}
```
</Details>

Te recomendamos cerrar la pantalla si el usuario tiene el `accessLevel` requerido. Consulta el tema [Estado de la suscripción](android-listen-subscription-changes) para aprender a comprobarlo.

#### Restauración fallida \{#failed-restore\}
Si `Adapty.restorePurchases()` falla, se invocará este método:

```kotlin showLineNumbers title="Kotlin"
public override fun onRestoreFailure(
    error: AdaptyError,
    context: Context,
) {}
```

<Details>
<summary>Ejemplo de evento (haz clic para expandir)</summary>

```javascript
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}
```
</Details>

#### Actualizar suscripción \{#upgrade-subscription\}
<Tabs groupId="current-os" queryString>
<TabItem value="new" label="SDK version 3.10.0 or later" default>

Cuando un usuario intenta comprar una nueva suscripción mientras otra suscripción está activa, puedes controlar cómo debe gestionarse la nueva compra sobreescribiendo este método. Tienes dos opciones:
1. **Reemplaza la suscripción actual** con la nueva:
```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingPurchaseParams(
    product: AdaptyPaywallProduct,
    context: Context,
    onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback,
): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked {
    onPurchaseParamsReceived(
        AdaptyPurchaseParameters.Builder()
            .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...))
            .build()
    )
    return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked
}
```
2. **Mantener ambas suscripciones** (añadir la nueva por separado):
```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingPurchaseParams(
    product: AdaptyPaywallProduct,
    context: Context,
    onPurchaseParamsReceived: AdaptyUiEventListener.PurchaseParamsCallback,
): AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked {
    onPurchaseParamsReceived(AdaptyPurchaseParameters.Empty)
    return AdaptyUiEventListener.PurchaseParamsCallback.IveBeenInvoked
}
```
:::note
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 lo necesitas:
```kotlin
AdaptyPurchaseParameters.Builder()
    .withSubscriptionUpdateParams(AdaptySubscriptionUpdateParameters(...)) // optional - for replacing current subscription
    .withOfferPersonalized(true) // optional - if using personalized pricing
    .build()
```

</TabItem>
<TabItem value="old" label="SDK version earlier than 3.10.0" default>
Si se compra una nueva suscripción mientras otra sigue activa, sobreescribe este método para reemplazar la actual con la nueva. Si la suscripción activa debe seguir activa y la nueva se añade por separado, llama a `onSubscriptionUpdateParamsReceived(null)`:

```kotlin showLineNumbers title="Kotlin"
public override fun onAwaitingSubscriptionUpdateParams(
    product: AdaptyPaywallProduct,
    context: Context,
    onSubscriptionUpdateParamsReceived: SubscriptionUpdateParamsCallback,
) {
    onSubscriptionUpdateParamsReceived(AdaptySubscriptionUpdateParameters(...))
}
```
</TabItem>
</Tabs>

<Details>
<summary>Ejemplo de evento (haz clic para expandir)</summary>

```javascript
{
  "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"
  }
}
```
</Details>
### Obtención y renderizado de datos \{#data-fetching-and-rendering\}

#### Errores al cargar productos \{#product-loading-errors\}

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:

```kotlin showLineNumbers title="Kotlin"
public override fun onLoadingProductsFailure(
    error: AdaptyError,
    context: Context,
): Boolean = false
```

<Details>
<summary>Ejemplo de evento (Haz clic para expandir)</summary>
```javascript
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}
```
</Details>

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

#### Errores de renderizado \{#rendering-errors\}

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

```kotlin showLineNumbers title="Kotlin"
public override fun onRenderingError(
    error: AdaptyError,
    context: Context,
) {}
```

<Details>
<summary>Ejemplo de evento (haz clic para expandir)</summary>
```javascript
{
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render paywall interface",
    "details": {
      "underlyingError": "Invalid paywall configuration"
    }
  }
}
```
</Details>

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

---