A movimentação do dinheiro e o fim do acesso são eventos separados nas duas plataformas, e só um deles acontece automaticamente. O Google deixa a remoção do direito de acesso atrás de uma opção que vem desativada por padrão. A Apple cuida das licenças, mas não consegue tocar em um consumível que alguém já gastou. Tudo o que o seu servidor deixa de limpar continua rodando com uma receita que você não tem mais.
Principais conclusões
Reembolsar um pedido do Play não altera o acesso do cliente, a menos que você diga o contrário. Ou a opção "Remover direito de acesso" é marcada no Play Console, ou sua chamada de API traz revoke=true. Nenhum dos dois acontece sozinho.
Reembolsos emitidos sem essa opção de revogação ficam completamente ausentes da Voided Purchases API. Qualquer processo de revogação construído apenas com base nesse feed vai ignorar silenciosamente cada um deles.
Licenças de itens não consumíveis e assinaturas são algo que a Apple pode retirar por você. Consumíveis são considerados entregues no momento em que são gastos, não deixando nada para o StoreKit retirar, então descontar o saldo fica por conta do seu próprio servidor.
A Apple às vezes reverte seus próprios reembolsos. Uma notificação REFUND_REVERSED indica que um reembolso concedido foi desfeito, o revocationDate volta a null, e tudo que você removeu precisa ser restabelecido.
Nove valores distintos preenchem o voidedReason do Google, passando por remorse e accidental_purchase até fraud, friendly_fraud e unacknowledged_purchase, de modo que um único feed tanto instrui você a cortar o acesso quanto sinaliza quais casos parecem abuso.
Enquanto uma compra reembolsada mantiver o acesso, você está bancando o processamento, o armazenamento e as chamadas de API dela com uma receita que já voltou para o comprador.
Por que o acesso sobrevive a um reembolso por padrão
Ambas as empresas tratam o pagamento e o direito de acesso como operações separadas. O pagamento segue a política da loja, e, uma vez aprovado, o reembolso é liquidado e o registro contábil reflete isso. O direito de acesso é outra questão, às vezes opcional, às vezes algo que cabe a você gerenciar, com frequência as duas coisas ao mesmo tempo, dependendo da plataforma e do tipo de produto. Nenhuma das lojas presume que devolver o dinheiro significa encerrar o acesso, porque muitas situações legítimas, um reembolso parcial, um crédito de cortesia, um gesto do suporte, têm como objetivo deixar o cliente com o que comprou.
Google Play: a caixa de seleção que passa despercebida
O Play Console divide o reembolso em duas decisões. Primeiro o valor, depois, de forma totalmente separada, se a opção "Remover direito de acesso" é marcada. A maioria dos agentes de suporte deixa essa opção como está, o que é razoável, já que a maior parte dos pedidos de reembolso realmente é um gesto de boa vontade e o cliente deve ficar com o produto. O que a tela de confirmação nunca menciona é que essa mesma caixa deixada intocada é como alguém acaba ficando com uma assinatura ou um item dentro do app depois de um reembolso que você pretendia que fosse definitivo.
A mesma armadilha existe na API
No código, a mesma bifurcação se apresenta. O endpoint orders.refund tem um parâmetro de consulta opcional chamado revoke, e defini-lo como true encerra o acesso ao item imediatamente, cancelando também todos os pagamentos futuros quando se trata de uma assinatura recorrente. Deixe o parâmetro de fora e o valor assumido é false: o reembolso é registrado, o registro da compra muda, e o cliente continua exatamente como antes.
Reembolso no Play Console com "Remover direito de acesso" deixado desmarcado, e o acesso continua. O pedido também nunca chega à Voided Purchases API.
Reembolso no Play Console com "Remover direito de acesso" marcado, e o acesso é encerrado imediatamente. O pedido aparece na Voided Purchases API.
Chamada a orders.refund sem o parâmetro revoke, que assim assume o valor false, e o acesso continua. Mais uma vez, nada chega à Voided Purchases API.
Chamada a orders.refund com revoke=true, e o acesso é encerrado imediatamente, com o pedido aparecendo na Voided Purchases API.
Um processo de revogação que leia apenas a Voided Purchases API nunca vai encontrar um reembolso emitido sem a flag revoke. O registro não está atrasado. Ele simplesmente nunca vai chegar, e o pedido fica fora desse feed como se o reembolso nunca tivesse acontecido.
É essa última linha que quebra as integrações. Construir um pipeline de revogação sem precisar acompanhar manualmente o Play Console é exatamente para isso que a Voided Purchases API existe, mas seu escopo são pedidos realmente anulados com revogação. Reembolsos processados sem a caixa marcada, ou sem revoke=true, ficam invisíveis ali por design, e não por qualquer falha.
Entendendo o voidedReason
Compras anuladas chegam com um voidedReason que explica a causa, abrangendo nove valores possíveis: other, remorse, not_received, defective, accidental_purchase, fraud, friendly_fraud, chargeback e unacknowledged_purchase. Tratar isso como mero registro histórico é um desperdício. Remorse e accidental_purchase descrevem reembolsos sem nada de anormal. Fraud, friendly_fraud e chargeback merecem entrar em uma fila de revisão de fraude em vez de um revoke-and-close automático, porque contas que geram esses valores repetem o padrão a uma taxa estatisticamente maior.
Quem constrói algo diretamente sobre esse feed precisa de um único número operacional: purchases.voidedpurchases.list permite 6,000 consultas por dia e 30 em qualquer janela de 30 segundos. Loops de polling muito frequentes esgotam essa cota rapidamente. O design pretendido são buscas programadas usando tokens de paginação.
Apple: licenças podem ser retiradas, consumíveis não
O App Store Server Notifications V2 envia uma notificação REFUND no instante em que um reembolso é liquidado, cobrindo consumíveis, não consumíveis, assinaturas de renovação automática e assinaturas sem renovação automática, sem distinção. O que deveria acontecer em seguida diverge completamente conforme o tipo de produto, porque apenas dois desses quatro tipos entram no mecanismo de revogação do próprio StoreKit.
Por que os consumíveis ficam de fora
Direitos de acesso permanentes pertencem aos não consumíveis e às assinaturas, e os sistemas da Apple conseguem desativá-los. Consumíveis não têm nada equivalente. Gastar um deles marca a entrega, o que não deixa nada para o StoreKit recuperar. Um cliente que comprou 500 moedas, as gastou e depois obteve um reembolso já recebeu tudo, do ponto de vista da Apple. O seu é o único registro contábil onde essas moedas ainda existem. Essa notificação REFUND é o seu único sinal para localizar o ID da transação original correspondente e descontar o saldo você mesmo, porque nada do lado da Apple vai fazer isso.
REFUND significa que uma compra consumível, não consumível, de renovação automática ou sem renovação foi reembolsada. Retire o acesso de itens não consumíveis e assinaturas imediatamente, e desconte você mesmo qualquer saldo de consumível.
REFUND_DECLINED significa que a Apple recusou o pedido de reembolso do cliente. Não há nada a fazer, já que o acesso nunca esteve em risco.
REFUND_REVERSED significa que um reembolso que a Apple já havia concedido foi desfeito. Restabeleça tudo o que a notificação REFUND original levou você a remover.
CONSUMPTION_REQUEST significa que a Apple quer suas evidências antes de decidir. Responda em até 12 horas, e observe que essa notificação chega antes de um REFUND, e não depois.
A notificação que quase todo mundo ignora
REFUND_REVERSED é a notificação que as integrações costumam deixar de fora, já que representa a Apple desfazendo uma decisão própria, algo que raramente alguém já prevê desde o início. Sua chegada faz o revocationDate voltar a null, o que significa que a Apple agora considera a compra perfeitamente válida outra vez. Tudo o que o seu servidor fez no REFUND original, excluir um direito de acesso, cancelar o registro de uma assinatura, zerar o saldo de um consumível, precisa ser revertido.
Não espere que um reembolso revertido se pareça com uma compra nova, porque não vai. Banimentos aplicados, acesso retirado, saldos marcados como gastos: REFUND_REVERSED é a instrução para desfazer exatamente essas coisas, e nenhuma notificação de acompanhamento vai chegar depois para lembrar você disso.
Fechando a lacuna, passo a passo
Torne revoke=true o padrão em todos os fluxos de reembolso do Play, tanto no Console quanto na API, reservando a alternativa para casos de suporte que justifiquem especificamente que o cliente mantenha o acesso.
Consulte purchases.voidedpurchases.list de forma programada usando tokens de paginação em vez de fazer loops muito frequentes, mantendo-se abaixo de 6,000 requisições por dia e 30 a cada 30 segundos.
Envie os valores de voidedReason fraud, friendly_fraud e chargeback para uma fila de revisão, em vez de um tratamento automático de revoke-and-close.
Inscreva-se no App Store Server Notifications V2 na Apple, e retire o acesso de itens não consumíveis e assinaturas assim que uma notificação REFUND os identificar.
Mantenha o ID da transação original armazenado para cada compra de consumível, para que uma notificação REFUND consiga localizar o saldo exato a descontar sem que ninguém precise procurar isso manualmente.
Implemente o REFUND_REVERSED de forma deliberada, e registre cada revogação em relação ao seu ID de transação ou de pedido, para que uma reversão restaure exatamente o que foi retirado.
O que essa lacuna realmente custa
A questão aqui não é a justiça para com o cliente. A questão é o que continua rodando depois que a receita já foi embora. Uma assinatura reembolsada que ainda acessa sua API, ainda armazena dados, ainda consome processamento não apresenta dificuldade moral alguma; apresenta uma fatura. A infraestrutura foi paga com base em uma compra cuja receita você já devolveu. Nenhuma das duas lojas rastreia essa despesa por você, porque, do ponto de vista delas, o reembolso se encerrou no instante em que o dinheiro foi movimentado. Controlar o lado do acesso é uma despesa inteiramente sua para gerenciar.
Por isso, o Refund Sensor trata a revogação como parte do pipeline de reembolso, e não como algo acoplado depois. Um evento REFUND e a mudança de acesso correspondente são registrados como uma única ação, e um evento REFUND_REVERSED restabelece o estado anterior sozinho, em vez de esperar que alguém perceba que um cliente ficou sem acesso a algo que a Apple já havia devolvido dias atrás.
Onde essas regras são documentadas
Perguntas frequentes
Não. O acesso só é encerrado quando a opção "Remover direito de acesso" foi marcada no Play Console, ou quando a chamada orders.refund trouxe revoke=true. Como nenhum dos dois é o padrão, os reembolsos são registrados regularmente enquanto os clientes continuam usando o produto.
Uma notificação REFUND chega, mas o StoreKit não consegue revogar um consumível porque gastá-lo já conta como entrega. Localizar a transação original e descontar o saldo é um trabalho que o seu próprio servidor precisa realizar.
Significa que um reembolso que a Apple já havia concedido foi desfeito. O revocationDate volta a null e a compra passa a contar como válida novamente. Qualquer acesso ou saldo que o REFUND original levou você a remover precisa ser restabelecido, e muitas integrações simplesmente nunca se inscrevem para receber essa notificação.
Porque esse endpoint retorna apenas pedidos anulados com revogação. Reembolsos processados sem a opção revoke não são incluídos, então qualquer pipeline que dependa apenas desse feed vai ignorá-los por completo.
Seis mil consultas por dia, e no máximo 30 dentro de qualquer janela de 30 segundos. O endpoint foi projetado para buscas programadas com tokens de paginação, e não para polling contínuo.



