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

# Checkout PIX de ponta a ponta

> Do pedido no seu sistema à liberação do produto, com webhook e uma rede de segurança.

O fluxo completo de uma venda, do jeito que ele sobrevive a rede instável e a comprador que fecha a
aba.

<Note>
  Os valores aqui são **inteiros em centavos**: R\$ 100,00 se envia como `10000`. Decimais são
  recusados com `422`, e o porquê está em [Valores em centavos](/essenciais/valores-em-centavos).
</Note>

<Steps>
  <Step title="Crie a cobrança com a sua referência">
    Gere a `Idempotency-Key` **antes** da chamada e grave-a junto do pedido. Envie `reference` com o seu
    identificador — é o que permite reencontrar a cobrança depois sem guardar o `id` que devolvemos.

    <CodeGroup>
      ```php PHP theme={null}
      $pedido->update(['chave_idempotencia' => (string) Str::uuid()]);

      $resposta = Http::withToken(config('services.vext.key'))
          ->withHeaders(['Idempotency-Key' => $pedido->chave_idempotencia])
          ->post('https://api.usevext.com/v1/charges', [
              'amount' => $pedido->total_em_centavos,   // inteiro, sempre
              'description' => $pedido->descricao,
              'reference' => "pedido-{$pedido->id}",
              'expires_in' => 1800,
              'customer' => [
                  'name' => $pedido->cliente->nome,
                  'email' => $pedido->cliente->email,
                  'document' => $pedido->cliente->cpf,
                  'document_type' => 'CPF',
              ],
          ]);

      $cobranca = $resposta->json();
      $pedido->update(['cobranca_id' => $cobranca['id']]);
      ```

      ```javascript JavaScript theme={null}
      pedido.chaveIdempotencia ??= crypto.randomUUID();
      await pedido.save();

      const resposta = await fetch('https://api.usevext.com/v1/charges', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${process.env.VEXT_API_KEY}`,
          'Idempotency-Key': pedido.chaveIdempotencia,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          amount: pedido.totalEmCentavos, // inteiro, sempre
          description: pedido.descricao,
          reference: `pedido-${pedido.id}`,
          expires_in: 1800,
          customer: {
            name: pedido.cliente.nome,
            email: pedido.cliente.email,
            document: pedido.cliente.cpf,
            document_type: 'CPF',
          },
        }),
      });

      const cobranca = await resposta.json();
      pedido.cobrancaId = cobranca.id;
      ```

      ```python Python theme={null}
      pedido.chave_idempotencia = pedido.chave_idempotencia or str(uuid.uuid4())
      pedido.save()

      resposta = requests.post(
          "https://api.usevext.com/v1/charges",
          headers={
              "Authorization": f"Bearer {os.environ['VEXT_API_KEY']}",
              "Idempotency-Key": pedido.chave_idempotencia,
          },
          json={
              "amount": pedido.total_em_centavos,  # inteiro, sempre
              "description": pedido.descricao,
              "reference": f"pedido-{pedido.id}",
              "expires_in": 1800,
              "customer": {
                  "name": pedido.cliente.nome,
                  "email": pedido.cliente.email,
                  "document": pedido.cliente.cpf,
                  "document_type": "CPF",
              },
          },
      )

      cobranca = resposta.json()
      pedido.cobranca_id = cobranca["id"]
      ```
    </CodeGroup>

    A resposta vem com `status: "pending"`. O QR existe; ninguém pagou nada ainda.
  </Step>

  <Step title="Renderize o QR a partir do payload">
    A resposta traz `pix.qr_code`, o payload copia-e-cola. A **imagem** não é devolvida: ela é derivada
    do payload na hora de exibir, e mandar um PNG em base64 em toda resposta multiplicaria o corpo por
    algo que você gera localmente em milissegundos.

    Use qualquer biblioteca de QR code — `endroid/qr-code` em PHP, `qrcode` em Node, `qrcode` em Python.
    Mostre também o payload como texto, com um botão de copiar: muita gente paga colando no aplicativo
    do banco em vez de apontar a câmera.

    Exiba o vencimento a partir de `pix.expires_at`.
  </Step>

  <Step title="Aja no charge.paid, nunca antes">
    <Warning>
      O produto é liberado no `charge.paid`, não na resposta de `POST /v1/charges` — essa devolve
      `pending`, ou seja, o QR foi gerado mas ninguém pagou ainda.
    </Warning>

    No seu endpoint de webhook: confira a assinatura, grave o evento, responda `200`, processe em fila.
    O passo a passo está em [Verificar a assinatura](/webhooks/assinatura) e
    [Entregas e retentativas](/webhooks/entregas-e-retentativas).

    ```php Depois da assinatura conferida theme={null}
    if ($evento['type'] === 'charge.paid') {
        $cobranca = $evento['data']['charge'];
        $pedido = Pedido::where('cobranca_id', $cobranca['id'])->firstOrFail();

        $pedido->marcarComoPago($cobranca['paid_at']);
        LiberarAcesso::dispatch($pedido);
    }
    ```
  </Step>

  <Step title="Mantenha uma rede de segurança">
    Webhook é entrega pela rede, e rede falha. Se o seu endpoint ficou fora do ar durante as
    retentativas, o pedido fica pendente para sempre.

    Uma tarefa periódica que consulta as cobranças `pending` mais antigas que alguns minutos resolve:

    ```bash theme={null}
    curl "https://api.usevext.com/v1/charges?status=pending&created_to=2026-08-04T14:00:00-03:00" \
      -H "Authorization: Bearer sk_live_..."
    ```

    Faça o polling **espaçado**, não em laço apertado — a cota de leitura é por chave, e gastá-la aqui
    deixa o resto da sua integração sem cota. Ver [Limites de requisição](/essenciais/limites-de-taxa).
  </Step>

  <Step title="Trate o que expira">
    Uma cobrança `expired` não pode ser paga nem estornada. Se o comprador voltar, crie **outra**
    cobrança, com uma `Idempotency-Key` nova — é uma tentativa nova de verdade, e reusar a chave antiga
    devolveria a cobrança vencida.
  </Step>
</Steps>

## Veja também

<CardGroup cols={2}>
  <Card title="Conciliação" icon="scale" href="/receitas/conciliacao">
    Fechar o dia e casar tudo com os seus pedidos.
  </Card>

  <Card title="Ciclo de vida" icon="workflow" href="/essenciais/ciclo-de-vida-da-cobranca">
    Os sete estados e quando agir em cada um.
  </Card>
</CardGroup>

***

Ficou algo de fora? Escreva para [suporte@usevext.com](mailto:suporte@usevext.com). Se o assunto for
uma chamada específica, informe o horário dela; se for uma entrega de webhook, informe o
`X-Vext-Delivery` — é por ele que localizamos a tentativa, a resposta do seu servidor e o horário.
