按 ID 查询 Cash-Out
通过 transaction_id 查询 PIX 交易的详情和状态。
端点
GET /api/external/transactions/:id请求头
| 头 | 类型 | 必填 | 描述 |
|---|---|---|---|
Authorization | String | 是 | ApiKey {client_id}:{client_secret} |
路径参数
| 参数 | 类型 | 必填 | 描述 |
|---|---|---|---|
id | String | 是 | 交易 ID(transaction_id) |
示例
curl -X GET https://api.owem.com.br/api/external/transactions/PIXOUT20260309a1b2c3d4e5f6 \
-H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET"响应根据交易状态而不同。此端点按以下优先级顺序在以下源中搜索:
- 已结算交易(
transactions按transaction_id,或按内部十六进制 UUID —Base.decode16) - 进行中的 PIX OUT(
outbound_requestsstage 1 或 2)按transaction_id - QR Code(
qrcodes按tx_id)— 如果有paid_at+payment_end_to_end_id,通过 E2E 解析transactions中链接的实际交易;否则返回 "未付款 QR Code" 结构
如果在任何源中都没有匹配,返回 404。
按 transaction_id 查询不会返回被拒绝的交易
同步 BACEN 拒绝(PACS.002 RJCT)和 force-voids 持久化在 failed_transactions 中,当您按 transaction_id 查询时此端点找不到它们。使用替代端点:
- GET /transactions/e2e/:e2e_id — 按
end_to_end_id在failed_transactions中搜索 - GET /transactions/ref/:external_id — 按
external_id在failed_transactions中搜索 - Webhook
pix.payout.rejected/pix.payout.failed— 带完整载荷的实时通知
输入验证拒绝(立即 400/422)不经过此流程 — 它们已在 POST 中返回同步错误。
响应 -- 已结算交易 (200)
{
"worked": true,
"data": {
"id": "c7f3a8b12d4e4f6a9c1b3e5f7a9b1d3e",
"transaction_id": "PIXOUT20260309a1b2c3d4e5f6",
"end_to_end_id": "E37839059202603091530abcdef01",
"external_id": "order-9876",
"type": "pix",
"direction": "outbound",
"status": "settled",
"amount": 300000,
"fee_amount": 350,
"net_amount": 300350,
"description": "Pagamento fornecedor",
"counterparty_name": "Joao Silva",
"recipient_key": "12345678901",
"created_at": "2026-03-09T15:30:00Z",
"completed_at": "2026-03-09T15:30:02Z"
}
}| 字段 | 类型 | 描述 |
|---|---|---|
data.id | String | 交易内部 UUID,带连字符的规范格式,36 个字符(例:"c7f3a8b1-2d4e-4f6a-9c1b-3e5f7a9b1d3e")。控制器在序列化之前通过 Ecto.UUID.load/1 传递 16 字节二进制 — 格式不是 32 字符十六进制小写无连字符 |
data.transaction_id | String | 交易唯一标识(出款常见前缀 PIXOUT,入款 PIXIN,但不保证 — 始终读取字段,不要从前缀派生) |
data.end_to_end_id | String | SPI/BACEN 的 End-to-End 标识 |
data.external_id | String | 您系统中的标识符。未提供时为 null |
data.type | String | 交易类型。常见值:pix、pix_return。后端对字段 t.type 执行 passthrough — 遗留记录可能返回其他值 |
data.direction | String | outbound(cash-out)、inbound(cash-in)、credit、debit。后两者在字段 t.direction 未持久化且后端通过 Helpers.infer_direction/2 基于 account_id × to_account_id 推断时出现 |
data.status | String | 归一化状态(见下表) |
data.amount | Integer | 值,以基础单位(÷ 10.000 得到雷亚尔)。300000 = R$ 30,00 |
data.fee_amount | Integer | 收取的手续费,以基础单位 |
data.net_amount | Integer | 净额,以基础单位 |
data.description | String | 创建时填写的描述 |
data.counterparty_name | String | 对手方姓名(付款方或收款方) |
data.recipient_key | String | 收款方 PIX 密钥(仅出款) |
data.created_at | String | 创建日期(ISO 8601) |
data.completed_at | String | 完成日期(ISO 8601),待处理时为 null |
响应 -- 进行中的 PIX OUT (200)
当交易仍在处理时(BACEN 确认之前)返回。
{
"worked": true,
"data": {
"status": "processing",
"transaction_id": "PIXOUT20260309a1b2c3d4e5f6",
"end_to_end_id": "E37839059202603091530abcdef01",
"amount": 300000,
"fee_amount": 350,
"net_amount": 300350,
"external_id": "order-9876",
"pix_key": "12345678901",
"description": "Pagamento fornecedor",
"type": "pix",
"direction": "outbound",
"payment_status": "processing",
"started_at": "2026-03-09T15:30:00Z",
"recipient": {
"name": "Joao Silva",
"key": "12345678901",
"key_type": "cpf"
}
}
}| 附加字段 | 类型 | 描述 |
|---|---|---|
data.recipient | Object | 通过 DICT 解析的收款方数据 |
data.recipient.name | String | 密钥持有人姓名 |
data.recipient.key | String | 使用的 PIX 密钥 |
data.recipient.key_type | String | 密钥类型(cpf、cnpj、email、phone、evp) |
data.started_at | String | 处理开始日期(ISO 8601) |
响应 -- 被拒绝的交易 (200)
由端点 /transactions/e2e/:e2e_id 和 GET /transactions/ref/:external_id 在 PIX 被 SPI 拒绝或处理期间失败时返回。GET /transactions/:id 不返回(见上文关于 failed_transactions 的警告)。
{
"worked": true,
"data": {
"status": "failed",
"transaction_id": "PIXOUT20260309a1b2c3d4e5f6",
"end_to_end_id": "E37839059202603091530abcdef01",
"amount": 300000,
"fee_amount": 350,
"external_id": "order-9876",
"type": "pix",
"direction": "outbound",
"payment_status": "failed",
"failure_reason": "rejected: AB03",
"reason_code": "AB03",
"reason_description": "Aborted by PSP of creditor",
"started_at": "2026-03-09T15:30:00Z",
"failed_at": "2026-03-09T15:30:05Z",
"recipient": {
"name": "Joao Silva",
"key": "12345678901"
}
}
}| 附加字段 | 类型 | 描述 |
|---|---|---|
data.failure_reason | String | 拒绝的原始原因(格式:BACEN 代码为 "rejected: {CODE}",或集成描述如 "dict_key_not_found"、"ambiguous key"、"insufficient balance") |
data.reason_code | String | 从 failure_reason 提取的结构化代码。对于 BACEN 拒绝,遵循 ISO 20022(AC03、AB03、ED05、DUPL 等)。当 failure_reason 不包含结构化代码时为 null |
data.reason_description | String | reason_code 的人类描述,英文(在 Fluxiq.UseCases.Pix.ReasonCodes 中集中)。示例:"Invalid creditor account number"(AC03)、"Aborted by PSP of creditor"(AB03)、"Settlement failed"(ED05)。代码未识别时为 null |
data.recipient | Object | 收款方数据(可用时)。在非常早期的故障中可能省略 |
data.failed_at | String | 拒绝日期/时间(ISO 8601) |
拒绝代码的结构
字段 reason_code 和 reason_description 派生自集中在 Fluxiq.UseCases.Pix.ReasonCodes 中的字典。用于程序化错误路由。仅将 failure_reason 用于日志和诊断。完整代码表见 PIX Cash-Out 按密钥 -- 错误代码。
响应 -- 未付款 QR Code (200)
当 ID 对应于尚未付款的 QR Code 时返回。
{
"worked": true,
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"transaction_id": "7popu57v6us7p6pcicgq12345",
"end_to_end_id": null,
"external_id": "order-9876",
"type": "pix_qrcode",
"status": "pending",
"amount": 300000,
"fee_amount": 0,
"net_amount": 300000,
"description": "Cobrança PIX",
"direction": "credit",
"counterparty_name": null,
"recipient_key": "12345678901",
"created_at": "2026-03-09T15:30:00Z",
"completed_at": null
}
}| QR 状态 | 描述 |
|---|---|
pending | QR Code 激活,等待付款 |
settled | QR Code 填充了 paid_at,但控制器无法通过 payment_end_to_end_id 解析 transactions 中的真实交易。在 Phase 1 ACSP(ACK to BACEN)和 Phase 2 ACCC(PG MERGE 完成)之间的窗口中,或当 QR↔TX 链接因其他原因失败时发生。活跃代码路径(show_transaction_controller.ex:83)— 不要当作错误处理;几秒钟后重新查询,后端应正常返回 "已结算交易" 结构 |
expired | QR Code 已过期(默认 TTL 60 分钟 — 可通过 account.qrcode_expiration_seconds 按账户配置) |
cancelled | QR Code 手动取消(或在 2026-04 静态→动态迁移期间批量取消) |
注:当 QR 已付款且后端可以通过 E2E 查找真实交易时,响应是上面的 "已结算交易" 结构(status: "settled" 和真实的 transaction_id,而不是 QR 的 tx_id)。仅当查找返回 nil 时,才会回到 "未付款 QR Code" 结构,status: "settled"。
status 字段的值
GET 的状态取决于源
GET /transactions/:id 在 4 个不同的表中搜索,并根据找到交易的源返回不同的状态。所有源之间没有统一的词汇。
| 状态 | 源 | 含义 |
|---|---|---|
processing | outbound_requests(stage 1 或 2) | 已发送 PACS.008,等待 BACEN 确认。余额处于 hold |
processing | outbound_requests(stage 4 — 重试队列) | 由 ClientLimiter / DictBucket.Guard 限制,等待自动重试(Oban PixOutRetryWorker,TTL 120min)。返回的结构与 stage 1/2 相同 — 通过入队时触发的 webhook pix.payout.queued 区分 |
settled | transactions(内部状态 1) | BACEN 已确认结算,余额已移动。最终成功状态 |
pending | transactions(内部状态 2) | 罕见情况 — 在提升为 settled 之前的中间状态的交易 |
failed | transactions(内部状态 3)或 failed_transactions | 最终失败状态。由 BACEN 拒绝(PACS.002 RJCT)、孤儿 force-voided(>30min 在 hold 中)、重试队列过期(DICT_QUEUE_TIMEOUT)或内部错误 |
兼容性说明:遗留记录(在词汇统一之前)可能显示 status: "completed" 或 status: "accepted"。将它们视为等同于 "settled"。
与 webhook 的对应
pix.payout.queued在请求处于outbound_requests.stage=4时对应于 GET 中的status: "processing"pix.payout.confirmed始终对应于 GET 中的status: "settled"pix.payout.failed和pix.payout.rejected始终对应于 GET 中的status: "failed"
对于 QR Code(cash-in,超出 PIX OUT 范围),qrcodes 表添加 4 个值:pending、settled、expired、cancelled — 在上面的 "未付款 QR Code" 部分描述。
错误响应 -- 404
{
"worked": false,
"detail": "Transação não encontrada"
}