PIX Cash-Out vía QR Code
Descripción General
El endpoint PIX Cash-Out vía QR Code le permite realizar pagos PIX a partir de un QR Code escaneado o copiado (copiar y pegar). El QR Code debe seguir el estándar EMV del Banco Central de Brasil. Los datos del destinatario se extraen automáticamente del QR Code, lo que simplifica el proceso de pago.
Este endpoint requiere un token Bearer válido. Consulte la documentación de autenticación para más detalles.
Clave PIX vs QR Code: ¿Qué endpoint usar?
La API FyPay ofrece dos endpoints para enviar pagos PIX. Elija el más adecuado para su caso de uso:
| Criterio | Cash-Out por Clave PIX | Cash-Out vía QR Code |
|---|---|---|
| Endpoint | POST /api/pix/cash-out | POST /api/pix/cash-out-qrcode |
| Cuándo usar | Conoce la clave PIX del destinatario | Tiene el QR Code generado por el receptor |
| Datos del destinatario | Clave y tipo obligatorios; nombre y documento recomendados | Incluidos en el QR Code (opcionales en la solicitud) |
| Validación del valor | Solo saldo y límites | Saldo, límites + valor del QR Code vs valor informado |
| Tipos de clave | CPF, CNPJ, email, teléfono, aleatoria | N/A (información dentro del QR Code) |
| Webhook de confirmación | Evento CashOut | Mismo evento CashOut |
| Respuesta | Misma estructura | Misma estructura |
- Use Cash-Out por Clave cuando su aplicación ya tiene los datos del destinatario (ej.: nómina, transferencias programáticas)
- Use Cash-Out vía QR Code cuando el pago se inicia a partir de un QR Code escaneado (ej.: PDV, pago de facturas, copiar y pegar)
Ambos endpoints devuelven la misma estructura de respuesta y disparan el mismo webhook CashOut cuando se confirman.
Características
- Pago vía QR Code estático o dinámico
- Validación automática del valor incluido en el QR Code
- Verificación automática de saldo antes del envío
- Identificación única por
externalId(idempotencia) - Cálculo automático de tarifas
Endpoint
POST /api/pix/cash-out-qrcode
Realiza un pago PIX a partir de un QR Code.
Encabezados Requeridos
Authorization: Bearer {token}
Content-Type: application/jsonCuerpo de la Solicitud
{
"value": 15.50,
"qrCode": "00020126580014br.gov.bcb.pix0136a1b2c3d4-e5f6-7890-abcd-ef1234567890520400005303986540515.505802BR5925DESTINATARIO LTDA6009SAO PAULO62070503***6304ABCD",
"externalId": "QRPAY-987654-20240119",
"description": "Pago a proveedor XYZ vía QR Code",
"name": "Destinatario Ltda",
"document": "12345678000190"
}Solicitud
curl -X POST https://api.fypay.me/api/pix/cash-out-qrcode \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"value": 15.50,
"qrCode": "00020126580014br.gov.bcb.pix0136a1b2c3d4-e5f6-7890-abcd-ef1234567890520400005303986540515.505802BR5925DESTINATARIO LTDA6009SAO PAULO62070503***6304ABCD",
"externalId": "QRPAY-987654-20240119",
"description": "Pago a proveedor XYZ vía QR Code",
"name": "Destinatario Ltda",
"document": "12345678000190"
}'Respuesta (201 Created)
{
"transactionId": "456",
"externalId": "QRPAY-987654-20240119",
"status": "PENDING",
"generateTime": "2024-01-19T14:30:00.000Z"
}Parámetros de la Solicitud
valuenumberobrigatorioValor del pago en reales (BRL). Debe tener como máximo 2 decimales. Si el QR Code contiene un valor incluido, el valor informado debe corresponder (tolerancia de 1 centavo).
Mínimo: 0.01
Ejemplo: 15.50
qrCodestringobrigatorioContenido del QR Code PIX (string EMV). Puede obtenerse escaneando con la cámara o desde el campo copiar y pegar.
Mínimo: 50 caracteres
Máximo: 500 caracteres
Formato: Debe comenzar con 000201 (estándar EMV PIX del Banco Central)
Ejemplo: "00020126580014br.gov.bcb.pix0136a1b2c3d4-e5f6-7890-abcd-ef1234567890520400005303986540515.505802BR5925DESTINATARIO LTDA6009SAO PAULO62070503***6304ABCD"
externalIdstringobrigatorioIdentificador externo único de la transacción, por cuenta. Garantiza la idempotencia — enviar el mismo externalId dos veces resulta en error 409 con code PIX_DUPLICATE_EXTERNAL_ID.
Máximo: 36 caracteres
Recomendación: Use un formato que garantice unicidad
Ejemplo: "QRPAY-987654-20240119"
descriptionstringDescripción del pago que aparecerá en el extracto del destinatario.
Máximo: 140 caracteres
Predeterminado: Vacío
Ejemplo: "Pago a proveedor XYZ vía QR Code"
namestringNombre del destinatario. Opcional e informativo — cuando se omite, se utilizan los datos del QR Code.
Máximo: 100 caracteres
Ejemplo: "Destinatario Ltda"
documentstringCPF o CNPJ del destinatario (solo números, con dígitos verificadores válidos). Opcional — cuando se omite, se utilizan los datos del QR Code.
CPF: 11 dígitos
CNPJ: 14 dígitos
Ejemplo: "12345678000190"
Estructura de la Respuesta
transactionIdstringsempre presenteID interno de la transacción generado por FyPay.
Ejemplo: "456"
externalIdstringsempre presenteID externo proporcionado en la solicitud (mismo valor que la entrada).
Ejemplo: "QRPAY-987654-20240119"
statusstringsempre presenteEstado actual de la transacción.
Valores posibles:
PENDING: Pago en procesamientoCONFIRMED: Pago confirmado y finalizadoERROR: Error en el procesamiento
Ejemplo: "PENDING"
Nota: La mayoría de los pagos PIX se confirma en pocos segundos
generateTimestringsempre presenteFecha y hora de creación del pago (ISO 8601 UTC).
Ejemplo: "2024-01-19T14:30:00.000Z"
Ejemplos de Implementación
Node.js / TypeScript
import axios from 'axios';
interface CashOutQrCodeRequest {
value: number;
qrCode: string;
externalId: string;
description?: string;
name?: string;
document?: string;
}
interface CashOutQrCodeResponse {
transactionId: string;
externalId: string;
status: 'PENDING' | 'CONFIRMED' | 'ERROR';
generateTime: string;
}
async function payWithQrCode(
token: string,
qrCode: string,
amount: number,
description?: string
): Promise<CashOutQrCodeResponse> {
const payload: CashOutQrCodeRequest = {
value: amount,
qrCode,
externalId: `QRPAY-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
description: description || 'Pago vía QR Code PIX'
};
try {
const response = await axios.post<CashOutQrCodeResponse>(
'https://api.fypay.me/api/pix/cash-out-qrcode',
payload,
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
);
console.log('¡Pago vía QR Code iniciado!');
console.log(`ID de la Transacción: ${response.data.transactionId}`);
console.log(`Estado: ${response.data.status}`);
console.log(`Valor: R$ ${amount.toFixed(2)}`);
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
const errorData = error.response?.data;
console.error('Error al realizar el pago:', errorData);
if (error.response?.status === 400) {
if (errorData?.code === 'INVALID_QR_CODE') {
throw new Error('QR Code inválido o mal formado');
}
if (errorData?.code === 'QR_CODE_VALUE_MISMATCH') {
throw new Error('El valor informado difiere del valor del QR Code');
}
if (errorData?.code === 'PIX_INSUFFICIENT_BALANCE') {
throw new Error('Saldo insuficiente para realizar el pago');
}
throw new Error('Datos inválidos: ' + errorData?.message);
}
if (error.response?.status === 409) {
throw new Error('externalId ya utilizado en otra transacción');
}
throw new Error(errorData?.message || 'Error al realizar el pago vía QR Code');
}
throw error;
}
}
// Uso - Pago vía QR Code escaneado
const qrCodeContent = '00020126580014br.gov.bcb.pix0136a1b2c3d4...6304ABCD';
payWithQrCode('su_token_aqui', qrCodeContent, 15.50, 'Pago a proveedor');Python
import requests
from datetime import datetime
from typing import Dict, Optional
import uuid
def pay_with_qr_code(
token: str,
qr_code: str,
amount: float,
description: Optional[str] = None
) -> Dict:
"""
Envía un pago PIX vía QR Code
Args:
token: Token Bearer válido
qr_code: Contenido del QR Code PIX (string EMV)
amount: Valor en reales
description: Descripción del pago (opcional)
Returns:
Datos del pago iniciado
"""
url = 'https://api.fypay.me/api/pix/cash-out-qrcode'
payload = {
'value': round(amount, 2),
'qrCode': qr_code,
'externalId': f'QRPAY-{int(datetime.now().timestamp())}-{uuid.uuid4().hex[:8]}',
'description': description or 'Pago vía QR Code PIX'
}
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('¡Pago vía QR Code iniciado!')
print(f"ID de la Transacción: {data['transactionId']}")
print(f"Estado: {data['status']}")
print(f"Valor: R$ {amount:.2f}")
return data
except requests.exceptions.HTTPError as e:
error_data = e.response.json() if e.response else {}
if e.response.status_code == 400:
code = error_data.get('code', '')
if code == 'INVALID_QR_CODE':
raise Exception('QR Code inválido o mal formado')
if code == 'QR_CODE_VALUE_MISMATCH':
raise Exception('El valor informado difiere del valor del QR Code')
if code == 'PIX_INSUFFICIENT_BALANCE':
raise Exception('Saldo insuficiente para realizar el pago')
raise Exception(f"Datos inválidos: {error_data.get('message')}")
if e.response.status_code == 409:
raise Exception('externalId ya utilizado en otra transacción')
raise Exception(f"Error al realizar el pago: {error_data.get('message', str(e))}")
# Uso
token = 'su_token_aqui'
qr_code = '00020126580014br.gov.bcb.pix0136a1b2c3d4...6304ABCD'
payment = pay_with_qr_code(
token=token,
qr_code=qr_code,
amount=15.50,
description='Pago a proveedor XYZ vía QR Code'
)PHP
<?php
function payWithQrCode(
string $token,
string $qrCode,
float $amount,
?string $description = null
): array {
$url = 'https://api.fypay.me/api/pix/cash-out-qrcode';
$payload = [
'value' => round($amount, 2),
'qrCode' => $qrCode,
'externalId' => 'QRPAY-' . time() . '-' . bin2hex(random_bytes(4)),
'description' => $description ?? 'Pago vía QR Code PIX'
];
$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);
$errorCode = $errorData['code'] ?? '';
$errorMessage = $errorData['message'] ?? "HTTP $httpCode";
if ($httpCode === 400) {
if ($errorCode === 'INVALID_QR_CODE') {
throw new Exception('QR Code inválido o mal formado');
}
if ($errorCode === 'QR_CODE_VALUE_MISMATCH') {
throw new Exception('El valor informado difiere del valor del QR Code');
}
if ($errorCode === 'PIX_INSUFFICIENT_BALANCE') {
throw new Exception('Saldo insuficiente para realizar el pago');
}
}
if ($httpCode === 409) {
throw new Exception('externalId ya utilizado en otra transacción');
}
throw new Exception("Error al realizar el pago: $errorMessage");
}
$data = json_decode($response, true);
echo "¡Pago vía QR Code iniciado!" . PHP_EOL;
echo "ID de la Transacción: {$data['transactionId']}" . PHP_EOL;
echo "Estado: {$data['status']}" . PHP_EOL;
echo "Valor: R$ " . number_format($amount, 2, ',', '.') . PHP_EOL;
return $data;
}
// Uso
$token = 'su_token_aqui';
$qrCode = '00020126580014br.gov.bcb.pix0136a1b2c3d4...6304ABCD';
$payment = payWithQrCode($token, $qrCode, 15.50, 'Pago a proveedor XYZ');Casos de Uso
1. PDV — Pagar vía QR Code en la Caja
class PointOfSalePayment {
constructor(private token: string) {}
async payFromScannedQrCode(qrCodeContent: string, amount: number) {
// Validar el QR Code localmente antes de enviar
if (!this.isValidPixQrCode(qrCodeContent)) {
throw new Error('QR Code inválido. Verifique e intente de nuevo.');
}
const payment = await payWithQrCode(
this.token,
qrCodeContent,
amount,
`Pago PDV - ${new Date().toLocaleDateString('es')}`
);
console.log(`Pago iniciado: ${payment.transactionId}`);
return payment;
}
private isValidPixQrCode(qrCode: string): boolean {
return qrCode.length >= 50 && qrCode.startsWith('000201');
}
}2. Pago a Proveedor vía QR Code
class SupplierPayment:
"""Procesa pagos a proveedores vía QR Code"""
def __init__(self, token: str):
self.token = token
def pay_supplier_invoice(self, qr_code: str, invoice_amount: float, invoice_id: str):
"""Paga la factura del proveedor vía QR Code"""
# Verificar el saldo antes
balance = get_balance(self.token)
if balance['netBalance'] < invoice_amount:
raise Exception(f"Saldo insuficiente. Disponible: R$ {balance['netBalance']:.2f}")
payment = pay_with_qr_code(
token=self.token,
qr_code=qr_code,
amount=invoice_amount,
description=f'Pago de factura #{invoice_id}'
)
return {
'invoice_id': invoice_id,
'transaction_id': payment['transactionId'],
'status': payment['status'],
'amount': invoice_amount
}3. Automatización de Pagos Recurrentes
class RecurringQrCodePayment {
constructor(token) {
this.token = token;
}
async processPaymentBatch(payments) {
const results = { successful: [], failed: [] };
for (const payment of payments) {
try {
const result = await payWithQrCode(
this.token,
payment.qrCode,
payment.amount,
payment.description
);
results.successful.push({
reference: payment.reference,
transactionId: result.transactionId,
amount: payment.amount
});
// Esperar entre pagos
await new Promise(resolve => setTimeout(resolve, 1000));
} catch (error) {
results.failed.push({
reference: payment.reference,
error: error.message
});
}
}
return results;
}
}Validación del QR Code
El QR Code PIX sigue el estándar EMV (Europay, Mastercard, Visa) definido por el Banco Central de Brasil. Antes de enviarlo a la API, puede validarlo localmente:
function isValidPixQrCode(qrCode: string): boolean {
// Verificar tamaño mínimo y máximo
if (qrCode.length < 50 || qrCode.length > 500) {
return false;
}
// Verificar el prefijo EMV obligatorio
if (!qrCode.startsWith('000201')) {
return false;
}
return true;
}Estructura del QR Code EMV PIX:
000201— Payload Format Indicator (obligatorio)0102XX— Point of Initiation Method (11= estático,12= dinámico)- Campos con datos del receptor, valor, ciudad, etc.
6304XXXX— CRC16 (checksum de validación)
La validación completa del QR Code (decodificación EMV, verificación de CRC y extracción de datos) la realiza automáticamente la API. La validación local sirve solo para filtrar QR Codes claramente inválidos.
Códigos de Respuesta
| Código | Error | Descripción |
|---|---|---|
201 | — | Pago PIX vía QR Code iniciado con éxito |
400 | INVALID_QR_CODE | QR Code inválido o mal formado |
400 | QR_CODE_VALUE_MISMATCH | El valor informado difiere del valor incluido en el QR Code |
400 | PIX_INSUFFICIENT_BALANCE | Saldo insuficiente para realizar la transacción |
400 | PIX_TRANSACTION_LIMIT_EXCEEDED, PIX_DAILY_LIMIT_EXCEEDED, PIX_NIGHT_LIMIT_EXCEEDED, PIX_LIMIT_EXCEEDED | Límite de la cuenta excedido |
400 | PUB_REQUEST_BODY_INVALID | Campo ausente o fuera de formato (por ejemplo, externalId con más de 36 caracteres o qrCode que no comienza con 000201); details trae el error de cada campo |
401 | — | Token no proporcionado, expirado o inválido |
409 | PIX_DUPLICATE_EXTERNAL_ID | externalId ya utilizado en otra transacción |
500 | — | Error inesperado en el procesamiento. Consulte por el externalId antes de intentar de nuevo |
Consulte la Referencia de la API para detalles completos de los campos de respuesta.
Mejores Prácticas
Notas Importantes
- Valor mínimo: R$ 0,01 | Decimales: Máximo 2
externalId: Máximo 36 caracteres, único por cuentadescription: Máximo 140 caracteres- Formato del QR Code: Debe comenzar con
000201y tener entre 50 y 500 caracteres - QR Codes dinámicos: Se aceptan QR Codes sin valor incluido — el campo
valuedefine el valor del pago - QR Codes estáticos con valor: El valor informado en
valuedebe corresponder al valor incluido en el QR Code (tolerancia de 1 centavo)