Skip to content

Estornar Pagamento

Estorna (devolve) o valor de um pagamento já confirmado.

IMPORTANTE

O estorno só é possível após o pagamento ser confirmado. Para cancelar antes do pagamento, use Cancelar.

PUT /api/charge/{chargeId}/pix/refund

Estorna um pagamento Pix.

Endpoint

PUT https://api.divipay.com.br/api/charge/{chargeId}/pix/refund

Headers

HeaderValorObrigatório
AuthorizationBearerSim

Path Parameters

ParâmetroTipoDescrição
chargeIdstringID da cobrança

Query Parameters

ParâmetroTipoDescriçãoObrigatório
amountstringValor a estornar (parcial ou total)Não
main_refundstring"true" ou "false" - Enviar para conta principalNão

Exemplo de Requisição

bash
curl -X PUT "https://api.divipay.com.br/api/charge/cob_abc123/pix/refund" \
  -H "Authorization: Bearer SEU_TOKEN"
bash
curl -X PUT "https://api.divipay.com.br/api/charge/cob_abc123/pix/refund?amount=50.00" \
  -H "Authorization: Bearer SEU_TOKEN"
bash
curl -X PUT "https://api.divipay.com.br/api/charge/cob_abc123/pix/refund?main_refund=true" \
  -H "Authorization: Bearer SEU_TOKEN"
javascript
// Estorno total
const response = await fetch(
  'https://api.divipay.com.br/api/charge/cob_abc123/pix/refund',
  {
    method: 'PUT',
    headers: {
      'Authorization': 'Bearer SEU_TOKEN'
    }
  }
);

// Estorno parcial
const response = await fetch(
  'https://api.divipay.com.br/api/charge/cob_abc123/pix/refund?amount=50.00',
  {
    method: 'PUT',
    headers: {
      'Authorization': 'Bearer SEU_TOKEN'
    }
  }
);

const result = await response.json();
python
import requests

# Estorno total
response = requests.put(
    'https://api.divipay.com.br/api/charge/cob_abc123/pix/refund',
    headers={
        'Authorization': 'Bearer SEU_TOKEN'
    }
)

# Estorno parcial
response = requests.put(
    'https://api.divipay.com.br/api/charge/cob_abc123/pix/refund',
    params={'amount': '50.00'},
    headers={
        'Authorization': 'Bearer SEU_TOKEN'
    }
)

result = response.json()

Resposta de Sucesso

Status: 200 OK

json
{
  "id": "cob_abc123",
  "status": "REFUNDED",
  "refundedAmount": 100.00,
  "refundedAt": "2024-11-04T16:00:00.000Z",
  "message": "Refund processed successfully"
}

PUT /api/charge/{chargeId}/credit-card/refund

Estorna um pagamento de Cartão de Crédito.

Endpoint

PUT https://api.divipay.com.br/api/charge/{chargeId}/credit-card/refund

Body Parameters

CampoTipoDescriçãoObrigatório
amountnumberValor a estornarSim

Exemplo de Requisição

bash
curl -X PUT https://api.divipay.com.br/api/charge/cc_abc123/credit-card/refund \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 100.00
  }'

Resposta

json
{
  "id": "cc_abc123",
  "status": "REFUNDED",
  "ok": true,
  "message": "Refund processed",
  "amountRefunded": 100.00
}

Tipos de Estorno

1. Estorno Total

Devolve o valor completo da transação:

javascript
await fetch(
  `https://api.divipay.com.br/api/charge/${chargeId}/pix/refund`,
  {
    method: 'PUT',
    headers: { 'Authorization': `Bearer ${token}` }
  }
);

2. Estorno Parcial

Devolve apenas parte do valor:

javascript
await fetch(
  `https://api.divipay.com.br/api/charge/${chargeId}/pix/refund?amount=50.00`,
  {
    method: 'PUT',
    headers: { 'Authorization': `Bearer ${token}` }
  }
);

3. Estorno para Conta Principal

Envia o valor para a conta principal (taxas aplicadas novamente):

javascript
await fetch(
  `https://api.divipay.com.br/api/charge/${chargeId}/pix/refund?main_refund=true`,
  {
    method: 'PUT',
    headers: { 'Authorization': `Bearer ${token}` }
  }
);

Exemplo de Uso

Estornar Pagamento

javascript
async function refundCharge(chargeId, amount = null) {
  try {
    let url = `https://api.divipay.com.br/api/charge/${chargeId}/pix/refund`;
    
    if (amount) {
      url += `?amount=${amount}`;
    }

    const response = await fetch(url, {
      method: 'PUT',
      headers: {
        'Authorization': `Bearer ${token}`
      }
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.message);
    }

    const result = await response.json();
    console.log('✅ Estorno processado:', result);
    return result;

  } catch (error) {
    console.error('❌ Erro ao estornar:', error.message);
    throw error;
  }
}

// Estorno total
await refundCharge('cob_abc123');

// Estorno parcial
await refundCharge('cob_abc123', 50.00);

Estornar com Verificação

javascript
async function safeRefund(chargeId, amount = null) {
  try {
    // 1. Verificar status
    const charge = await fetch(
      `https://api.divipay.com.br/api/charge/${chargeId}`,
      {
        headers: { 'Authorization': `Bearer ${token}` }
      }
    ).then(r => r.json());

    // 2. Validar se pode estornar
    if (charge.status !== 'PAID') {
      throw new Error('Apenas cobranças pagas podem ser estornadas');
    }

    // 3. Validar valor
    if (amount && amount > charge.amount) {
      throw new Error('Valor do estorno maior que o valor da cobrança');
    }

    // 4. Processar estorno
    const result = await refundCharge(chargeId, amount);
    
    console.log(`✅ Estornado: R$ ${result.refundedAmount}`);
    return result;

  } catch (error) {
    console.error('❌ Erro:', error.message);
    throw error;
  }
}

Estornar Pedido Completo

javascript
async function refundOrder(orderId, reason) {
  try {
    // 1. Buscar pedido
    const order = await db.orders.findUnique({
      where: { id: orderId },
      include: { charge: true }
    });

    if (!order.charge) {
      throw new Error('Pedido sem cobrança associada');
    }

    // 2. Estornar na DiviPay
    const result = await refundCharge(order.charge.chargeId);

    // 3. Atualizar banco de dados
    await db.orders.update({
      where: { id: orderId },
      data: {
        status: 'REFUNDED',
        refundedAt: new Date(),
        refundReason: reason
      }
    });

    // 4. Notificar cliente
    await sendRefundEmail(order.customer.email, {
      orderId,
      amount: result.refundedAmount,
      reason
    });

    console.log(`✅ Pedido ${orderId} estornado`);
    return result;

  } catch (error) {
    console.error('❌ Erro ao estornar pedido:', error);
    throw error;
  }
}

// Uso
await refundOrder('order-123', 'Produto com defeito');

Estorno Parcial Múltiplo

javascript
async function partialRefunds(chargeId, refunds) {
  const results = [];
  let totalRefunded = 0;

  for (const refund of refunds) {
    try {
      const result = await refundCharge(chargeId, refund.amount);
      
      results.push({
        success: true,
        amount: refund.amount,
        reason: refund.reason
      });

      totalRefunded += refund.amount;

    } catch (error) {
      results.push({
        success: false,
        amount: refund.amount,
        error: error.message
      });
    }
  }

  return {
    totalRefunded,
    results
  };
}

// Uso - Estornar em partes
const results = await partialRefunds('cob_abc123', [
  { amount: 30.00, reason: 'Item 1 devolvido' },
  { amount: 20.00, reason: 'Item 2 devolvido' }
]);

Interface de Estorno

javascript
// React Component
function RefundButton({ chargeId, maxAmount, onRefund }) {
  const [amount, setAmount] = useState(maxAmount);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  async function handleRefund() {
    const reason = prompt('Motivo do estorno:');
    if (!reason) return;

    if (!confirm(`Estornar R$ ${amount.toFixed(2)}?`)) {
      return;
    }

    setLoading(true);
    setError(null);

    try {
      await refundCharge(chargeId, amount);
      alert('Estorno processado com sucesso!');
      onRefund?.();
    } catch (err) {
      setError(err.message);
      alert('Erro ao estornar: ' + err.message);
    } finally {
      setLoading(false);
    }
  }

  return (
    <div>
      <label>
        Valor do estorno:
        <input
          type="number"
          value={amount}
          onChange={(e) => setAmount(parseFloat(e.target.value))}
          max={maxAmount}
          step="0.01"
          disabled={loading}
        />
      </label>
      
      <button 
        onClick={handleRefund} 
        disabled={loading || amount <= 0 || amount > maxAmount}
        style={{ 
          backgroundColor: 'orange', 
          color: 'white',
          padding: '10px 20px',
          border: 'none',
          borderRadius: '5px',
          cursor: loading ? 'not-allowed' : 'pointer'
        }}
      >
        {loading ? 'Processando...' : `Estornar R$ ${amount.toFixed(2)}`}
      </button>
      
      {error && <p style={{ color: 'red' }}>{error}</p>}
    </div>
  );
}

Webhook

Quando um estorno é processado:

json
{
  "event": "charge.refunded",
  "chargeId": "cob_abc123",
  "referenceId": "pedido-123",
  "amount": 100.00,
  "refundedAmount": 100.00,
  "refundedAt": "2024-11-04T16:00:00.000Z",
  "status": "REFUNDED"
}

Respostas de Erro

412 Precondition Failed

Cobrança não foi paga ainda.

json
{
  "statusCode": 412,
  "message": "Cannot refund unpaid charge",
  "error": "Precondition Failed"
}

400 Bad Request

Valor inválido.

json
{
  "statusCode": 400,
  "message": "Refund amount exceeds charge amount",
  "error": "Bad Request"
}

404 Not Found

Cobrança não encontrada.

json
{
  "statusCode": 404,
  "message": "Charge not found",
  "error": "Not Found"
}

Casos de Uso

1. Produto com Defeito

javascript
async function refundDefectiveProduct(orderId) {
  await refundOrder(orderId, 'Produto com defeito');
  
  await db.returns.create({
    data: {
      orderId,
      reason: 'DEFECTIVE',
      status: 'APPROVED',
      refundedAt: new Date()
    }
  });
}

2. Arrependimento do Cliente

javascript
async function refundCustomerRegret(orderId) {
  const order = await db.orders.findUnique({
    where: { id: orderId }
  });

  // Verificar prazo de 7 dias
  const daysSincePurchase = Math.floor(
    (Date.now() - order.paidAt.getTime()) / (1000 * 60 * 60 * 24)
  );

  if (daysSincePurchase > 7) {
    throw new Error('Prazo de arrependimento expirado');
  }

  await refundOrder(orderId, 'Direito de arrependimento');
}

3. Cobrança Duplicada

javascript
async function refundDuplicate(chargeId) {
  await refundCharge(chargeId);
  
  await db.duplicates.create({
    data: {
      chargeId,
      detectedAt: new Date(),
      refundedAt: new Date()
    }
  });
}

Boas Práticas

1. Sempre Registrar Motivo

javascript
await db.refunds.create({
  data: {
    chargeId,
    amount: refundedAmount,
    reason: 'Produto com defeito',
    requestedBy: userId,
    processedAt: new Date()
  }
});

2. Notificar Cliente

javascript
async function refundWithNotification(chargeId, amount, reason) {
  const result = await refundCharge(chargeId, amount);
  
  await sendEmail({
    to: customer.email,
    subject: 'Estorno Processado',
    body: `
      Seu estorno foi processado com sucesso!
      Valor: R$ ${amount.toFixed(2)}
      Motivo: ${reason}
      O valor será creditado em até 5 dias úteis.
    `
  });
}

3. Validar Permissões

javascript
async function authorizedRefund(chargeId, userId) {
  const user = await db.users.findUnique({
    where: { id: userId }
  });

  if (!user.canRefund) {
    throw new Error('Usuário sem permissão para estornar');
  }

  return await refundCharge(chargeId);
}

4. Auditoria

javascript
await db.auditLog.create({
  data: {
    action: 'REFUND',
    chargeId,
    amount: refundedAmount,
    userId,
    reason,
    timestamp: new Date()
  }
});

Prazos

  • Pix: Estorno imediato
  • Cartão de Crédito: 5-10 dias úteis
  • Boleto: Não aplicável (use transferência manual)

Próximos Passos

Documentação da API DiviPay