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

# Receber por PIX

> Os dois endpoints de PIX, quando usar cada um e como reconciliar sem passar vergonha.

Existem dois jeitos de gerar um PIX, e a diferença não é estilo: eles aceitam recursos
diferentes.

**Os dois aceitam valor solto.** Não é que um seja "com produto" e o outro "sem". Em
`/v1/payments` o `productId` é opcional: sem ele, você manda `valueCents` e funciona
igual. O que separa de verdade é o que cada um faz **além** disso.

| Você quer | Use | ID |
| - | - | - |
| Só gerar um PIX de um valor | `POST /v1/charges` | `psc_` |
| Dividir o valor com outras contas | `POST /v1/charges` com `splits` | `psplit_` |
| O preço vir de um produto cadastrado | `POST /v1/payments` com `productId` | `psa_` |
| Cobrar em [Litecoin](https://docs.purin.cash/guias/cripto.md) | `POST /v1/payments` com `paymentMethod: "ltc"` | `psa_` |
| [Assinatura](https://docs.purin.cash/guias/assinaturas.md) recorrente | `POST /v1/subscriptions` | `psa_sub_` |

> [!TIP]
>
> Regra prática: **quer só um valor, usa `charges`.** É o caminho mais curto, e é o único
> que faz split. Vá pra `payments` quando precisar de catálogo, cripto ou assinatura.

Uma coisa que **os dois** aceitam: o campo `supplier`, que amarra a cobrança a um produto
da sua loja pra [entrega automática](https://docs.purin.cash/guias/entrega.md#amarrando-o-produto). Isso é
independente de qual endpoint você escolheu.

## Criando

```bash Valor livre theme={null}
curl -X POST https://api.purin.cash/v1/charges \
  -H "Authorization: Bearer $PURINCASH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "valueCents": 1990,
    "description": "Plano Pro",
    "expiresIn": 900,
    "callbackUrl": "https://minhaloja.com/webhooks/purincash",
    "customer": { "name": "João Silva", "email": "joao@exemplo.com" },
    "metadata": "{\"pedido\":\"1042\"}"
  }'
```

```bash A partir de um produto theme={null}
curl -X POST https://api.purin.cash/v1/payments \
  -H "Authorization: Bearer $PURINCASH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "productId": "665f1a2b3c4d5e6f7a8b9c0d",
    "callbackUrl": "https://minhaloja.com/webhooks/purincash",
    "customer": { "name": "João Silva", "externalId": "user_42" }
  }'
```

```json Resposta theme={null}
{
  "paymentId": "psc_a1b2c3d4e5f6",
  "status": "pending",
  "amountCents": 1990,
  "currency": "BRL",
  "environment": "live",
  "pix": {
    "brCode": "00020126580014br.gov.bcb.pix0136...",
    "qrCodeImage": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
  },
  "expiresAt": "2026-03-18T12:30:00.000Z"
}
```

> [!NOTE]
>
> Em `/v1/payments`, se você mandar `productId` e `valueCents` ao mesmo tempo, o preço do
> produto vence. Produto em moeda diferente de BRL é convertido para real na hora da
> cobrança.
>
> Sem `productId`, o `valueCents` manda e a `description` vira o nome do pagamento
> (padrão `"Pagamento"` quando você não mandar nenhuma).

> [!WARNING]
>
> Cuidado pra não confundir dois campos com nome parecido:
>
> `productId` é produto **de cobrança**, criado por `POST /v1/products`. Ele define o
> preço.
>
> `supplier.productId` é produto **da loja**, no formato `prod_xxx`. Ele não define preço
> nenhum: serve pra entrega automática. Passar um no lugar do outro devolve `404`.

## Prazo de validade

`expiresIn`, em **segundos**. De 60 (um minuto) a 604800 (sete dias). Sem o campo, a
cobrança vale 30 minutos, que é o que sempre valeu.

```json theme={null}
{ "valueCents": 1990, "expiresIn": 900 }
```

Valor fora da faixa devolve `400` em vez de cair no padrão. É de propósito: o erro mais
provável é mandar minutos achando que o campo é em minutos, e `expiresIn: 15` virar uma
cobrança de quinze segundos seria bem pior que uma mensagem de erro.

A resposta traz `expiresAt` com o instante exato do vencimento. Use esse valor no seu
contador, em vez de somar o prazo no seu relógio: o que vale é a hora do servidor.

> [!NOTE]
>
> O prazo vale para PIX. Cobrança no cartão e assinatura têm ciclo próprio e ignoram o
> campo.

## Valor mínimo

O piso da plataforma é **R\$ 0,80** (`valueCents: 80`), mas cada loja pode configurar um
mínimo maior no painel. Quando o valor não passa, o erro `400` já diz qual é o mínimo
daquela conta:

```json theme={null}
{ "error": "valueCents must be >= 500 (R$ 5.00), or provide productId" }
```

Vale ler esse número em vez de fixar `80` no seu código.

## Ciclo de vida

**1. pending**

A cobrança existe e o `brCode` funciona. Dura o `expiresIn` que você escolheu, ou 30
minutos quando você não escolheu nenhum.

**2. paid**

O PIX caiu. O webhook sai nesse momento e o valor entra no seu saldo.

**3. expired**

Passou do prazo sem pagamento. Para tentar de novo, crie outra cobrança.

**4. refunded / cancelled**

Estorno ou cancelamento posterior. Vale conferir esses status antes de entregar algo
de valor alto.

> [!WARNING]
>
> `expired` é o estado da cobrança, e não uma garantia de que o dinheiro não entra mais.
> Dependendo de qual instituição processa o PIX da sua loja, um pagamento feito depois do
> prazo ainda pode ser aceito pelo banco. Quando isso acontece a gente credita o valor e
> dispara o `charge.paid` normalmente, porque o cliente pagou de verdade e não receber o
> produto seria pior.
>
> Ou seja: **`expired` não dispensa você de tratar o webhook**. Se o seu fluxo não pode
> entregar depois do prazo, decida isso no seu handler comparando `paidAt` com `expiresAt`,
> e estorne se for o caso.

## Reconciliando

Faça as duas coisas. Elas cobrem falhas diferentes.

> [accordion] Webhook: rápido, mas não confiável sozinho
>
> A entrega é feita assim que o pagamento confirma, com reentrega automática em caso de
> falha. Ainda assim, a sua URL é pública e qualquer um pode chamá-la.
>
> Sempre [valide a assinatura](https://docs.purin.cash/webhooks/assinatura.md) e deduplique pelo header
> `X-Webhook-Id`.

> [accordion] Consulta: lenta, mas definitiva
>
> `GET /v1/charges/{paymentId}` e `GET /v1/payments/{paymentId}` devolvem o estado
> atual. Use antes de entregar algo caro, e como rede de segurança quando o webhook não
> chegou.
>
> ```bash theme={null}
> curl https://api.purin.cash/v1/charges/psc_a1b2c3d4e5f6 \
>   -H "Authorization: Bearer $PURINCASH_KEY"
> ```

> [accordion] Polling: último recurso
>
> Se precisar mesmo, use intervalo de 5 segundos ou mais e pare no primeiro status
> final. O [limite de 120 requisições por minuto](https://docs.purin.cash/guias/limites.md) vale para tudo
> somado.

> [!WARNING]
>
> Antes de liberar o produto, confira `amountCents` além do `status`. Um pagamento pago
> com valor menor que o esperado não deveria virar entrega.

## Dados do pagador

No webhook `charge.paid` chegam campos que não existem na criação, porque vêm do banco:

| Campo | Conteúdo |
| - | - |
| `payer` | Nome de quem pagou |
| `bank` | Banco de origem, no formato `código - nome` |
| `endToEndId` | Identificador end-to-end da transação no Bacen |
| `txId` | txid da transação |

Servem para conciliação bancária e para responder [disputa](https://docs.purin.cash/guias/disputas.md). CPF,
telefone e endereço não são enviados em webhook.

> [card] [Dividir o valor](https://docs.purin.cash/guias/splits.md)
>
> Marketplace, comissão ou sociedade, resolvido no momento do pagamento.

> [card] [Entregar sozinho](https://docs.purin.cash/guias/entrega.md)
>
> Chave ou conta liberada assim que o PIX confirma.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.