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

# Sua primeira cobrança

> Do zero ao PIX pago em sandbox, sem mover dinheiro de verdade.

Este guia vai até o fim: chave, cobrança, pagamento simulado e webhook recebido. Tudo em
sandbox, então nada aqui movimenta dinheiro.

<Steps>
  <Step title="Gere uma chave de sandbox" icon="key">
    Entre no painel, abra [API e Desenvolvedores](https://purincash.com/dashboard/api) e
    gere uma chave de sandbox. Ela começa com `ps_test_`.

    A chave inteira aparece uma única vez, na hora da criação. Se perder, revogue e gere
    outra. Cada conta pode manter até 10 chaves ativas.

    <Warning>
      Chave de API é credencial de servidor. Ela nunca deve aparecer em JavaScript de
      navegador, app mobile, repositório público ou print de tela.
    </Warning>
  </Step>

  <Step title="Crie a cobrança" icon="qrcode">
    O endpoint mais direto é `POST /v1/charges`: valor livre, sem precisar cadastrar
    produto antes.

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

      ```js Node.js theme={null}
      const res = await fetch("https://api.purincash.com/v1/charges", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.PURINCASH_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          valueCents: 1990,
          description: "Plano Pro",
          callbackUrl: "https://minhaloja.com/webhooks/purincash",
          customer: { name: "João Silva", email: "joao@exemplo.com" },
          metadata: JSON.stringify({ pedido: "1042" }),
        }),
      });

      const cobranca = await res.json();
      console.log(cobranca.paymentId, cobranca.pix.brCode);
      ```

      ```python Python theme={null}
      import os, json, requests

      resposta = requests.post(
          "https://api.purincash.com/v1/charges",
          headers={"Authorization": f"Bearer {os.environ['PURINCASH_KEY']}"},
          json={
              "valueCents": 1990,
              "description": "Plano Pro",
              "callbackUrl": "https://minhaloja.com/webhooks/purincash",
              "customer": {"name": "João Silva", "email": "joao@exemplo.com"},
              "metadata": json.dumps({"pedido": "1042"}),
          },
          timeout=15,
      )
      cobranca = resposta.json()
      print(cobranca["paymentId"], cobranca["pix"]["brCode"])
      ```

      ```php PHP theme={null}
      $ch = curl_init("https://api.purincash.com/v1/charges");
      curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => [
          "Authorization: Bearer " . getenv("PURINCASH_KEY"),
          "Content-Type: application/json",
        ],
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => json_encode([
          "valueCents"  => 1990,
          "description" => "Plano Pro",
          "callbackUrl" => "https://minhaloja.com/webhooks/purincash",
          "customer"    => ["name" => "João Silva", "email" => "joao@exemplo.com"],
          "metadata"    => json_encode(["pedido" => "1042"]),
        ]),
      ]);

      $cobranca = json_decode(curl_exec($ch), true);
      echo $cobranca["paymentId"], PHP_EOL, $cobranca["pix"]["brCode"];
      ```
    </CodeGroup>

    A resposta vem assim:

    ```json Resposta 201 theme={null}
    {
      "paymentId": "psc_a1b2c3d4e5f6",
      "status": "pending",
      "amountCents": 1990,
      "currency": "BRL",
      "environment": "sandbox",
      "pix": {
        "brCode": "00020126580014br.gov.bcb.pix0136...",
        "qrCodeImage": "https://qr.exemplo.com/psc_a1b2c3d4e5f6.png"
      },
      "expiresAt": "2026-03-18T12:30:00.000Z"
    }
    ```

    Guarde o `paymentId` junto do seu pedido. É por ele que você vai reconciliar tudo
    depois.

    <Tip>
      Quer amarrar a cobrança ao seu banco de dados sem tabela auxiliar? Use `metadata`.
      Ele volta intacto no webhook e na consulta, então dá pra guardar ali o id do pedido.
    </Tip>
  </Step>

  <Step title="Mostre o PIX pro cliente" icon="mobile">
    São dois caminhos, e você normalmente oferece os dois na mesma tela:

    | Campo             | O que fazer com ele                                              |
    | ----------------- | ---------------------------------------------------------------- |
    | `pix.brCode`      | Renderize como texto com um botão de copiar. É o copia e cola.   |
    | `pix.qrCodeImage` | URL da imagem PNG do QR. Trate como opaca, o domínio pode mudar. |

    Se preferir gerar o QR você mesmo, use qualquer biblioteca de QR Code passando o
    `brCode` como conteúdo. O resultado é o mesmo.

    <Warning>
      Cobrança PIX expira em 30 minutos (`expiresAt`). Depois disso o código para de
      funcionar e o status vira `expired`. Se o cliente sumiu e voltou, crie outra.
    </Warning>

    <Note>
      Você está em sandbox, então esse `brCode` é falso: vem como `SANDBOX_PIX_...` e
      nenhum banco reconhece. Serve pra você montar a tela; quem confirma o pagamento é o
      passo seguinte.
    </Note>
  </Step>

  <Step title="Simule o pagamento" icon="flask">
    Em sandbox ninguém vai pagar de verdade, então você marca como pago na mão:

    ```bash theme={null}
    curl -X POST https://api.purincash.com/v1/sandbox/charges/psc_a1b2c3d4e5f6/simulate-paid \
      -H "Authorization: Bearer $PURINCASH_KEY"
    ```

    Isso muda o status para `paid` e dispara o webhook na sua `callbackUrl`, igualzinho
    ao que acontece em produção. Não passa por aprovação de ninguém: é essa chamada e
    pronto.

    <Note>
      Para pagamentos criados em `POST /v1/payments` o endpoint é
      `/v1/sandbox/payments/{paymentId}/simulate-paid`. A diferença é só o recurso.
    </Note>
  </Step>

  <Step title="Receba o webhook" icon="bell">
    A gente faz `POST` na sua URL com o corpo do evento e o header
    `X-Webhook-Signature`:

    ```json theme={null}
    {
      "event": "charge.paid",
      "paymentId": "psc_a1b2c3d4e5f6",
      "amountCents": 1990,
      "status": "paid",
      "paidAt": "2026-03-18T12:05:00.000Z",
      "customer": { "name": "João Silva", "email": "joao@exemplo.com" },
      "metadata": "{\"pedido\":\"1042\"}",
      "sandbox": true
    }
    ```

    Antes de liberar qualquer coisa pro cliente, [valide a
    assinatura](/webhooks/assinatura). A sua URL é pública, então qualquer pessoa
    consegue mandar um JSON dizendo `"status": "paid"`.

    Sem servidor exposto ainda? Use um túnel local (`ngrok http 3000`, `cloudflared
            tunnel`) ou um coletor tipo webhook.site só pra ver o payload chegando.
  </Step>

  <Step title="Confirme pela API" icon="magnifying-glass">
    Webhook é notificação, não fonte da verdade. Antes de entregar o produto, confirme:

    ```bash theme={null}
    curl https://api.purincash.com/v1/charges/psc_a1b2c3d4e5f6 \
      -H "Authorization: Bearer $PURINCASH_KEY"
    ```

    Se o `status` vier `paid` e o `amountCents` bater com o que você cobrou, pode
    liberar.
  </Step>
</Steps>

## Passando pra produção

Troque `ps_test_` por `ps_live_`. Só isso. Nenhuma URL muda, nenhum parâmetro muda.

Antes de virar a chave, dá uma passada em [Antes de ir pra produção](/guias/producao).

<CardGroup cols={2}>
  <Card title="Vender produto cadastrado" icon="box" href="/guias/produtos">
    Cadastre o preço uma vez e cobre por `productId`.
  </Card>

  <Card title="Entregar automaticamente" icon="truck-fast" href="/guias/entrega">
    Chave, conta ou link liberado no instante do pagamento.
  </Card>
</CardGroup>
