Report: State of in-app subscriptions in the US 2023 Get a report

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

Vlad Guriev

Updated: marzo 20, 2023

Content

62fdf1dbf601140886432256 jp android tutorial 1 configuration 1

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. 

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.

60fef7a69f036b3bfc996258 jsaoezesquwst5gknnib6kc3xqckd8yldedvgpfkaagrqbg9xksbckpwo

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

60fef7a7b3b5c7c37e286978 qy go34alibtdmnfdkaii3wg1fcjv5bexstyoefeazq 2oagsnx37gndqykcx6rcwcyu7ahpfzug gusku2bg0sxtq nkmglkw5uuvlabnnlnmp7j2ljfc9 nkzhyhbhegp1nl8r

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

60fef7a7d069f50dac0202a5 edgfvm0dhtqew hin9r1uqwvyzuzna1nw4auk hsl8kvgoc2uev8ulj5yhinnvdwv6xau1c1kzy2vd0fmpc8ezyurr3vtl 1qbj19ewbfwqspewed3d echiz1gjqfrfpeiwm0yb
  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á.  
60fef7a755430242d074b04c 5mix72nt8rn7nbjrkxd7l7pfit6yuebw9f8x0bke47mx1pbvvagpf4uar w dlkpkkfl9q7udzodqctd451avnwnvqccvtmz0wwj90f8md8mbmrl464wo

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

60fef7a8d11d44258c894e7d 658mke hovj itrvd6mamegcdp6wvefdt0bdamvvssnwg0cca

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.  

60fef7a7ebfe90328d0a6a28 3k q7t5g7luv4axvw25thwlj3aibgsnnkahdtfeleaba31hqwgvo1yin2qayyh1r97tp4p nyvavypbvrt2

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<Purchase>?) {
       // 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.

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<String>,
   @BillingClient.SkuType type: String,
   listener: SkuDetailsResponseListener
) {
   onConnected {
       billingClient.querySkuDetailsAsync(
           SkuDetailsParams.newBuilder().setSkusList(skusList).setType(type).build(),
           listener
       )
   }
}

Subscribe to Adapty newsletter

Get fresh paywall ideas, subscription insights, and mobile app news every month!

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<Purchase>?) {
       // 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.