Visão Geral
O estorno PIX permite devolver valores pagos via PIX de forma total ou parcial. Este guia explica como implementar estornos PIX em sua aplicação usando a API EconPay.
Importante: Estornos PIX são processados através da Firebank e seguem as regras estabelecidas pelo Banco Central do Brasil.
Quando Usar Estorno PIX
O estorno PIX é ideal para:
Cancelamento de Compras: Cliente desiste da compra após pagamento
Devolução de Produtos: Produto com defeito ou não entregue
Ajuste de Valores: Cobrança incorreta ou desconto posterior
Reembolso Parcial: Devolução de apenas parte do valor pago
Requisitos
Para processar um estorno PIX, a transação deve atender aos seguintes requisitos:
A transação deve estar com status PAID (paga). Transações pendentes, canceladas ou já estornadas não podem ser estornadas. // Verificar status antes de estornar
const transaction = await getTransaction ( transactionId );
if ( transaction . status !== 'PAID' ) {
console . error ( 'Transação não pode ser estornada' );
return ;
}
O estorno deve ser solicitado em até 90 dias após o pagamento original. // Verificar prazo
const paymentDate = new Date ( transaction . created_at );
const today = new Date ();
const daysDiff = Math . floor (( today - paymentDate ) / ( 1000 * 60 * 60 * 24 ));
if ( daysDiff > 90 ) {
console . error ( 'Prazo de estorno expirado' );
return ;
}
Sua conta deve ter saldo suficiente para processar o estorno. O valor será debitado da sua conta e creditado na conta do pagador.
O valor do estorno (ou soma de estornos parciais) não pode exceder o valor original da transação. // Validar valor do estorno
if ( refundAmount > transaction . amount ) {
console . error ( 'Valor do estorno excede valor original' );
return ;
}
Tipos de Estorno
Estorno Total
Devolve 100% do valor da transação PIX.
// Estorno total - não enviar campo amount
const refund = await fetch ( 'https://api.econpay.com.br/payments/pix/refund' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
transaction_id: 123 ,
description: 'Cancelamento da compra'
})
});
const result = await refund . json ();
console . log ( 'Estorno total processado:' , result );
Estorno Parcial
Devolve apenas parte do valor da transação PIX.
// Estorno parcial - enviar campo amount
const refund = await fetch ( 'https://api.econpay.com.br/payments/pix/refund' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
transaction_id: 124 ,
amount: 50.00 , // R$ 50,00
description: 'Devolução parcial - 1 item'
})
});
const result = await refund . json ();
console . log ( 'Estorno parcial processado:' , result );
Múltiplos Estornos Parciais: Você pode fazer vários estornos parciais, mas a soma de todos não pode exceder o valor original da transação.
Fluxo Completo
Buscar Transação
Primeiro, busque a transação para verificar se ela pode ser estornada. const transaction = await fetch (
`https://api.econpay.com.br/transactions/ ${ transactionId } ` ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const data = await transaction . json ();
Validar Elegibilidade
Verifique se a transação atende aos requisitos para estorno. function canRefund ( transaction ) {
// Verificar status
if ( transaction . status !== 'PAID' ) {
return { can: false , reason: 'Status inválido' };
}
// Verificar tipo
if ( transaction . type !== 'pix' ) {
return { can: false , reason: 'Não é transação PIX' };
}
// Verificar prazo
const paymentDate = new Date ( transaction . created_at );
const today = new Date ();
const daysDiff = Math . floor (( today - paymentDate ) / ( 1000 * 60 * 60 * 24 ));
if ( daysDiff > 90 ) {
return { can: false , reason: 'Prazo expirado' };
}
return { can: true };
}
const eligibility = canRefund ( data . transaction );
if ( ! eligibility . can ) {
console . error ( 'Não pode estornar:' , eligibility . reason );
return ;
}
Processar Estorno
Envie a requisição de estorno para a API. const refund = await fetch ( 'https://api.econpay.com.br/payments/pix/refund' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
transaction_id: transactionId ,
amount: refundAmount , // Opcional
description: 'Motivo do estorno'
})
});
const result = await refund . json ();
Tratar Resposta
Verifique o resultado e atualize sua aplicação. if ( result . success ) {
console . log ( 'Estorno processado com sucesso!' );
console . log ( 'Transaction ID:' , result . transaction . id );
console . log ( 'Valor estornado:' , result . transaction . refunded_amount / 100 );
console . log ( 'Data do estorno:' , result . transaction . refunded_at );
// Atualizar interface do usuário
updateUI ({
status: 'REFUNDED' ,
message: 'Estorno processado com sucesso'
});
} else {
console . error ( 'Erro ao processar estorno:' , result . error );
// Mostrar mensagem de erro ao usuário
showError ( result . error );
}
Exemplos Práticos
Exemplo 1: Cancelamento de Compra
async function cancelarCompra ( transactionId ) {
try {
// 1. Buscar transação
const transactionResponse = await fetch (
`https://api.econpay.com.br/transactions/ ${ transactionId } ` ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const { transaction } = await transactionResponse . json ();
// 2. Validar se pode estornar
if ( transaction . status !== 'PAID' ) {
throw new Error ( 'Transação não está paga' );
}
if ( transaction . type !== 'pix' ) {
throw new Error ( 'Transação não é PIX' );
}
// 3. Processar estorno total
const refundResponse = await fetch (
'https://api.econpay.com.br/payments/pix/refund' ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
transaction_id: transactionId ,
description: 'Cancelamento da compra pelo cliente'
})
}
);
const refund = await refundResponse . json ();
if ( refund . success ) {
console . log ( 'Compra cancelada com sucesso!' );
console . log ( 'Valor estornado: R$' , refund . transaction . refunded_amount / 100 );
return refund ;
} else {
throw new Error ( refund . error );
}
} catch ( error ) {
console . error ( 'Erro ao cancelar compra:' , error . message );
throw error ;
}
}
// Usar
cancelarCompra ( 123 )
. then ( refund => {
alert ( 'Compra cancelada! Valor será creditado em instantes.' );
})
. catch ( error => {
alert ( 'Erro ao cancelar: ' + error . message );
});
Exemplo 2: Devolução Parcial
async function devolverProduto ( transactionId , valorProduto , nomeProduto ) {
try {
// Processar estorno parcial
const refundResponse = await fetch (
'https://api.econpay.com.br/payments/pix/refund' ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
transaction_id: transactionId ,
amount: valorProduto ,
description: `Devolução do produto: ${ nomeProduto } `
})
}
);
const refund = await refundResponse . json ();
if ( refund . success ) {
console . log ( 'Produto devolvido com sucesso!' );
console . log ( 'Valor estornado: R$' , refund . transaction . refunded_amount / 100 );
// Registrar devolução no sistema
await registrarDevolucao ({
transactionId ,
produto: nomeProduto ,
valor: valorProduto ,
data: refund . transaction . refunded_at
});
return refund ;
} else {
throw new Error ( refund . error );
}
} catch ( error ) {
console . error ( 'Erro ao devolver produto:' , error . message );
throw error ;
}
}
// Usar
devolverProduto ( 124 , 50.00 , 'Camiseta Azul' )
. then ( refund => {
alert ( 'Produto devolvido! R$ 50,00 será creditado.' );
})
. catch ( error => {
alert ( 'Erro na devolução: ' + error . message );
});
Exemplo 3: Múltiplos Estornos Parciais
async function devolverMultiplosProdutos ( transactionId , produtos ) {
const resultados = [];
for ( const produto of produtos ) {
try {
const refund = await fetch (
'https://api.econpay.com.br/payments/pix/refund' ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
transaction_id: transactionId ,
amount: produto . valor ,
description: `Devolução: ${ produto . nome } `
})
}
);
const result = await refund . json ();
if ( result . success ) {
resultados . push ({
produto: produto . nome ,
valor: produto . valor ,
sucesso: true
});
} else {
resultados . push ({
produto: produto . nome ,
valor: produto . valor ,
sucesso: false ,
erro: result . error
});
}
} catch ( error ) {
resultados . push ({
produto: produto . nome ,
valor: produto . valor ,
sucesso: false ,
erro: error . message
});
}
}
return resultados ;
}
// Usar
const produtos = [
{ nome: 'Camiseta' , valor: 30.00 },
{ nome: 'Calça' , valor: 50.00 }
];
devolverMultiplosProdutos ( 125 , produtos )
. then ( resultados => {
const sucesso = resultados . filter ( r => r . sucesso );
const falha = resultados . filter ( r => ! r . sucesso );
console . log ( ` ${ sucesso . length } produtos devolvidos com sucesso` );
console . log ( ` ${ falha . length } produtos com erro` );
if ( falha . length > 0 ) {
console . error ( 'Erros:' , falha );
}
});
Tratamento de Erros
Erros Comuns
Erro: TRANSACTION_NOT_FOUNDCausa: ID da transação inválido ou transação não existeSolução: if ( error . code === 'TRANSACTION_NOT_FOUND' ) {
console . error ( 'Transação não encontrada. Verifique o ID.' );
// Solicitar ID correto ao usuário
}
Erro: TRANSACTION_NOT_PIXCausa: Tentativa de estornar transação que não é PIXSolução: if ( error . code === 'TRANSACTION_NOT_PIX' ) {
console . error ( 'Use /payments/reversal para outros tipos de pagamento' );
// Redirecionar para endpoint correto
}
Erro: REFUND_PERIOD_EXPIREDCausa: Mais de 90 dias desde o pagamentoSolução: if ( error . code === 'REFUND_PERIOD_EXPIRED' ) {
console . error ( 'Prazo de estorno expirado (90 dias)' );
// Informar usuário sobre prazo expirado
// Sugerir contato com suporte para casos especiais
}
Erro: INSUFFICIENT_BALANCECausa: Conta sem saldo para processar estornoSolução: if ( error . code === 'INSUFFICIENT_BALANCE' ) {
console . error ( 'Saldo insuficiente para estorno' );
// Solicitar recarga de saldo
// Ou aguardar entrada de novos pagamentos
}
Erro: REFUND_VALUE_EXCEEDS_ORIGINALCausa: Valor do estorno maior que valor originalSolução: if ( error . code === 'REFUND_VALUE_EXCEEDS_ORIGINAL' ) {
console . error ( 'Valor do estorno excede valor original' );
// Ajustar valor do estorno
// Verificar se já houve estornos parciais anteriores
}
Exemplo de Tratamento Completo
async function processarEstornoComTratamento ( transactionId , amount , description ) {
try {
const response = await fetch ( 'https://api.econpay.com.br/payments/pix/refund' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
transaction_id: transactionId ,
amount ,
description
})
});
const result = await response . json ();
if ( ! response . ok ) {
// Tratar erros HTTP
switch ( response . status ) {
case 400 :
throw new Error ( `Dados inválidos: ${ result . error } ` );
case 404 :
throw new Error ( 'Transação não encontrada' );
case 500 :
throw new Error ( 'Erro no servidor. Tente novamente mais tarde.' );
default :
throw new Error ( `Erro desconhecido: ${ result . error } ` );
}
}
if ( ! result . success ) {
throw new Error ( result . error );
}
return result ;
} catch ( error ) {
console . error ( 'Erro ao processar estorno:' , error . message );
// Log para monitoramento
logError ({
type: 'PIX_REFUND_ERROR' ,
transactionId ,
amount ,
error: error . message ,
timestamp: new Date (). toISOString ()
});
throw error ;
}
}
Boas Práticas
Validar Antes de Estornar Sempre valide se a transação pode ser estornada antes de enviar a requisição
Informar o Cliente Notifique o cliente sobre o estorno e o prazo para crédito
Registrar Motivo Sempre inclua uma descrição clara do motivo do estorno
Monitorar Erros Implemente logs para rastrear estornos com erro
Próximos Passos