cnabpayment

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 25, 2026 License: Apache-2.0 Imports: 4 Imported by: 0

README

cnab-payment

cnab-payment is a Go library for streaming CNAB payment return files into a canonical, bank-agnostic stream of payment occurrences.

CNAB ("Centro Nacional de Automação Bancária") is a family of fixed-width file formats used by Brazilian banks. Every bank implements its own layout, which forces integrators to write bespoke parsing code per bank. This library provides a single streaming entry point that reads a file's first record, resolves the matching layout, and hands the rest of the stream off to a pluggable layout parser — while exposing one small, stable API regardless of which bank produced the file.

import "github.com/cnab-dev/cnab-payment"

p := cnabpayment.NewParser(cnabpayment.NewItau240LayoutParser())

result, err := p.Parse(r, func(o cnabpayment.Occurrence) error {
    fmt.Println(o.PaymentID, o.Type, o.RawType, o.CreatedAt, o.Format)
    return nil
}, nil)

Status

Early foundation, first layout parser implemented. The public API, streaming infrastructure, and the first layout parser — Itaú CNAB240 payment return files — are in place. Other banks and CNAB400 are not supported yet.

The current milestone is scoped strictly to payment return parsing. Remittance file generation (exporting) is out of scope.

A single public package

github.com/cnab-dev/cnab-payment (package cnabpayment) is the only importable package. Everything else — the domain model, the scanner, the LayoutParser/Parser orchestration, and every built-in layout parser's actual implementation — lives under internal/ and is not part of the public API:

internal/core        canonical domain model + physical-record scanner:
                       Occurrence, ParseResult, RecordError,
                       OccurrenceHandler/RecordErrorHandler, Scanner, Record
                       (re-exported at root as type aliases)
internal/registry     the LayoutParser interface + the Parser that resolves
                        and delegates to one; imports only internal/core
                        (re-exported at root as type aliases)
internal/itau240        the built-in Itaú CNAB240 layout parser: all Itaú-
                         and CNAB240-specific knowledge (record grammar,
                         detail grouping, field extraction) lives here,
                         hidden from the public API

The root package re-exports Occurrence, OccurrenceType, ParseResult, RecordError, OccurrenceHandler, RecordErrorHandler, Scanner, Record, LayoutParser, and Parser from internal/core/internal/registry via Go type aliases — they're the same types, just reachable without an extra import. This is also what lets a custom, external LayoutParser implementation satisfy the interface without importing anything beyond the root package, and what lets internal/itau240 satisfy LayoutParser structurally without ever importing internal/registry or the root package — keeping the whole module's internal dependency graph a one-way DAG (root → internal/registry/internal/itau240internal/core), with no cycle possible.

NewParser: explicit, always

func NewParser(first LayoutParser, rest ...LayoutParser) *Parser

There is no default and no auto-loading — NewParser() doesn't compile; at least one LayoutParser is required. Built-in layout parsers are regular values you construct explicitly, just like custom ones, so they compose freely in the same Parser:

cnabpayment.NewParser(cnabpayment.NewItau240LayoutParser())

cnabpayment.NewParser(myCustomLayoutParser)

cnabpayment.NewParser(myCustomLayoutParser, cnabpayment.NewItau240LayoutParser())

Architecture

io.Reader
    │
    ▼
Scanner            reads one physical record at a time
    │
    ▼
first Record
    │
    ▼
Parser             resolves a LayoutParser via Detect(first)
    │
    ▼
LayoutParser        takes over from the scanner's current position
    │
    ▼
Occurrences
  • Streaming, not buffering. The scanner reads one physical record at a time from an io.Reader; nothing peeks, rewinds, or requires an io.Seeker. The first record is read exactly once, then handed to the resolved layout parser so it isn't read again.
  • Constant memory. No file header, batch, record set, or trailer is held in memory as a whole; occurrences are emitted as soon as they're fully parsed.
  • Callback model. OccurrenceHandler receives every occurrence as soon as it's ready; RecordErrorHandler receives recoverable, record-level errors. Returning an error from either callback aborts parsing. A nil RecordErrorHandler means such errors are ignored and parsing continues. error returned from Parse itself is reserved for fatal failures.
  • Extensible through layout parsers. All bank-specific knowledge lives behind the LayoutParser interface. The root package and its internal/ infrastructure contain no bank-specific logic.

Public API

package cnabpayment

func NewParser(first LayoutParser, rest ...LayoutParser) *Parser

func (p *Parser) Parse(
    r io.Reader,
    onOccurrence OccurrenceHandler,
    onRecordError RecordErrorHandler,
) (ParseResult, error)

type LayoutParser interface {
    Detect(first Record) bool

    Parse(
        s Scanner,
        first Record,
        onOccurrence OccurrenceHandler,
        onRecordError RecordErrorHandler,
    ) (ParseResult, error)
}

Built-in layout parsers

Itaú CNAB240

NewItau240LayoutParser() constructs it; the actual implementation lives in internal/itau240 and is not importable directly. It detects and streams Itaú CNAB240 payment return files, extracting per logical payment a PaymentID and an occurrence RawType from the batch's mandatory segment (A for crédito em conta, J for boleto, or O for boleto de outro banco), plus an optional ConfirmationID and ExternalID from a Segment Z detail record when one follows it — ConfirmationID is a bank-issued confirmation identifier, ExternalID a bank-generated identifier, both empty when no Segment Z is present. CreatedAt is the return file's own generation date/time, taken from the file header (assumed to be Brasília time, UTC-3) and attached to every occurrence in the file.

Type is a canonical OccurrenceType (REJECTED, SCHEDULED, SETTLED, or UNKNOWN) derived from the first two characters of RawType — e.g. RJREJECTED, BDSCHEDULED, 00SETTLED. Any other prefix maps to UNKNOWN; this mapping table, like all other Itaú-specific knowledge, lives inside internal/itau240 — not in the public API.

Format is a ReceiptFormat lookup key, "BBB-S-TT-FF": the bank compensation code, the mandatory segment (A/J/O/N), and the batch header's "Tipo de Serviço"/"Forma de Lançamento" codes (e.g. 341-A-20-41 for a TED). It is populated on every occurrence, not just settled ones — receipts only make sense for SETTLED occurrences, but the key itself just describes the batch/segment. This library does not ship receipt templates; Format only tells a caller which one of its own templates applies.

Consecutive detail records that share a batch sequence number are treated as one logical payment: each segment's fields are merged into the same occurrence, and the group is only resolved once the sequence number changes or the batch trailer is reached.

License

Apache-2.0. See LICENSE.

Documentation

Overview

Package cnabpayment streams Brazilian CNAB payment return files into a canonical, bank-agnostic sequence of payment occurrences.

This is the only public package in the module: Occurrence, ParseResult, RecordError, the OccurrenceHandler/RecordErrorHandler callbacks, Scanner/Record, and the LayoutParser abstraction all live here. Built-in layout parsers' actual implementations (currently Itaú CNAB240) are under internal/ and are not importable on their own — each is exposed at this package's root through a constructor, e.g. NewItau240LayoutParser, so it can be passed to NewParser explicitly, alone or alongside your own custom LayoutParser implementations.

p := cnabpayment.NewParser(cnabpayment.NewItau240LayoutParser())

result, err := p.Parse(r, func(o cnabpayment.Occurrence) error {
	fmt.Println(o.PaymentID, o.Type, o.RawType, o.CreatedAt, o.Format)
	return nil
}, nil)

Parsing begins immediately: the parser reads the first physical record, uses it to resolve the appropriate LayoutParser, and delegates the rest of the stream to that layout parser from the parser's current position onward. It never peeks, rewinds, or requires an io.Seeker, and the first record is never read twice.

NewParser requires at least one LayoutParser — there is no default.

See the repository README for project philosophy, current status, and roadmap.

Index

Constants

View Source
const (
	// OccurrenceTypeRejected means the payment was rejected.
	OccurrenceTypeRejected = core.OccurrenceTypeRejected

	// OccurrenceTypeScheduled means the payment was accepted and
	// scheduled, but not yet settled.
	OccurrenceTypeScheduled = core.OccurrenceTypeScheduled

	// OccurrenceTypeSettled means the payment was settled.
	OccurrenceTypeSettled = core.OccurrenceTypeSettled

	// OccurrenceTypeUnknown means the layout parser could not classify
	// RawType into one of the other OccurrenceType values.
	OccurrenceTypeUnknown = core.OccurrenceTypeUnknown
)

Variables

View Source
var ErrEmptyInput = registry.ErrEmptyInput

ErrEmptyInput is returned by Parse when the input contains no records at all, so no layout can be resolved.

View Source
var ErrNoLayoutParser = registry.ErrNoLayoutParser

ErrNoLayoutParser is returned by Parse when no configured layout parser recognizes the input file's first record.

Functions

This section is empty.

Types

type LayoutParser

type LayoutParser = registry.LayoutParser

LayoutParser implements the layout-specific knowledge required to recognize a CNAB file from its first record and parse the remainder of the stream into canonical occurrences.

Custom implementations are supported: pass one or more to NewParser to use them instead of the built-in layout parsers.

func NewItau240LayoutParser

func NewItau240LayoutParser() LayoutParser

NewItau240LayoutParser creates the built-in LayoutParser for Itaú CNAB240 payment return files. Pass it to NewParser explicitly — alone, or alongside your own custom LayoutParser implementations.

type Occurrence

type Occurrence = core.Occurrence

Occurrence is the canonical representation of a single payment occurrence extracted from a CNAB return file, independent of which bank or layout produced it. Its field set is expected to grow as more layout parsers are added and more of a return file's business information becomes worth extracting.

type OccurrenceHandler

type OccurrenceHandler = core.OccurrenceHandler

OccurrenceHandler is invoked for every canonical occurrence emitted while parsing, as soon as it is fully parsed. Returning an error aborts parsing.

type OccurrenceType

type OccurrenceType = core.OccurrenceType

OccurrenceType classifies an Occurrence's canonical outcome, independent of any bank-specific raw code. Layout parsers derive it from RawType.

type ParseResult

type ParseResult = core.ParseResult

ParseResult reports statistics about a parse run, whether it completed successfully or was aborted. It is deliberately minimal and expected to grow as the parser gains capabilities.

type Parser

type Parser = registry.Parser

Parser resolves the layout of a CNAB file and streams it into canonical occurrences. A Parser holds no per-parse state, so a single instance can be reused, including across concurrent Parse calls.

func NewParser

func NewParser(first LayoutParser, rest ...LayoutParser) *Parser

NewParser creates a Parser that can resolve any of the given layout parsers from a file's first record. At least one is required — there is no default; construct built-in layout parsers explicitly (e.g. NewItau240LayoutParser) and pass them in, alongside any custom LayoutParser implementations you need.

type ReceiptFormat added in v0.2.0

type ReceiptFormat = core.ReceiptFormat

ReceiptFormat identifies which receipt template a settled Occurrence corresponds to, as "BBB-S-TT-FF": bank compensation code, batch segment, and the Febraban "Tipo de Serviço"/"Forma de Lançamento" codes. It is a lookup key only — this package does not ship receipt templates.

type Record

type Record = core.Record

Record is a single physical record read from the input, exactly as found on disk, with its record separator stripped.

type RecordError

type RecordError = core.RecordError

RecordError describes a recoverable failure encountered while parsing a single record. Unlike a fatal parse error, a RecordError does not by itself stop parsing; it is reported to a RecordErrorHandler, which decides whether to continue.

type RecordErrorHandler

type RecordErrorHandler = core.RecordErrorHandler

RecordErrorHandler is invoked for every recoverable record-level error encountered while parsing. Returning an error aborts parsing. If nil is passed to a parser, record errors are ignored and parsing continues.

type Scanner

type Scanner = core.Scanner

Scanner reads physical records from an input sequentially. Callers must check Next before calling Record, and must check Err after Next returns false to distinguish a clean end of input from a read failure.

A custom LayoutParser's Parse method receives a Scanner already positioned right after the file's first record, and continues reading from it.

func NewScanner

func NewScanner(r io.Reader) Scanner

NewScanner creates a Scanner that reads physical records from r, one line at a time, split on '\n' with any trailing '\r' stripped.

Directories

Path Synopsis
internal
core
Package core defines the canonical domain model and physical-record scanner shared by the root package and every layout parser, independent of any bank-specific CNAB layout.
Package core defines the canonical domain model and physical-record scanner shared by the root package and every layout parser, independent of any bank-specific CNAB layout.
itau240
Package itau240 implements the LayoutParser contract required by github.com/cnab-dev/cnab-payment (via internal/registry), satisfied structurally without importing that package — see internal/core for the shared types this depends on instead.
Package itau240 implements the LayoutParser contract required by github.com/cnab-dev/cnab-payment (via internal/registry), satisfied structurally without importing that package — see internal/core for the shared types this depends on instead.
registry
Package registry holds the LayoutParser extension-point interface and the Parser that resolves and delegates to one, kept separate from internal/core so that layout parser implementations only ever need to depend on the shared domain model, never on this orchestration layer.
Package registry holds the LayoutParser extension-point interface and the Parser that resolves and delegates to one, kept separate from internal/core so that layout parser implementations only ever need to depend on the shared domain model, never on this orchestration layer.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL