Pular para o conteúdo
LeztyPay docs
Painel

Criar uma cobrança

Uma chamada, um header de idempotência, e a resposta já traz o QR Code.

#!/usr/bin/env bash
# Cria uma cobrança PIX. Precisa de LM_API_URL e LM_API_KEY no ambiente.
#   export LM_API_URL=https://api.leztypay.com
#   export LM_API_KEY=sk_test_...
set -euo pipefail

curl --fail-with-body -sS "$LM_API_URL/v1/transactions" \
  -H "Authorization: Bearer $LM_API_KEY" \
  -H "Idempotency-Key: pedido-A123-tentativa-1" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 10000,
    "payment_method": "pix",
    "customer": {
      "name": "Maria Silva",
      "email": "maria@exemplo.com",
      "document": "123.456.789-09"
    },
    "items": [{ "name": "Curso X", "quantity": 1, "unit_amount": 10000 }],
    "pix": { "expires_in_seconds": 1800 },
    "postback_url": "https://loja.exemplo.com/webhooks/leztypay",
    "metadata": { "order_id": "A123" }
  }'

O mesmo em Node, com tratamento de erro:

// Cria uma cobrança PIX com o fetch global do Node 22. Sem dependência.
//   LM_API_URL=https://api.leztypay.com LM_API_KEY=sk_test_... node create-transaction.mjs
const apiUrl = process.env.LM_API_URL;
const apiKey = process.env.LM_API_KEY;

// A chave de idempotência é do SEU pedido, não da requisição. Repetir a mesma chave com o
// mesmo corpo devolve a cobrança já criada em vez de criar uma segunda.
const idempotencyKey = 'pedido-A123-tentativa-1';

const res = await fetch(`${apiUrl}/v1/transactions`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Idempotency-Key': idempotencyKey,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    amount: 10000,
    payment_method: 'pix',
    customer: {
      name: 'Maria Silva',
      email: 'maria@exemplo.com',
      document: '123.456.789-09',
    },
    items: [{ name: 'Curso X', quantity: 1, unit_amount: 10000 }],
    pix: { expires_in_seconds: 1800 },
    postback_url: 'https://loja.exemplo.com/webhooks/leztypay',
    metadata: { order_id: 'A123' },
  }),
});

const body = await res.json();

if (!res.ok) {
  // Todo erro tem o mesmo envelope. `request_id` é o que o suporte pede.
  console.error(res.status, body.error.code, body.error.message, body.error.request_id);
  process.exit(1);
}

if (res.headers.get('Idempotent-Replayed') === 'true') {
  console.log('resposta salva de uma tentativa anterior com a mesma chave');
}

console.log('id:', body.id);
console.log('status:', body.status);
console.log('expira em:', body.pix.expires_at);
console.log('copia e cola:', body.pix.payload);

A lista completa do que entra e do que volta está na referência da operação. Aqui ficam só as regras que a referência não conta.

O mínimo

amount e customer são obrigatórios. Todo o resto tem padrão.

O cliente precisa de nome, e-mail e documento. O documento é CPF ou CNPJ e é validado de verdade, com dígito verificador; pode vir formatado ou só com dígitos, tanto faz. Documento inválido é 400 invalid_document.

payment_method aceita apenas pix, e é o padrão. Qualquer outro valor é 400 unsupported_payment_method.

A taxa é congelada na criação

A resposta traz fee, net e reserve já calculados com as condições vigentes naquele instante, e eles não mudam mais. Uma cobrança criada hoje liquida com a taxa de hoje, mesmo que seja paga daqui a uma semana com outra tabela em vigor.

Se a taxa consumisse a cobrança inteira, a criação é recusada com 400 amount_too_small. É por isso que um valor acima do mínimo ainda pode ser recusado por ser pequeno demais.

Os campos que existem para você, não para nós

  • metadata é o seu campo livre: um objeto plano de strings que volta igual em toda consulta e em todo evento. É onde mora o id do pedido na sua loja. Nós não lemos, não indexamos e não interpretamos nada dele. Tetos em Limites.
  • items é informativo. A soma dos itens não precisa bater com amount, e não validamos isso. Serve para o extrato e para o painel ficarem legíveis.
  • description viaja junto para o processador de pagamentos. Se você não mandar, geramos uma.
  • tracking carrega parâmetros de origem da venda. Só as sete chaves previstas são aceitas; chave desconhecida é 400 validation_failed.

postback_url: webhook só desta cobrança

postback_url aponta o destino do webhook daquela cobrança, sem passar pelo endpoint da conta. É o caminho rápido para testar e para quem precisa separar destinos por fluxo.

Regras: precisa ser https e precisa apontar para um destino público. Endereço interno, IP privado ou localhost são recusados com 400 invalid_url, e não é configurável: é o que impede a API de ser usada para varrer a rede de quem a hospeda.

Em produção, prefira o endpoint da conta, cadastrado no painel: ele tem segredo próprio, recebe todos os eventos e aparece no histórico de entregas.

Quando a criação falha

  • 400 de validação: nada foi criado. Corrija e repita com a mesma Idempotency-Key.
  • 403 seller_not_allowed: a conta não pode cobrar neste ambiente agora. Ver Autenticação.
  • 409 idempotency_conflict ou 409 idempotency_in_progress: ver Idempotência.
  • 503 acquirer_timeout, acquirer_rejected ou acquirer_unavailable: o processador falhou. A cobrança pode ter ficado registrada como failed, sem QR. Repita com uma chave de idempotência nova, porque a mesma chave devolveria esse mesmo 503 de novo.

Cobrança failed responde com pix.payload e pix.qr_base64 nulos. Nunca mostre a tela de pagamento antes de conferir que eles vieram preenchidos.

Próximo passo

QR Code: o que fazer com o pix.payload e com o pix.qr_base64 que acabaram de chegar.

Referência: Cria uma cobrança PIX.