Your first invoice¶
This guide builds a sales NF-e with one line item, from scratch to the distribution file, against the homologation environment. Copy it, paste it, swap the issuer details for your own and run it.
Homologation has no fiscal value
Everything here uses nfe.Homologacao. Invoices issued in that environment
are not valid fiscal documents and SEFAZ discards them periodically — it is
where you get to make mistakes freely. Only switch to nfe.Producao once
your tax scenario has been checked.
1. Load the certificate¶
cert, err := certificado.CarregarArquivo("certificado.pfx", os.Getenv("GONFE_SENHA"))
if err != nil {
return err
}
log.Println(cert.Descrever())
Descrever prints the company name, CNPJ, issuing authority and expiry date. It
is worth logging that at startup, because an expired certificate is the most
annoying failure to diagnose.
2. Invoice identification¶
n := nfe.Nova(nfe.ModeloNFe)
ide := &n.InfNFe.Ide
ide.NatOp = "VENDA DE MERCADORIA ADQUIRIDA DE TERCEIROS"
ide.Serie = 1
ide.NNF = 57
ide.DhEmi = tipos.AgoraEm(uf.RS.Fuso())
ide.CMunFG = 4314902 // IBGE code for the municipality
ide.TpAmb = nfe.Homologacao
ide.IndFinal = "1" // sale to an end consumer
ide.IndPres = nfe.PresencaPresencial
nfe.Nova already fills in the structural fields with the layout defaults:
version 4.00, normal issuance, portrait DANFE, internal outbound operation.
The time zone matters
SEFAZ checks that the offset in dhEmi matches the legal time zone of the
issuer's state. uf.RS.Fuso() returns UTC−03:00; uf.AM.Fuso() returns
UTC−04:00; uf.AC.Fuso(), UTC−05:00. Using the time zone of the machine
running the server is a common mistake when that server sits in another
region.
3. Issuer¶
n.InfNFe.Emit = nfe.Emit{
CNPJ: "12345678000195",
XNome: "COMERCIO EXEMPLO LTDA",
XFant: "EXEMPLO",
IE: "0961234567",
CRT: nfe.RegimeNormal, // or nfe.SimplesNacional
EnderEmit: nfe.Endereco{
XLgr: "AV IPIRANGA", Nro: "1000", XBairro: "PRAIA DE BELAS",
CMun: 4314902, XMun: "PORTO ALEGRE", UF: "RS", CEP: "90160091",
CPais: 1058, XPais: "BRASIL",
},
}
The state in the issuer's address determines the code that goes into the access
key — you do not need to fill ide.CUF by hand.
4. Recipient¶
n.InfNFe.Dest = &nfe.Dest{
CPF: "52998224725",
XNome: nfe.TextoObrigatorioHomologacao,
IndIEDest: nfe.NaoContribuinte,
EnderDest: &nfe.Endereco{
XLgr: "RUA DAS FLORES", Nro: "42", XBairro: "CENTRO",
CMun: 4314902, XMun: "PORTO ALEGRE", UF: "RS", CEP: "90010000",
CPais: 1058, XPais: "BRASIL",
},
}
The recipient name is fixed in homologation
In homologation SEFAZ requires the recipient to be named exactly
NF-E EMITIDA EM AMBIENTE DE HOMOLOGACAO - SEM VALOR FISCAL. The constant
nfe.TextoObrigatorioHomologacao holds that text, and Validar reports the
error if it differs.
5. Line items¶
valor := tipos.D("149.90")
n.InfNFe.Det = []nfe.Det{{
Prod: nfe.Prod{
CProd: "TEC-001", CEAN: "SEM GTIN", XProd: "TECLADO MECANICO ABNT2",
NCM: "84716053", CFOP: "5102", UCom: "UN",
QCom: tipos.D("1"), VUnCom: valor, VProd: valor,
CEANTrib: "SEM GTIN", UTrib: "UN", QTrib: tipos.D("1"), VUnTrib: valor,
IndTot: nfe.CompoeTotal,
},
Imposto: nfe.Imposto{
ICMS: &nfe.ICMS{ICMS00: &nfe.ICMS00{
Orig: nfe.OrigemNacional, CST: "00", ModBC: "3",
VBC: valor, PICMS: tipos.D("18.00"),
VICMS: valor.Percentual(tipos.D("18.00"), 2),
}},
PIS: &nfe.PIS{PISNT: &nfe.PISNT{CST: "07"}},
COFINS: &nfe.COFINS{COFINSNT: &nfe.COFINSNT{CST: "07"}},
},
}}
Three things to notice:
tipos.Dinstead of numeric literals. Never usefloat64for a fiscal value; see Decimal values.Percentualcomputes the tax with commercial rounding at the field's precision.valor.Percentual(aliquota, 2)isvalor × rate ÷ 100rounded to two digits."SEM GTIN"when there is no barcode. The field is mandatory; leaving it empty is a guaranteed rejection.
The ICMS group is a union: fill in exactly one field, chosen by the CST (normal
regime) or the CSOSN (Simples Nacional). A service line item uses
Imposto.ISSQN in place of ICMS.
6. Freight and payment¶
n.InfNFe.Transp = nfe.Transp{ModFrete: nfe.SemFrete}
n.InfNFe.Pag = &nfe.Pag{DetPag: []nfe.DetPag{{
TPag: nfe.PagamentoCartaoCredito,
VPag: valor,
}}}
The payment group is mandatory in layout 4.00, including for the NF-e. The sum
of the payments has to match the invoice total plus any change given — Validar
checks that.
7. Prepare, validate and sign¶
if err := n.Preparar(); err != nil {
return err
}
if err := n.Validar(); err != nil {
return err // returns nfe.Erros, one entry per inconsistency
}
assinada, err := n.AssinarCom(cert)
if err != nil {
return err
}
log.Println("chave:", n.Chave())
Preparar normalises the text fields, adjusts the scale of every decimal,
computes the totals group and builds the access key. It is idempotent: calling
it twice gives the same result.
8. Transmit and collect the protocol¶
cliente, err := sefaz.NovoCliente(sefaz.Config{
UF: uf.RS, Ambiente: nfe.Homologacao,
Modelo: nfe.ModeloNFe, Certificado: cert,
})
if err != nil {
return err
}
ctx, cancelar := context.WithTimeout(context.Background(), 2*time.Minute)
defer cancelar()
lote, err := nfe.MontarLote(nfe.ProximoIdLote(time.Now().Unix()), false, assinada)
if err != nil {
return err
}
envio, err := cliente.Autorizar(ctx, lote)
if err != nil {
return err
}
resultado, err := cliente.EsperarProcessamento(ctx, envio.Recibo(), 3*time.Second, 20)
if err != nil {
return err
}
prot := resultado.ProtocoloDa(n.Chave())
if !prot.Autorizada() {
return fmt.Errorf("nota não autorizada: %s", prot.Resumo())
}
9. Store the distribution file¶
proc, err := nfe.MontarNFeProc(assinada, prot)
if err != nil {
return err
}
os.WriteFile(n.Chave()+"-procNFe.xml", nfe.XMLDeclarado(proc), 0o644)
The nfeProc is the document you must keep for five years and hand to the
recipient. It joins the signed invoice with the authorisation protocol,
preserving byte for byte the data that was signed.
The complete program¶
The runnable example, with error handling and environment variables, is in
exemplos/emitir-nfe.