Value objects encapsulate complex validation and formatting rules. They are immutable, equality-by-value, and have no identity. The fiscal module has two value objects representing core NF-e concepts.
Files: value-objects/access-key.ts, value-objects/tax-id.ts
The NF-e access key (chave de acesso) is a 44-digit numeric string that uniquely identifies every electronic invoice in Brazil.
35 2002 09944445000162 55 101 000123456 7 89012345 3
── ──── ────────────── ── ─── ───────── ─ ──────── ─
cUF AAMM CNPJ mod ser nNF tpEmis cNF DV
| Position | Digits | Field | Description |
|---|---|---|---|
| 0–1 | 2 | cUF | IBGE state code (e.g. "35" = SP) |
| 2–5 | 4 | AAMM | Year+month of emission |
| 6–19 | 14 | CNPJ | Issuer's CNPJ |
| 20–21 | 2 | mod | Model (55 or 65) |
| 22–24 | 3 | serie | Series number |
| 25–33 | 9 | nNF | Invoice number |
| 34 | 1 | tpEmis | Emission type (1=normal, 9=offline) |
| 35–42 | 8 | cNF | Random numeric code |
| 43 | 1 | cDV | Check digit (mod-11) |
class AccessKey {
// Build from components (generates check digit)
static build(params: AccessKeyParams): AccessKey
// Validate existing key
constructor(key: string) // throws if not 44 numeric digits
// Mod-11 check digit calculation (weights 2-9 cycling)
static calculateMod11(digits: string): string
// Accessors
stateCode(): string // "35"
yearMonth(): string // "2002"
taxId(): string // "09944445000162"
model(): number // 55
series(): number // 101
number(): number // 123456
emissionType(): number // 7
numericCode(): string // "89012345"
checkDigit(): string // "3"
toString(): string // full 44-digit key
}- Take the first 43 digits
- Apply weights 2, 3, 4, 5, 6, 7, 8, 9 cycling from right to left
- Sum the products
remainder = sum % 11- If remainder is 0 or 1, check digit = 0; otherwise check digit = 11 - remainder
- Generated by
xml-builder.tsduring XML construction - Stored in
invoices.access_keycolumn - Used in SEFAZ requests (cancellation, consultation, events)
- Encoded in NFC-e QR codes
Discriminates between CPF (11 digits, individuals) and CNPJ (14 digits, companies) and handles XML tag generation.
class TaxId {
constructor(value: string)
isCpf(): boolean // length ≤ 11
isCnpj(): boolean // length > 11
tagName(): string // "CPF" or "CNPJ"
padded(): string // zero-pad to 11 (CPF) or 14 (CNPJ)
toXmlTag(): string // "<CPF>01234567890</CPF>" or "<CNPJ>09944445000162</CNPJ>"
toString(): string // raw value
}Multiple places in the XML need CPF/CNPJ tags: <emit>, <dest>, <retirada>, <entrega>, <autXML>, event envelopes. Without TaxId, each place would need:
// Without value object (repeated everywhere):
const isCompany = taxId.length > 11;
const tagName = isCompany ? "CNPJ" : "CPF";
const padded = taxId.padStart(isCompany ? 14 : 11, "0");
const xmlTag = `<${tagName}>${padded}</${tagName}>`;
// With value object (one line):
const xmlTag = new TaxId(taxId).toXmlTag();- CPF: pad to 11 digits (
"1234567890"→"01234567890") - CNPJ: pad to 14 digits (
"9944445000162"→"09944445000162")
This is necessary because some systems store tax IDs without leading zeros.