SefazNfe (sefaz_nfe v0.1.0)

Copy Markdown View Source

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

authorization_result(opts)

@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.

authorize(opts)

@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.

cancel(opts)

@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.

cce(opts)

@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.

consult_protocol(opts)

@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.

dist_dfe(opts)

@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.

manifest(type, opts)

@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.

service_status(opts)

@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.

start_dist_dfe_poller(opts)

@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).

void_numbers(opts)

@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.