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": "[email protected]",
      "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: '[email protected]',
      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

Só amount e customer são obrigatórios. Todo o resto tem padrão. Em cobrança PIX, amount tem mínimo de R$ 3,00 (300); abaixo disso é 400 amount_too_small.

O cliente precisa de nome, e-mail e documento. Em cobrança PIX, 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 pix, que é o padrão, voucher, a cobrança em moeda local descrita mais abaixo, e crypto, a cobrança em USDT ou USDC descrita em Cobrança em cripto. Qualquer outro valor é 400 unsupported_payment_method.

A taxa é congelada na criação

A resposta traz fee e net já calculados (reserve vem 0 no PIX) 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, domínios reservados para exemplo (example.com, example.net, example.org, localhost e os finais .example, .test, .invalid, .localhost) são recusados com 400 validation_failed e param: postback_url. No ambiente de teste não há essa checagem.

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.

Cobrança em moeda local: voucher

Com payment_method: "voucher" a cobrança sai na moeda do comprador, em vez de PIX. Hoje as moedas aceitas são MXN (México), CLP (Chile), COP (Colômbia) e PEN (Peru); qualquer outra é 400 unsupported_currency.

{
  "payment_method": "voucher",
  "currency": "MXN",
  "amount": 10000,
  "customer": {
    "name": "Juan Perez",
    "email": "[email protected]",
    "document": "XAXX010101000",
    "document_type": "RFC"
  }
}

O que muda em relação ao PIX:

  • currency é obrigatório.
  • amount vai na moeda local, com duas casas implícitas em toda moeda: 10000 são 100,00 pesos mexicanos e 500000 são 5.000 pesos chilenos.
  • Valor mínimo por moeda: MXN 70,00 (7000), CLP 3.800 (380000), COP 13.000 (1300000) e PEN 14,00 (1400), perto de R$ 20 em cada. Abaixo disso é 400 amount_too_small, com param: "amount".
  • customer.document_type é obrigatório e depende da moeda: RFC ou CURP em MXN, RUT ou RUN em CLP, CC ou NIT em COP, DNI ou RUC em PEN. Documento que não bate com o tipo é 400 invalid_document_for_country. O mesmo erro (com param: "customer.document") volta quando o processador recusa o documento na criação: a cobrança fica failed, é erro do comprador e não adianta repetir com o mesmo documento.
  • customer.address é opcional. Se vier, country precisa ser o país da moeda (MX, CL, CO ou PE).
  • splits é aceito só com kind: percent, e aceita base (gross ou net). fixed é 400 split_rejected_for_voucher. Veja Split em voucher. O objeto pix não é aceito (400 validation_failed).

Na resposta, pix vem null e os dados de pagamento vêm em voucher: barcode, digitable_line, url, expiration_date, instructions e receipt_url. Qualquer um deles pode ser null, conforme o meio de pagamento do país: mostre ao comprador os que vierem preenchidos. No Peru, por exemplo, vem só digitable_line (o código de pagamento), sem url. currency e local_amount trazem a moeda e o valor cobrados. amount, fee e net continuam em reais: são a conversão do dia da criação e valem como estimativa até a cobrança ficar paid.

Os eventos são os mesmos transaction.*, com payment_method: "voucher". Cobrança voucher não pode ser estornada: o pedido de estorno responde 409 refund_unsupported_for_payment_method.

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: o processador não respondeu a tempo e a cobrança pode ter sido criada lá. Ela fica pending, sem QR. Espere 60 s e repita com a mesma Idempotency-Key: a Lezty Pay pergunta de novo ao processador pela mesma cobrança, sem criar outra, e devolve o 201 com o QR. Antes dos 60 s a mesma chave responde 409 idempotency_in_progress. Se o 503 voltar com Idempotent-Replayed: true, a falha ficou gravada (processador que não garante a mesma cobrança): aí sim use chave nova. Trocar de chave antes disso pode gerar duas cobranças.
  • 503 acquirer_rejected ou acquirer_unavailable: o processador recusou ou estava fora. A cobrança fica 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, voucher, cripto, cartão ou carteira).