> ## Documentation Index
> Fetch the complete documentation index at: https://docs.beinfi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Reembolso

> Devolver o dinheiro — e o que acontece com o acesso do comprador, que é a parte que ninguém pergunta antes.

Devolver dinheiro é uma chamada. A pergunta difícil vem depois: **o comprador
ainda tem o produto?** Num produto digital ele já baixou, e o link continua no
e-mail dele.

Esta página responde as duas.

## O reembolso é contra o pagamento, não contra a fatura

Uma fatura pode ter várias tentativas de cobrança e só uma pegou o dinheiro. É
essa que você estorna.

```ts theme={null}
const [pagamento] = await infi.payments.listForInvoice(invoiceId);
await infi.payments.refund(pagamento.id, { reason: "cliente desistiu" });
```

Só um pagamento **`confirmed`** pode ser estornado, e de onde o dinheiro sai
depende do seu modelo de coleta. Em **BYOP** o estorno roda na conta do provedor
que você conectou: é o seu dinheiro voltando da sua conta. Em **Infi Managed** a
cobrança foi recebida na nossa estrutura, então o estorno sai de lá e é
descontado do seu repasse — inclusive de repasses futuros, se o valor já tiver
sido repassado.

## Total ou parcial: é o valor que decide o acesso

Omitir `amount` estorna tudo. E aqui está a regra que importa:

| Você estorna             | O download do comprador |
| ------------------------ | ----------------------- |
| tudo (`amount` omitido)  | **para de funcionar**   |
| parte (`amount: "5.00"`) | continua funcionando    |

A lógica é essa: um estorno total desfaz a venda, então a capacidade que a venda
criou tem que morrer com ela. Um estorno parcial **não** desfaz a venda — R$5 de
volta num guia de R$100 é cortesia, não cancelamento — e cortar o arquivo ali
puniria justamente o cliente que você acabou de tentar agradar.

Um valor acima do total é tratado como total, não recusado.

### Quando você quer o contrário

```ts theme={null}
// devolve tudo e deixa ele ficar com o arquivo
await infi.payments.refund(pagamento.id, { revokeAccess: false });

// corta o acesso mesmo estornando só uma parte
await infi.payments.refund(pagamento.id, { amount: "5.00", revokeAccess: true });
```

`revokeAccess: false` é a política de muito infoproduto: brigar custa mais que o
arquivo. **Não mande o campo** se você não quer sobrescrever — a derivação acima
é o comportamento certo em quase todo caso.

## O que o comprador vê depois

O link antigo dele responde **`410 Gone`**, com `error_code` e `message` no
corpo da resposta:

```json theme={null}
{
  "error_code": "download_revoked",
  "message": "This download is no longer available: the purchase behind it was refunded."
}
```

É 410 e não 404 de propósito. Quem tem o token já provou que tinha o token, então
"isso existiu" não vaza nada — e um 404 pareceria link quebrado, o que transforma
um reembolso resolvido num ticket de suporte.

Na sua página, o grant volta com `revokedAt` e **continua na lista**:

```ts theme={null}
const [grant] = await infi.invoices.deliverable(invoiceId);
if (grant?.revokedAt) mostrarAvisoDeEstorno();
else if (grant) mostrarBotao(grant.downloadUrl);
```

Ele não desaparece porque um grant que sumisse pareceria entrega que nunca
rodou — dois problemas bem diferentes com a mesma cara.

## Ler o que você estornou

`status` só vira `refunded` quando o valor inteiro voltou. Um estorno parcial
deixa o pagamento `confirmed`, e quem diz quanto voltou é `refundedAmount`.

```ts theme={null}
const p = await infi.payments.get(pagamento.id);
p.status;          // "confirmed" — voltou só R$5 de R$100
p.refundedAmount;  // "5"         — R$5 devolvidos

await infi.payments.refunds(pagamento.id);
// [{ id, amount: "5.00", createdAt }]
```

Os valores são strings decimais, sem garantia de duas casas: `"5"` e `"5.00"`
representam o mesmo valor. Não compare o texto bruto para decidir quanto voltou.

`refunds()` retorna os registros individuais, com valor, data e identificador.
O campo `reason` é opcional no retorno.

<Warning>
  **Guarde o motivo no seu sistema.** No sandbox, `reason` pode não voltar na listagem mesmo quando enviado no
  reembolso. Se você precisa dele para atendimento ou auditoria, registre o motivo
  no seu sistema junto do ID do pagamento. Não dependa desse campo no retorno.
</Warning>

## A fatura continua `paid`

De propósito. A fatura registra que foi paga, porque **foi** — e contabilidade
não apaga fato, ela lança o contrário dele. O estorno é um registro próprio, com
seu lançamento reverso no ledger.

Consequência prática: se você somar faturas `paid` pro seu relatório de vendas,
uma venda estornada entra inteira. Subtraia `refundedAmount` dos pagamentos.

<Warning>
  **Crédito pré-pago NÃO volta.** Se a compra era um pacote de créditos, o estorno devolve o dinheiro e **não**
  remove os créditos — eles continuam gastáveis. A carteira só tem lançamentos de
  concessão e consumo, e um estorno não escreve nada nela.

  O motivo de não ser automático: se o comprador já consumiu 800 de 1000 créditos,
  não existe resposta óbvia — e escrever a errada num saldo é pior que não
  escrever. Por enquanto, se você vende crédito, debite na mão o que sobrou depois
  de estornar.
</Warning>

## Webhook

O estorno emite `payment.refunded`, com `accessRevoked` dizendo se o download
caiu:

```json theme={null}
{ "paymentId": "…", "invoiceId": "…", "amount": "100.00",
  "currency": "BRL", "accessRevoked": true }
```

`accessRevoked` está aí porque o seu sistema quase sempre tem acesso próprio pra
cortar — assinatura, feature flag, cargo no Discord. Assine em
<a href="/webhooks">webhooks</a>.

## Chargeback é a mesma mecânica, sem você

Quando o comprador contesta no banco, o provedor manda `PAYMENT_REFUNDED` ou o
evento de chargeback e o mesmo caminho roda: pagamento vira `charged_back`,
lançamento reverso, e o acesso cai — a rede leva o valor inteiro, então a
derivação por valor revoga. Você não precisa fazer nada, e não tem como impedir.

O evento emitido é `payment.chargeback`.

<Info>
  **Estornar duas vezes é seguro.** O caminho de reversão só age sobre um pagamento `confirmed`. Um webhook repetido
  do provedor, ou um retry seu, não lança no ledger de novo nem reescreve quando o
  acesso caiu — e a data da revogação é preservada, porque é dela que uma disputa
  depende.
</Info>
