PIX Cash-Out 复制粘贴码
使用从 PIX QR Code 中提取的 EMV 代码(复制粘贴码)执行 PIX 转账。
端点
POST /api/external/pix/cash-out请求头
| 头 | 类型 | 必填 | 描述 |
|---|---|---|---|
Authorization | String | 是 | ApiKey {client_id}:{client_secret} |
Content-Type | String | 是 | application/json |
hmac | String | 是 | body 的 HMAC-SHA512 签名(十六进制) |
Idempotency-Key | String | 否 | 避免重复处理的唯一键(最大 256 字符)。行为与 PIX Cash-Out 按密钥 中记录的相同 |
必需权限
API Key 必须具有 transfer:write 权限才能发送 PIX。没有该权限,请求返回 403 Forbidden。参见 如何配置权限。
请求体
| 字段 | 类型 | 必填 | 描述 |
|---|---|---|---|
amount | Integer | 是 | 金额,以**分(centavos)**为单位。R$ 30,00 = 3000。在动态 QR 中,此值会被 QR URL 的查询覆盖 — 见下文 |
emv | String | 是 | PIX QR Code 的 EMV 复制粘贴码。别名 codigo_copia_cola 也被接受,但 emv 是规范的 — 请优先使用 |
description | String | 否 | 转账描述(最多 140 个字符) |
external_id | String | 否 | 您系统中的标识符,用于追踪。trim 后最多 128 字符。仅 a-zA-Z0-9._:- 字符。在响应和 webhook 中返回。无效值会被静默丢弃(交易继续,external_id: null)。详见 PIX Cash-Out 按密钥。 |
end_to_end_id | String | 否 | BACEN 格式的 End-to-End ID。建议省略 — 后端会确定性生成。仅在协调的重新处理场景中发送 |
复制粘贴码
emv 字段接受从 PIX QR Code 复制的完整 EMV 代码。API 自动从 EMV 载荷中提取 PIX 密钥、收款方数据和收款的原始值。后端在继续之前验证 CRC-16/CCITT-FFFF 和 TLV 结构。
静态 QR vs 动态 QR
API 以不同方式处理这两种类型:
静态 QR(PoIM=11,无 URL):
- PIX 密钥和 metadata 来自 EMV 载荷本身
- 请求中发送的
amount被直接使用作为转账值 - 无外部查询 — DICT 验证后立即结算
动态 QR(PoIM=12,tag 26/25 中有 URL):
- 后端对嵌入 EMV 载荷中的 URL 执行
Provider.consult_qrcode并接收 JWS(BACEN Manual 2.9.0) - 权威值来自 JWS 响应(
valor.original)并覆盖客户端发送的amount - 如果 URL 查询失败(超时、JWS 无效、QR 在发行方已过期),返回 HTTP 422 及
detail: "QR Code dinamico nao pode ser resolvido" - 使用覆盖值作为真相来源 — 响应中的
net_amount反映实际收取的值
动态 QR 不要依赖发送的值
在动态 QR 中,始终查询 POST 响应以获得有效的 amount。后端会忽略发送值与 QR 发行方返回值之间的任何差异 — 这是 BACEN 的要求。
货币值
输入值以分为单位(R$ 1,00 = 100)。响应值以基础单位为单位(R$ 1,00 = 10000)。要将响应转换为雷亚尔,请除以 10.000。绝不使用浮点数。
示例
curl -X POST https://api.owem.com.br/api/external/pix/cash-out \
-H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" \
-H "hmac: $HMAC" \
-d '{
"amount": 3000,
"emv": "00020126580014br.gov.bcb.pix0136a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d5204000053039865802BR5913NOME RECEBEDOR6008BRASILIA62070503***6304ABCD",
"description": "Pagamento via QR Code",
"external_id": "invoice-4521"
}'成功响应 -- 200 / 202
{
"worked": true,
"final": false,
"transaction_id": "PIXOUT20260309a1b2c3d4e5f6",
"end_to_end_id": "E37839059202603091530abcdef01",
"external_id": "invoice-4521",
"amount": 300000,
"fee_amount": 350,
"net_amount": 300350,
"status": "accepted",
"detail": "PIX enviado para processamento"
}HTTP 200 vs 202
- HTTP 200:交易已结算(
final: true,status: "settled")。 - HTTP 202:交易已被接受处理(
final: false)。status可能为"accepted"(正常流程)、"queued"(DICT rate-limit — 每 3 秒自动重试最多 120 分钟,session 155)或"pending_approval"(等待批准)。通过轮询或 webhook 跟踪状态。
| 字段 | 类型 | 描述 |
|---|---|---|
worked | Boolean | true 表示请求已被接受 |
final | Boolean | 当交易达到终止状态(已结算或已拒绝)时为 true。仍在处理中时为 false |
transaction_id | String | 交易唯一标识 |
end_to_end_id | String | SPI/BACEN 的 End-to-End 标识 |
external_id | String | 您的标识符,按发送原样返回。未提供时为 null |
amount | Integer | 转账值,以基础单位(÷ 10.000 得到雷亚尔)。300000 = R$ 30,00。在动态 QR 中,反映 JWS 查询返回的权威值 — 可能与 request 中发送的 amount 不同 |
fee_amount | Integer | 收取的手续费,以基础单位(÷ 10.000 得到雷亚尔) |
net_amount | Integer | 付款账户总借记值(amount + fee_amount)— 不是收款方收到的值。参见 PIX Cash-Out 按密钥 了解 cash-out 和 cash-in 之间的语义不对称性 |
status | String | 其中之一:accepted、settled、queued、pending_approval。详见 PIX Cash-Out 按密钥 |
detail | String | 描述消息 |
拒绝代码
EMV 的 cash-out 拒绝遵循与 PIX Cash-Out 按密钥 完全相同的标准 — 两种 body 格式(格式 A {status: "failed", errors: [...]} 和格式 B {errors: {bad_request: "..."}})、三类错误(验证 / 集成 / rate-limit 已入队)以及相同的 UPPERCASE × lowercase 词汇。下面的项目仅突出显示EMV 路径特有的代码。
EMV 特有的验证错误(HTTP 400 / 422)
| HTTP | 格式 | 带代码的字段 | 含义 |
|---|---|---|---|
| 400 | B | errors.bad_request: "invalid emv payload" | EMV 载荷格式错误(CRC-16 无效、TLV 不完整、缺少必需 tag)— 在 Orchestrator 之前由 parser 检测 |
| 400 | B | errors.bad_request: "invalid or missing amount" | amount 缺失、零、负数或非整数(静态 QR) |
| 422 | A | errors[0].code: "same_institution_transfer" | QR 由 Owem 账户生成 — 不支持同机构 PIX(使用 TEF)。注:HTTP 422(非 400),结构 {status: "failed", errors: [{code: "same_institution_transfer", params: []}]} |
| 422 | — | detail: "QR Code dinamico nao pode ser resolvido" | 查询动态 QR 的 URL 失败(超时、JWS 无效、QR 在发行方已过期)— 此路径在控制器中由 Orchestrator 之前处理,返回遗留结构 {errors: {unprocessable_entity: "..."}} |
| 422 | A | errors[0].code: "insufficient_balance" | 可用余额小于 amount + fee_amount |
其他错误
对于代码 dict_key_not_found、dict_key_blocked、dict_rate_limited(同步)、dict_bucket_exhausted(同步)、dict_lookup_failed、provider_rejected、provider_schema_error、provider_unknown_error — 全部以格式 A 返回 HTTP 400(不是 429 或 503)。详见 PIX Cash-Out 按密钥 — 与 DICT provider 的集成错误。
带自动重试的 rate-limit(HTTP 202 queued)
当每 merchant 的 ClientLimiter(默认 DICT_CLIENT_MAX_PER_MIN=120)或全局的 DictBucket.Guard(refill DICT_BUCKET_REFILL_RATE=18/min,capacity 250)在到达 OnZ 之前检测到超限时,cash-out 会被加入 Fluxiq.Workers.PixOutRetryWorker 队列并返回 HTTP 202,status: "queued"。flag pix_out_retry_queue_enabled 自 session 155 起在 PRD 中为 ON。详见 PIX Cash-Out 按密钥 — 带自动重试的 rate-limit。
异步 BACEN 拒绝
初始接受(HTTP 202)后,SPI/BACEN 可能通过 PACS.002 RJCT 拒绝。此拒绝:
- 不会改变原始 HTTP 响应(已作为 202 发送)
- 通过
GET /transactions/:id以status: "failed"+reason_code(AC03、AB03、ED05 等)+reason_description(英文)显示 - 触发 webhook
pix.payout.rejected,带相同的结构化字段
参见 按 ID 查询 Cash-Out 获取完整的 BACEN 代码列表。
下一步
创建转账后,通过以下方式跟踪状态:
- 按 ID 查询
- 按 E2E ID 查询
- 按 Tag 查询
- 按 External ID 查询 --
GET /api/external/transactions/ref/{external_id}
或通过 Webhook 自动接收确认。