Compras no aplicativo para Android, parte 1: configuração e adição ao projeto.
Updated: March 20, 2023
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ê:
- Tem uma conta de desenvolvedor no Google Play.
- 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.
Em seguida, no menu à esquerda, encontre a seção Produtos, selecione Subscriptions e depois clique em Create a Subscription.
Depois, veremos o configurador de assinaturas. Seguem alguns pontos importantes.
- 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.
- O nome da assinatura que o usuário verá na loja.
- Descrição da assinatura. O usuário também a verá.
Role para baixo e escolha o período de assinatura. No nosso caso, é uma semana. Defina o preço.
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.
Desça e escolha (se necessário):
- Período de avaliação gratuito.
- Preço inicial, que é uma oferta para os primeiros períodos de pagamento.
- 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.
- 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:
- Adicione uma Biblioteca de Faturamento.
- Desenvolva uma classe para interação com produtos do Google Play.
- Implemente todos os métodos para processar compras.
- Adicione a validação do servidor para uma compra.
- 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.