✨ Read how Fotorama reduced app’s subscription refund rate by 40% with Refund Saver

Compras no aplicativo para Android, parte 1: configuração e adição ao projeto.

Vlad Guriev

Updated: March 20, 2023

Content

62fdf1dbf601140886432256 jp android tutorial 1 configuration 1

As compras no aplicativo (in-app purchases), especialmente as assinaturas, são os métodos mais populares de monetizar um aplicativo (app). Por um lado, uma assinatura permite a um desenvolvedor investir no desenvolvimento do conteúdo e do produto, por outro, ajuda os usuários a obter um aplicativo, em geral, de mais alta qualidade. As compras no aplicativo estão sujeitas a 30% de comissão, mas se um usuário é assinante há mais de um ano ou um aplicativo ganha menos de US$1 milhão por ano, a comissão é de 15%.

Neste artigo, explicaremos como proceder:  

  • Crie um produto no Google Play Console;
  • Configure as assinaturas: especifique duração, preço, períodos de avaliação (trials); 
  • Adicione uma lista de produtos em um aplicativo. 

Como criar uma assinatura

Antes de começarmos, verifique se você:

  1. Tem uma conta de desenvolvedor no Google Play.
  2. Assinou todos os acordos e está pronto para começar a trabalhar.  

Agora, vamos ao que interessa: criar nosso primeiro produto.  

Entre na sua conta de desenvolvedor e escolha o aplicativo.

60fef7a69f036b3bfc996258 jsaoezesquwst5gknnib6kc3xqckd8yldedvgpfkaagrqbg9xksbckpwo

Em seguida, no menu à esquerda, encontre a seção Produtos, selecione Subscriptions e depois clique em Create a Subscription

60fef7a7b3b5c7c37e286978 qy go34alibtdmnfdkaii3wg1fcjv5bexstyoefeazq 2oagsnx37gndqykcx6rcwcyu7ahpfzug gusku2bg0sxtq nkmglkw5uuvlabnnlnmp7j2ljfc9 nkzhyhbhegp1nl8r

Depois, veremos o configurador de assinaturas. Seguem alguns pontos importantes.   

60fef7a7d069f50dac0202a5 edgfvm0dhtqew hin9r1uqwvyzuzna1nw4auk hsl8kvgoc2uev8ulj5yhinnvdwv6xau1c1kzy2vd0fmpc8ezyurr3vtl 1qbj19ewbfwqspewed3d echiz1gjqfrfpeiwm0yb
  1. Crie o ID que será usado no aplicativo. É uma boa ideia adicionar um período de assinatura ou alguma outra informação útil para identificar; assim, você pode criar produtos em um estilo, e facilitar a análise das estatísticas de vendas.
  2. O nome da assinatura que o usuário verá na loja.  
  3. Descrição da assinatura. O usuário também a verá.  
60fef7a755430242d074b04c 5mix72nt8rn7nbjrkxd7l7pfit6yuebw9f8x0bke47mx1pbvvagpf4uar w dlkpkkfl9q7udzodqctd451avnwnvqccvtmz0wwj90f8md8mbmrl464wo

Role para baixo e escolha o período de assinatura. No nosso caso, é uma semana. Defina o preço.  

60fef7a8d11d44258c894e7d 658mke hovj itrvd6mamegcdp6wvefdt0bdamvvssnwg0cca

Normalmente, você define o preço na moeda básica da conta, e o sistema converte o preço automaticamente. Mas você também pode editar o preço para um país específico de modo manual.  

Observe que o Google mostra o imposto para cada país. É ótimo, porque o App Store Connect não faz isso.  

60fef7a7ebfe90328d0a6a28 3k q7t5g7luv4axvw25thwlj3aibgsnnkahdtfeleaba31hqwgvo1yin2qayyh1r97tp4p nyvavypbvrt2

Desça e escolha (se necessário): 

  1. Período de avaliação gratuito.
  2. Preço inicial, que é uma oferta para os primeiros períodos de pagamento.  
  3. Período de carência (Grace period). Caso um usuário tenha problemas de pagamento, você ainda pode dar a ele acesso premium por alguns dias.
  4. Uma oportunidade de se inscrever novamente na Play Store, e não no aplicativo, após o cancelamento. 

Comparação entre o processo de compra no Play Console e App Store Connect

Independentemente do fato de que as assinaturas são monetizadas mais efetivamente no iOS, o sistema administrativo da Play Console é mais prático, é mais bem organizado e localizado, e funciona com maior rapidez.

O processo de criação do produto é realizado da maneira mais simples possível. Aqui explicamos como criar produtos no iOS.

Obtenção de uma lista de produtos em um aplicativo

Uma vez criados os produtos, vamos trabalhar na arquitetura para aceitar e processar as compras. Em geral, o processo é assim:   

  1. Adicione uma Biblioteca de Faturamento.
  2. Desenvolva uma classe para interação com produtos do Google Play.
  3. Implemente todos os métodos para processar compras.  
  4. Adicione a validação do servidor para uma compra.  
  5. Colete dados de analytics.

Nesta parte, vamos analisar de modo mais detalhado os dois primeiros pontos.  

Como adicionar uma biblioteca de faturamento a um projeto:

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

No momento desta publicação, a versão mais recente é a 4.0.0. Você pode substitui-la por qualquer outra versão a qualquer momento.  

Vamos criar uma classe de wrapper que cobrirá a lógica de interação com o Google Play e iniciar o BillingClient a partir da Biblioteca de Faturamento nela contida. Vamos chamar esta classe de BillingClientWrapper.

Esta classe implementará a interface PurchasesUpdatedListener. Vamos agora substituir um método para isso – onPurchasesUpdated(billingResult: BillingResult, purchaseList: MutableList<Purchase>?) é necessário logo após a realização de uma compra, mas descreveremos o processo de implementação no próximo artigo. 

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 recomenda evitar ter mais de uma conexão ativa entre o BillingClient e o Google Play para evitar que um retorno de chamada sobre uma compra realizada seja executada várias vezes. Assim, você deve ter um único BillingClient em uma em uma classe singleton. A classe no exemplo não é um singleton, mas podemos usar a injeção de dependência (por exemplo, com a ajuda de Dagger ou Koin) neste sentido, permitindo que apenas uma instância exista em um único momento no tempo. 

Para fazer qualquer solicitação com a Biblioteca de Faturamento, o BillingClient deve ter uma conexão ativa com o Google Play no momento em que a solicitação está sendo feita, mas a conexão pode ser perdida em algum momento. Por uma questão de praticidade, vamos escrever um wrapper que nos permita fazer qualquer solicitação somente quando a conexão estiver ativa.   

Para obter os produtos, precisamos das IDs que definimos no mercado. Mas não é suficiente para uma solicitação, precisamos também do tipo de produto (assinaturas ou compras pontuais), por isso podemos obter uma lista geral de produtos “combinando” os resultados de duas solicitações.   

A solicitação de produtos é assíncrona, portanto, precisamos de um retorno de chamada que nos forneça uma lista de produtos ou retorne um modelo de erro. Quando ocorre um erro, a Biblioteca de Faturamento retorna um de seus BillingResponseCodes, bem como uma debugMessage. Vamos criar uma interface de retorno de chamada e um modelo para um erro:  

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

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

Segue o código de um método privado para obter dados sobre um tipo específico de produtos e um método público que irá “combinar” os resultados de duas solicitações e fornecer a um usuário a lista final de produtos ou exibir uma mensagem de erro.  

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

Assim, conseguimos informações valiosas sobre os produtos (SkuDetails) onde podemos ver nomes localizados, preços, tipo de produto, assim como período de cobrança e informações sobre preço inicial e período de avaliação (se estiver disponível para este usuário) de assinaturas. Veja como é a classe 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
   }
}

Por hoje é isso. Nos próximos artigos, vamos tratar da implementação da compra, realização de testes e tratamento de erros.  

Unlock 2024 subscription secrets
Access our free 2024 in-app subscription report to view essential benchmarks and market trends.
Includes cheat sheets!
Get your free report
Unlock 2024 subscription holiday secrets