Safira Paydocs
集成指南

PIX Cash-Out(付款)

概述

PIX Cash-Out 端点允许您向任何有效的 PIX 密钥(CPF、CNPJ、手机号、邮箱或随机密钥)发送即时 PIX 付款。付款将实时处理,金额将立即从您的账户中扣除。

对于通过 PIX 二维码付款(扫描或复制粘贴),请使用专用的 二维码 Cash-Out 端点。此端点专用于 PIX 密钥付款。

此端点需要有效的 Bearer token。详情请参阅认证文档

功能特性

  • 全天候即时付款
  • 支持所有 PIX 密钥类型
  • 自动验证收款方数据
  • 通过 externalId 唯一标识
  • 可自定义收款方描述
  • 自动余额验证

端点

POST /api/pix/cash-out

发起 PIX 付款。

必需请求头

Authorization: Bearer {token}
Content-Type: application/json

请求体

{
  "value": 250.50,
  "details": {
    "key": "12345678901",
    "keyType": "DOCUMENT",
    "name": "Ana Costa",
    "document": "12345678901"
  },
  "externalId": "PAYMENT-987654-20240119",
  "description": "Pagamento de fornecedor"
}

请求

curl -X POST https://api.safirapay.com/api/pix/cash-out \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "value": 250.50,
    "details": {
      "key": "12345678901",
      "keyType": "DOCUMENT",
      "name": "Ana Costa",
      "document": "12345678901"
    },
    "externalId": "PAYMENT-987654-20240119",
    "description": "Pagamento de fornecedor"
  }'

响应 (201 Created)

{
  "transactionId": "9876",
  "externalId": "PAYMENT-987654-20240119",
  "status": "PENDING",
  "generateTime": "2024-01-19T15:45:00.000Z"
}

请求参数

valuenumberobrigatorio

付款金额,单位为巴西雷亚尔 (BRL)。最多 2 位小数。

最小值: 0.01

示例: 250.50

detailsobjectobrigatorio

目标 PIX 密钥信息。

details.keystringobrigatorio

目标 PIX 密钥。

支持的格式:

  • CPF:12345678901(11 位数字)
  • CNPJ:12345678000199(14 位数字)
  • 邮箱:usuario@exemplo.com
  • 手机号:5511999999999(含国家和区号)
  • 随机密钥:UUID 格式 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
details.keyTypestringobrigatorio

PIX 密钥类型。

可选值:

  • DOCUMENT - CPF 或 CNPJ
  • EMAIL - 邮箱地址
  • PHONE - 手机号码
  • RANDOM - 随机密钥 (UUID)

示例: "DOCUMENT"

details.namestringobrigatorio

目标 PIX 密钥持有人的全名。

验证: 名称必须与 PIX 密钥注册的名称匹配

示例: "Ana Costa"

details.documentstringobrigatorio

持有人的 CPF 或 CNPJ(仅数字)。

CPF: 11 位数字

CNPJ: 14 位数字

验证: 证件号必须与 PIX 密钥注册的证件号匹配

示例: "12345678901"

externalIdstringobrigatorio

唯一的外部交易标识符。

最大长度: 255 个字符

建议: 使用确保唯一性的格式

示例: "PAYMENT-987654-20240119-154500"

descriptionstring

付款描述,将显示在收款方的账单中。

最大长度: 140 个字符

默认值:

示例: "Pagamento de fornecedor - Nota Fiscal 12345"

响应结构

transactionIdstringsempre presente

Avista 生成的内部交易 ID。

示例: "9876"

externalIdstringsempre presente

请求中提供的外部 ID(与输入值相同)。

示例: "PAYMENT-987654-20240119"

statusstringsempre presente

当前交易状态。

可选值:

  • PENDING:付款处理中
  • CONFIRMED:付款已确认并完成
  • ERROR:处理错误

示例: "PENDING"

注意: 大多数 PIX 付款在几秒内即可确认

generateTimestringsempre presente

付款创建日期和时间(ISO 8601 UTC)。

示例: "2024-01-19T15:45:00.000Z"

实现示例

Node.js / TypeScript

import axios from 'axios';

interface CashOutRequest {
  value: number;
  details: {
    key: string;
    keyType: 'DOCUMENT' | 'EMAIL' | 'PHONE' | 'RANDOM';
    name: string;
    document: string;
  };
  externalId: string;
  description?: string;
}

interface CashOutResponse {
  transactionId: string;
  externalId: string;
  status: 'PENDING' | 'CONFIRMED' | 'ERROR';
  generateTime: string;
}

async function sendPixPayment(
  token: string,
  recipientKey: string,
  recipientKeyType: 'DOCUMENT' | 'EMAIL' | 'PHONE' | 'RANDOM',
  recipientName: string,
  recipientDocument: string,
  amount: number,
  description?: string
): Promise<CashOutResponse> {
  const payload: CashOutRequest = {
    value: amount,
    details: {
      key: recipientKey,
      keyType: recipientKeyType,
      name: recipientName,
      document: recipientDocument
    },
    externalId: `PAY-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
    description: description || `Pagamento PIX de R$ ${amount.toFixed(2)}`
  };

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

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

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

      // Handle specific errors
      if (error.response?.status === 400) {
        if (errorData?.message?.includes('saldo insuficiente')) {
          throw new Error('Insufficient balance to make the payment');
        }
        throw new Error('Invalid data: ' + errorData?.message);
      }

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

// Usage - Payment by CPF
sendPixPayment(
  'your_token_here',
  '12345678901',
  'DOCUMENT',
  'Ana Costa',
  '12345678901',
  250.50,
  'Pagamento de fornecedor'
);

// Usage - Payment by Email
sendPixPayment(
  'your_token_here',
  'ana.costa@email.com',
  'EMAIL',
  'Ana Costa',
  '12345678901',
  100.00,
  'Reembolso'
);

// Usage - Payment by Phone
sendPixPayment(
  'your_token_here',
  '5511999999999',
  'PHONE',
  'Ana Costa',
  '12345678901',
  50.00
);

Python

import requests
from datetime import datetime
from typing import Dict, Optional
import uuid

def send_pix_payment(
    token: str,
    recipient_key: str,
    recipient_key_type: str,
    recipient_name: str,
    recipient_document: str,
    amount: float,
    description: Optional[str] = None
) -> Dict:
    """
    Send a PIX payment

    Args:
        token: Valid Bearer token
        recipient_key: Recipient's PIX key
        recipient_key_type: Key type (DOCUMENT, EMAIL, PHONE, RANDOM)
        recipient_name: Recipient's name
        recipient_document: Recipient's CPF or CNPJ
        amount: Amount in BRL
        description: Payment description (optional)

    Returns:
        Initiated payment data
    """
    url = 'https://api.safirapay.com/api/pix/cash-out'

    payload = {
        'value': round(amount, 2),
        'details': {
            'key': recipient_key,
            'keyType': recipient_key_type,
            'name': recipient_name,
            'document': recipient_document
        },
        'externalId': f'PAY-{int(datetime.now().timestamp())}-{uuid.uuid4().hex[:8]}',
        'description': description or f'Pagamento PIX de R$ {amount:.2f}'
    }

    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 payment initiated successfully!')
        print(f"Transaction ID: {data['transactionId']}")
        print(f"Status: {data['status']}")
        print(f"Amount: R$ {amount:.2f}")
        print(f"Recipient: {recipient_name}")

        return data

    except requests.exceptions.HTTPError as e:
        error_data = e.response.json() if e.response else {}

        # Handle specific errors
        if e.response.status_code == 400:
            if 'saldo insuficiente' in error_data.get('message', '').lower():
                raise Exception('Insufficient balance to make the payment')
            raise Exception(f"Invalid data: {error_data.get('message')}")

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

# Usage
token = 'your_token_here'

# Payment by CPF
payment = send_pix_payment(
    token=token,
    recipient_key='12345678901',
    recipient_key_type='DOCUMENT',
    recipient_name='Ana Costa',
    recipient_document='12345678901',
    amount=250.50,
    description='Pagamento de fornecedor'
)

PIX 密钥验证

在发送付款前验证 PIX 密钥格式:

function validatePixKey(key: string, keyType: string): boolean {
  switch (keyType) {
    case 'DOCUMENT':
      // CPF: 11 digits or CNPJ: 14 digits
      return /^\d{11}$|^\d{14}$/.test(key);

    case 'EMAIL':
      return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(key);

    case 'PHONE':
      // Format: +5511999999999 (country code + area code + number)
      return /^55\d{10,11}$/.test(key);

    case 'RANDOM':
      // UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
      return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(key);

    default:
      return false;
  }
}

余额验证

在付款前始终检查余额以避免 400 错误。

async function safePayment(
  token: string,
  amount: number,
  recipient: RecipientData
) {
  // Query balance
  const balance = await getBalance(token);

  // Check if there is sufficient balance
  if (balance.netBalance < amount) {
    throw new Error(
      `Insufficient balance. Available: R$ ${balance.netBalance.toFixed(2)} | ` +
      `Required: R$ ${amount.toFixed(2)}`
    );
  }

  // Proceed with payment
  return await sendPixPayment(token, ...recipient, amount);
}

响应状态码

状态码描述含义
201付款已发起PIX 转账发起成功
400余额不足余额不足以完成交易
400数据无效请检查必填字段和格式
401令牌无效未提供令牌、令牌已过期或无效

请参阅 API 参考 获取响应字段的完整详情。

最佳实践

重要说明

  • 最小金额: R$ 0.01

后续步骤

本页目录