PIX Refund-In (Devolución)
Descripción General
El endpoint de PIX Refund-In le permite revertir (devolver) pagos PIX recibidos a través de cobros generados vía Cash-In. Las devoluciones pueden ser totales o parciales y deben solicitarse dentro de 89 días desde la recepción.
Este endpoint requiere un Bearer token válido. Consulte la documentación de autenticación para más detalles.
Características
- Devoluciones totales o parciales
- Múltiples devoluciones parciales para la misma transacción
- Ventana de hasta 89 días
- Procesamiento instantáneo
- Seguimiento por motivo de devolución
Cuándo Usar Devoluciones
Devolución Total
Retorna el 100% del monto recibido al pagador original.
Casos de uso:
- Cancelación completa del pedido
- Producto no enviado
- Pago duplicado
- Monto de cobro incorrecto
Devolución Parcial
Retorna solo una parte del monto recibido.
Casos de uso:
- Devolución de artículos específicos
- Compensación por problemas con producto/servicio
- Ajustes de valor
- Descuento retroactivo
Endpoint
POST /api/pix/refund-in/{id}
Solicita la devolución de un pago recibido.
Encabezados Requeridos
Authorization: Bearer {token}
Content-Type: application/jsonParámetros de Ruta
idstringobrigatorioID de la transacción original (Cash-In) a devolver.
Ejemplo: "7845"
Cuerpo de la Solicitud
{
"refundValue": 75.00,
"reason": "Cliente solicitou devolução de 1 item do pedido"
}Solicitud
curl -X POST https://api.safirapay.com/api/pix/refund-in/7845 \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"refundValue": 75.00,
"reason": "Cliente solicitou devolução de 1 item do pedido"
}'Respuesta (201 Created)
{
"transactionId": "7846",
"externalId": "D123456789",
"status": "PENDING",
"refundValue": 75.00,
"providerTransactionId": "7ef4fc3f-a187-495e-857c-e84d70612761",
"generateTime": "2024-01-19T16:30:00.000Z"
}Parámetros de la Solicitud
refundValuenumberobrigatorioMonto a devolver en BRL (Reales brasileños). Debe tener como máximo 2 decimales.
Validaciones:
- Debe ser mayor o igual a 0.01
- No puede exceder el monto disponible para devolución
- La suma de todas las devoluciones no puede exceder el monto original
Ejemplo: 75.00
reasonstringMotivo de la devolución (opcional, pero recomendado).
Máximo: 255 caracteres
Ejemplo: "Cliente solicitou devolução de 1 item do pedido"
Recomendación: Siempre proporcione un motivo claro para fines de auditoría
externalIdstringID externo para identificación de la devolución (opcional).
En la API del BACEN, corresponde al parámetro 'id' de la URL.
Ejemplo: "D123456789"
Estructura de la Respuesta
transactionIdstringsempre presenteID de la nueva transacción de devolución generada.
Ejemplo: "7846"
Nota: Este es un ID diferente al de la transacción original
externalIdstringsempre presenteID externo de la transacción de devolución.
Ejemplo: "D123456789"
statusstringsempre presenteEstado actual de la transacción de devolución.
Valores posibles:
PENDING: Devolución en procesoCONFIRMED: Devolución confirmada y completadaERROR: Error de procesamiento
Ejemplo: "PENDING"
refundValuenumbersempre presenteMonto de la devolución en BRL.
Ejemplo: 75.00
providerTransactionIdstringsempre presenteID de la transacción en el proveedor (utilizado para correlación con webhooks).
Ejemplo: "7ef4fc3f-a187-495e-857c-e84d70612761"
generateTimestringsempre presenteFecha y hora de generación de la devolución (ISO 8601 UTC).
Ejemplo: "2024-01-19T16:30:00.000Z"
Ejemplos de Implementación
Node.js / TypeScript
import axios from 'axios';
interface RefundRequest {
refundValue: number;
reason?: string;
externalId?: string;
}
interface RefundResponse {
transactionId: string;
externalId: string;
status: 'PENDING' | 'CONFIRMED' | 'ERROR';
refundValue: number;
providerTransactionId: string;
generateTime: string;
}
async function refundPixPayment(
token: string,
originalTransactionId: string,
refundAmount: number,
reason?: string
): Promise<RefundResponse> {
const payload: RefundRequest = {
refundValue: refundAmount,
reason: reason || 'Estorno solicitado pelo cliente'
};
try {
const response = await axios.post<RefundResponse>(
`https://api.safirapay.com/api/pix/refund-in/${originalTransactionId}`,
payload,
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
);
console.log('PIX refund initiated successfully!');
console.log(`Refund Transaction ID: ${response.data.transactionId}`);
console.log(`Original External ID: ${response.data.externalId}`);
console.log(`Refund Amount: R$ ${response.data.refundValue.toFixed(2)}`);
console.log(`Status: ${response.data.status}`);
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
const errorData = error.response?.data;
console.error('Error processing refund:', errorData);
// Handle specific errors
if (error.response?.status === 400) {
if (errorData?.message?.includes('prazo excedido')) {
throw new Error('The 89-day refund window has expired');
}
if (errorData?.message?.includes('valor inválido')) {
throw new Error('Refund amount exceeds the available amount for refund');
}
}
if (error.response?.status === 404) {
throw new Error('Original transaction not found');
}
throw new Error(errorData?.message || 'Error processing refund');
}
throw error;
}
}
// Usage - Full Refund
async function fullRefund(token: string, transactionId: string, originalValue: number) {
return await refundPixPayment(
token,
transactionId,
originalValue,
'Cancelamento total do pedido'
);
}
// Usage - Partial Refund
async function partialRefund(token: string, transactionId: string, itemValue: number) {
return await refundPixPayment(
token,
transactionId,
itemValue,
'Devolução de 1 item do pedido'
);
}
// Practical example
const token = 'your_token_here';
const transactionId = '7845';
// Refund R$ 75.00 from a R$ 150.00 transaction
refundPixPayment(token, transactionId, 75.00, 'Cliente solicitou devolução parcial');Python
import requests
from datetime import datetime
from typing import Dict, Optional
def refund_pix_payment(
token: str,
original_transaction_id: str,
refund_amount: float,
reason: Optional[str] = None
) -> Dict:
"""
Refund a received PIX payment
Args:
token: Valid Bearer token
original_transaction_id: Original transaction ID (Cash-In)
refund_amount: Amount to be refunded
reason: Refund reason (optional)
Returns:
Created refund data
"""
url = f'https://api.safirapay.com/api/pix/refund-in/{original_transaction_id}'
payload = {
'refundValue': round(refund_amount, 2),
'reason': reason or 'Estorno solicitado pelo cliente'
}
headers = {
'Authorization': f'Bearer {token}',
'Content-Type': 'application/json'
}
try:
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
data = response.json()
print('PIX refund initiated successfully!')
print(f"Refund Transaction ID: {data['transactionId']}")
print(f"Original External ID: {data['externalId']}")
print(f"Refund Amount: R$ {data['refundValue']:.2f}")
print(f"Status: {data['status']}")
return data
except requests.exceptions.HTTPError as e:
error_data = e.response.json() if e.response else {}
error_message = error_data.get('message', str(e))
# Handle specific errors
if e.response.status_code == 400:
if 'prazo excedido' in error_message:
raise Exception('The 89-day refund window has expired')
if 'valor inválido' in error_message:
raise Exception('Refund amount exceeds the available amount for refund')
raise Exception(f'Invalid data: {error_message}')
if e.response.status_code == 404:
raise Exception('Original transaction not found')
raise Exception(f'Error processing refund: {error_message}')
# Usage
token = 'your_token_here'
transaction_id = '7845'
# Partial refund
refund = refund_pix_payment(
token=token,
original_transaction_id=transaction_id,
refund_amount=75.00,
reason='Cliente solicitou devolução de 1 item do pedido'
)
# Full refund
def full_refund(token: str, transaction_id: str, original_value: float):
"""Performs a full refund"""
return refund_pix_payment(
token=token,
original_transaction_id=transaction_id,
refund_amount=original_value,
reason='Cancelamento total do pedido'
)PHP
<?php
function refundPixPayment(
string $token,
string $originalTransactionId,
float $refundAmount,
?string $reason = null
): array {
$url = "https://api.safirapay.com/api/pix/refund-in/$originalTransactionId";
$payload = [
'refundValue' => round($refundAmount, 2),
'reason' => $reason ?? 'Estorno solicitado pelo cliente'
];
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $token,
'Content-Type: application/json'
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 201) {
$errorData = json_decode($response, true);
$errorMessage = $errorData['message'] ?? "HTTP $httpCode";
if ($httpCode === 400) {
if (stripos($errorMessage, 'prazo excedido') !== false) {
throw new Exception('The 89-day refund window has expired');
}
if (stripos($errorMessage, 'valor inválido') !== false) {
throw new Exception('Refund amount exceeds the available amount for refund');
}
}
if ($httpCode === 404) {
throw new Exception('Original transaction not found');
}
throw new Exception("Error processing refund: $errorMessage");
}
$data = json_decode($response, true);
echo "PIX refund initiated successfully!" . PHP_EOL;
echo "Refund Transaction ID: {$data['transactionId']}" . PHP_EOL;
echo "Original External ID: {$data['externalId']}" . PHP_EOL;
echo "Refund Amount: R$ " . number_format($data['refundValue'], 2, ',', '.') . PHP_EOL;
echo "Status: {$data['status']}" . PHP_EOL;
return $data;
}
// Usage
$token = 'your_token_here';
$transactionId = '7845';
// Partial refund
$refund = refundPixPayment(
$token,
$transactionId,
75.00,
'Cliente solicitou devolução de 1 item do pedido'
);Códigos de Respuesta
| Código | Descripción | Significado |
|---|---|---|
201 | Devolución Creada | Devolución PIX iniciada exitosamente |
400 | Monto Inválido | El monto de la devolución excede el monto disponible |
400 | Ventana Excedida | La ventana de 89 días para devolución ha expirado |
401 | Token Inválido | Token no proporcionado, expirado o inválido |
404 | Transacción No Encontrada | Transacción padre no encontrada |
Consulte la Referencia de la API para detalles completos de los campos de respuesta.
Mejores Prácticas
Notas Importantes
Las devoluciones no pueden cancelarse una vez iniciadas. Asegúrese de que los montos sean correctos antes de procesar.
- Ventana máxima: 89 días después de la recepción
- Monto mínimo: R$ 0.01
- Múltiples devoluciones: Permitidas, siempre que la suma no exceda el monto original