Pular para conteúdo

13 — OFX (extrato bancário e conciliação)

O módulo pycobranca.ofx lê o extrato bancário OFX e concilia as transações com os boletos emitidos. Complementa o retorno CNAB: enquanto o retorno traz o que o banco processou dos títulos registrados, o OFX traz o extrato da conta — útil para conferir recebimentos que chegaram por boleto.

Recursos

  • OFX v1 (SGML) e v2 (XML) — os campos-folha são lidos por um tokenizador tolerante que atende os dois formatos.
  • Encoding: normaliza Latin‑1 → UTF‑8 (padrão dos bancos brasileiros).
  • Extração de nosso número do campo memo, por banco (Sicoob, Itaú, BB, Bradesco, Caixa) e um padrão genérico.
  • Conciliação contra os nossos números emitidos, com a lista de pendentes.
  • Python puro, sem dependências novas.

Ler um extrato

from pycobranca.ofx import Extrato

extrato = Extrato.ler("extrato.ofx")  # caminho, bytes ou objeto com .read()

extrato.org  # "ITAU"
extrato.fid  # "341"
extrato.agencia  # "1234"  (BRANCHID)
extrato.conta_numero  # "56789-0"
extrato.saldo_valor  # Decimal("5230.75")
extrato.periodo  # (date(2025, 2, 5), date(2025, 2, 10))

for t in extrato.transacoes:
    # tipo: "CREDIT" | "DEBIT" (derivado do sinal do valor); valor sempre positivo
    print(t.tipo, t.data, t.valor, t.nosso_numero_extraido, t.memo)

Extrato.ler(..., somente_creditos=True) mantém apenas os recebimentos. As propriedades extrato.creditos e extrato.debitos filtram sem reparsear.

Validação: se o conteúdo não tiver marcação de OFX (<OFX>/OFXHEADER), ler/parse levantam OFXInvalido em vez de devolver um extrato vazio — assim o consumidor distingue arquivo inválido de extrato válido sem transações. Ver contrato de erros.

Serialização

extrato.to_dict() devolve uma estrutura JSON-friendly (banco, conta, período, saldo, transações e resumo com totais/somatórios), pronta para consumo via REST. O contrato é verificado por testes contra os schemas ExtratoOFX e TransacaoOFX de contrato_rest.json.

Conciliar com os boletos emitidos

from pycobranca.ofx import Extrato, concilia

extrato = Extrato.ler("extrato.ofx")
resultado = concilia(extrato, ["12345678", "0000087654"])  # nossos números emitidos

resultado.conciliadas  # [(Transacao, nosso_numero), ...]
resultado.nao_conciliadas  # transações sem correspondência
resultado.pendentes  # nossos números esperados que não apareceram no extrato
resultado.to_dict()  # visão JSON-friendly

A correspondência considera o nosso número extraído do memo (comparação exata ou sem zeros à esquerda) e, como fallback, a presença do nosso número no texto do memo. Por padrão só os créditos entram na conciliação (somente_creditos=True).

Onde a lógica vive

Todo o processamento de OFX (parse, extração de nosso número e conciliação) vive no pacote, sem dependência de HTTP. A lógica é testada uma vez e reaproveitada por qualquer consumidor — um script, um SDK ou um serviço REST — que apenas chama Extrato.ler(...)/concilia(...) e serializa com to_dict().