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

# Devolvendo um pagamento feito

> Guia passo a passo para devolver total ou parcialmente um pagamento já concluído, acompanhar o dinheiro e tratar cartão e PIX com segurança.

Quando um pagamento já foi concluído, **cancelar a compra no seu sistema não
move o dinheiro de volta**. Para devolver o valor ao comprador, crie um
`refund`: um objeto financeiro próprio, ligado à cobrança original e com um
status que informa se a devolução ainda está em processamento, foi concluída ou
falhou.

Este guia mostra como decidir entre cancelamento e devolução, criar um refund
total ou parcial, acompanhar o resultado e reconciliar o dinheiro em cartão e
PIX.

<Info>
  A regra principal é simples: se o `payment_intent` está `succeeded`, devolva o
  dinheiro com `POST /v1/refunds`. Se ele ainda não recebeu o dinheiro, cancele
  a tentativa com `POST /v1/payment-intents/{id}/cancel`.
</Info>

## Primeiro: cancelar ou devolver?

Olhe o `status` atual do `payment_intent` antes de escolher a operação.

| Situação                                             | Operação correta                                      | O que acontece com o dinheiro                                                                   |
| ---------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| `requires_payment_method` ou `requires_confirmation` | Cancelar                                              | Nenhum valor foi movimentado.                                                                   |
| `pending` em PIX ou boleto                           | Cancelar e continuar acompanhando eventos             | Ainda não há pagamento confirmado, mas uma instrução já emitida pode ser paga antes de expirar. |
| `processing`                                         | Cancelar com cautela e continuar acompanhando eventos | Existe uma corrida entre o cancelamento e a confirmação financeira.                             |
| `requires_capture` no cartão                         | Cancelar                                              | A autorização é desfeita; o valor não é capturado.                                              |
| `succeeded`                                          | Criar refund                                          | O valor já foi capturado e precisa percorrer o fluxo de devolução.                              |
| Refund anterior foi parcial                          | Criar outro refund pelo saldo restante                | A soma dos refunds não pode superar o valor capturado.                                          |

| Passo | Condição                                     | Ação                                           |
| ----- | -------------------------------------------- | ---------------------------------------------- |
| 1     | Sempre                                       | Consulte o Payment Intent.                     |
| 2     | O pagamento ainda não está `succeeded`       | Cancele a tentativa em vez de criar um refund. |
| 3     | `status = succeeded` e a devolução é total   | Crie o refund sem `amount`.                    |
| 4     | `status = succeeded` e a devolução é parcial | Crie o refund com `amount` em centavos.        |
| 5     | Refund criado                                | Acompanhe `refund.status`.                     |
| 6     | `refund.status = succeeded`                  | Marque a devolução como concluída.             |
| 7     | O refund ainda não teve sucesso              | Aguarde o processamento ou trate a falha.      |

## O que acontece com o dinheiro

A chamada para criar um refund inicia um fluxo financeiro; ela não é apenas uma
mudança de status no seu pedido.

1. A Chargefy valida se a `charge` foi paga, capturada e ainda tem valor
   disponível para devolução.
2. O valor solicitado fica logicamente comprometido com aquele refund. Isso
   impede que dois refunds concorrentes devolvam mais do que foi capturado.
3. A devolução é enviada pela mesma transação financeira usada no pagamento.
4. O objeto `refund` informa o resultado em `status`.
5. Somente `status: "succeeded"` confirma que a devolução foi processada.

### Cartão de crédito

O crédito volta para o **mesmo cartão usado na compra**. O comprador não informa
outro cartão e sua integração não envia dados de destino.

Quando o refund chega a `succeeded`, a devolução foi processada. O tempo para o
crédito aparecer na fatura ou no limite disponível ainda depende do emissor do
cartão e do fechamento da fatura. Por isso, não prometa que o comprador verá o
crédito imediatamente na tela do banco.

### PIX

O valor volta pelo fluxo da **transação PIX original**. Sua integração não envia
uma nova chave PIX e não escolhe uma conta de destino.

Refunds de PIX costumam concluir rapidamente, mas a regra de integração é a
mesma do cartão: espere `refund.status: "succeeded"` antes de marcar a devolução
como concluída.

<Warning>
  As taxas cobradas pela Chargefy não são devolvidas quando um refund é criado.
  O `amount` do refund representa o valor devolvido ao comprador, não uma
  reversão das taxas da transação.
</Warning>

## Antes de começar

Você precisa de:

* uma API key com permissão de escrita;
* o ID do `payment_intent` (`pi_*`) ou da `charge` (`ch_*`);
* uma cobrança paga e capturada;
* o valor a devolver, quando a devolução for parcial;
* uma chave de idempotência única para essa operação.

Para plataformas, envie também o header `Organization` com a organização
conectada dona do pagamento.

## Passo a passo

<Steps>
  <Step title="Confirme que o pagamento foi concluído">
    Consulte o `payment_intent` e verifique o `status`. Um refund só faz sentido
    quando existe uma `charge` paga e capturada.

    ```bash theme={"theme":"css-variables"}
    curl -X GET "https://api.chargefy.io/v1/payment-intents/pi_ni5v4XMdviG3f8rQ" \
      -H "Authorization: Bearer {{API_KEY}}"
    ```

    Se o retorno estiver em `requires_capture`, o cartão foi autorizado, mas o
    dinheiro ainda não foi capturado. Nesse caso, cancele o Payment Intent em
    vez de criar um refund.
  </Step>

  <Step title="Escolha a referência da devolução">
    O endpoint aceita **exatamente um** destes campos:

    * `payment_intent`: caminho recomendado quando o seu sistema acompanha a
      cobrança pelo processo completo. A Chargefy encontra a charge capturada
      correspondente.
    * `charge`: use quando você precisa devolver uma tentativa específica, por
      exemplo, ao reconciliar duas cobranças duplicadas.

    Não envie os dois campos no mesmo request.
  </Step>

  <Step title="Escolha entre devolução total e parcial">
    Para devolver todo o saldo ainda disponível, omita `amount`. Para devolver
    apenas uma parte, envie `amount` como inteiro em centavos.

    Por exemplo, `5000` significa R\$ 50,00. Nunca envie `50.00`, `50,00` ou uma
    string formatada.
  </Step>

  <Step title="Gere uma chave de idempotência">
    Use uma chave única por devolução lógica e envie-a somente no header
    `Idempotency-Key`. Se a resposta se perder, repita o mesmo request com a
    mesma chave.

    Não gere uma chave nova só porque ocorreu timeout: isso pode criar uma
    segunda devolução.
  </Step>

  <Step title="Crie o refund">
    Faça `POST /v1/refunds` usando uma das variantes abaixo. A resposta é o objeto
    `refund` completo.
  </Step>

  <Step title="Decida pelo status do refund">
    Trate `succeeded` como conclusão. Para `pending` ou `requires_action`,
    mantenha a operação em andamento. Para `failed` ou `canceled`, não informe ao
    comprador que o dinheiro foi devolvido.
  </Step>

  <Step title="Acompanhe os webhooks">
    Persista o `refund.id`, processe os eventos de forma idempotente e atualize
    o seu pedido usando sempre o estado completo de `data.object`.
  </Step>
</Steps>

## Payloads de criação

### (a) Devolução total pelo Payment Intent

É o caminho mais simples quando o seu sistema já guarda o `pi_*`. A ausência de
`amount` significa: devolva todo o valor capturado que ainda está disponível.

```bash theme={"theme":"css-variables"}
curl -X POST "https://api.chargefy.io/v1/refunds" \
  -H "Authorization: Bearer {{API_KEY}}" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -d '{
    "payment_intent": "pi_ni5v4XMdviG3f8rQ",
    "reason": "requested_by_customer"
  }'
```

### (b) Devolução total por uma charge específica

Use esta forma quando houver mais de uma tentativa relacionada ao mesmo pedido
e você souber exatamente qual `charge` precisa ser devolvida.

```bash theme={"theme":"css-variables"}
curl -X POST "https://api.chargefy.io/v1/refunds" \
  -H "Authorization: Bearer {{API_KEY}}" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440001" \
  -d '{
    "charge": "ch_gcEDaQeT4xKJtBrG",
    "metadata": {},
    "reason": "duplicate"
  }'
```

### (c) Devolução parcial

Envie `amount` quando somente parte da compra deve voltar ao comprador.

```bash theme={"theme":"css-variables"}
curl -X POST "https://api.chargefy.io/v1/refunds" \
  -H "Authorization: Bearer {{API_KEY}}" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440002" \
  -d '{
    "amount": 5000,
    "charge": "ch_gcEDaQeT4xKJtBrG",
    "metadata": {},
    "reason": "requested_by_customer"
  }'
```

Os motivos aceitos são:

| `reason`                | Quando usar                                                                      |
| ----------------------- | -------------------------------------------------------------------------------- |
| `duplicate`             | A mesma compra foi cobrada mais de uma vez.                                      |
| `fraudulent`            | A organização identificou suspeita de fraude e decidiu devolver voluntariamente. |
| `requested_by_customer` | O comprador pediu cancelamento, troca ou devolução.                              |

`reason` é opcional. Use `metadata` para guardar referências livres do seu
sistema, como o ID do pedido ou do atendimento. Não coloque regra de negócio
essencial apenas em `metadata`.

## Exemplo de resposta

O endpoint retorna `200 OK` com o objeto refund completo. Neste exemplo, uma
devolução parcial de R\$ 50,00 já foi processada:

```json theme={"theme":"css-variables"}
{
  "id": "re_LV4WjSG4YeCeJUEy",
  "object": "refund",
  "amount": 5000,
  "balance_transaction": null,
  "charge": "ch_gcEDaQeT4xKJtBrG",
  "created_at": "2026-07-17T14:30:00Z",
  "currency": "brl",
  "customer": "cus_qEhzHmYjtKCpYKJs",
  "description": null,
  "destination_details": null,
  "failure_balance_transaction": null,
  "failure_reason": null,
  "instructions_email": null,
  "livemode": true,
  "metadata": {},
  "next_action": null,
  "payment_intent": "pi_ni5v4XMdviG3f8rQ",
  "pending_reason": null,
  "reason": "requested_by_customer",
  "receipt_number": null,
  "source_transfer_reversal": null,
  "status": "succeeded",
  "transfer_reversal": null,
  "updated_at": "2026-07-17T14:30:01Z"
}
```

## Como interpretar cada status

| `status`          | Significado de produto                                    | O que fazer                                                                                                    |
| ----------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| `pending`         | A devolução foi registrada e ainda está sendo processada. | Aguarde `refund.updated` e mantenha o pedido como devolução em andamento.                                      |
| `requires_action` | É necessária uma ação adicional para concluir.            | Leia `next_action` e não marque como concluída.                                                                |
| `succeeded`       | A devolução foi processada.                               | Marque o refund como concluído e informe o comprador sobre o prazo de visualização do crédito.                 |
| `failed`          | A devolução não foi concluída.                            | Leia `failure_reason`, reconcilie o refund e decida se a operação precisa de suporte ou de uma nova tentativa. |
| `canceled`        | O refund foi encerrado sem conclusão.                     | Não trate o dinheiro como devolvido.                                                                           |

<Warning>
  `refund.created` confirma que a solicitação entrou no ciclo de processamento;
  não confirma que o comprador recebeu o valor. A confirmação financeira é
  `data.object.status: "succeeded"` na resposta, em `refund.updated` ou numa
  consulta posterior.
</Warning>

## Webhooks que sua integração deve tratar

| Evento            | Para que serve                                                      |
| ----------------- | ------------------------------------------------------------------- |
| `refund.created`  | Registra o refund e seu estado inicial. Pode chegar como `pending`. |
| `refund.updated`  | Informa mudança de status, inclusive a conclusão em `succeeded`.    |
| `refund.failed`   | Informa que a devolução falhou.                                     |
| `charge.refunded` | Informa que uma devolução da charge foi concluída.                  |

Exemplo resumido de um refund que saiu de `pending` para `succeeded`:

```json theme={"theme":"css-variables"}
{
  "id": "evt_868qDT5N5JWYAHtR",
  "object": "event",
  "...": "demais campos do evento",
  "data": {
    "object": {
      "id": "re_LV4WjSG4YeCeJUEy",
      "object": "refund",
      "...": "demais campos do refund",
      "status": "succeeded"
    },
    "previous_attributes": {
      "status": "pending"
    }
  },
  "type": "refund.updated"
}
```

Seu receiver deve:

1. validar a assinatura do webhook;
2. deduplicar pelo `event.id`;
3. localizar a devolução por `data.object.id`;
4. substituir o estado local pelo objeto completo de `data.object`;
5. confirmar a devolução ao comprador somente quando `status` for `succeeded`.

## Cenários comuns

### O comprador desistiu depois de pagar com cartão

O pagamento já está `succeeded`. Crie um refund total pelo `payment_intent` e
use `reason: "requested_by_customer"`. Informe que o crédito pode levar algum
tempo para aparecer na fatura, mesmo depois de o refund ficar `succeeded`.

### Houve duas cobranças para o mesmo pedido

Identifique a `charge` que deve ser revertida e crie o refund por ela com
`reason: "duplicate"`. Não devolva pelo pedido de forma genérica: duas charges
distintas exigem reconciliação explícita para evitar devolver a cobrança
correta por engano.

### Apenas um item do pedido foi devolvido

Some o valor daquele item e envie `amount` em centavos. O refund será parcial e
o restante da charge continuará pago. Você pode criar outros refunds parciais
depois, até consumir todo o valor capturado disponível.

### Um PIX foi pago depois de o pedido ser cancelado

Se a confirmação financeira chegou, existe uma charge paga mesmo que o seu
pedido já esteja cancelado. Crie um refund usando essa `charge` ou o
`payment_intent` relacionado. Não tente “cancelar o PIX pago”: depois da
liquidação, o caminho correto é a devolução.

### A API retornou timeout ou erro `5xx`

Não conclua que nada aconteceu. Consulte o refund pelo ID conhecido ou liste os
refunds filtrando por `charge` ou `payment_intent`. Depois, repita a chamada com
o **mesmo** `Idempotency-Key` e o mesmo corpo.

Uma chave nova representa uma nova operação e pode causar devolução duplicada.

### O refund falhou por saldo insuficiente

O objeto pode chegar a `failed` com `failure_reason: "insufficient_funds"`.
Mantenha a devolução como pendente de resolução no seu backoffice; não informe
ao comprador que o dinheiro voltou. Consulte o refund e acione o suporte se a
causa não puder ser resolvida operacionalmente.

## Erros comuns da API

| Status | Situação                                                           | Como corrigir                                                            |
| ------ | ------------------------------------------------------------------ | ------------------------------------------------------------------------ |
| `400`  | Nenhum de `charge` ou `payment_intent` foi enviado.                | Envie exatamente uma referência.                                         |
| `400`  | `charge` e `payment_intent` foram enviados juntos.                 | Remova um dos campos.                                                    |
| `400`  | `amount` não é inteiro positivo ou supera o saldo disponível.      | Recalcule o valor em centavos e consulte os refunds anteriores.          |
| `400`  | O Payment Intent está `requires_capture`.                          | Cancele a autorização em vez de criar refund.                            |
| `404`  | A referência não existe na organização e no ambiente autenticados. | Confira ID, API key, `livemode` e header `Organization`.                 |
| `409`  | A charge não está paga/capturada ou já foi totalmente devolvida.   | Consulte a charge e o saldo reembolsável.                                |
| `422`  | A devolução não pode ser processada automaticamente.               | Não crie outra devolução às cegas; consulte o objeto e acione o suporte. |
| `5xx`  | Falha transitória ou resposta financeira inconclusiva.             | Reconcilie e repita com a mesma chave de idempotência.                   |

## O que não fazer

* Não use o status do seu pedido como prova de que o dinheiro voltou.
* Não trate `refund.created` como confirmação financeira.
* Não tente cancelar um Payment Intent que já está `succeeded`.
* Não crie uma nova chave de idempotência para repetir a mesma devolução.
* Não use apenas `charge.status` para reconciliar; guarde o objeto `refund` e
  acompanhe o seu `status`.
* Não prometa prazo exato de visualização no cartão depois de `succeeded`.
* Não tente desfazer um refund concluído. Para cobrar novamente, crie uma nova
  tentativa de pagamento autorizada pelo comprador.

## Checklist para produção

* [ ] Guardar `payment_intent`, `charge` e `refund` no pedido.
* [ ] Enviar valores monetários como inteiros em centavos.
* [ ] Usar `Idempotency-Key` em toda criação de refund.
* [ ] Processar `refund.created`, `refund.updated` e `refund.failed`.
* [ ] Deduplicar webhooks por `event.id`.
* [ ] Tratar somente `refund.status: "succeeded"` como conclusão.
* [ ] Separar “pedido cancelado”, “refund solicitado” e “dinheiro devolvido” no
  seu modelo de status.
* [ ] Testar devolução total, parcial, retry e falha antes de operar em produção.

## Próximos passos

<CardGroup cols={2}>
  <Card title="Criar um refund" icon="rotate-left" href="/api-reference/refunds/create">
    Contrato completo do `POST /v1/refunds`, incluindo campos e erros.
  </Card>

  <Card title="Objeto refund" icon="brackets-curly" href="/api-reference/refunds">
    Consulte todos os campos, status e motivos de falha.
  </Card>

  <Card title="Idempotência" icon="fingerprint" href="/api-reference/idempotency">
    Saiba como repetir mutações sem executar a mesma operação duas vezes.
  </Card>

  <Card title="Cancelar uma tentativa" icon="ban" href="/payments/cancel-payment-attempt">
    Use este fluxo quando o dinheiro ainda não foi capturado.
  </Card>
</CardGroup>
