A detecção de reembolsos no StoreKit 2 se resume a uma propriedade da transação, e é a revocationDate
Quando a Apple reembolsa um dos seus clientes, o reembolso já está dentro do seu app, na revocationDate da transação, antes de o job do seu servidor rodar. Aqui você vê onde a detecção de reembolsos do StoreKit 2 aparece no dispositivo, o que revocationDate e revocationReason dizem, e por que o cliente serve para a rapidez e o servidor para a verdade.

Principais conclusões
- No StoreKit 2 uma compra reembolsada carrega uma revocationDate não nil na sua Transaction, então seu app pode detectar o reembolso sozinho, sem chamar o seu servidor.
- A revocationDate é definida quando a App Store reembolsa uma transação ou quando um cliente a perde pelo Family Sharing, então uma data não nil nem sempre é um reembolso.
- A revocationReason diz o porquê: developerIssue significa que o cliente apontou um problema no seu app, e other cobre todos os demais motivos de reembolso.
- Transaction.currentEntitlements já exclui as compras reembolsadas e revogadas, então o filtro de acesso mais limpo do lado do cliente é simplesmente se um produto ainda aparece ali.
- Transaction.updates só entrega um reembolso que aconteceu enquanto seu app estava fechado se você começar a escutar na inicialização, então um Task ausente significa um reembolso perdido.
- A detecção do lado do cliente só dispara enquanto o app está aberto, por isso o REFUND das App Store Server Notifications V2 continua sendo o sinal autoritativo que impede você de pagar para atender um usuário reembolsado.
- Um reembolso pode ser revertido, e quando isso acontece, os campos de revogação são removidos da transação e espera-se que você restaure o acesso que cortou.
A maioria dos apps fica sabendo de um reembolso da Apple pelo seu servidor, por uma App Store Server Notification, e nunca percebe que o mesmo reembolso já está dentro do app. Ele está na transação, em uma propriedade chamada revocationDate, e lê-la permite que seu app corte o acesso de um cliente reembolsado na próxima vez que ele abrir, em vez de esperar por um job do backend. A detecção de reembolsos do StoreKit 2 é um sinal do lado do cliente que a maioria das equipes pula. Aqui você vê exatamente onde um reembolso aparece no dispositivo, o que ele diz, o que não diz, e por que ele deve ficar ao lado das suas notificações de servidor e não no lugar delas.
Onde um reembolso aparece dentro do StoreKit 2
O StoreKit 2 entrega as transações como valores assinados, e um reembolso não apaga a transação. Ele a marca. Duas propriedades da Transaction carregam a marca, e ambas permanecem nil por toda a vida de uma compra saudável. Quando uma delas deixa de ser nil, a App Store retomou a compra.
revocationDate é o campo que muda
revocationDate é um Date opcional. A própria descrição da Apple é exata: é a data em que a App Store reembolsou a transação ou a revogou do Family Sharing. Para uma compra que ainda é válida, é nil. No momento em que um reembolso é processado, ela guarda o carimbo de tempo desse reembolso. Essa única verificação, se revocationDate não é nil, é toda a detecção de reembolsos do lado do cliente. Todo o resto é nuance empilhada em cima.
revocationReason diz por que a Apple a retirou
revocationReason fica ao lado da data e explica a causa. O StoreKit lhe dá dois valores que importam para reembolsos. developerIssue significa que o cliente disse à Apple que o reembolso se deveu a um problema real ou percebido no seu app. other cobre todos os demais motivos. Um terceiro valor, upgradedToBundle, não é um reembolso; ele sinaliza uma transação que a App Store revogou porque o cliente passou para um pacote de assinatura. Leia o motivo antes de agir, porque developerIssue é o que vale a pena contar: um grupo deles é o seu próprio produto dizendo onde ele falhou.
| Propriedade | Tipo | O que significa um valor não nil |
|---|---|---|
revocationDate | Date? | A App Store reembolsou esta transação, ou a revogou pelo Family Sharing, nesta data |
revocationReason é developerIssue | motivo | O cliente apontou um problema real ou percebido no seu app |
revocationReason é other | motivo | O reembolso aconteceu por algum outro motivo que a Apple não detalha |
revocationReason é upgradedToBundle | motivo | Não é um reembolso; a transação foi revogada porque o cliente mudou para um pacote de assinatura |
currentEntitlements já descarta uma compra reembolsada
Você nem sempre precisa ler os campos de revogação você mesmo. Transaction.currentEntitlements é a sequência de compras a que um cliente ainda tem direito agora, e a Apple a monta para deixar de fora as que você não deveria honrar. Um produto que a App Store reembolsou ou revogou não aparece nela. Nem as assinaturas expiradas, nem os consumíveis, que somem no momento em que são usados.
Isso faz de currentEntitlements o filtro mais limpo para o acesso. Pergunte a ela o que o cliente possui, conceda exatamente isso, e um reembolso remove o direito por você sem uma única verificação de revocationDate. Os campos de revogação são para quando você quer o detalhe, a data e o motivo, para registrar o evento ou reagir a ele. A lista de direitos é para a pergunta simples de manter ou não as luzes acesas.
A detecção de reembolsos do StoreKit 2 na prática, desde a inicialização e enquanto o app roda
Há dois momentos em que seu app pode captar um reembolso no dispositivo, e eles precisam de código diferente. Um é enquanto o app está aberto e um reembolso acontece ao vivo ou em outro dispositivo. O outro é na inicialização, colocando em dia tudo o que mudou enquanto você estava fechado. Falhe no segundo e a sua detecção de reembolsos do StoreKit 2 tem um buraco exatamente onde a maioria dos reembolsos cai, porque os clientes raramente estão com o seu app aberto quando pedem um.
Comece a escutar na inicialização ou você perde os reembolsos que aconteceram enquanto estava fechado
Transaction.updates é a sequência assíncrona que emite uma transação sempre que o sistema cria ou atualiza uma fora do seu app ou em outro dispositivo, um reembolso incluído. A instrução da Apple é direta: inicie um Task que a percorra assim que seu app iniciar, ou você pode perder as transações que ela entrega uma única vez na inicialização. Um reembolso que chegou de madrugada aparece por updates na próxima vez que o app abre, mas só se já houver um ouvinte rodando para recebê-lo. Sem ouvinte, sem evento, e o reembolso fica invisível até que outra coisa o reconcilie.
Uma compra no mesmo dispositivo não chega por updates
Uma armadilha pega quem testa reembolsos na mão. Uma compra normal feita no mesmo dispositivo não chega por updates; o StoreKit a retorna direto do resultado da chamada de compra. updates é para as mudanças fora de banda: reembolsos, aprovações de Ask to Buy, resgates de códigos de oferta e compras feitas em outro lugar. Então construa o seu tratamento de reembolsos em torno de updates e currentEntitlements, não em torno do fluxo de compra, porque o reembolso nunca voltará pelo caminho que a venda tomou.

O que a detecção do lado do cliente não pode fazer por você
Ler reembolsos no dispositivo é rápido e é de graça, mas tem um teto, e fingir que não tem é como a receita vaza. O dispositivo só sabe o que o StoreKit lhe disse, e o StoreKit só fala enquanto o seu app está rodando. Um cliente que recebe um reembolso e nunca mais abre o seu app é um cliente que a sua verificação do lado do cliente nunca vê.
Uma revocationDate nem sempre é um reembolso
O mesmo campo muda por causa do Family Sharing. Quando um cliente perde o acesso a uma compra compartilhada, porque o organizador o removeu ou o compartilhamento terminou, essa transação também recebe uma revocationDate. Então uma data não nil significa que o cliente não tem mais esta compra, que é exatamente o que você precisa para o controle de acesso, mas nem sempre significa que voltou dinheiro. Se você está contando reembolsos para a receita, separe as revogações de Family Sharing das reais antes de confiar no número.
Um reembolso pode ser revertido
Um reembolso nem sempre é final. A Apple pode reverter um, e quando o faz, os campos de revogação são removidos da transação e a compra volta a ser válida. Se você cortou o acesso no reembolso, espera-se que o restaure na reversão. No dispositivo isso aparece como outro evento de updates com uma transação limpa; no seu servidor é uma notificação distinta, REFUND_REVERSED. Trate só o reembolso e você deixará um cliente pagante ilhado, sem acesso e com um recibo que funciona.
Quanto uma revogação tardia realmente custa
Um reembolso raramente é só o preço de venda saindo da sua conta. Quando ele é liquidado, você normalmente já gastou dinheiro de verdade atendendo aquela compra, e esse gasto não volta. As imagens geradas custam minutos de GPU. As respostas do chat custam chamadas à API do modelo que foram cobradas de você por token. Os uploads custam armazenamento que você ainda paga para manter. Se a compra financiou um pagamento a um criador, esse dinheiro já saiu pela porta. Nada disso é revertido com o reembolso.
A detecção do lado do cliente encolhe a janela na única parte que você ainda pode controlar, que é o gasto futuro. Quanto antes você souber que uma compra foi reembolsada, antes você para de atendê-la. Mas o dispositivo só avisa enquanto o app está aberto, então um usuário reembolsado que nunca volta mantém o acesso do lado do servidor que você concedeu, custando a você em silêncio toda vez que um job em segundo plano ou um dispositivo sincronizado age em nome dele. O cliente torna a revogação rápida. Ele não a torna garantida.
Use o cliente para a rapidez e o servidor para a verdade
O design limpo usa os dois sinais para aquilo em que cada um é bom. No dispositivo, Transaction.updates e currentEntitlements dão a você uma reação local e instantânea no momento em que um cliente reembolsado abre o app, boa para a interface e para finalizar mudanças de direito sem uma ida e volta. No servidor, as App Store Server Notifications V2 enviam uma mensagem REFUND que chega independentemente de o app ser reaberto ou não, que é o único sinal que interrompe de forma confiável o gasto do seu backend em uma conta reembolsada.
| Sinal | Onde vive | Dispara quando | Confie nele para |
|---|---|---|---|
revocationDate em uma transação | Dispositivo, StoreKit 2 | Seu app lê a transação | Dizer que uma compra específica foi reembolsada ou revogada |
Transaction.updates | Dispositivo, StoreKit 2 | Um reembolso chega enquanto o app roda, ou na inicialização se você escutar | Reagir na hora para um cliente que está presente |
currentEntitlements | Dispositivo, StoreKit 2 | Você verifica o que o cliente possui agora | Filtrar o acesso sem rastrear os reembolsos você mesmo |
Notificação REFUND | Seu servidor, App Store Server Notifications V2 | A Apple processa o reembolso, com o app aberto ou não | Interromper o gasto do lado do servidor em um cliente que nunca volta |
Conecte os sinais do dispositivo para o cliente que está com o telefone na mão, e a notificação do servidor para o que não está. O reembolso aparece nos dois lugares de propósito. Ler só um deles é como uma conta reembolsada continua custando a você depois de a venda já ter ido embora.
Perguntas frequentes
- Como eu detecto um reembolso no StoreKit 2?
- Verifique a revocationDate da transação. Ela é nil para uma compra válida e guarda uma data assim que a App Store reembolsa a transação, então uma revocationDate não nil é o sinal de que uma compra foi reembolsada ou revogada.
- Qual é a diferença entre revocationDate e revocationReason?
- revocationDate é quando a App Store retomou a compra, e revocationReason é o porquê. O motivo é developerIssue quando o cliente apontou um problema no seu app e other para qualquer outra coisa.
- Uma compra reembolsada ainda aparece em currentEntitlements?
- Não. Transaction.currentEntitlements exclui as compras que a App Store reembolsou ou revogou, então um produto reembolsado cai sozinho dos direitos do cliente, o que faz dele um filtro seguro para o acesso.
- O StoreKit vai avisar meu app de um reembolso que aconteceu enquanto o app estava fechado?
- Só se você escutar desde a inicialização. Transaction.updates entrega essas mudanças uma única vez no início, então você precisa iniciar um Task que a percorra quando seu app inicia ou o reembolso é perdido até que outra coisa o reconcilie.
- A detecção de reembolsos do lado do cliente é suficiente sozinha?
- Não. O dispositivo só fica sabendo de um reembolso enquanto seu app está rodando, então um cliente que nunca reabre o app é invisível para ela. O REFUND das App Store Server Notifications V2 é o sinal que chega até você de qualquer forma.
- Uma revocationDate sempre significa que o cliente foi reembolsado?
- Não. A revocationDate também é definida quando um cliente perde uma compra pelo Family Sharing, então uma data não nil significa que ele não tem mais a compra mas nem sempre que o dinheiro foi devolvido.
Fontes e leituras adicionais
- Apple Developer: Transaction.revocationDate (StoreKit)
- Apple Developer: Transaction.RevocationReason (StoreKit)
- Apple Developer: Transaction.currentEntitlements (StoreKit)
- Apple Developer: Transaction.updates (StoreKit)
- Apple Developer: App Store Server Notifications V2 notificationType (REFUND, REFUND_REVERSED)
- Apple Developer Tech Talk: Support customers with StoreKit 2 and App Store Server API
RefundHalt
O piloto automático de reembolsos para App Store e Google Play
Continue lendo
Cada janela de reembolso da App Store e do Google Play é uma contagem regressiva, e aqui está quantas horas cada uma te dá
Cada reembolso da App Store e do Google Play dispara um relógio, e a maioria deles corre sem você. A menor janela de reembolso da Apple é de 12 horas, a janela de chargeback do Google é de 24, e a partir de 3 de agosto de 2026 uma janela de chargeback perdida é uma conta, não apenas uma venda perdida. Aqui está cada prazo que afeta a sua conta.
Uma notificação de compra anulada permite que seu servidor do Google Play revogue o acesso no instante em que um reembolso é concluído
O Google Play pode enviar ao seu servidor uma notificação de compra anulada no instante em que uma compra é reembolsada, sofre estorno ou é anulada. Ela carrega um purchaseToken, um orderId, um productType e um refundType, e significa uma coisa, revogar o acesso. Veja como lê-la e conectá-la.