Pular para o conteúdo principal

Registrar Venda

O registro de venda é a operação principal da integração EloFiel. A cada venda finalizada no caixa, você envia os dados para a API e ela calcula e credita automaticamente o benefício ao cliente.

Endpoint

POST /api/v1/venda
X-Api-Key: SUA_API_KEY_AQUI

Passo a passo

1. Prepare os dados da venda

Os campos obrigatórios são cnpjLoja, cpfCliente e valorVenda:

{
"cnpjLoja": "12.345.678/0001-90",
"cpfCliente": "123.456.789-09",
"valorVenda": 150.00,
"documentoExterno": "PDV-001234",
"observacao": null,
"usarCashbackDisponivel": false
}
CampoTipoObrigatórioDescrição
cnpjLojastringSimCNPJ do estabelecimento
cpfClientestringSimCPF do cliente
valorVendanumberSimValor bruto da venda em R$
documentoExternostringNãoNúmero do cupom fiscal ou ID no PDV
observacaostringNãoObservação livre
usarCashbackDisponivelbooleanNãoModelo 2true = aplica o saldo de cashback acumulado como desconto (padrão false). Veja Cenário 5
usarCashbackGeradobooleanNãoModelo 3true = aplica o cashback que esta venda geraria como desconto imediato (padrão false). Chamar POST /api/v1/venda/preview antes para exibir o valor. Veja Cenário 6
clienteobjectNãoDados para auto-cadastro se o CPF não existir
Exclusividade mútua

usarCashbackDisponivel e usarCashbackGerado não podem ser true simultaneamente. A API retorna 422 se ambos forem enviados como true.

Rastreio com documentoExterno

Use documentoExterno para registrar o número do cupom fiscal ou ID da venda no seu PDV. Isso facilita a conciliação e o suporte.

2. Envie a requisição

curl -X POST https://api-sandbox.elofiel.com.br/api/v1/venda \
-H "Content-Type: application/json" \
-H "X-Api-Key: SUA_API_KEY_AQUI" \
-d '{
"cnpjLoja": "12.345.678/0001-90",
"cpfCliente": "123.456.789-09",
"valorVenda": 150.00,
"documentoExterno": "PDV-001234",
"observacao": null
}'

3. Processe a resposta

{
"sucesso": true,
"mensagem": "Venda registrada com sucesso.",
"dados": {
"vendaId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"clienteId": "8e9f1234-abcd-4321-efgh-000000000001",
"clienteNome": "Maria Silva",
"valorVenda": 150.00,
"cashbackUsado": 0.00,
"valorFinal": 150.00,
"beneficiosAplicados": [
{
"movimentacaoId": "aaaaaaaa-1111-2222-3333-444444444444",
"campanhaId": "cccccccc-aaaa-bbbb-dddd-eeeeeeeeeeee",
"campanhaDescricao": "Cashback 5% Verão",
"tipoBeneficio": "Cashback",
"pontosCreditados": 0,
"cashbackCreditado": 7.50,
"statusBeneficio": "Disponivel",
"dataLiberacao": null,
"dataExpiracao": "2026-09-29"
}
],
"pontosCreditados": 0,
"cashbackCreditado": 7.50,
"saldoAtual": {
"saldoPontos": 0,
"saldoCashback": 22.50,
"saldoPendente": 0.00
}
},
"erros": []
}
Issue #3 — Categoria de Benefício

A partir de 2026-05-20, beneficiosAplicados[] pode conter 1 OU 2 entries (máx. 1 Cashback + 1 Pontos por loja). Os campos pontosCreditados/cashbackCreditado no nível raiz são agregados (somatórios da lista).

Campo da respostaDescrição
cashbackUsadoModelo 2 — cashback do saldo anterior descontado nesta venda. 0 quando usarCashbackDisponivel: false
descontoGeradoModelo 3 — cashback gerado por esta venda e aplicado como desconto imediato. 0 quando usarCashbackGerado: false
valorFinalValor efetivamente pago após todos os descontos (valorVenda − cashbackUsado − descontoGerado). Use este campo no comprovante
cashbackCreditadoNovo cashback gerado nesta venda, calculado sobre valorFinal

Interpretando a resposta

Iterando sobre beneficiosAplicados

Cada elemento da lista beneficiosAplicados[] traz um benefício distinto:

const { dados } = resultado;

for (const beneficio of dados.beneficiosAplicados) {
if (beneficio.statusBeneficio === 'Disponivel') {
if (beneficio.tipoBeneficio === 'Cashback') {
console.log(`Cashback de R$ ${beneficio.cashbackCreditado} (${beneficio.campanhaDescricao}) já disponível!`);
} else {
console.log(`${beneficio.pontosCreditados} pontos creditados (${beneficio.campanhaDescricao})`);
}
} else if (beneficio.statusBeneficio === 'Pendente') {
const dataLib = new Date(beneficio.dataLiberacao).toLocaleDateString('pt-BR');
console.log(`Cashback de R$ ${beneficio.cashbackCreditado} disponível a partir de ${dataLib}`);
}
}

console.log(`Total agregado: R$ ${dados.cashbackCreditado} cashback + ${dados.pontosCreditados} pontos`);
console.log(`Saldo atual: R$ ${dados.saldoAtual.saldoCashback}`);

Cenários comuns

Cenário 1: Venda com 1 campanha (apenas Cashback)

Apenas a campanha "Cashback 5%" ativa para a loja. Venda de R$ 150,00:

{
"beneficiosAplicados": [
{
"campanhaDescricao": "Cashback 5% Verão",
"tipoBeneficio": "Cashback",
"cashbackCreditado": 7.50,
"statusBeneficio": "Disponivel"
}
],
"cashbackCreditado": 7.50,
"pontosCreditados": 0
}

Cenário 2: Venda com 2 campanhas (Cashback + Pontos — Categoria de Benefício)

Loja com 1 cashback + 1 pontos ativas (issue #3). Venda de R$ 100,00:

{
"beneficiosAplicados": [
{
"campanhaDescricao": "Cashback 5% Verão",
"tipoBeneficio": "Cashback",
"cashbackCreditado": 5.00,
"statusBeneficio": "Disponivel"
},
{
"campanhaDescricao": "1 ponto por real",
"tipoBeneficio": "Pontos",
"pontosCreditados": 100,
"statusBeneficio": "Disponivel"
}
],
"cashbackCreditado": 5.00,
"pontosCreditados": 100
}

Cenário 3: Venda com cashback diferido

Campanha com prazo de carência de 7 dias. Venda de R$ 200,00:

{
"beneficiosAplicados": [
{
"campanhaDescricao": "Cashback Fidelidade",
"tipoBeneficio": "Cashback",
"cashbackCreditado": 10.00,
"statusBeneficio": "Pendente",
"dataLiberacao": "2026-04-05"
}
]
}
Cashback pendente

Quando statusBeneficio = "Pendente", o valor não estará disponível para resgate até dataLiberacao. Não ofereça desconto ao cliente nesse caso.

Cenário 4: Venda sem campanha elegível

Se nenhuma campanha ativa tiver compraMinima <= valorVenda, a API retorna 422 Unprocessable Entity — a venda NÃO é registrada.

Cenário 5: Checkout com cashback disponível (Modelo 2)

Permite que o cliente use o saldo de cashback acumulado em compras anteriores como desconto na compra atual — tudo em uma única chamada.

Pré-requisito

A campanha precisa ter permiteCheckoutCashbackNaVenda: true (retornado por GET /api/v1/configuracao). Se a campanha não permitir, o campo usarCashbackDisponivel é ignorado silenciosamente.

Request:

{
"cnpjLoja": "12.345.678/0001-90",
"cpfCliente": "123.456.789-09",
"valorVenda": 100.00,
"documentoExterno": "PDV-005678",
"usarCashbackDisponivel": true
}

Response (cliente tinha R$ 20,00 de saldo, campanha permite desconto de até 30%):

{
"dados": {
"valorVenda": 100.00,
"cashbackUsado": 20.00,
"valorFinal": 80.00,
"cashbackCreditado": 4.00,
"saldoAtual": {
"saldoCashback": 4.00
}
}
}

O cliente pagou R$ 80,00 e já ganhou R$ 4,00 de novo cashback (5% sobre R$ 80,00). Nenhuma chamada adicional de resgate é necessária — a API aplica desconto, consome lotes FIFO e gera o novo crédito atomicamente.

// Verificar se checkout está habilitado antes de oferecer ao cliente
const config = await eloFielRequest('GET', `/api/v1/configuracao?cnpjLoja=${CNPJ_LOJA}`);

const resultado = await eloFielRequest('POST', '/api/v1/venda', {
cnpjLoja: CNPJ_LOJA,
cpfCliente: '123.456.789-09',
valorVenda: 100.00,
documentoExterno: 'PDV-005678',
usarCashbackDisponivel: config.permiteCheckoutCashbackNaVenda && clienteQuerUsarSaldo,
});

console.log(`Desconto aplicado: R$ ${resultado.cashbackUsado}`);
console.log(`Total pago: R$ ${resultado.valorFinal}`);
console.log(`Novo cashback: R$ ${resultado.cashbackCreditado}`);
Comprovante

Exiba cashbackUsado como linha de desconto e valorFinal como total no comprovante. Use valorVenda para o subtotal antes do desconto.

Cenário 6: Desconto sobre cashback gerado (Modelo 3)

O PDV oferece ao cliente um desconto imediato igual ao cashback que esta venda geraria — sem consumir saldo acumulado.

Pré-requisito

A campanha precisa ter permiteDescontoSobreGerado: true (retornado por GET /api/v1/configuracao). Verifique antes de exibir o botão.

Passo 1 — Simular o desconto antes de confirmar

Chame POST /api/v1/venda/preview para calcular o valor e decidir se exibe a opção ao cliente:

const preview = await eloFielRequest('POST', '/api/v1/venda/preview', {
cnpjLoja: CNPJ_LOJA,
cpfCliente: '123.456.789-09',
valorVenda: 100.00,
});

if (!preview.permiteAplicarDesconto) {
// Exibir preview.motivoBloqueio ao operador e prosseguir sem desconto
console.log(`Desconto indisponível: ${preview.motivoBloqueio}`);
} else {
// Exibir diálogo de confirmação:
// "Desconto de R$ X.XX — Total a pagar: R$ Y.YY. Confirmar?"
console.log(`Desconto disponível: R$ ${preview.cashbackGeradoPrevisto}`);
console.log(`Total com desconto: R$ ${preview.valorComDesconto}`);
}

Passo 2 — Registrar a venda com usarCashbackGerado: true

{
"cnpjLoja": "12.345.678/0001-90",
"cpfCliente": "123.456.789-09",
"valorVenda": 100.00,
"documentoExterno": "PDV-009900",
"usarCashbackGerado": true
}

Response (campanha 10%, desconto total permitido):

{
"dados": {
"valorVenda": 100.00,
"descontoGerado": 10.00,
"cashbackUsado": 0.00,
"valorFinal": 90.00,
"cashbackCreditado": 0.00
}
}

O cliente pagou R$ 90,00 e o cashback gerado foi usado como desconto imediato — cashbackCreditado é 0 porque o benefício foi convertido em desconto.

// Fluxo completo Modelo 3
const config = await eloFielRequest('GET', `/api/v1/configuracao?cnpjLoja=${CNPJ_LOJA}`);

if (config.permiteDescontoSobreGerado) {
const preview = await eloFielRequest('POST', '/api/v1/venda/preview', {
cnpjLoja: CNPJ_LOJA,
cpfCliente: cpfCliente,
valorVenda: valorVenda,
});

const clienteAceitou = preview.permiteAplicarDesconto
&& await confirmarComCliente(preview.cashbackGeradoPrevisto, preview.valorComDesconto);

const resultado = await eloFielRequest('POST', '/api/v1/venda', {
cnpjLoja: CNPJ_LOJA,
cpfCliente: cpfCliente,
valorVenda: valorVenda,
usarCashbackGerado: clienteAceitou,
});

if (clienteAceitou) {
console.log(`Desconto aplicado: R$ ${resultado.descontoGerado}`);
}
console.log(`Total pago: R$ ${resultado.valorFinal}`);
}
Modelo 3 funciona na primeira compra

Se o CPF não existir ainda, envie o objeto cliente junto com usarCashbackGerado: true. O auto-cadastro e o desconto imediato ocorrem na mesma chamada.

Exclusividade mútua

Não envie usarCashbackDisponivel: true e usarCashbackGerado: true na mesma requisição. A API retorna 422.

Cenário 4: Cliente novo (CPF ainda não cadastrado)

Se o CPF não existir, o cliente é criado automaticamente. Para registrar o nome já na venda, envie o objeto cliente:

{
"cnpjLoja": "12.345.678/0001-90",
"cpfCliente": "111.222.333-44",
"valorVenda": 200.00,
"cliente": {
"nome": "João Pereira",
"email": "joao@email.com"
}
}

Resposta:

{
"clienteId": "novo-uuid-gerado-aqui",
"clienteNome": "João Pereira",
"cashbackCreditado": 10.00
}

Se cliente não for enviado, clienteNome retorna null e o cadastro pode ser completado pelo próprio consumidor no app do Clube.

Auto-cadastro de cliente na primeira venda

Se o CPF não existir no sistema e você quiser cadastrar o cliente na mesma chamada da venda, envie o objeto opcional cliente:

{
"cnpjLoja": "12.345.678/0001-90",
"cpfCliente": "123.456.789-09",
"valorVenda": 150.00,
"documentoExterno": "PDV-001234",
"cliente": {
"nome": "Maria Silva",
"email": "maria@email.com",
"telefone": "(44) 99999-0000"
}
}
CampoTipoObrigatórioDescrição
cliente.nomestringSim (se cliente presente)Nome completo (mín. 2, máx. 150 chars)
cliente.emailstringNãoE-mail do cliente
cliente.telefonestringNãoTelefone do cliente

Comportamentos:

  • Se o CPF já existir, o objeto cliente é ignorado — o cadastro existente é mantido.
  • Se o CPF não existir e cliente não for enviado, o cliente é criado sem nome (pode ser atualizado pelo próprio consumidor no aplicativo do Clube).
  • Se o CPF não existir e cliente.nome estiver em branco, a venda retorna HTTP 400.
Recomendação

Colete nome e e-mail no terminal do PDV na primeira compra do cliente. Além de melhorar a experiência, o e-mail permite envio do PIN de acesso ao Clube.

Tratamento de erros

HTTPSituaçãoerros[0]
400Campo obrigatório ausente"O campo CnpjLoja é obrigatório."
400CPF inválido"CPF informado é inválido."
401X-Api-Key ausente ou inválida"Não autorizado."
500Erro interno"Erro interno do servidor."
if (!resultado.sucesso) {
const erro = resultado.erros[0] ?? resultado.mensagem;
console.error('Erro ao registrar venda:', erro);
// Não bloquear o PDV — registrar localmente e tentar novamente depois
}
Não bloqueie o PDV em caso de falha

Se a API retornar erro ou timeout, a venda já foi processada no seu caixa. Registre a falha localmente e implemente uma fila de reprocessamento para não prejudicar o atendimento.