SEFAZ NF-e transport (modelo 55).
Does not calculate taxes. The ERP builds the XML; this library signs and talks to the official 4.00 webservices (and DistDFe 1.00 on the AN).
Every service is live over mTLS. The library signs, sends and parses; it
never composes an NF-e (AD-001), so authorize/1 takes the XML the ERP
built and returns it to SEFAZ byte for byte.
Summary
Functions
NFeRetAutorizacao4 for a receipt (n_rec).
Sign + NFeAutorizacao4. Does not mutate tax nodes once SOAP exists.
Event 110111, cancelling an authorized NF-e via NFeRecepcaoEvento4.
Event 110110, the Carta de Correção Eletrônica, via NFeRecepcaoEvento4.
NFeConsultaProtocolo4 by 44-digit access key.
NFeDistribuicaoDFe on the Ambiente Nacional. Caller persists ult_nsu.
Manifestação do destinatário for a document received through DistDFe.
NFeStatusServico4 for one UF.
Starts a DistDFe poller for one tax ID under SefazNfe.DistDFe.Supervisor.
NFeInutilizacao4, closing a range of NF-e numbers that was never used.
Functions
@spec authorization_result(map()) :: {:ok, SefazNfe.Result.t()} | {:error, term()}
NFeRetAutorizacao4 for a receipt (n_rec).
What an asynchronous authorize/1 leaves to be collected. cStat 105 means
the batch is still processing and the caller should ask again; 104 means it
finished, and the document's own outcome is read from the nested protocol.
@spec authorize(map()) :: {:ok, SefazNfe.Result.t()} | {:error, term()}
Sign + NFeAutorizacao4. Does not mutate tax nodes once SOAP exists.
The pipeline is validate, sign, wrap in enviNFe, POST, parse.
The lote is synchronous by default: it carries one document, and SEFAZ
rejects an asynchronous request for a single-document batch as cStat 452.
When SEFAZ answers a receipt anyway — it may, under load — the protocol is
collected later with authorization_result/1.
XSD validation runs only when SefazNfe.Schema is configured, and then it
fails before the network — a local error naming the offending element beats
cStat 225, which names nothing.
The result carries :signed_xml — what was sent — and, once a protocol
exists, :xml holding the nfeProc. Store :signed_xml even on a receipt:
an asynchronous lote answers cStat 103 with no protocol, and without those
bytes the protocol collected later by authorization_result/1 cannot be
attached to anything. SefazNfe.Result.proc/2 joins the two.
tpAmb is MOC data rather than a boolean — 1 is produção, 2 is homologação —
so a mismatch against :environment is refused before anything is sent.
@spec cancel(map()) :: {:ok, SefazNfe.Result.t()} | {:error, term()}
Event 110111, cancelling an authorized NF-e via NFeRecepcaoEvento4.
Needs the protocol the authorization returned, and a justification of at least 15 characters as the MOC requires.
@spec cce(map()) :: {:ok, SefazNfe.Result.t()} | {:error, term()}
Event 110110, the Carta de Correção Eletrônica, via NFeRecepcaoEvento4.
:sequence numbers the correction; each one replaces the previous text
rather than adding to it.
@spec consult_protocol(map()) :: {:ok, SefazNfe.Result.t()} | {:error, term()}
NFeConsultaProtocolo4 by 44-digit access key.
The idempotent way back after a crash between a receipt and a protocol: ask SEFAZ what it did with a document instead of sending the batch again.
@spec dist_dfe(map()) :: {:ok, SefazNfe.DistDFe.t()} | {:error, term()}
NFeDistribuicaoDFe on the Ambiente Nacional. Caller persists ult_nsu.
:tax_id is the CNPJ or CPF of the interested party — the distDFeInt
envelope carries it, so it is required even though the URL is always the AN.
:uf is the querying party's own state, which the envelope carries as
cUFAutor. It is required and must be a real UF: the Ambiente Nacional's own
code is rejected there as cStat 215.
A CPF is 11 digits; a CNPJ is 14 and alphanumeric since NT 2025.002 (CNPJ
alfa), meaning 12 characters of [A-Z0-9] plus a two digit DV. Only the shape
is checked here — the check digits and the registration itself are SEFAZ's to
validate.
@spec manifest(atom(), map()) :: {:ok, SefazNfe.Result.t()} | {:error, term()}
Manifestação do destinatário for a document received through DistDFe.
type is one of :confirmation, :awareness, :unaware or
:not_performed; the last one needs a :justification. Confirming an
operation is also what releases the full XML of a note DistDFe only
summarised.
These events are processed by the Ambiente Nacional, so no :uf is needed.
@spec service_status(map()) :: {:ok, SefazNfe.Result.t()} | {:error, term()}
NFeStatusServico4 for one UF.
The cheapest official call there is, and the one that proves the A1, the mTLS
handshake and the endpoint table in a single round trip. cStat 107 means the
authorizer is in operation; 108 and 109 mean it is not, and both arrive as
{:ok, result} because SEFAZ answered.
@spec start_dist_dfe_poller(keyword()) :: DynamicSupervisor.on_start_child()
Starts a DistDFe poller for one tax ID under SefazNfe.DistDFe.Supervisor.
opts must include :tax_id, :cert and :handler — the poller delivers
every page to the handler, which owns the cursor. See
SefazNfe.DistDFe.Poller for the handler contract and the remaining options
(:environment, :ult_nsu, :interval, :fetch).
@spec void_numbers(map()) :: {:ok, SefazNfe.Result.t()} | {:error, term()}
NFeInutilizacao4, closing a range of NF-e numbers that was never used.
A broken sequence still has to be accounted for, and this is how. :model
defaults to 55 and :year to the current one.