PIX Cash-Out(付款)
概述
PIX Cash-Out 端点允许您向任意有效的 PIX 密钥(CPF、CNPJ、电话、邮箱或随机密钥)发起 PIX 付款。付款一经受理,金额即在账户余额中预留,最终结果通过 webhook 送达。
对于通过 PIX 二维码付款(扫描或复制粘贴),请使用专用的 二维码 Cash-Out 端点。此端点专用于 PIX 密钥付款。
此端点需要有效的 Bearer token。详情请参阅认证文档。
功能特性
- 全天候 24/7 即时付款
- 支持所有 PIX 密钥类型(CPF、CNPJ、电话、邮箱、随机密钥)
- 根据
keyType校验密钥格式 - 自动检查余额和限额
- 通过
externalId唯一标识(幂等性) - 可为收款方自定义描述
- 通过 webhook 异步确认(状态 PENDING → CONFIRMED 或 ERROR)
端点
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": "供应商付款"
}请求
curl -X POST https://api.fypay.me/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": "供应商付款"
}'各密钥类型接受的确切格式请参阅下方的 PIX 密钥格式 部分。
响应 (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 密钥。格式必须与所提供的 keyType 相符。
接受的格式:
- CPF:
12345678901(11 位,仅数字) - CNPJ:
12345678000199(14 位,仅数字) - 邮箱:
usuario@exemplo.com - 电话:
11999999999(10 或 11 位 — 区号 + 号码,不含国家代码+55) - 随机密钥: UUID 格式
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
完整表格请参阅 PIX 密钥格式。
details.keyTypestringobrigatorioPIX 密钥类型。该值不区分大小写(会自动转换为大写)。
接受的值:
DOCUMENT— CPF 或 CNPJ(根据位数自动区分)EMAIL— 邮箱地址PHONE— 电话号码(区号 + 号码)RANDOM— 随机密钥(EVP/UUID)
为什么是必填? API 不会通过 DICT 查询来推断密钥类型。类型必须在请求中提供,以便系统在发送给清算银行之前规范为正确的格式(例如为 PHONE 密钥添加 +55、将 DOCUMENT 转换为 CPF/CNPJ)。
示例: "DOCUMENT"
details.namestring目标 PIX 密钥持有人的姓名。在 API 中为可选,但建议填写;部分清算机构要求提供该字段。
最大长度: 100 个字符
用途: 信息性字段 — 用于对账单记录和识别收款方。清算银行不会将其与 PIX 密钥的登记信息进行校验。提交与持有人不同的姓名不会导致交易出错。
示例: "Ana Costa"
details.documentstring目标 PIX 密钥持有人的 CPF 或 CNPJ(仅数字)。会进行算法校验(校验位)。
CPF: 11 位 | CNPJ: 14 位
在 API 中为可选,但强烈建议所有密钥类型都提供 — 包括 EMAIL、PHONE 和 RANDOM。部分清算机构要求提供该字段,缺少时会拒绝付款。该字段会转发给您账户的清算银行。FyPay API 仅校验格式:不会将 details.document 与 details.key 进行比对,不会查询 DICT,两者不一致时也不会阻止操作。 与密钥真实持有人的比对仅在部分清算机构中进行;在其他清算机构中,不一致的值不会阻止付款,款项会入账给密钥的所有者。参见按清算机构划分的持有人核验。
视您账户的清算机构而定,提供证件号还可能使不存在密钥的拒绝结果立即返回,而不是稍后通过 webhook 到达。参见 Pix 密钥出金保护机制。
按 keyType 的场景:
DOCUMENT:details.document必须与details.key完全相同(两者都是 CPF/CNPJ)。API 不会校验两者是否相同:请在发送前在您这边确保这一点。如果不一致,款项会付给密钥的所有者,核验持有人的清算机构除外。EMAIL、PHONE、RANDOM:无法从密钥推导出证件号。集成方需要向最终用户索取 CPF/CNPJ 并填入此字段。
UX 提示: 当密钥类型不是 DOCUMENT 时,请始终在界面中显示 CPF/CNPJ 输入框。
示例: "12345678901"
externalIdstringobrigatorio交易的外部唯一标识符。在每个账户内必须唯一 — 如果重复,API 将返回 PIX_DUPLICATE_EXTERNAL_ID 错误(HTTP 400,附带 details.existingTransactionId)。不能以 MIRROR: 开头(保留前缀)。
最大长度: 50 个字符
建议: 使用能保证唯一性的格式,例如 PAY-{timestamp}-{uuid}
示例: "PAYMENT-987654-20240119"
descriptionstring将显示在收款方对账单中的付款描述。
最大长度: 140 个字符
默认值: 空
示例: "供应商付款 - 发票 12345"
响应结构
transactionIdstringsempre presenteFyPay 生成的交易内部 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 || `PIX 付款 R$ ${amount.toFixed(2)}`
};
try {
const response = await axios.post<CashOutResponse>(
'https://api.fypay.me/api/pix/cash-out',
payload,
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
);
console.log('PIX 付款已成功发起!');
console.log(`交易 ID:${response.data.transactionId}`);
console.log(`状态:${response.data.status}`);
console.log(`金额:R$ ${amount.toFixed(2)}`);
console.log(`收款方:${recipientName}`);
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
const errorData = error.response?.data;
console.error('付款出错:', errorData);
// 处理特定错误
if (error.response?.status === 400) {
if (errorData?.code === 'PIX_INSUFFICIENT_BALANCE') {
throw new Error('余额不足,无法完成付款');
}
throw new Error('数据无效:' + errorData?.message);
}
if (error.response?.status === 422 || error.response?.status === 429) {
// 密钥出金保护机制:参见 /zh/docs/guides/pix-cash-out-protections
const retryAfter = error.response.headers['retry-after'];
throw new Error(`${errorData?.code}:请在 ${retryAfter ?? '?'} 秒后重试`);
}
throw new Error(errorData?.message || 'PIX 付款出错');
}
throw error;
}
}
// 用法 - 通过 CPF 付款
sendPixPayment(
'your_token_here',
'12345678901',
'DOCUMENT',
'Ana Costa',
'12345678901',
250.50,
'供应商付款'
);
// 用法 - 通过邮箱付款
sendPixPayment(
'your_token_here',
'ana.costa@email.com',
'EMAIL',
'Ana Costa',
'12345678901',
100.00,
'退款'
);
// 用法 - 通过电话付款(区号 + 号码,不含国家代码 +55)
sendPixPayment(
'your_token_here',
'11999999999',
'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:
"""
发送 PIX 付款
Args:
token: 有效的 Bearer token
recipient_key: 收款方的 PIX 密钥
recipient_key_type: 密钥类型(DOCUMENT、EMAIL、PHONE、RANDOM)
recipient_name: 收款方姓名
recipient_document: 收款方的 CPF 或 CNPJ
amount: 金额(雷亚尔)
description: 付款描述(可选)
Returns:
已发起付款的数据
"""
url = 'https://api.fypay.me/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'PIX 付款 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 付款已成功发起!')
print(f"交易 ID:{data['transactionId']}")
print(f"状态:{data['status']}")
print(f"金额:R$ {amount:.2f}")
print(f"收款方:{recipient_name}")
return data
except requests.exceptions.HTTPError as e:
error_data = e.response.json() if e.response else {}
# 处理特定错误
if e.response.status_code == 400:
if error_data.get('code') == 'PIX_INSUFFICIENT_BALANCE':
raise Exception('余额不足,无法完成付款')
raise Exception(f"数据无效:{error_data.get('message')}")
raise Exception(f"付款出错:{error_data.get('message', str(e))}")
# 用法
token = 'your_token_here'
# 通过 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='供应商付款'
)PHP
<?php
function sendPixPayment(
string $token,
string $recipientKey,
string $recipientKeyType,
string $recipientName,
string $recipientDocument,
float $amount,
?string $description = null
): array {
$url = 'https://api.fypay.me/api/pix/cash-out';
$payload = [
'value' => round($amount, 2),
'details' => [
'key' => $recipientKey,
'keyType' => $recipientKeyType,
'name' => $recipientName,
'document' => $recipientDocument
],
'externalId' => 'PAY-' . time() . '-' . bin2hex(random_bytes(4)),
'description' => $description ?? "PIX 付款 R$ " . number_format($amount, 2, ',', '.')
];
$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 && ($errorData['code'] ?? '') === 'PIX_INSUFFICIENT_BALANCE') {
throw new Exception('余额不足,无法完成付款');
}
throw new Exception("付款出错:$errorMessage");
}
$data = json_decode($response, true);
echo "PIX 付款已成功发起!" . PHP_EOL;
echo "交易 ID:{$data['transactionId']}" . PHP_EOL;
echo "状态:{$data['status']}" . PHP_EOL;
echo "金额:R$ " . number_format($amount, 2, ',', '.') . PHP_EOL;
echo "收款方:$recipientName" . PHP_EOL;
return $data;
}
// 用法
$token = 'your_token_here';
$payment = sendPixPayment(
$token,
'12345678901',
'DOCUMENT',
'Ana Costa',
'12345678901',
250.50,
'供应商付款'
);使用场景
1. 工资发放
class PayrollProcessor {
constructor(private token: string) {}
async processPayroll(employees: Employee[]) {
const results = {
successful: [],
failed: []
};
for (const employee of employees) {
try {
// 每笔付款前检查余额
const balance = await getBalance(this.token);
if (balance.netBalance < employee.salary) {
throw new Error('余额不足');
}
// 执行付款
const payment = await sendPixPayment(
this.token,
employee.pixKey,
employee.pixKeyType,
employee.fullName,
employee.document,
employee.salary,
`工资 ${new Date().toLocaleDateString('pt-BR', { month: 'long', year: 'numeric' })}`
);
results.successful.push({
employee: employee.fullName,
amount: employee.salary,
transactionId: payment.transactionId
});
// 每笔付款之间等待 1 秒
await this.sleep(1000);
} catch (error) {
results.failed.push({
employee: employee.fullName,
error: error.message
});
}
}
return results;
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
// 用法
interface Employee {
fullName: string;
document: string;
pixKey: string;
pixKeyType: 'DOCUMENT' | 'EMAIL' | 'PHONE' | 'RANDOM';
salary: number;
}
const payroll = new PayrollProcessor('your_token_here');
const employees: Employee[] = [
{
fullName: 'Pedro Santos',
document: '12345678901',
pixKey: '12345678901',
pixKeyType: 'DOCUMENT',
salary: 3500.00
},
// ... 更多员工
];
const results = await payroll.processPayroll(employees);
console.log(`成功付款:${results.successful.length}`);
console.log(`失败付款:${results.failed.length}`);2. 电商平台 - 向卖家分账
class MarketplacePayouts:
"""处理电商平台向卖家的分账"""
def __init__(self, token: str):
self.token = token
def process_seller_payouts(self, sales_data: list) -> dict:
"""根据销售额处理分账"""
results = {'successful': [], 'failed': []}
# 按卖家汇总销售额
seller_totals = self.group_sales_by_seller(sales_data)
for seller_id, total_amount in seller_totals.items():
try:
# 获取卖家数据
seller = self.get_seller_data(seller_id)
# 计算扣除佣金后的金额
commission = total_amount * 0.10 # 10% 佣金
payout_amount = total_amount - commission
# 执行付款
payment = send_pix_payment(
token=self.token,
recipient_key=seller['pix_key'],
recipient_key_type=seller['pix_key_type'],
recipient_name=seller['name'],
recipient_document=seller['document'],
amount=payout_amount,
description=f'销售分账 - {len(sales_data)} 笔交易'
)
results['successful'].append({
'seller': seller['name'],
'gross_amount': total_amount,
'commission': commission,
'net_amount': payout_amount,
'transaction_id': payment['transactionId']
})
# 在数据库中记录分账
self.record_payout(seller_id, payment)
except Exception as e:
results['failed'].append({
'seller_id': seller_id,
'error': str(e)
})
return results
def group_sales_by_seller(self, sales_data: list) -> dict:
"""按卖家汇总销售额"""
totals = {}
for sale in sales_data:
seller_id = sale['seller_id']
totals[seller_id] = totals.get(seller_id, 0) + sale['amount']
return totals3. 退款系统
class RefundSystem {
constructor(token) {
this.token = token;
}
async processRefund(orderId, refundReason) {
// 获取订单数据
const order = await this.getOrderData(orderId);
// 验证是否允许退款
if (!this.canRefund(order)) {
throw new Error('该订单不允许退款');
}
// 将款项付回给客户
const refund = await sendPixPayment(
this.token,
order.customer.pixKey,
order.customer.pixKeyType,
order.customer.name,
order.customer.document,
order.amount,
`退款 - 订单 ${orderId} - ${refundReason}`
);
// 更新订单状态
await this.updateOrderStatus(orderId, 'REFUNDED', refund.transactionId);
// 向客户发送通知
await this.notifyCustomer(order.customer.email, refund);
return refund;
}
canRefund(order) {
// 检查订单是否已付款且仍在期限内
const daysSincePurchase = (Date.now() - new Date(order.paidAt)) / (1000 * 60 * 60 * 24);
return order.status === 'PAID' && daysSincePurchase <= 7;
}
}PIX 密钥格式
下表详细说明每种密钥类型接受的确切格式。details.key 字段会根据所提供的 details.keyType 进行校验 — 如果格式不符,API 返回 400 错误。
keyType | details.key 的预期格式 | 校验正则 | 有效示例 | 无效示例 |
|---|---|---|---|---|
DOCUMENT | CPF:11 位数字 | ^\d{11}$(含校验位验证) | 12345678901 | 123.456.789-01(含标点)、1234567890(10 位) |
DOCUMENT | CNPJ:14 位数字 | ^\d{14}$(含校验位验证) | 12345678000195 | 12.345.678/0001-95(含标点) |
EMAIL | 有效的邮箱地址 | ^[^\s@]+@[^\s@]+\.[^\s@]{2,}$ | usuario@exemplo.com | usuario@(无域名) |
PHONE | 区号 + 号码(10 或 11 位) | ^\d{10,11}$ | 11999999999(手机)、1133334444(座机) | 5511999999999(含国家代码)、+5511999999999(含 + 国家代码) |
RANDOM | UUID(带或不带连字符) | ^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$ 或 ^[a-fA-F0-9]{32}$ | a1b2c3d4-e5f6-4890-abcd-ef1234567890 | a1b2c3d4(不完整) |
PHONE 密钥 — 不要包含国家代码(+55)。 只发送区号 + 号码(10 或 11 位)。API 在发送给清算银行之前会自动添加国家代码 +55。
DOCUMENT 密钥 — 只发送数字。 CPF 和 CNPJ 不应包含标点(点、斜杠或连字符)。除了位数之外,API 还会校验 CPF/CNPJ 的校验位。
keyType 自动检测
如果您希望根据密钥的值实现 keyType 的自动检测,请使用以下优先级逻辑:
- 包含
@→EMAIL - UUID v4 格式 →
RANDOM - 14 位数字 + 有效 CNPJ(校验位) →
DOCUMENT - 11 位数字 + 有效 CPF(校验位) →
DOCUMENT - 10-11 位数字 + 不是有效 CPF →
PHONE
function inferPixKeyType(key: string): 'DOCUMENT' | 'EMAIL' | 'PHONE' | 'RANDOM' | null {
const trimmed = key.trim();
// 1. UUID(随机密钥 / EVP)
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
if (uuidRegex.test(trimmed)) return 'RANDOM';
// 2. 邮箱
if (trimmed.includes('@')) return 'EMAIL';
// 3. 仅数字 → DOCUMENT 或 PHONE
const digits = trimmed.replace(/\D/g, '');
// 14 位 → CNPJ
if (digits.length === 14) return 'DOCUMENT';
// 11 位 → CPF 或 PHONE
// 通过校验 CPF 的校验位来区分
if (digits.length === 11) {
return isValidCpf(digits) ? 'DOCUMENT' : 'PHONE';
}
// 10 位 → 座机(区号 + 8 位)
if (digits.length === 10) return 'PHONE';
return null; // 无法识别的格式
}
// CPF 算法校验(校验位)
function isValidCpf(cpf: string): boolean {
if (/^(\d)\1{10}$/.test(cpf)) return false; // 拒绝 11111111111 这类序列
let sum = 0;
for (let i = 0; i < 9; i++) sum += parseInt(cpf[i]) * (10 - i);
let check = 11 - (sum % 11);
if (check >= 10) check = 0;
if (check !== parseInt(cpf[9])) return false;
sum = 0;
for (let i = 0; i < 10; i++) sum += parseInt(cpf[i]) * (11 - i);
check = 11 - (sum % 11);
if (check >= 10) check = 0;
return check === parseInt(cpf[10]);
}CPF 与电话的歧义: 11 位的值既可能是 CPF,也可能是手机号(区号 + 9 位)。上面的逻辑使用 CPF 校验位验证来区分:校验位匹配则为 CPF,否则为电话。即便如此,我们仍建议让最终用户在界面中明确选择密钥类型,以避免边界情况下的错误。
PIX 密钥验证
发送付款之前,请在本地验证 PIX 密钥的格式:
function validatePixKey(key: string, keyType: string): boolean {
switch (keyType) {
case 'DOCUMENT':
// CPF:11 位或 CNPJ:14 位(仅数字)
return /^\d{11}$/.test(key) || /^\d{14}$/.test(key);
case 'EMAIL':
// 带有效域名的邮箱(顶级域名至少 2 个字符)
return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(key);
case 'PHONE':
// 区号 + 号码:10 位(座机)或 11 位(手机)
// 不要包含国家代码(+55)— API 会自动添加
return /^\d{10,11}$/.test(key);
case 'RANDOM':
// 带连字符的 UUID 或不带连字符的 32 位十六进制字符
return /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/.test(key)
|| /^[a-fA-F0-9]{32}$/.test(key);
default:
return false;
}
}余额验证
在发起付款前检查余额,以避免 400 PIX_INSUFFICIENT_BALANCE 错误。
async function safePayment(
token: string,
amount: number,
recipient: RecipientData
) {
// 查询余额
const balance = await getBalance(token);
// 检查余额是否充足
if (balance.netBalance < amount) {
throw new Error(
`余额不足。可用:R$ ${balance.netBalance.toFixed(2)} | ` +
`所需:R$ ${amount.toFixed(2)}`
);
}
// 继续付款
return await sendPixPayment(token, ...recipient, amount);
}状态监控
要跟踪付款确认情况,请查询 GET /api/pix/transaction/\{id\}(接受数字 transactionId 或您的 externalId):
async function monitorPaymentStatus(transactionId, timeout = 60000) {
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
const status = await checkTransactionStatus(transactionId);
if (status === 'CONFIRMED') {
console.log('付款已确认!');
return true;
}
if (status === 'ERROR') {
throw new Error('付款失败');
}
// 等待 2 秒后再次检查
await new Promise(resolve => setTimeout(resolve, 2000));
}
throw new Error('超时:付款未在预期时间内确认');
}交易状态流程
创建 PIX Cash-Out 后,交易会经历一个异步流程:
POST /api/pix/cash-out
│
▼
┌─────────┐
│ PENDING │ ← API 立即响应(201)
└────┬─────┘
│ (在清算银行处理)
│
┌─────┴──────┐
▼ ▼
┌──────────┐ ┌───────┐
│CONFIRMED │ │ ERROR │ ← 通过 webhook 通知
└──────────┘ └───────┘- API 立即返回
status: "PENDING"(201 Created) - 付款被发送到清算银行进行处理
- 最终结果(
CONFIRMED或ERROR)通过 Cash-Out webhook 通知 - 或者,使用 轮询端点 查询状态
大多数 PIX 付款会在几秒内确认。我们建议将接收 webhook 作为主要的确认方式,轮询仅作为备用方案。
视您账户的清算机构而定,对提供商已告知不存在的密钥,或提供商封锁查询期间的出金,API 可能会立即响应(422 或 429,不创建交易,也不发送 webhook)。参见 Pix 密钥出金保护机制。
HTTP 响应状态码
| 状态码 | 描述 | 含义 |
|---|---|---|
201 | 付款已发起 | PIX 转账已成功发起(状态 PENDING) |
400 | 数据无效 | 缺少必填字段、格式无效、externalId 重复、余额不足或超出限额 |
401 | 未授权 | Bearer token 未提供、已过期或无效 |
422 | 密钥未找到 | PIX_KEY_NOT_FOUND:提供商告知该密钥不存在。当响应来自被否定密钥的记录时,响应体包含 details.cached: true 和 details.retryAfterSeconds。参见 Pix 密钥出金保护机制 |
429 | 不存在的密钥过多 | TOO_MANY_PIX_KEYS_NOT_FOUND:因查询不存在的密钥过多,密钥出金已被暂时封锁。请等待 Retry-After 响应头指定的时间。参见 Pix 密钥出金保护机制 |
500 | 内部错误 | 处理过程中出现意外错误。重试前请先通过 externalId 查询 |
业务错误
除 HTTP 状态码外,API 还会在响应体中返回特定的错误码。部分错误以同步方式出现(在 API 响应中),另一些以异步方式出现(通过 webhook,当清算银行拒绝该操作时)。
同步错误(API 响应)
在 HTTP 响应中立即返回,状态码为 400(密钥出金保护机制中为 422 和 429):
错误码(code) | 消息 | 原因 |
|---|---|---|
PIX_DUPLICATE_EXTERNAL_ID | Esta transação já existe no sistema | 该 externalId 已在此账户中使用过(details.existingTransactionId 返回已存在的交易) |
PIX_INSUFFICIENT_BALANCE | Saldo insuficiente para realizar esta operação | 可用余额小于金额 + 手续费 |
PIX_TRANSACTION_LIMIT_EXCEEDED | Ultrapassa o limite máximo por transação | 金额超过配置的单笔交易限额 |
PIX_DAILY_LIMIT_EXCEEDED | Você atingiu o limite diário de transações | 当日累计超过每日限额 |
PIX_NIGHT_LIMIT_EXCEEDED | O limite noturno é mais restrito | 夜间操作超过降低后的限额(20:00–6:00,巴西利亚时间) |
PIX_LIMIT_EXCEEDED | Ultrapassa o limite permitido | 超出账户的其他限额 |
| 验证错误 | 按字段的消息 | 金额小于 0.01、超过 2 位小数、密钥格式与 keyType 不符、证件号无效等 |
PIX_KEY_NOT_FOUND | Chave Pix não encontrada | 状态 422。带 details.cached: true 时为保存的结果:请遵守 details.retryAfterSeconds。不带 cached 时来自本次调用中的提供商。参见 Pix 密钥出金保护机制 |
TOO_MANY_PIX_KEYS_NOT_FOUND | Muitas tentativas com chaves Pix inexistentes. Saque por chave bloqueado temporariamente; tente novamente em instantes. | 状态 429:因查询不存在的密钥过多,密钥出金已被暂时封锁。请等待 Retry-After 响应头指定的时间。参见 Pix 密钥出金保护机制 |
异步错误(通过 webhook)
当清算银行拒绝该操作时,交易状态变为 ERROR。errorCode 和 errorMessage 字段会在 Cash-Out webhook 的 payload 中发送。errorCode 的值因账户的清算机构而异,不应用于业务逻辑:请将其视为参考信息,并向用户展示 errorMessage。
{
"event": "CashOut",
"status": "ERROR",
"transactionId": "12345",
"externalId": "PAYMENT-987654-20240119",
"errorCode": "TAX_ID_MISMATCH",
"errorMessage": "O CPF/CNPJ informado não corresponde ao titular da conta.",
...
}部分清算机构返回的标准化错误码示例:
errorCode | errorMessage | 原因 | 可重试? |
|---|---|---|---|
TAX_ID_MISMATCH | O CPF/CNPJ informado não corresponde ao titular da conta. | details.document 与 PIX 密钥所有者不符。在其他核验持有人的清算机构中,不一致会以该清算机构自己的代码和消息返回 | 否 |
INVALID_TAX_ID | CPF ou CNPJ inválido. | 提交的证件号未通过银行校验 | 否 |
BLOCKED_ACCOUNT | A conta de destino está bloqueada e não pode receber transferências. | 目标账户被司法或行政冻结 | 否 |
ACCOUNT_CLOSED | A conta de destino está encerrada. | 目标账户已注销 | 否 |
ORDER_REJECTED | A transação foi rejeitada pelo banco destinatário. | 目标银行拒绝了该操作 | 否 |
PAYMENT_EXPIRED | A transação expirou antes de ser processada. | 超出处理时间 | 是 |
SETTLEMENT_TIMEOUT | O banco destinatário não respondeu a tempo. | 与目标银行通信超时 | 是 |
对于可重试的错误(PAYMENT_EXPIRED、SETTLEMENT_TIMEOUT),使用新的 externalId 重新发送交易是安全的。对于网络故障(无响应),请参阅下方的「安全重试」— 这些情况下请复用同一个 externalId。
发生异步错误时,交易会被自动取消(账本回滚),余额会被退回。无需采取任何额外操作 — 只需将失败原因通知最终用户即可。
请参阅 API 参考 获取响应字段的完整详情。
最佳实践
重要说明
- 最低金额: R$ 0,01 | 小数位: 最多 2 位
externalId: 最多 50 个字符,每个账户内唯一(幂等性)description: 最多 140 个字符(可选)details.name: 可选,最多 100 个字符 — 仅作信息用途,银行不会校验details.document: 在 API 中为可选,建议所有密钥类型都提供 — API 仅校验格式;与密钥持有人的比对取决于账户的清算机构- PHONE 密钥: 只发送区号 + 号码(10-11 位)。API 会自动添加
+55