Quando uma chamada da Biblioteca Play Faturamento gera uma ação, a biblioteca retorna uma resposta
BillingResult
para informar o resultado aos desenvolvedores. Por exemplo, se você usar
queryProductDetailsAsync
para receber as ofertas disponíveis para o usuário, o código de resposta vai conter um
código OK e fornecer o objeto ProductDetails
correto. Ele também pode conter uma resposta diferente que indica por que o objeto
ProductDetails
não pôde ser fornecido.
Nem todos os códigos de resposta são erros. A página de referência BillingResponseCode
fornece uma descrição detalhada de cada uma das respostas
discutidas neste guia.
Confira alguns exemplos de códigos de resposta que não indicam erros:
BillingClient.BillingResponseCode.OK
: a ação gerada pela chamada foi concluída.BillingClient.BillingResponseCode.USER_CANCELED
: para ações que mostram fluxos de interface da Play Store para o usuário, essa resposta indica que o usuário sainterface desses fluxos sem conclinterfacer o processo.
Quando o código de resposta indica um erro, a causa pode ser
condições temporárias. Portanto, a recuperação é possível. Quando uma chamada para um método da Biblioteca Play Faturamento
retorna um valor BillingResponseCode
que indica uma condição recuperável, é necessário tentar fazer a chamada novamente. Em
outros casos, as condições não são consideradas temporárias, então não recomendamos uma
nova tentativa.
Erros temporários exigem estratégias diferentes de nova tentativa dependendo de fatores como
se o erro ocorre quando os usuários estão em sessão (por exemplo, ao passar
por um fluxo de compra) ou em segundo plano (por
exemplo, quando você consulta as compras do usuário durante onResume
).
A seção de estratégias de nova tentativa abaixo mostra exemplos dessas
estratégias diferentes, e a seção "Respostas de BillingResult
que aceitam novas tentativas"
recomenda qual estratégia funciona melhor para cada código de resposta.
Além do código, algumas respostas de erro incluem mensagens para fins de depuração e geração de registros.
Estratégias de nova tentativa
Nova tentativa simples
Em situações em que o usuário está em sessão, é melhor implementar uma estratégia de nova tentativa simples para que o erro interfira o mínimo possível na experiência. Recomendamos essa estratégia com um número máximo de tentativas como condição de saída.
O exemplo a seguir demonstra uma estratégia de nova tentativa simples para lidar com um erro
ao estabelecer uma conexão
BillingClient
:
class BillingClientWrapper(context: Context) : PurchasesUpdatedListener {
// Initialize the BillingClient.
private val billingClient = BillingClient.newBuilder(context)
.setListener(this)
.enablePendingPurchases()
.build()
// Establish a connection to Google Play.
fun startBillingConnection() {
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
Log.d(TAG, "Billing response OK")
// The BillingClient is ready. You can now query Products Purchases.
} else {
Log.e(TAG, billingResult.debugMessage)
retryBillingServiceConnection()
}
}
override fun onBillingServiceDisconnected() {
Log.e(TAG, "GBPL Service disconnected")
retryBillingServiceConnection()
}
})
}
// Billing connection retry logic. This is a simple max retry pattern
private fun retryBillingServiceConnection() {
val maxTries = 3
var tries = 1
var isConnectionEstablished = false
do {
try {
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
isConnectionEstablished = true
Log.d(TAG, "Billing connection retry succeeded.")
} else {
Log.e(
TAG,
"Billing connection retry failed: ${billingResult.debugMessage}"
)
}
}
})
} catch (e: Exception) {
e.message?.let { Log.e(TAG, it) }
tries++
}
} while (tries <= maxTries && !isConnectionEstablished)
}
...
}
Nova tentativa com espera exponencial
Recomendamos o uso de espera exponencial para operações da Biblioteca Play Faturamento que aconteçam em segundo plano e não afetem a experiência do usuário durante a sessão.
Por exemplo, seria apropriado implementar isso ao confirmar novas compras, porque essa operação pode acontecer em segundo plano e a confirmação não precisa acontecer em tempo real se ocorrer um erro.
private fun acknowledge(purchaseToken: String): BillingResult {
val params = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchaseToken)
.build()
var ackResult = BillingResult()
billingClient.acknowledgePurchase(params) { billingResult ->
ackResult = billingResult
}
return ackResult
}
suspend fun acknowledgePurchase(purchaseToken: String) {
val retryDelayMs = 2000L
val retryFactor = 2
val maxTries = 3
withContext(Dispatchers.IO) {
acknowledge(purchaseToken)
}
AcknowledgePurchaseResponseListener { acknowledgePurchaseResult ->
val playBillingResponseCode =
PlayBillingResponseCode(acknowledgePurchaseResult.responseCode)
when (playBillingResponseCode) {
BillingClient.BillingResponseCode.OK -> {
Log.i(TAG, "Acknowledgement was successful")
}
BillingClient.BillingResponseCode.ITEM_NOT_OWNED -> {
// This is possibly related to a stale Play cache.
// Querying purchases again.
Log.d(TAG, "Acknowledgement failed with ITEM_NOT_OWNED")
billingClient.queryPurchasesAsync(
QueryPurchasesParams.newBuilder()
.setProductType(BillingClient.ProductType.SUBS)
.build()
)
{ billingResult, purchaseList ->
when (billingResult.responseCode) {
BillingClient.BillingResponseCode.OK -> {
purchaseList.forEach { purchase ->
acknowledge(purchase.purchaseToken)
}
}
}
}
}
in setOf(
BillingClient.BillingResponseCode.ERROR,
BillingClient.BillingResponseCode.SERVICE_DISCONNECTED,
BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE,
) -> {
Log.d(
TAG,
"Acknowledgement failed, but can be retried --
Response Code: ${acknowledgePurchaseResult.responseCode} --
Debug Message: ${acknowledgePurchaseResult.debugMessage}"
)
runBlocking {
exponentialRetry(
maxTries = maxTries,
initialDelay = retryDelayMs,
retryFactor = retryFactor
) { acknowledge(purchaseToken) }
}
}
in setOf(
BillingClient.BillingResponseCode.BILLING_UNAVAILABLE,
BillingClient.BillingResponseCode.DEVELOPER_ERROR,
BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED,
) -> {
Log.e(
TAG,
"Acknowledgement failed and cannot be retried --
Response Code: ${acknowledgePurchaseResult.responseCode} --
Debug Message: ${acknowledgePurchaseResult.debugMessage}"
)
throw Exception("Failed to acknowledge the purchase!")
}
}
}
}
private suspend fun <T> exponentialRetry(
maxTries: Int = Int.MAX_VALUE,
initialDelay: Long = Long.MAX_VALUE,
retryFactor: Int = Int.MAX_VALUE,
block: suspend () -> T
): T? {
var currentDelay = initialDelay
var retryAttempt = 1
do {
runCatching {
delay(currentDelay)
block()
}
.onSuccess {
Log.d(TAG, "Retry succeeded")
return@onSuccess;
}
.onFailure { throwable ->
Log.e(
TAG,
"Retry Failed -- Cause: ${throwable.cause} -- Message: ${throwable.message}"
)
}
currentDelay *= retryFactor
retryAttempt++
} while (retryAttempt < maxTries)
return block() // last attempt
}
Respostas de BillingResult que aceitam novas tentativas
NETWORK_ERROR (código do erro: 12)
Problema
Esse erro indica que houve um problema com a conexão de rede entre o dispositivo e os sistemas do Google Play.
Possível solução
Para recuperar, use novas tentativas simples ou com espera exponencial, dependendo de qual ação gerou o erro.
SERVICE_TIMEOUT (código do erro: -3)
Problema
Esse erro indica que a solicitação atingiu o tempo limite máximo antes de o Google Play responder. Isso pode ser causado, por exemplo, por um atraso na execução da ação solicitada pela chamada da Biblioteca Play Faturamento.
Possível solução
Esse geralmente é um problema temporário. Tente fazer a solicitação outra vez usando uma estratégia simples ou de espera exponencial, dependendo da ação que retornou o erro.
Ao contrário do SERVICE_DISCONNECTED
abaixo, a conexão com o serviço do Google Play Faturamento não é interrompida, e você
só precisa repetir a operação da biblioteca.
SERVICE_DISCONNECTED (código do erro: -1)
Problema
Esse erro fatal indica que a conexão do app cliente com o serviço da Google Play
Store pelo BillingClient
foi interrompida.
Possível solução
Para evitar esse erro o máximo possível, sempre verifique a conexão com o Google
Play Services antes de fazer chamadas com a Biblioteca Play Faturamento chamando
BillingClient.isReady()
.
Para recuperar o SERVICE_DISCONNECTED
,
o app cliente precisa tentar restabelecer a conexão usando
BillingClient.startConnection
.
Assim como com o SERVICE_TIMEOUT
,
use novas tentativas simples ou com espera exponencial, dependendo de qual ação gerou
o erro.
SERVICE_UNAVAILABLE (código do erro: 2)
Observação importante:
A partir da Biblioteca Google Play Faturamento 6.0.0, o erro SERVICE_UNAVAILABLE
não
é mais retornado para problemas de rede. Ele é retornado quando o serviço de faturamento está
indisponível e os cenários de caso SERVICE_TIMEOUT
foram descontinuados.
Problema
Esse erro temporário indica que o serviço do Google Play Faturamento está indisponível. Na maioria dos casos, isso significa que há um problema de conexão de rede em algum lugar entre o dispositivo cliente e os serviços do Google Play Faturamento.
Possível solução
Esse geralmente é um problema temporário. Tente fazer a solicitação outra vez usando uma estratégia simples ou de espera exponencial, dependendo da ação que retornou o erro.
Ao contrário do SERVICE_DISCONNECTED
,
a conexão com o serviço do Google Play Faturamento não é interrompida, e você precisa
repetir a operação.
BILLING_UNAVAILABLE (código do erro: 3)
Problema
Esse erro indica que ocorreu um problema no faturamento do usuário durante o processo de compra. Confira alguns exemplos de quando isso pode ocorrer:
- O app Play Store no dispositivo do usuário está desatualizado.
- O usuário está em um país onde não há suporte.
- Esse é um usuário corporativo, e o administrador dele desativou a possibilidade de usuários fazerem compras.
- O Google Play não consegue fazer a cobrança na forma de pagamento do usuário. Por exemplo, o cartão de crédito do usuário pode ter expirado.
Possível solução
É improvável que novas tentativas automáticas ajudem neste caso. No entanto, uma nova tentativa manual pode ajudar se o usuário resolver a condição que causou o problema. Por exemplo, se o usuário atualizar a Play Store para uma versão com suporte, uma nova tentativa manual da operação inicial poderá funcionar.
Se esse erro ocorrer quando o usuário não estiver em sessão, é possível que a nova tentativa não faça
sentido
Quando você recebe um erro BILLING_UNAVAILABLE
como resultado do fluxo de compra, é muito provável que o usuário tenha recebido
feedback do Google Play durante o processo de compra e esteja ciente do que
deu errado. Nesse caso, você pode mostrar uma mensagem de erro especificando que algo
deu errado e incluir um botão "Tente de novo" para dar ao usuário a opção de
uma nova tentativa manual depois de resolver o problema.
ERROR (código do erro: 6)
Problema
Esse é um erro fatal que indica um problema interno com o Google Play.
Possível solução
Às vezes, problemas internos do Google Play que levam a um ERROR
são temporários, e uma nova tentativa com uma espera exponencial pode ser implementada para fazer a
mitigação. Quando os usuários estão em sessão, é preferível fazer uma nova tentativa simples.
ITEM_ALREADY_OWNED
Problema
Essa resposta indica que o usuário do Google Play já é proprietário da assinatura ou do produto de compra única que ele está tentando comprar. Na maioria dos casos, esse não é um erro temporário, exceto quando causado por um cache desatualizado do Google Play.
Possível solução
Para evitar esse erro quando a causa não for um problema de cache, não ofereça um
produto para compra quando o usuário já for proprietário dele. Verifique os
direitos do usuário ao mostrar os produtos disponíveis para compra e
filtre o que ele pode comprar de acordo com esses direitos.
Quando o app cliente recebe esse erro devido a um problema de cache, ele aciona
o cache do Google Play para ser atualizado com os dados mais recentes do back-end.
Tentar de novo após o erro deve resolver essa instância temporária específica nesse
caso. Chame BillingClient.queryPurchasesAsync()
depois de receber um ITEM_ALREADY_OWNED
para verificar se o usuário adquiriu o produto. Se
não for o caso, implemente uma lógica de nova tentativa simples para tentar fazer a compra outra vez.
ITEM_NOT_OWNED
Problema
Essa resposta de compra indica que o usuário do Google Play não é proprietário da assinatura ou do produto de compra única que está tentando substituir, confirmar ou consumir. Na maioria dos casos, esse não é um erro temporário, exceto quando é causado por um cache desatualizado do Google Play.
Possível solução
Quando o erro é recebido devido a um problema de cache, ele aciona o cache do Google
Play para ser atualizado com os dados mais recentes do back-end. Tentar
de novo com uma estratégia simples após o erro deve resolver essa
instância temporária específica. Chame BillingClient.queryPurchasesAsync()
depois de receber um ITEM_NOT_OWNED
para verificar se o usuário
adquiriu o produto. Se não for o caso, use uma lógica simples para tentar
fazer a compra outra vez.
Respostas de BillingResult que não aceitam novas tentativas
Não é possível se recuperar desses erros usando a lógica de nova tentativa.
FEATURE_NOT_SUPPORTED
Problema
Esse erro que não aceita novas tentativas indica que o recurso do Google Play Faturamento não tem suporte ao dispositivo do usuário, provavelmente devido a uma versão antiga da Play Store.
Por exemplo, talvez alguns dispositivos dos usuários não tenham suporte a mensagens no app.
Possível mitigação
Use BillingClient.isFeatureSupported()
para conferir o suporte a recursos antes de fazer uma chamada para a Biblioteca Play
Faturamento.
when {
billingClient.isReady -> {
if (billingClient.isFeatureSupported(BillingClient.FeatureType.IN_APP_MESSAGING)) {
// use feature
}
}
}
USER_CANCELED
Problema
O usuário clicou para sair da interface do fluxo de faturamento.
Possível solução
Esse erro é apenas informativo, e falhas podem ocorrer sem maiores problemas.
ITEM_UNAVAILABLE
Problema
O produto de compra única ou a assinatura do Google Play Faturamento não está disponível para compra por esse usuário.
Possível mitigação
Confira se o app atualiza os detalhes do produto usando queryProductDetailsAsync
, conforme recomendado. Considere a frequência
com que seu catálogo de produtos muda na configuração do Play Console para implementar
mais atualizações, caso necessário.
Tente vender produtos no Google Play Faturamento que retornem as informações
corretas com a queryProductDetailsAsync
.
Verifique se há inconsistências na configuração de qualificação do produto.
Por exemplo, você pode estar consultando um produto que está disponível apenas para uma
região diferente daquela em que o usuário está tentando comprar.
Caso você queira disponibilizar um produto para compra, ele precisa estar
ativo, e o app precisa estar publicado e disponível no país do usuário.
Às vezes, principalmente durante o teste, tudo está correto na configuração do produto, mas os usuários ainda recebem esse erro. Isso pode ocorrer devido a um atraso de propagação dos detalhes do produto nos servidores do Google. Tente de novo mais tarde.
DEVELOPER_ERROR
Problema
Esse é um erro fatal que indica o uso inadequado de uma API.
Por exemplo, fornecer parâmetros incorretos para BillingClient.launchBillingFlow
pode
causar esse erro.
Possível solução
Confira se você está usando corretamente as diferentes chamadas da Biblioteca Play Faturamento. Além disso, confira a mensagem de depuração para saber mais sobre o erro.