BlogRight arrowTutorialRight ArrowLas compras dentro de la aplicación de Android, parte 1: configuración y adición al proyecto
BlogRight arrowTutorialRight ArrowLas compras dentro de la aplicación de Android, parte 1: configuración y adición al proyecto

Las compras dentro de la aplicación de Android, parte 1: configuración y adición al proyecto

Las compras dentro de la aplicación de Android, parte 1: configuración y adición al proyecto
Listen to the episode
Las compras dentro de la aplicación de Android, parte 1: configuración y adición al proyecto

Las compras dentro de la aplicación (In-app purchase (IAP)), especialmente las suscripciones (subscriptions), son los métodos más populares para monetizar una aplicación. Por un lado, la suscripción permite al desarrollador invertir en el desarrollo del contenido y del producto, y por otro lado, ayuda a los usuarios a obtener una aplicación (app) de mayor calidad en general. Las compras dentro de la aplicación están sujetas a una comisión del 30%, pero si un usuario ha estado suscrito durante más de un año o una aplicación gana menos de 1$ al año, la comisión es del 15%.

En este artículo, te explicaremos cómo:  

  • Crear un producto en Google Play Console;
  • Configurar las suscripciones: cómo especificar la duración, el precio y las pruebas; 
  • Añadir una lista de productos a una aplicación. 
⭐️ Download our guide on in-app techniques which will make in-app purchases in your app perfect

Crear una suscripción

Antes de empezar, asegúrate de:

  1. Tener una cuenta de desarrollador en Google Play.
  2. Haber firmado todos los acuerdos y estar preparados para empezar a trabajar.  

Ahora, pongamos manos a la obra y creemos nuestro primer producto.  

Cambia a tu cuenta de desarrollador y elige la aplicación.

A continuación, en el menú de la izquierda, busca la sección Productos, selecciona Subscriptions y haz clic en Create a Subscription

A continuación, veremos el configurador de la suscripción. Estos son algunos puntos importantes.   

  1. Crea el ID que se utilizará en la aplicación. Es una buena idea añadir un periodo de suscripción o alguna otra información útil al ID, así podrás crear productos de un solo estilo y facilitar el análisis de las estadísticas de ventas.
  2. El nombre de la suscripción que el usuario verá en la tienda.  
  3. Descripción de la suscripción. El usuario también la verá.  

Desplázate hacia abajo y elige el periodo de suscripción. En nuestro caso, es una semana. Establece el precio.  

Normalmente, estableces el precio en la moneda básica de la cuenta, y el sistema convierte el precio automáticamente. Pero también puedes editar manualmente el precio para un país determinado.  

Por favor, ten en cuenta que Google muestra el impuesto para cada país. Es genial, en cambio App Store Connect no lo hace.  

Desplázate hacia abajo y elige (si es necesario): 

  1. El periodo de prueba gratis (Free trial).
  2. El precio de introducción, que es una oferta para los primeros periodos de pago.  
  3. El periodo de gracia (Grace Period). Si un usuario tiene problemas de pago, puedes seguir proporcionándole acceso premium durante cierto número de días.
  4. Una oportunidad para volver a suscribirse desde Play Store, no desde la aplicación, después de la cancelación. 

Comparación del proceso de compra en Play Console y App Store Connect

Independientemente de que las suscripciones se monetizan más eficazmente en iOS, el tablero de administración de Play Console es más cómodo, está mejor organizado y localizado, y funciona más rápido.

El proceso de creación de productos es lo más sencillo posible. Aquí te explicamos cómo crear productos en iOS.

Obtener una lista de productos en una aplicación

Una vez creados los productos, vamos a trabajar en la arquitectura para aceptar y procesar las compras. En general, el proceso tiene el siguiente aspecto:   

  1. Añade una biblioteca de facturación.
  2. Desarrolla una clase para interactuar con los productos de Google Play.
  3. Implementa todos los métodos para procesar las compras.  
  4. Añade la validación de una compra (purchase validation) por el servidor.  
  5. Recopila la analítica.

En esta parte, vamos a examinar más detalladamente los dos primeros puntos.  

Añadir la Biblioteca de Facturación a un proyecto:

implementation "com.android.billingclient:billing:4.0.0"

En el momento de escribir esto, la última versión es la 4.0.0. Podrás sustituirla por cualquier otra en cualquier momento.  

Vamos a crear una clase contenedora que cubra la lógica de interacción con Google Play e inicialice BillingClient desde la Biblioteca de Facturación en la misma. Llamemos a esta clase BillingClientWrapper.

Esta clase implementará la interfaz PurchasesUpdatedListener. Ahora anularemos un método para ello:  onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList<Purchase>?): es necesario justo después de que se realice una compra, pero describiremos el proceso de implementación en el próximo artículo. 

import android.content.Context
import com.android.billingclient.api.*

class BillingClientWrapper(context: Context) : PurchasesUpdatedListener {

   private val billingClient = BillingClient
       .newBuilder(context)
       .enablePendingPurchases()
       .setListener(this)
       .build()

   override fun onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList?) {
       // here come callbacks about new purchases
   }
}

Google recomienda evitar tener más de una conexión activa entre BillingClient y Google Play a fin de evitar que una devolución de llamada sobre una compra realizada se ejecute varias veces. Así, deberías tener un único BillingClient en una clase singleton. La clase del ejemplo no es un singleton, pero podemos utilizar la inyección de dependencia (por ejemplo, con la ayuda de Dagger o Koin) de esta forma, permitiendo que sólo exista una instancia en un momento dado. 

Para realizar cualquier solicitud con la Biblioteca de Facturación, BillingClient debe tener una conexión activa con Google Play en el momento en que se realiza la solicitud, pero la conexión puede perderse en algún momento. Por comodidad, vamos a escribir una envoltura que nos permita realizar cualquier solicitud sólo cuando la conexión esté activa.   

Para obtener los productos, necesitamos sus ID que establecimos en el mercado. Pero no basta con una solicitud, también necesitamos el tipo de producto (suscripciones o compras únicas), por eso podemos obtener una lista general de productos "combinando" los resultados de dos solicitudes.

Start for free

Convenient in-app purchases infrastructure.

Adapty SDK has it all:
— server-side purchase validation,
— all side cases covered,
— simple implementation.

Start for free

La solicitud de productos es asíncrona, por lo que necesitamos una devolución de llamada que nos proporcione una lista de productos o devuelva un modelo de error. Al ocurrir un error, la Biblioteca de Facturación devuelve uno de sus BillingResponseCodes, así como el debugMessage. Vamos a crear una interfaz de devolución de llamada y un modelo para un error:

interface OnQueryProductsListener {
  fun onSuccess(products: List < SkuDetails > )
  fun onFailure(error: Error)
}

class Error(val responseCode: Int, val debugMessage: String)

Aquí está el código de un método privado para obtener datos sobre un tipo específico de productos y un método público que "combinará" los resultados de dos solicitudes y proporcionará al usuario la lista final de productos o mostrará un mensaje de error.

fun queryProducts(listener: OnQueryProductsListener) {
   val skusList = listOf("premium_sub_month", "premium_sub_year", "some_inapp")

   queryProductsForType(
       skusList,
       BillingClient.SkuType.SUBS
   ) { billingResult, skuDetailsList ->
       if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
           val products = skuDetailsList ?: mutableListOf()
           queryProductsForType(
               skusList,
               BillingClient.SkuType.INAPP
           ) { billingResult, skuDetailsList ->
               if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                   products.addAll(skuDetailsList ?: listOf())
                   listener.onSuccess(products)
               } else {
                   listener.onFailure(
                       Error(billingResult.responseCode, billingResult.debugMessage)
                   )
               }
           }
       } else {
           listener.onFailure(
               Error(billingResult.responseCode, billingResult.debugMessage)
           )
       }
   }
}

private fun queryProductsForType(
   skusList: List,
   @BillingClient.SkuType type: String,
   listener: SkuDetailsResponseListener
) {
   onConnected {
       billingClient.querySkuDetailsAsync(
           SkuDetailsParams.newBuilder().setSkusList(skusList).setType(type).build(),
           listener
       )
   }
}

Así, obtendremos información valiosa sobre los productos (SkuDetails), donde podremos ver los nombres localizados, los precios, el tipo de producto, así como el periodo de facturación y la información sobre el precio de lanzamiento y el periodo de prueba (si está disponible para este usuario) para las suscripciones. Este es el aspecto de la clase final:   

import android.content.Context
import com.android.billingclient.api.*
class BillingClientWrapper(context: Context) : PurchasesUpdatedListener {
   interface OnQueryProductsListener {
       fun onSuccess(products: List<SkuDetails>)
       fun onFailure(error: Error)
   }
   class Error(val responseCode: Int, val debugMessage: String)
   private val billingClient = BillingClient
       .newBuilder(context)
       .enablePendingPurchases()
       .setListener(this)
       .build()
   fun queryProducts(listener: OnQueryProductsListener) {
       val skusList = listOf("premium_sub_month", "premium_sub_year", "some_inapp")
       queryProductsForType(
           skusList,
           BillingClient.SkuType.SUBS
       ) { billingResult, skuDetailsList ->
           if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
               val products = skuDetailsList ?: mutableListOf()
               queryProductsForType(
                   skusList,
                   BillingClient.SkuType.INAPP
               ) { billingResult, skuDetailsList ->
                   if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                       products.addAll(skuDetailsList ?: listOf())
                       listener.onSuccess(products)
                   } else {
                       listener.onFailure(
                           Error(billingResult.responseCode, billingResult.debugMessage)
                       )
                   }
               }
           } else {
               listener.onFailure(
                   Error(billingResult.responseCode, billingResult.debugMessage)
               )
           }
       }
   }
   private fun queryProductsForType(
       skusList: List<String>,
       @BillingClient.SkuType type: String,
       listener: SkuDetailsResponseListener
   ) {
       onConnected {
           billingClient.querySkuDetailsAsync(
               SkuDetailsParams.newBuilder().setSkusList(skusList).setType(type).build(),
               listener
           )
       }
   }
   private fun onConnected(block: () -> Unit) {
       billingClient.startConnection(object : BillingClientStateListener {
           override fun onBillingSetupFinished(billingResult: BillingResult) {
               block()
           }
           override fun onBillingServiceDisconnected() {
               // Try to restart the connection on the next request to
               // Google Play by calling the startConnection() method.
           }
       })
   }
   override fun onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList?) {
       // here come callbacks about new purchases
   }
}

Eso es todo por hoy. En los próximos artículos, te hablaremos de la implementación de la compra, las pruebas y el manejo de errores.  

Further reading

Adapty November Updates: Charts Performance, Subscriptions Report
Adapty November Updates: Charts Performance, Subscriptions Report
December 2, 2021
3 min read
Adapty June Updates: funnels, paywall visits export to Amazon S3, Adjust OAuth, and hiring!
Adapty June Updates: funnels, paywall visits export to Amazon S3, Adjust OAuth, and hiring!
July 7, 2022
5 min read
Are 3rd-party payments in iOS apps as beneficial as they seem?
Are 3rd-party payments in iOS apps as beneficial as they seem?
October 7, 2021
6 min read