PIX 违规(完整流程)
Owem Pay 中 PIX 违规生命周期的端到端视图 — 它如何产生、如何自动处理、何时封锁余额、如何抗辩以及如何在您的系统中对账。
一句话总结
PIX 违规是对方针对您收到的 PIX 报告的争议。它可能在解决之前对争议值产生预防性封锁。订阅 webhook pix.infraction.* 以在期限内采取行动。
1. 什么是 PIX 违规?
违规(BACEN 词汇中的 REFUND_REQUEST 类型)是付款方机构在 DICT 中注册的正式报告,声称您收到的 PIX 显示出欺诈、诈骗、操作错误或退款请求的迹象。
官方渠道是 BACEN DICT — 没有客户可以直接对您发起违规;它始终通过付款方机构和 BACEN 到达 Owem。
| 技术字段 | 描述 |
|---|---|
infraction_type | BACEN 中的违规类型。值:REFUND_REQUEST(退款请求)和 REFUND_CANCELLED(取消先前的请求) |
status | BACEN 中的状态。UPPERCASE 值:ACKNOWLEDGED(已识别,正在分析)、CLOSED(已完成)、CANCELLED(在分析前被对方取消) |
amount | 以子分计的争议值(不一定是原始 PIX 的总值) |
e2e_id | 被争议的原始 PIX 的 End-to-End ID |
defense_deadline | 提交抗辩的 BACEN 期限(ISO 8601 UTC) |
counterpart_ispb | 发起违规的付款方机构的 ISPB |
analysis_result | 关闭时的最终决定(CLOSED):AGREED(执行退款)或 DISAGREED(抗辩被接受,无退款) |
来源:schemas/pix_compliance/pix_infraction.ex。
不要与 MED 混淆
MED(特殊退款机制)是在确认的欺诈违规需要偿还时执行退款的监管机制 — 它是已接受违规的结果,不是初始事件。参见下方的 违规 ↔ MED 关系 部分。
2. Owem 如何接收违规
Owem 不通过推送接收违规。后端通过 worker Fluxiq.Workers.ComplianceSyncWorker 每 15 分钟主动轮询 DICT OnZ/BACEN。
发现流程:
BACEN DICT
▲
│ GET /v3/dict/infracao/list(每次调用 1000 条分页)
│
ComplianceSyncWorker (*/15 * * * *)
│
├─ upsert_infraction/2 → 持久化到 pix_infractions
│ ├─ 如果是新的(is_new=true):dispatch "pix.infraction.created"
│ └─ 如果是更新(status/analysis_result 改变):dispatch "pix.infraction.resolved"
│
└─ maybe_auto_deny_or_block/2 → 路由决定(见第 3 节)来源:workers/compliance_sync_worker.ex、use_cases/pix_compliance/pix_compliance.ex。
BACEN → Webhook 延迟
在对方开启的违规最多在 15 分钟内到达 Owem(ComplianceSyncWorker 的下一个周期)。一旦行在 pix_infractions 中插入/更新,就会触发 webhook pix.infraction.created。
3. 分类与自动操作
一旦检测到具有 ACKNOWLEDGED 或 OPEN 状态的 REFUND_REQUEST 违规,后端会在没有手动干预的情况下将其分类到三条自动路径之一:
3.1 您的交易中不存在 E2E → auto-deny
如果违规引用的 e2e_id 不在您的 transactions 表中(没有带该 E2E 的 PIX IN 到达您的系统),后端假设这是对方的操作错误并立即执行 auto-deny。
- OnZ 操作:
POST /v3/dict/infracao/{id},AnalysisResult=DISAGREED - 更新
pix_infractions→status=CLOSED、analysis_result=DISAGREED、resolved_at=now - 触发 webhook
pix.infraction.resolved - 不创建预防性封锁
- 不执行退款
3.2 值 ≤ R$ 1.000(阈值可配置)→ auto-deny
低值违规自动被拒绝。默认阈值为 R$ 1.000,00(10_000_000 子分,常量 @default_auto_deny_threshold),但可通过表 med_configurations(字段 min_threshold_amount)按账户或merchant 配置。
- 应用与 3.1 相同的 auto-deny 操作
- 发送给 BACEN 的标准理由是:"Verificado pelo time de compliance e sem evidencias concretas nao temos como fazer devolucao"
- 触发 webhook
pix.infraction.resolved - 不创建预防性封锁
- 收到的 PIX 值继续可用在余额中
每账户自定义阈值
处理合法高额票据的账户(例:高值市场)可以通过 Owem admin 申请增加阈值。对任何值都倾向于手动抗辩的账户可以申请 threshold=0(不自动拒绝)。
3.3 值 > R$ 1.000 且 E2E 存在 → MED 预防性封锁
这是影响您余额的路径。在检测到合格违规时,后端:
- 更新
pix_infractions.status为PROCESSING(避免在下个 sync 周期再次进入) - 在
med_cautelar_blocks中创建一行,带争议值 - 在 TigerBeetle 中创建 phantom hold — 针对受影响账户钱包的 pending 转账,减少可用余额(余额 中的
available)按争议值 - 触发 webhook
pix.refund.requested(查看载荷) - 触发 webhook
pix.infraction.created(查看载荷)
值会被封锁直到:
- BACEN 抗辩期限到期(见第 6 节)
- 您通过门户提交抗辩(见第 5 节)
- BACEN 解决违规(第 7 节)
- 对方在分析前取消(
status=CANCELLED)
来源:use_cases/pix_compliance/pix_compliance.ex:495-537、use_cases/pix_compliance/med/processor.ex。
4. 违规 ↔ MED 关系
违规和 MED(特殊退款机制)是同一周期的不同层:
| 层 | 它是什么 | 表 | 主要 webhook |
|---|---|---|---|
| 违规 | BACEN DICT 中的正式报告 | pix_infractions | pix.infraction.created |
| MED 预防性封锁 | 分析期间在 TigerBeetle 中的余额保留 | med_cautelar_blocks | pix.refund.requested |
| MED 退款 | AGREED 决定后执行的 PACS.004 | transactions(type=pix_return) | pix.refund.completed + pix.payout.returned |
[BACEN 中创建违规]
│
▼
ComplianceSyncWorker 检测(15 分钟轮询)
│
├─ pix.infraction.created(始终,当 is_new=true 时)
│
│ 如果 > R$ 1.000 且 E2E 存在:
▼
MED Processor → cautelar block + TB phantom hold
│
├─ pix.refund.requested(封锁已创建)
│
│ 等待决定(merchant 抗辩或期限过期或 auto-accept session 135)
▼
[解决]
│
├─ AGREED → PACS.004 dispatched
│ ├─ pix.refund.completed
│ └─ pix.payout.returned(E2E 中的 D 前缀)
│
└─ DISAGREED → 封锁已释放
└─ BlockRelease.release_for_e2e — TB phantom 的 void + 余额的释放
│
▼
pix.infraction.resolved(关闭时始终 — AGREED 或 DISAGREED)关系是按 E2E 的 1 对 1,不是按 ID 的 1 对 1
同一笔 PIX 交易(相同 e2e_id)不能同时有两个活跃的违规 — ComplianceSyncWorker 在 cautelar_block_exists_for_infraction?/1 中做 guard 以避免封锁重复。如果对方取消并开启具有相同 E2E 的新违规,新的封锁取代前一个。
另请参见:med-list 和 med-detail 用于通过 External API 查询预防性封锁。
5. merchant 如何抗辩
抗辩不通过 External API 提供
抗辩提交(上传证据 + 理由)仅通过 merchant portal(https://merchant.owem.com.br)。今天没有 External API 端点 POST /api/external/infractions/:id/defense。这是已知的限制 — 见第 8 节。
merchant portal 中的抗辩流程:
- 认证的操作员访问门户中的 Compliance → Infrações(
/compliance) - 查看
defense_deadline接近的ACKNOWLEDGED违规列表 - 点击违规并填写:
- 抗辩文本(最多 ~5000 字符,自由 — 作为
AnalysisDetails发送给 BACEN) - 附件(最多 5 个文件,每个 10MB — 如截屏、合同、对话历史等证据)
- 抗辩文本(最多 ~5000 字符,自由 — 作为
- 提交 → 后端:
- 将附件上传到 GCS 存储
- 调用 OnZ
POST /v3/dict/infracao/{id}/defense,带文本 + 附件 URL - 本地更新
pix_infractions.status=defense_submitted - 触发 webhook
pix.infraction.defense_submitted
来源:controllers/merchant/infractions/defense_controller.ex。
谁可以抗辩
在 merchant portal 中具有 infractions:write 权限的任何用户。子账户 operators(非 primary holder)只要受影响的账户在他们的 account_ids 中,也可以提交抗辩。primary holders 可以为 merchant 的任何账户抗辩。
6. 期限与自动过期
6.1 defense_deadline(BACEN 期限)
BACEN 定义 merchant 响应的期限。它在 webhook pix.infraction.created 的 defense_deadline 字段(ISO 8601 UTC)中提供,并传播到 pix_infractions.defense_deadline。
通常,此期限为违规创建后的 7 个日历日,但可能因类型(REFUND_REQUEST vs 严重欺诈)而不同。
6.2 通过 worker MedDefenseExpiration 自动过期
为避免机构的监管风险(因未在期限内响应而受 BACEN 罚款),Owem 每 5 分钟通过 Oban cron 运行 worker Fluxiq.Workers.MedDefenseExpiration:
- 搜索
deadline在接下来 30 分钟内且analysis_status仍为pending或defense_submitted的预防性封锁 - 对于每个封锁,强制裁决
founded(接受争议)并触发Med.execute_return/1→ PACS.004 发送到 BACEN - 释放封锁的余额(见 TB phantom void)
- 审计日志的 actor 是
system,不是 admin
结果:如果您没有及时抗辩,系统在 BACEN 期限到期前自动接受争议并退回值。
来源:workers/med_defense_expiration.ex(cron */5 * * * *)。
auto-accept 是保守的
默认行为是保护机构免受监管风险 — 这就是为什么默认是接受争议并退回。如果您需要不同的逻辑(例:总是自动抗辩),请联系 compliance@owem.com.br。
7. 解决与最终 webhook
违规总是在以下三个终止状态之一结束:
| 最终状态 | Analysis result | 余额发生了什么 | 触发的 webhook |
|---|---|---|---|
CLOSED | AGREED | 值通过 PACS.004 退回给对方 | pix.infraction.resolved + pix.refund.completed + pix.payout.returned |
CLOSED | DISAGREED | 封锁已释放,值返回 available | pix.infraction.resolved |
CANCELLED | null | 对方在分析前取消 — 封锁已释放 | pix.infraction.resolved |
在所有情况下,TB 预防性封锁通过 Fluxiq.UseCases.PixCompliance.Med.BlockRelease.release_for_e2e/2 释放,它在 TigerBeetle 中执行 pending 转账的 void 并更新 med_cautelar_blocks.status。
来源:use_cases/pix_compliance/med/block_release.ex。
AGREED 和 DISAGREED 之间的区别
- AGREED:您同意(明确或通过 auto-accept)退款。PIX OUT(
pacs.004)被执行,值离开您的账户,您收到pix.payout.returned,status="returned"。 - DISAGREED:您抗辩,BACEN 接受了抗辩,或违规被 auto-denied(≤R$1k 或 E2E 不存在)。值保留给您,不执行 PIX OUT。
8. 当前限制(External API)
功能 gap — 通过 External API 的操作
今天,在 /api/external/* 命名空间中只有通过 MED 的间接查询可用:
- 存在
GET /api/external/med(列出 MED)和GET /api/external/med/:id(MED 详情)— 都返回 MED 封锁,不直接返回违规 - 不存在
GET /api/external/infractions或POST /api/external/infractions/:id/defense - 违规的完整列表和抗辩流程仅通过 merchant portal 提供(JWT-based,非 API Key)
- Webhook
pix.infraction.*是自动通知的官方形式
如果您的用例需要通过 External API 自动化(例:以编程方式提交抗辩),请联系 compliance@owem.com.br 优先处理。
9. 相关 Webhook
以下三个事件是您在违规周期中的眼睛和耳朵。在 注册 Webhook 中订阅所有这些:
| 事件 | 何时触发 | 完整载荷 |
|---|---|---|
pix.infraction.created | 检测到新违规且创建预防性封锁(>R$1k 且 E2E 存在) | webhooks-payloads#pix-infraction-created |
pix.infraction.resolved | 状态转为 CLOSED 或 CANCELLED(AGREED、DISAGREED、auto-deny、auto-accept) | webhooks-payloads#pix-infraction-resolved |
pix.infraction.defense_submitted | 通过门户提交抗辩(merchant 或 admin) | webhooks-payloads#pix-infraction-defense-submitted |
相关事件(MED 层):
| 事件 | 何时触发 |
|---|---|
pix.refund.requested | 在 med_cautelar_blocks 中创建了预防性封锁(账户已保留余额) |
pix.refund.completed | PACS.004 退款已执行(AGREED 或 auto-accept) |
pix.payout.returned | PIX 已退回,E2E 前缀 D — 您的余额已借记以补偿付款方 |
≤R$1k 的静默 auto-deny
在 auto-deny ≤R$1k/E2E 不存在路径(第 3 节的路径 3.1 和 3.2)中,webhook pix.infraction.created 不触发。仅在关闭时触发 pix.infraction.resolved。这避免了在没有决定可做的情况下向 merchant 发送噪音。
如果您想要完全可见性(包括 auto-denies),请通过 merchant portal /compliance 查询 — 那里显示所有违规,与路径无关。
10. 在您的系统中对账
在您的 DRE / ERP 中对账违规的建议:
10.1 订阅 webhook
pix.infraction.created→ 将原始 PIX IN 标记为"已争议"(内部 flag)pix.refund.requested→ 在您的 ledger 中将值保留为"有争议"pix.infraction.resolved:- 如果
analysis_result=DISAGREED→ 释放保留,PIX IN 保持最终 - 如果
analysis_result=AGREED→ 将 PIX IN 标记为"已退回",创建出账
- 如果
pix.refund.completed→ 确认退款 PIX OUT 的离开
10.2 使用 e2e_id 作为关联键
每个违规都携带指向原始 PIX IN 的 e2e_id。在您的系统中将此字段用作 FK 以:
- 通过
GET /transactions/ref/:external_id(如果您存储了external_id)或直接通过 E2E 定位 PIX IN - 与
med_cautelar_blocks(med-detail 中的字段original_end_to_end_id)交叉查询 - 审计完整周期
10.3 保留 analysis_details 的历史
字段 analysis_details 包含发送给 BACEN 的理由。在 auto-deny 情况下,它带有标准消息。在提交抗辩的情况下,它带有您的操作员编写的文本。这些数据是可审计的,应保留以供监管可追溯性(LGPD Art. 46、BCB Resolução 4893)。
技术参考
backend/lib/fluxiq/use_cases/pix_compliance/pix_compliance.ex— 编排backend/lib/fluxiq/use_cases/pix_compliance/med/processor.ex— MED Processor(phantom hold + PACS.004)backend/lib/fluxiq/use_cases/pix_compliance/med/block_release.ex— 封锁释放backend/lib/fluxiq/workers/compliance_sync_worker.ex— DICT 轮询(15 分钟)backend/lib/fluxiq/workers/med_defense_expiration.ex— 自动过期(5 分钟)backend/lib/fluxiq/schemas/pix_compliance/pix_infraction.ex— schemabackend/lib/fluxiq/schemas/pix_compliance/med/med_cautelar_block.ex— 封锁的 schema
FAQ
我可以通过 Owem API 对另一个 merchant 发起违规吗?
不可以。 违规仅由付款方机构开启,而不是收款方。如果您是 PIX 的付款方并想争议,请使用您自己的客户服务渠道(银行应用、ombudsman)。Owem 仅在收款方端操作。
如果我不订阅 webhook pix.infraction.* 会发生什么?
系统继续工作 — auto-deny 和 auto-accept 自动发生。但您只有通过查看 /balance 中的 available 才会发现封锁(它会比安全的 balance 小)。要列出活跃封锁,请查询 GET /api/external/med。
为什么我的余额在没有解释的情况下下降?
如果您看到 available 小于 balance,可能有活跃的 MED 预防性封锁(>R$1k + 有效 E2E)。查询 GET /api/external/med 查看所有。订阅 pix.refund.requested 以在创建新封锁时实时通知。
我可以增加或禁用 R$1.000 阈值吗?
可以。请联系 compliance@owem.com.br。阈值存储在 med_configurations.min_threshold_amount 中,可以配置:
- 按 merchant(适用于所有账户)
- 按特定账户(merchant 的覆盖)
- 系统默认:R$1.000(10.000.000 子分)
增加阈值减少预防性封锁的数量(更多 auto-denies)。将阈值归零消除 auto-deny,使任何争议值都转到手动分析。
Owem 以我的名义做过决定吗?
是,在两个明确自动化的场景中:
- Auto-deny ≤R$1k 或 E2E 不存在 — 系统以 merchant 名义自动拒绝。发送给 BACEN 的标准理由是 "Verificado pelo time de compliance e sem evidencias concretas nao temos como fazer devolucao"。
- 接近 BACEN 期限的 auto-accept — 如果预防性封锁距离
defense_deadline<30 分钟且 merchant 没有提交抗辩,workerMedDefenseExpiration接受争议作为founded裁决并触发退款。
这两种行为都是保守的,以保护机构免受 BACEN 监管罚款。
merchant 的历史是否通过 API 可见?
部分 — 通过 GET /api/external/med(封锁)和 GET /api/external/transactions/ref/:external_id(原始交易和退款)。今天带有所有 BACEN 字段(包括 analysisDetails、fraudType、situationType)的违规完整历史仅在 merchant portal(/compliance)中可用。