fiscal-rsfiscal-rs

Fluxo Completo de Emissão

Guia passo a passo do ciclo de vida completo de um documento fiscal eletrônico no fiscal-rs — da configuração à persistência.

O processo de emissão de uma NF-e ou NFC-e envolve cinco etapas principais: configurar, construir, assinar, transmitir e armazenar. O fiscal-rs distribui essas responsabilidades entre três crates, cada um isolado em sua função.

Visão geral do fluxo

Loading diagram...

Interação entre os crates

Loading diagram...

Etapa 1 — Configurar os dados

Antes de construir o XML, prepare as estruturas de dados do emissor, destinatário, itens e pagamentos.

Emissor (IssuerData)

use fiscal::types::*;
use fiscal::newtypes::IbgeCode;

let issuer = IssuerData::new(
    "25028332000105",       // CNPJ
    "140950881119",         // Inscrição Estadual
    "Minha Empresa LTDA",   // Razão social
    TaxRegime::SimplesNacional,
    "SP",                   // UF
    IbgeCode("3550308".to_string()), // Código IBGE do município
    "Sao Paulo",            // Nome do município
    "Av Paulista",          // Logradouro
    "1000",                 // Número
    "Bela Vista",           // Bairro
    "01310100",             // CEP
)
.trade_name("Minha Loja");

Destinatário (RecipientData) — obrigatório para NF-e

let recipient = RecipientData::new("17812455000295", "Cliente LTDA")
    .state_tax_id("142304338112")
    .street("Rua do Cliente")
    .street_number("200")
    .district("Centro")
    .city_code(IbgeCode::new("3550308"))
    .city_name("SAO PAULO")
    .state_code("SP")
    .zip_code("05302001");

Para NFC-e com valor inferior a R$200,00, o destinatário é opcional.

Itens (InvoiceItemData)

use fiscal::newtypes::{Cents, Rate};

let item = InvoiceItemData::new(
    1,                  // número do item (nItem)
    "001",              // código do produto (cProd)
    "Produto Exemplo",  // descrição
    "84715010",         // NCM
    "5102",             // CFOP — venda interna
    "UN",               // unidade
    1.0,                // quantidade
    Cents(10000),       // valor unitário (R$100,00)
    Cents(10000),       // valor total
    "102",              // CST ICMS (Simples Nacional)
    Rate(0),            // alíquota ICMS
    Cents(0),           // base de cálculo ICMS
    "99",               // CST PIS
    "99",               // CST COFINS
);

Pagamentos (PaymentData)

// Código "01" = Dinheiro, "03" = Cartão de Crédito, "04" = Cartão de Débito
let pagamento_dinheiro = PaymentData::new("01", Cents(10000));
let pagamento_cartao  = PaymentData::new("03", Cents(5000));

Etapa 2 — Construir o XML (InvoiceBuilder)

O InvoiceBuilder usa o padrão typestate: o compilador impede chamadas fora de ordem.

Draft  →  .build()  →  Built  →  .sign_with()  →  Signed

NF-e (modelo 55)

use fiscal::xml_builder::InvoiceBuilder;

let built = InvoiceBuilder::new(
        issuer.clone(),
        SefazEnvironment::Homologation,
        InvoiceModel::Nfe,
    )
    .series(1)
    .invoice_number(502)
    .operation_nature("VENDA")
    .recipient(recipient)
    .items(vec![item])
    .payments(vec![pagamento_dinheiro])
    .build()?;

// Disponível após build():
println!("XML:   {}", built.xml());
println!("Chave: {}", built.access_key()); // 44 dígitos

NFC-e (modelo 65)

let built = InvoiceBuilder::new(
        issuer.clone(),
        SefazEnvironment::Homologation,
        InvoiceModel::Nfce,
    )
    .series(1)
    .invoice_number(1)
    .add_item(item)
    .payments(vec![pagamento_dinheiro])
    .build()?;

Tratamento de erros na construção

O método .build() retorna Result<InvoiceBuilder<Built>, FiscalError>. Possíveis erros:

Variante do FiscalErrorCausa
InvalidTaxData(msg)CNPJ, IE ou código de estado inválido
MissingRequiredField { field }Campo obrigatório ausente (ex.: itens, pagamentos)
UnsupportedIcmsCst(cst)CST ICMS não reconhecido
UnsupportedIcmsCsosn(csosn)CSOSN não reconhecido
XmlGeneration(msg)Falha na serialização do XML
match builder.build() {
    Ok(built) => { /* prosseguir com assinatura */ }
    Err(FiscalError::InvalidTaxData(msg)) => {
        eprintln!("Dado fiscal inválido: {msg}");
    }
    Err(FiscalError::MissingRequiredField { field }) => {
        eprintln!("Campo obrigatório ausente: {field}");
    }
    Err(e) => {
        eprintln!("Erro na construção: {e}");
    }
}

Etapa 3 — Assinar o XML

A assinatura digital usa RSA-SHA1 com envelope XMLDSig, conforme exigido pelo SEFAZ. O crate fiscal-crypto é responsável pela operação criptográfica; o fiscal-core permanece independente de implementação graças ao closure sign_with.

Carregar o certificado A1

use fiscal::certificate::{load_certificate, sign_xml};

let pfx_bytes = std::fs::read("certificado.pfx")
    .expect("Arquivo PFX não encontrado");

let cert = load_certificate(&pfx_bytes, "senha_do_certificado")?;
// cert.private_key  — chave privada em PEM
// cert.certificate  — certificado X.509 em PEM

Assinar e obter o XML final

let signed = built.sign_with(|xml| {
    sign_xml(xml, &cert.private_key, &cert.certificate)
})?;

let xml_assinado = signed.signed_xml();   // XML com <Signature>
let chave_acesso = signed.access_key();   // 44 dígitos
let xml_original = signed.unsigned_xml(); // XML sem assinatura

Tratamento de erros na assinatura

O closure dentro de sign_with retorna Result<String, FiscalError>. Erros possíveis:

Variante do FiscalErrorCausa
Certificate("Failed to parse private key: ...")PEM da chave privada inválido ou corrompido
Certificate("Invalid PFX data: ...")Arquivo PFX inválido
Certificate("Failed to parse PFX (wrong password?): ...")Senha incorreta
Certificate("<infNFe> element not found...")XML não contém o elemento esperado
Certificate("RSA-SHA1 signing failed: ...")Falha na operação criptográfica

Etapa 4 — Transmitir ao SEFAZ

O SefazClient gerencia a conexão mTLS com os webservices SEFAZ. A identidade do certificado é configurada na construção do client.

Criar o client

use fiscal_sefaz::client::SefazClient;

let client = SefazClient::new(&pfx_bytes, "senha_do_certificado")?;

Verificar status do SEFAZ (opcional, recomendado)

let status = client.status("SP", SefazEnvironment::Homologation).await?;
if status.status_code == "107" {
    println!("SEFAZ em operação: {}", status.status_message);
}

Enviar para autorização

let response = client.authorize(
    "SP",
    SefazEnvironment::Homologation,
    signed.signed_xml(),
    "lot_001",  // identificador do lote
).await?;

match response.status_code.as_str() {
    "100" | "150" => {
        println!("Autorizada! Protocolo: {}", response.protocol);
    }
    "110" | "301" | "302" | "303" => {
        println!("Denegada: [{}] {}", response.status_code, response.status_message);
    }
    _ => {
        println!("Rejeitada: [{}] {}", response.status_code, response.status_message);
    }
}

Tratamento de erros na transmissão

Variante do FiscalErrorCausa
Network("connection refused...")SEFAZ fora do ar ou rede indisponível
Network("timeout...")Timeout de 90s excedido
Certificate("Failed to load PFX identity: ...")Certificado inválido para mTLS
InvalidStateCode(uf)UF não encontrada na tabela de URLs
XmlParsing(msg)Resposta SOAP malformada

Contingência

Quando o SEFAZ está indisponível (especialmente para NFC-e), salve o XML assinado localmente com status contingency e reenvie posteriormente:

// Tentativa de envio com fallback
let resultado = match client.authorize("SP", env, signed_xml, lot_id).await {
    Ok(resp) => resp,
    Err(FiscalError::Network(_)) => {
        // Salvar localmente para sincronização posterior
        salvar_contingencia(signed_xml, &chave_acesso).await?;
        return Ok(StatusContingencia);
    }
    Err(e) => return Err(e),
};

Etapa 5 — Armazenar (attach_protocol)

Após a autorização, junte o XML da requisição com o protocolo da resposta para formar o <nfeProc> -- o documento fiscal definitivo.

use fiscal::complement::attach_protocol;

let nfe_proc = attach_protocol(
    signed.signed_xml(),  // XML assinado enviado
    &response.raw_xml,    // XML da resposta SEFAZ
)?;

// nfe_proc contém:
// <?xml version="1.0" encoding="UTF-8"?>
// <nfeProc versao="4.00" xmlns="http://www.portalfiscal.inf.br/nfe">
//   <NFe>...</NFe>
//   <protNFe>...</protNFe>
// </nfeProc>

salvar_no_banco(&nfe_proc, &chave_acesso).await?;

Tratamento de erros no armazenamento

Variante do FiscalErrorCausa
XmlParsing("Could not find <NFe> tag...")XML de requisição sem <NFe>
XmlParsing("Could not find <protNFe>...")Resposta sem protocolo
SefazRejection { code, message }Status do protocolo indica rejeição

Exemplo completo: NF-e ponta a ponta

use fiscal::types::*;
use fiscal::newtypes::{Cents, IbgeCode, Rate};
use fiscal::xml_builder::InvoiceBuilder;
use fiscal::certificate::{load_certificate, sign_xml};
use fiscal::complement::attach_protocol;
use fiscal_sefaz::client::SefazClient;

async fn emitir_nfe() -> Result<String, fiscal::FiscalError> {
    // 1. Configurar
    let issuer = IssuerData::new(
        "25028332000105", "140950881119", "Minha Empresa LTDA",
        TaxRegime::SimplesNacional, "SP",
        IbgeCode("3550308".to_string()), "Sao Paulo",
        "Av Paulista", "1000", "Bela Vista", "01310100",
    );

    let recipient = RecipientData::new("17812455000295", "Cliente LTDA")
        .state_tax_id("142304338112")
        .street("Rua do Cliente").street_number("200")
        .district("Centro")
        .city_code(IbgeCode::new("3550308")).city_name("SAO PAULO")
        .state_code("SP").zip_code("05302001");

    let item = InvoiceItemData::new(
        1, "001", "Produto A", "84715010", "5102", "UN",
        2.0, Cents(5000), Cents(10000),
        "102", Rate(0), Cents(0), "99", "99",
    );

    let payment = PaymentData::new("01", Cents(10000));

    // 2. Construir
    let built = InvoiceBuilder::new(issuer, SefazEnvironment::Homologation, InvoiceModel::Nfe)
        .series(1)
        .invoice_number(502)
        .operation_nature("VENDA")
        .recipient(recipient)
        .add_item(item)
        .payments(vec![payment])
        .build()?;

    // 3. Assinar
    let pfx = std::fs::read("certificado.pfx").expect("PFX não encontrado");
    let cert = load_certificate(&pfx, "senha")?;

    let signed = built.sign_with(|xml| {
        sign_xml(xml, &cert.private_key, &cert.certificate)
    })?;

    // 4. Transmitir
    let client = SefazClient::new(&pfx, "senha")?;
    let response = client
        .authorize("SP", SefazEnvironment::Homologation, signed.signed_xml(), "1")
        .await?;

    // 5. Armazenar
    let nfe_proc = attach_protocol(signed.signed_xml(), &response.raw_xml)?;

    println!("NF-e autorizada! Chave: {}", signed.access_key());
    Ok(nfe_proc)
}

Exemplo completo: NFC-e ponta a ponta

async fn emitir_nfce() -> Result<String, fiscal::FiscalError> {
    // 1. Configurar — emissor igual, sem destinatário (venda < R$200)
    let issuer = IssuerData::new(
        "25028332000105", "140950881119", "Minha Loja",
        TaxRegime::SimplesNacional, "SP",
        IbgeCode("3550308".to_string()), "Sao Paulo",
        "Av Paulista", "1000", "Bela Vista", "01310100",
    )
    .trade_name("Minha Loja");

    let item = InvoiceItemData::new(
        1, "002", "Cafe Expresso", "09012110", "5102", "UN",
        1.0, Cents(800), Cents(800),
        "102", Rate(0), Cents(0), "99", "99",
    );

    // 2. Construir — modelo NFC-e, sem destinatário
    let built = InvoiceBuilder::new(issuer, SefazEnvironment::Homologation, InvoiceModel::Nfce)
        .series(1)
        .invoice_number(47)
        .add_item(item)
        .payments(vec![PaymentData::new("01", Cents(800))])
        .build()?;

    // 3. Assinar
    let pfx = std::fs::read("certificado.pfx").expect("PFX não encontrado");
    let cert = load_certificate(&pfx, "senha")?;
    let signed = built.sign_with(|xml| {
        sign_xml(xml, &cert.private_key, &cert.certificate)
    })?;

    // 4. Transmitir (com fallback para contingência)
    let client = SefazClient::new(&pfx, "senha")?;
    let response = match client
        .authorize("SP", SefazEnvironment::Homologation, signed.signed_xml(), "1")
        .await
    {
        Ok(resp) => resp,
        Err(FiscalError::Network(msg)) => {
            eprintln!("SEFAZ indisponível: {msg}. Salvando em contingência.");
            // Armazenar XML assinado para reenvio posterior
            return Ok(signed.signed_xml().to_string());
        }
        Err(e) => return Err(e),
    };

    // 5. Armazenar
    let nfe_proc = attach_protocol(signed.signed_xml(), &response.raw_xml)?;
    Ok(nfe_proc)
}

Ciclo de vida dos status

Loading diagram...

Referência de códigos SEFAZ

Código (cStat)SignificadoAção
100Autorizado o uso da NF-eAnexar protocolo e armazenar
150Autorizado fora do prazoIdem
110Uso denegadoRegistrar como denegada
301-303Denegada (irregularidade fiscal)Registrar como denegada
102Inutilização homologadaArmazenar comprovante
107Serviço em operaçãoStatus check OK
135Evento registrado e vinculadoEvento processado com sucesso
136Evento registrado (não vinculado)Evento processado
204Duplicidade de NF-eNF-e já existe com esta chave

On this page