Safira Paydocs
Guías de Integración

PIX Cash-Out vía QR Code

Descripción General

El endpoint de 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, simplificando el proceso de pago.

Este endpoint requiere un Bearer token válido. Consulte la documentación de autenticación para más detalles.

Clave PIX vs QR Code: ¿Qué endpoint usar?

La API de Avista 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 destinatarioRequeridos (clave, tipo, nombre, documento)Incrustados en el QR Code (opcionales en la solicitud)
Validación de valorSolo saldo y límitesSaldo, límites + valor del QR Code vs valor proporcionado
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, pagos programáticos)
  • Use Cash-Out vía QR Code cuando el pago se inicia desde un QR Code escaneado (ej.: PDV, pago de facturas, copiar y pegar)

Ambos endpoints retornan la misma estructura de respuesta y disparan el mismo webhook CashOut al confirmar.

Características

  • Pago vía QR Code estático o dinámico
  • Validación automática del valor incrustado en el QR Code
  • Verificación automática de saldo antes del envío
  • Identificación única vía 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": "Pagamento fornecedor XYZ via QR Code",
  "name": "Destinatario Ltda",
  "document": "12345678000190"
}

Solicitud

curl -X POST https://api.safirapay.com/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": "Pagamento fornecedor XYZ via 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

Monto del pago en BRL (Reales brasileños). Debe tener como máximo 2 decimales. Si el QR Code contiene un valor incrustado, el valor proporcionado debe coincidir (tolerancia de 1 centavo).

Mínimo: 0.01

Ejemplo: 15.50

qrCodestringobrigatorio

Contenido del QR Code PIX (cadena EMV). Puede obtenerse mediante escaneo con cámara o del 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. Garantiza idempotencia -- enviar el mismo externalId dos veces resulta en un error 409.

Máximo: 255 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: "Pagamento fornecedor XYZ via QR Code"

namestring

Nombre del destinatario. Opcional -- cuando se omite, se usan los datos del QR Code.

Ejemplo: "Destinatario Ltda"

documentstring

CPF o CNPJ del destinatario (solo números). Opcional -- cuando se omite, se usan 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 Avista.

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 proceso
  • CONFIRMED: Pago confirmado y completado
  • ERROR: Error de procesamiento

Ejemplo: "PENDING"

Nota: La mayoría de los pagos PIX se confirman 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 || 'Pagamento via QR Code PIX'
  };

  try {
    const response = await axios.post<CashOutQrCodeResponse>(
      'https://api.safirapay.com/api/pix/cash-out-qrcode',
      payload,
      {
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        }
      }
    );

    console.log('QR Code payment initiated!');
    console.log(`Transaction ID: ${response.data.transactionId}`);
    console.log(`Status: ${response.data.status}`);
    console.log(`Amount: R$ ${amount.toFixed(2)}`);

    return response.data;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      const errorData = error.response?.data;
      console.error('Error making payment:', errorData);

      if (error.response?.status === 400) {
        if (errorData?.code === 'INVALID_QR_CODE') {
          throw new Error('Invalid or malformed QR Code');
        }
        if (errorData?.code === 'QR_CODE_VALUE_MISMATCH') {
          throw new Error('Provided value differs from the QR Code value');
        }
        if (errorData?.code === 'INSUFFICIENT_BALANCE') {
          throw new Error('Insufficient balance to make the payment');
        }
        throw new Error('Invalid data: ' + errorData?.message);
      }

      if (error.response?.status === 409) {
        throw new Error('externalId already used in another transaction');
      }

      throw new Error(errorData?.message || 'Error making QR Code payment');
    }
    throw error;
  }
}

// Usage - Payment via scanned QR Code
const qrCodeContent = '00020126580014br.gov.bcb.pix0136a1b2c3d4...6304ABCD';
payWithQrCode('your_token_here', qrCodeContent, 15.50, 'Pagamento fornecedor');

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:
    """
    Send a PIX payment via QR Code

    Args:
        token: Valid Bearer token
        qr_code: PIX QR Code content (EMV string)
        amount: Amount in BRL
        description: Payment description (optional)

    Returns:
        Initiated payment data
    """
    url = 'https://api.safirapay.com/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 'Pagamento via 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('QR Code payment initiated!')
        print(f"Transaction ID: {data['transactionId']}")
        print(f"Status: {data['status']}")
        print(f"Amount: 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('Invalid or malformed QR Code')
            if code == 'QR_CODE_VALUE_MISMATCH':
                raise Exception('Provided value differs from the QR Code value')
            if code == 'INSUFFICIENT_BALANCE':
                raise Exception('Insufficient balance to make the payment')
            raise Exception(f"Invalid data: {error_data.get('message')}")

        if e.response.status_code == 409:
            raise Exception('externalId already used in another transaction')

        raise Exception(f"Error making payment: {error_data.get('message', str(e))}")

# Usage
token = 'your_token_here'
qr_code = '00020126580014br.gov.bcb.pix0136a1b2c3d4...6304ABCD'

payment = pay_with_qr_code(
    token=token,
    qr_code=qr_code,
    amount=15.50,
    description='Pagamento fornecedor XYZ via QR Code'
)

PHP

<?php

function payWithQrCode(
    string $token,
    string $qrCode,
    float $amount,
    ?string $description = null
): array {
    $url = 'https://api.safirapay.com/api/pix/cash-out-qrcode';

    $payload = [
        'value' => round($amount, 2),
        'qrCode' => $qrCode,
        'externalId' => 'QRPAY-' . time() . '-' . bin2hex(random_bytes(4)),
        'description' => $description ?? 'Pagamento via 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('Invalid or malformed QR Code');
            }
            if ($errorCode === 'QR_CODE_VALUE_MISMATCH') {
                throw new Exception('Provided value differs from the QR Code value');
            }
            if ($errorCode === 'INSUFFICIENT_BALANCE') {
                throw new Exception('Insufficient balance to make the payment');
            }
        }

        if ($httpCode === 409) {
            throw new Exception('externalId already used in another transaction');
        }

        throw new Exception("Error making payment: $errorMessage");
    }

    $data = json_decode($response, true);

    echo "QR Code payment initiated!" . PHP_EOL;
    echo "Transaction ID: {$data['transactionId']}" . PHP_EOL;
    echo "Status: {$data['status']}" . PHP_EOL;
    echo "Amount: R$ " . number_format($amount, 2, ',', '.') . PHP_EOL;

    return $data;
}

// Usage
$token = 'your_token_here';
$qrCode = '00020126580014br.gov.bcb.pix0136a1b2c3d4...6304ABCD';

$payment = payWithQrCode($token, $qrCode, 15.50, 'Pagamento fornecedor XYZ');

Validación de 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 {
  // Check minimum and maximum length
  if (qrCode.length < 50 || qrCode.length > 500) {
    return false;
  }

  // Check mandatory EMV prefix
  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 CRC y extracción de datos) se realiza automáticamente por 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 exitosamente
400INVALID_QR_CODEQR Code inválido o malformado
400QR_CODE_VALUE_MISMATCHEl valor proporcionado difiere del valor incrustado en el QR Code
400INSUFFICIENT_BALANCESaldo insuficiente para completar la transacción
401--Token no proporcionado, expirado o inválido
409DUPLICATE_EXTERNAL_IDexternalId ya utilizado en otra transacción

Consulte la Referencia de la API para detalles completos de los campos de respuesta.

Mejores Prácticas

Notas Importantes

  • Monto mínimo: R$ 0.01
  • 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 incrustado -- el campo value define el monto del pago
  • QR Codes estáticos con valor: El valor proporcionado en value debe coincidir con el valor incrustado en el QR Code (tolerancia de 1 centavo)

Próximos Pasos

En esta página