Kotlin Multiplatform - Gestionar eventos del paywall

Los paywalls 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 reaccionar. Estos eventos incluyen pulsaciones de botones (botones de cierre, URLs, selecciones de producto, etc.) y notificaciones sobre acciones relacionadas con compras en el paywall. A continuación aprenderás cómo responder a estos eventos.

Esta guía es solo para paywalls del nuevo Paywall Builder.

Para controlar o monitorizar los procesos que ocurren en la pantalla del paywall dentro de tu app, implementa los métodos de la interfaz AdaptyUIPaywallsEventsObserver. Algunos métodos tienen implementaciones por defecto que gestionan automáticamente los escenarios más comunes.

En estos métodos es donde añades tu lógica personalizada para responder a los eventos del paywall. Puedes usar view.dismiss() para cerrar el paywall o implementar cualquier otro comportamiento personalizado que necesites.

Eventos generados por el usuario

Aparición y desaparición del paywall

Cuando un paywall aparece o desaparece, se invocan estos métodos:

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
}
  • En iOS, paywallViewDidAppear también se invoca cuando el usuario pulsa el botón de web paywall dentro de un paywall y se abre un web paywall en un navegador in-app.
  • En iOS, paywallViewDidDisappear también se invoca cuando un web paywall abierto desde un paywall en un navegador in-app desaparece de la pantalla.
Ejemplos de eventos (haz clic para expandir)
// Paywall appeared
{
  // No additional data
}

// Paywall disappeared
{
  // No additional data
}

Selección de producto

Si el usuario selecciona un producto para comprarlo, se invoca este método:

override fun paywallViewDidSelectProduct(view: AdaptyUIPaywallView, productId: String) {
    // Handle product selection
    // You can update UI or track analytics here
}
Ejemplo de evento (haz clic para expandir)
{
  "productId": "premium_monthly"
}

Inicio de compra

Si el usuario inicia el proceso de compra, se invoca este método:

override fun paywallViewDidStartPurchase(view: AdaptyUIPaywallView, product: AdaptyPaywallProduct) {
    // Handle purchase start
    // You can show loading indicators or track analytics here
}
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 exitosa, cancelada o pendiente

Si una compra tiene éxito, se invoca este método. Por defecto, cierra automáticamente el paywall a menos que el usuario haya cancelado la compra:

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
        }
    }
}
Ejemplos de eventos (haz clic para expandir)
// 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"
  }
}

Te recomendamos cerrar la pantalla del paywall cuando la compra sea exitosa.

Compra fallida

Si una compra falla por un error, se invoca este método. Esto incluye errores de StoreKit/Google Play Billing (restricciones de pago, productos inválidos, fallos de red), errores de verificación de transacciones y errores del sistema. Ten en cuenta que las cancelaciones del usuario activan paywallViewDidFinishPurchase con un resultado de cancelación, y los pagos pendientes no activan este método.

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
}
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"
  },
  "error": {
    "code": "purchase_failed",
    "message": "Purchase failed due to insufficient funds",
    "details": {
      "underlyingError": "Insufficient funds in account"
    }
  }
}

Inicio de restauración

Si el usuario inicia el proceso de restauración, se invoca este método:

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

Restauración exitosa

Si la restauración de una compra tiene éxito, se invoca este método:

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()
    }
}
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 cómo comprobarlo.

Restauración fallida

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

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
}
Ejemplo de evento (haz clic para expandir)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

Finalización de la navegación de pago web

Si el usuario inicia el proceso de compra mediante un web paywall, se invoca este método:

override fun paywallViewDidFinishWebPaymentNavigation(
    view: AdaptyUIPaywallView,
    product: AdaptyPaywallProduct?,
    error: AdaptyError?
) {
    if (error != null) {
        // Handle web payment navigation error
    } else {
        // Handle successful web payment navigation
    }
}
Ejemplos de eventos (haz clic para expandir)
// 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"
    }
  }
}

Carga de datos y renderizado

Errores de carga de productos

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

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
}
Ejemplo de evento (haz clic para expandir)
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

Errores de renderizado

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

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
}
Ejemplo de evento (haz clic para expandir)
{
    "error": {
        "code": "rendering_failed",
            "message": "Failed to render paywall interface",
            "details": {
            "underlyingError": "Invalid paywall configuration"
        }
    }
}

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