docs
Guías de Integración

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:

CriterioCash-Out por Clave PIXCash-Out vía QR Code
EndpointPOST /api/pix/cash-outPOST /api/pix/cash-out-qrcode
Cuándo usarConoce la clave PIX del destinatarioTiene el QR Code generado por el receptor
Datos del destinatarioClave y tipo obligatorios; nombre y documento recomendadosIncluidos en el QR Code (opcionales en la solicitud)
Validación del valorSolo saldo y límitesSaldo, límites + valor del QR Code vs valor informado
Tipos de claveCPF, CNPJ, email, teléfono, aleatoriaN/A (información dentro del QR Code)
Webhook de confirmaciónEvento CashOutMismo evento CashOut
RespuestaMisma estructuraMisma 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/json

Cuerpo 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

valuenumberobrigatorio

Valor 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

qrCodestringobrigatorio

Contenido 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"

externalIdstringobrigatorio

Identificador 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"

descriptionstring

Descripció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"

namestring

Nombre del destinatario. Opcional e informativo — cuando se omite, se utilizan los datos del QR Code.

Máximo: 100 caracteres

Ejemplo: "Destinatario Ltda"

documentstring

CPF 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 presente

ID interno de la transacción generado por FyPay.

Ejemplo: "456"

externalIdstringsempre presente

ID externo proporcionado en la solicitud (mismo valor que la entrada).

Ejemplo: "QRPAY-987654-20240119"

statusstringsempre presente

Estado actual de la transacción.

Valores posibles:

  • PENDING: Pago en procesamiento
  • CONFIRMED: Pago confirmado y finalizado
  • ERROR: Error en el procesamiento

Ejemplo: "PENDING"

Nota: La mayoría de los pagos PIX se confirma en pocos segundos

generateTimestringsempre presente

Fecha 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ódigoErrorDescripción
201—Pago PIX vía QR Code iniciado con éxito
400INVALID_QR_CODEQR Code inválido o mal formado
400QR_CODE_VALUE_MISMATCHEl valor informado difiere del valor incluido en el QR Code
400PIX_INSUFFICIENT_BALANCESaldo insuficiente para realizar la transacción
400PIX_TRANSACTION_LIMIT_EXCEEDED, PIX_DAILY_LIMIT_EXCEEDED, PIX_NIGHT_LIMIT_EXCEEDED, PIX_LIMIT_EXCEEDEDLímite de la cuenta excedido
400PUB_REQUEST_BODY_INVALIDCampo 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
409PIX_DUPLICATE_EXTERNAL_IDexternalId 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 cuenta
  • description: Máximo 140 caracteres
  • Formato del QR Code: Debe comenzar con 000201 y tener entre 50 y 500 caracteres
  • QR Codes dinámicos: Se aceptan QR Codes sin valor incluido — el campo value define el valor del pago
  • QR Codes estáticos con valor: El valor informado en value debe corresponder al valor incluido en el QR Code (tolerancia de 1 centavo)

Próximos Pasos

En esta página