Journal OTC
Pré-requisitos de acesso
- Permissão (módulo):
viewOtc - Licença/Feature: Nenhuma
- Contêiner do menu: GERAL → grupo Jornais de transações → Journal OTC (
/otc-journal)
O que é / quando usar
É o desk de OTC (balcão) do BackOffice: lista as ordens de câmbio cripto↔fiat (tipicamente USDT ↔ BRL), com a taxa travada, o total cobrado do cliente, o total no provedor, o spread (receita da casa) e o status interno de cada ordem. Além de consultar, o operador executa ações administrativas sobre uma ordem: forçar atualização de status, cancelar, liberar reserva e — em modo interno — confirmar/liquidar a operação.
As ordens podem operar em dois modos:
- ENOR — liquidação via provedor externo (eNor). O ciclo de vida é dirigido pelo scheduler de polling que consulta o provedor.
- INTERNAL — liquidação in-house: a plataforma debita o token de pagamento do usuário, credita a tesouraria e paga o usuário no token de destino a partir da tesouraria.
Pré-condições
- Permissão:
viewOtc(permissão dupla — enum CPM + módulo dinâmico no DB). As ações de mutação (cancelar/confirmar/liberar) são sensíveis e podem exigir re-autenticação (step-up senha+MFA) conforme política do tenant; o gating final é validado no API-Gateway. - Licença/Feature: nenhuma.
- Dependências de outras telas: Nenhuma. As ordens nascem do fluxo de OTC (PaymentManagementService). Para o modo INTERNAL liquidar, a configuração
customerToPay(conta-tesouraria) precisa existir.
Passo a passo
- Acesse Jornais de transações → Journal OTC.
- Confira os cards de resumo no topo (totais do período filtrado).
- Aplique filtros: período (de/até), lado (compra/venda), ID do usuário, faixa de USDT e chips de status (multi-seleção). Clique em Aplicar.
- Clique em Exportar CSV para baixar a lista filtrada.
- Clique no ícone de detalhe (
receipt_long) de uma ordem para abrir o modal com a timeline de eventos e as ações administrativas.
Cards de resumo
| Card | O que mostra |
|---|---|
| Total de ordens | Quantidade de ordens no período. |
| Total USDT | Volume total em USDT. |
| Total BRL (cliente) | Soma do que foi cobrado dos clientes. |
| Total spread (BRL) | Receita da casa no período (delta entre preço do cliente e preço do provedor). |
| Entregues / Pendentes / Falhas / Canceladas | Contagens por desfecho. |
Filtros e colunas
| Filtro / Coluna | O que mostra / faz | Origem do dado |
|---|---|---|
| De / Até | Janela de datas. | from / to. |
Lado (side) | client_buys_usdt (BUY) ou client_sells_usdt (SELL). | side. |
| ID do usuário | Filtra por usuário. | userId. |
| USDT mín/máx | Faixa de volume. | usdtMin / usdtMax. |
| Chips de status | Multi-seleção: LOCKED, PIX_SCHEDULED, PIX_SENT, ONCHAIN_PENDING, ONCHAIN_CONFIRMED, PAYMENT_SENT, DELIVERED, CANCELLED, EXPIRED, FAILED. | status[]. |
Data (createdAt) | Criação da ordem. | createdAt. |
| Usuário | Dono da ordem. | userId. |
Modo (otcMode) | ENOR ou INTERNAL. | otcMode. |
| Lado | BUY/SELL. | side. |
| USDT | Quantidade negociada. | usdtAmount. |
Taxa travada (lockedRate) | Câmbio congelado para o cliente. | lockedRate. |
| BRL cliente | Total cobrado do cliente. | brlTotal. |
| BRL provedor (eNor) | Total no provedor. | enorBrlTotal (cai para brlTotal se ausente). |
| Spread | Valor + percentual (bps/100). Vazio (—) quando zero. | spreadAmount / spreadBps. |
| Status interno | Estado da ordem (cor por classe). | internalStatus. |
| Detalhe (ação) | Abre o modal da ordem. | openDetail(o). |
Ações e modais
O modal de detalhe (OtcOrderDetailModalComponent) mostra a ordem, o pagamento, a liquidação e a timeline de eventos (cada evento expansível com request/response JSON). As ações abrem um diálogo de confirmação com campo de motivo (auditado) e um tom visual (azul/laranja/vermelho):
| Ação | Quando aparece | O que faz no backend |
|---|---|---|
Forçar atualização (force-refresh) | Sempre que há enorOrderId. | Reconsulta o status no provedor (eNor) e atualiza o estado local; se vier client_delivered/cancelled, marca DELIVERED/CANCELLED. Útil quando o polling travou. Registra evento ADMIN_FORCE_REFRESH. |
Cancelar (cancel) | Status em LOCKED, PIX_SCHEDULED, PIX_SENT, ONCHAIN_PENDING, ONCHAIN_CONFIRMED, PAYMENT_SENT. | Cancela a ordem no provedor e libera a reserva do usuário (best-effort) se houver reserveReference. Marca CANCELLED. Evento ADMIN_CANCEL. |
Liberar reserva (release-reserve) | Quando há reserva pendente ou status em CANCELLED/FAILED/EXPIRED. | Libera a reserva travada do usuário (para ordens com reserva "pendurada"). Evento ADMIN_RELEASE_RESERVE. |
Confirmar negociação (confirm-trade) | Apenas modo INTERNAL e status LOCKED. | Liquida a ordem in-house: (1) compromete a reserva do usuário para a tesouraria (debita o token de pagamento, credita customerToPay); (2) paga o usuário no token de destino a partir da tesouraria; (3) marca DELIVERED. Evento ADMIN_CONFIRM_TRADE. |
Todas as ações pedem confirmação e aceitam um motivo, que é gravado como evento de auditoria na ordem.
Regras de negócio / cuidados
Confirmar negociação (INTERNAL) é liquidação real e irreversível
confirm-trademove valor de fato: debita o usuário, credita a tesouraria e paga o usuário no token de destino. O backend rejeita a ação se a ordem não forINTERNAL, não estiverLOCKED, não tiver reserva, ou já estiver entregue — justamente para evitar dupla liquidação. Confirme o modo e o status antes de agir.- Ordens ENOR liquidam pelo scheduler de polling, nunca por
confirm-trade. Não tente liquidar uma ordem ENOR manualmente por aqui.
Atenção
- O spread é a receita da casa: o delta entre o preço cobrado do cliente (
brlTotal) e o preço do provedor (enorBrlTotal). É exatamente o padrão de "preço-limite × execução" — não confunda o spread com erro de cálculo. - Cancelar já tenta liberar a reserva; Liberar reserva é o reparo isolado para reservas que ficaram penduradas após
CANCELLED/FAILED/EXPIRED. EXPIREDsignifica que a janela da taxa travada venceu antes da liquidação.
- Valores financeiros:
usdtAmount,lockedRate,brlTotal,spreadAmountsão tratados como BigNumber/strings de alta precisão no backend — a UI formata apenas para leitura. Não reconcilie pelo valor exibido sem conferir a precisão. - Idempotência: os passos de liquidação interna se apoiam em referências de reserva/transação idempotentes; um retry que retorne
E00021("already processed") é sucesso.
Exemplos
Cenário 1 — Forçar atualização de uma ordem ENOR travada em PIX_SENT
Uma ordem ENOR ficou PIX_SENT por horas porque o polling não evoluiu. Abra o detalhe → Forçar atualização. O backend reconsulta o provedor: se já houver client_delivered, a ordem passa a DELIVERED e a timeline ganha o evento ADMIN_FORCE_REFRESH. Sem mover valor — apenas sincroniza o status.
Cenário 2 — Liquidar uma ordem INTERNAL (compra de USDT) confirmando a negociação
Cliente comprou USDT em modo INTERNAL; a ordem está LOCKED com reserva criada. Abra o detalhe → Confirmar negociação → informe o motivo. O backend: (1) compromete a reserva do usuário para a tesouraria customerToPay (debita BRL/token de pagamento, credita a tesouraria); (2) paga o USDT ao usuário a partir da tesouraria; (3) marca DELIVERED. A timeline registra ADMIN_CONFIRM_TRADE. Operação irreversível.
Cenário 3 — Cancelar e liberar reserva pendurada
Uma ordem FAILED deixou uma reserva travada no saldo do usuário. Abra o detalhe → Liberar reserva → informe o motivo. O backend libera a reserva (best-effort) e grava ADMIN_RELEASE_RESERVE. Se a ordem ainda estivesse ativa, o caminho seria Cancelar, que já libera a reserva no mesmo passo.