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
}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
cnpjLoja | string | Sim | CNPJ do estabelecimento |
cpfCliente | string | Sim | CPF do cliente |
valorVenda | number | Sim | Valor bruto da venda em R$ |
documentoExterno | string | Não | Número do cupom fiscal ou ID no PDV |
observacao | string | Não | Observação livre |
usarCashbackDisponivel | boolean | Não | Modelo 2 — true = aplica o saldo de cashback acumulado como desconto (padrão false). Veja Cenário 5 |
usarCashbackGerado | boolean | Não | Modelo 3 — true = 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 |
cliente | object | Não | Dados para auto-cadastro se o CPF não existir |
usarCashbackDisponivel e usarCashbackGerado não podem ser true simultaneamente. A API retorna 422 se ambos forem enviados como true.
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
- JavaScript
- PHP
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
}'
async function registrarVenda(dadosVenda) {
const response = await fetch('https://api-sandbox.elofiel.com.br/api/v1/venda', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Api-Key': process.env.ELOFIEL_API_KEY,
},
body: JSON.stringify(dadosVenda),
});
if (!response.ok) {
throw new Error(`Erro HTTP: ${response.status}`);
}
return response.json();
}
const resultado = await registrarVenda({
cnpjLoja: '12.345.678/0001-90',
cpfCliente: '123.456.789-09',
valorVenda: 150.00,
documentoExterno: 'PDV-001234',
observacao: null,
});
<?php
function registrarVenda(array $dados): array {
$ch = curl_init('https://api-sandbox.elofiel.com.br/api/v1/venda');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($dados),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-Api-Key: ' . getenv('ELOFIEL_API_KEY'),
],
]);
$resposta = curl_exec($ch);
curl_close($ch);
return json_decode($resposta, true);
}
$resultado = registrarVenda([
'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": []
}
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 resposta | Descrição |
|---|---|
cashbackUsado | Modelo 2 — cashback do saldo anterior descontado nesta venda. 0 quando usarCashbackDisponivel: false |
descontoGerado | Modelo 3 — cashback gerado por esta venda e aplicado como desconto imediato. 0 quando usarCashbackGerado: false |
valorFinal | Valor efetivamente pago após todos os descontos (valorVenda − cashbackUsado − descontoGerado). Use este campo no comprovante |
cashbackCreditado | Novo 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"
}
]
}
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.
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}`);
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.
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}`);
}
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.
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"
}
}
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
cliente.nome | string | Sim (se cliente presente) | Nome completo (mín. 2, máx. 150 chars) |
cliente.email | string | Não | E-mail do cliente |
cliente.telefone | string | Não | Telefone do cliente |
Comportamentos:
- Se o CPF já existir, o objeto
clienteé ignorado — o cadastro existente é mantido. - Se o CPF não existir e
clientenã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.nomeestiver em branco, a venda retorna HTTP 400.
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
| HTTP | Situação | erros[0] |
|---|---|---|
| 400 | Campo obrigatório ausente | "O campo CnpjLoja é obrigatório." |
| 400 | CPF inválido | "CPF informado é inválido." |
| 401 | X-Api-Key ausente ou inválida | "Não autorizado." |
| 500 | Erro 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
}
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.