Nfe.Paulistana
2.0.1
dotnet add package Nfe.Paulistana --version 2.0.1
NuGet\Install-Package Nfe.Paulistana -Version 2.0.1
<PackageReference Include="Nfe.Paulistana" Version="2.0.1" />
<PackageVersion Include="Nfe.Paulistana" Version="2.0.1" />
<PackageReference Include="Nfe.Paulistana" />
paket add Nfe.Paulistana --version 2.0.1
#r "nuget: Nfe.Paulistana, 2.0.1"
#:package Nfe.Paulistana@2.0.1
#addin nuget:?package=Nfe.Paulistana&version=2.0.1
#tool nuget:?package=Nfe.Paulistana&version=2.0.1
Nfe.Paulistana
Biblioteca .NET para integração com o webservice SOAP da Nota Fiscal de Serviços Eletrônica Paulistana (NFS-e) da Prefeitura de São Paulo.
Índice
- Instalação
- Pré-requisitos
- Registro de serviços
- Resiliência
- Diagnóstico
- Operações disponíveis
- Exemplos de uso
- Tratamento de erros
- Arquitetura
- Otimizações de performance
- Contribuindo
- Licença
Instalação
dotnet add package Nfe.Paulistana
Pré-requisitos
- .NET 8 ou superior
- Certificado digital A1 (
.pfx/.p12) habilitado para emissão de NFS-e pela Prefeitura de São Paulo
Registro de serviços
A biblioteca expõe três métodos de extensão sobre IServiceCollection. Cada método registra os serviços (typed HttpClient) e as factories (singletons para construção de pedidos assinados) da versão correspondente, além de CertificadoNfePaulistana como singleton compartilhado:
| Método | Quando usar |
|---|---|
AddNfePaulistanaV1 |
Integração exclusivamente com o schema v01 |
AddNfePaulistanaV2 |
Integração exclusivamente com o schema v02 |
AddNfePaulistanaAll |
Migração gradual - V1 e V2 coexistindo no mesmo processo |
Configuração básica
// Program.cs
builder.Services.AddNfePaulistanaV1(options =>
{
options.Certificado.FilePath = "/run/secrets/certificado.pfx";
options.Certificado.Password = "senha-do-certificado";
});
Fontes de certificado
CertificadoNfePaulistana aceita três fontes alternativas. Configure apenas uma delas:
// 1. Caminho de arquivo no sistema de arquivos
options.Certificado.FilePath = "/etc/certs/empresa.pfx";
options.Certificado.Password = "senha";
// 2. Bytes brutos (útil com Azure Key Vault / AWS Secrets Manager)
byte[] certBytes = await secretClient.GetSecretValueAsync("certificado");
options.Certificado.RawData = new ReadOnlyCollection<byte>(certBytes);
// 3. Handle nativo (certificado já carregado pelo sistema operacional ou hardware token)
options.Certificado.PointerHandle = handleNativo;
Já tem um
X509Certificate2? Usecert.Export(X509ContentType.Pfx, senha)para obter os bytes e configure viaRawData, ou passecert.Handlediretamente emPointerHandle.
Por padrão o carregamento usa DefaultKeySet, delegando ao sistema operacional a decisão de armazenamento — compatível com todas as plataformas. Sobrescreva quando necessário:
// Mantém a chave privada apenas na memória — requer suporte a chaves efêmeras na plataforma (Linux, Windows 10+/Server 2016+)
options.Certificado.KeyStorageFlags = X509KeyStorageFlags.EphemeralKeySet;
// Persiste no key store da máquina — necessário apenas em cenários que exigem acesso entre processos
options.Certificado.KeyStorageFlags = X509KeyStorageFlags.MachineKeySet;
Configuração de endpoint
O endpoint de produção é configurado automaticamente. Para sobrescrever (ex.: testes de integração contra um mock):
options.EndpointUrl = new Uri("https://mock-nfe.intranet.exemplo.com/lotenfe.asmx");
Resiliência
Comportamento padrão (sem configureClient)
Quando o parâmetro configureClient é omitido, a biblioteca registra automaticamente um handler interno de resiliência sem nenhuma dependência adicional. O comportamento é:
| Dimensão | Valor |
|---|---|
| Tentativas máximas | 3 |
| Backoff entre tentativas | 1 s → 2 s (exponencial) |
| Timeout por tentativa | Configurável via TimeoutPorTentativa (padrão: 10 s) |
| Códigos HTTP com retry | 408, 429, 500, 502, 503, 504 |
| Falha de rede | Retry em HttpRequestException |
| Cancelamento pelo chamador | Interrompe imediatamente, sem nova tentativa |
| Circuit breaker | Não incluso — use configureClient se necessário |
// Configuração mínima: resiliência embutida com timeout padrão de 10 s por tentativa
builder.Services.AddNfePaulistanaV1(options =>
{
options.Certificado.FilePath = "/run/secrets/certificado.pfx";
options.Certificado.Password = "senha";
});
// Ajustar o timeout por tentativa (em segundos)
builder.Services.AddNfePaulistanaV1(options =>
{
options.Certificado.FilePath = "/run/secrets/certificado.pfx";
options.Certificado.Password = "senha";
options.TimeoutPorTentativa = 15;
});
Resiliência customizada (com configureClient)
Ao fornecer configureClient, o handler interno é completamente ignorado — retry, backoff e timeout passam a ser responsabilidade exclusiva do consumidor. Isso vale mesmo que TimeoutPorTentativa esteja definido.
O parâmetro é aplicado uniformemente a cada IHttpClientBuilder registrado.
Microsoft.Extensions.Http.Resilience (recomendado para .NET 8+)
dotnet add package Microsoft.Extensions.Http.Resilience
builder.Services.AddNfePaulistanaV2(
options =>
{
options.Certificado.FilePath = "/run/secrets/certificado.pfx";
options.Certificado.Password = "senha";
},
configureClient: b => b.AddStandardResilienceHandler());
AddStandardResilienceHandler configura automaticamente retry com backoff exponencial, timeout por tentativa, circuit breaker e timeout total — tudo alinhado às recomendações do .NET.
Para personalizar as políticas:
builder.Services.AddNfePaulistanaV2(
options => options.Certificado.FilePath = "/run/secrets/certificado.pfx",
configureClient: b => b.AddResilienceHandler("nfe-pipeline", pipeline =>
{
pipeline.AddRetry(new HttpRetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(2),
BackoffType = DelayBackoffType.Exponential,
});
pipeline.AddCircuitBreaker(new HttpCircuitBreakerStrategyOptions
{
SamplingDuration = TimeSpan.FromSeconds(30),
FailureRatio = 0.5,
MinimumThroughput = 5,
BreakDuration = TimeSpan.FromSeconds(15),
});
pipeline.AddTimeout(TimeSpan.FromSeconds(10));
}));
Polly
dotnet add package Microsoft.Extensions.Http.Polly
using Polly;
using Polly.Extensions.Http;
IAsyncPolicy<HttpResponseMessage> RetryPolicy() =>
HttpPolicyExtensions
.HandleTransientHttpError()
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt)));
builder.Services.AddNfePaulistanaV2(
options => options.Certificado.FilePath = "/run/secrets/certificado.pfx",
configureClient: b => b.AddPolicyHandler(RetryPolicy()));
Cenário de migração gradual (V1 + V2 registradas simultaneamente)
builder.Services.AddNfePaulistanaAll(
options =>
{
options.Certificado.FilePath = "/run/secrets/certificado.pfx";
options.Certificado.Password = "senha";
},
configureClient: b => b.AddStandardResilienceHandler());
Diagnóstico
A biblioteca expõe um handler de diagnóstico SOAP via AddNfePaulistanaDiagnostics, que intercepta cada intercâmbio e entrega um SoapExchange com o XML de requisição, metadados da operação (SOAPAction, tempo decorrido, status HTTP) e, em caso de erro (4xx/5xx), o XML de resposta. Em respostas de sucesso (2xx) ResponseXml é uma string vazia — o corpo é lido de forma incremental pelo serviço sem ser materializado como string, reduzindo o consumo de memória em lotes grandes.
Logging estruturado via ILogger (recomendado)
builder.Services.AddNfePaulistanaV2(
options => options.Certificado.FilePath = "/run/secrets/certificado.pfx",
configureClient: b => b
.AddStandardResilienceHandler()
.AddNfePaulistanaDiagnostics(sp =>
{
var logger = sp.GetRequiredService<ILogger<Program>>();
return exchange => logger.LogDebug(
"[NFS-e] {SoapAction} → {Status} ({ElapsedMs}ms)",
exchange.SoapAction,
exchange.IsSuccess ? "OK" : "FALHA",
(long)exchange.Elapsed.TotalMilliseconds);
}));
Ordem importa:
AddNfePaulistanaDiagnosticsposicionado apósAddStandardResilienceHandlerobserva apenas o resultado final da cadeia de retries. Posicionado antes, recebe uma notificação por tentativa — útil para métricas de retry.
Callback direto (útil em testes de integração)
configureClient: b => b.AddNfePaulistanaDiagnostics(exchange =>
{
Console.WriteLine($"Request:\n{exchange.RequestXml}");
// ResponseXml é preenchido apenas em respostas de erro (4xx/5xx)
if (!exchange.IsSuccess)
Console.WriteLine($"Response (erro):\n{exchange.ResponseXml}");
})
SoapExchange expõe: SoapAction, RequestXml, ResponseXml (preenchido apenas em erros), Elapsed e IsSuccess.
Operações disponíveis
V1 — Schema v01 (AddNfePaulistanaV1)
| Interface | Operação WSDL | Descrição |
|---|---|---|
IEnvioRpsService |
EnvioRPS |
Envio de RPS unitário |
IEnvioLoteRpsService |
EnvioLoteRPS |
Envio de lote de RPS |
ICancelamentoNFeService |
CancelamentoNFe |
Cancelamento de NFS-e emitida |
IConsultaNFeService |
ConsultaNFe |
Consulta de NFS-e por chave |
IConsultaNFeRecebidasService |
ConsultaNFeRecebidas |
Consulta de NFS-e recebidas por período |
IConsultaNFeEmitidasService |
ConsultaNFeEmitidas |
Consulta de NFS-e emitidas por período |
IConsultaLoteService |
ConsultaLote |
Consulta de lote pelo número |
IConsultaInformacoesLoteService |
ConsultaInformacoesLote |
Informações do lote de envio |
IConsultaCNPJService |
ConsultaCNPJ |
Inscrições municipais vinculadas a um CNPJ |
Namespaces: Nfe.Paulistana.V1.Services, Nfe.Paulistana.V1.Builders
V2 — Schema v02 (AddNfePaulistanaV2)
| Interface | Operação WSDL | Diferença em relação à V1 |
|---|---|---|
IEnvioRpsService |
EnvioRPS |
Suporte a IBS/CBS (reforma tributária), tomador estrangeiro com NIF e endereço exterior |
IEnvioLoteRpsService |
EnvioLoteRPS |
Suporte a IBS/CBS (reforma tributária) e envio de eventos fiscais |
ICancelamentoNFeService |
CancelamentoNFe |
Schema v02 (operação equivalente à V1) |
IConsultaNFeService |
ConsultaNFe |
Schema v02 (operação equivalente à V1) |
IConsultaNFeRecebidasService |
ConsultaNFeRecebidas |
Schema v02 (operação equivalente à V1) |
IConsultaNFeEmitidasService |
ConsultaNFeEmitidas |
Schema v02 (operação equivalente à V1) |
IConsultaLoteService |
ConsultaLote |
Schema v02 (operação equivalente à V1) |
IConsultaInformacoesLoteService |
ConsultaInformacoesLote |
Inscrição municipal de 1 a 12 dígitos |
IConsultaCNPJService |
ConsultaCNPJ |
Suporte a CNPJ alfanumérico (Receita Federal 2026) |
Namespaces: Nfe.Paulistana.V2.Services, Nfe.Paulistana.V2.Builders
Exemplos de uso
Todos os serviços seguem o mesmo padrão: injete a interface do serviço e a Factory correspondente (ambos registrados automaticamente) e chame SendAsync.
Envio de RPS unitário (V1)
public class EmissaoNfeService(
IEnvioRpsService envioRpsService,
PedidoEnvioFactory factory)
{
public async Task<RetornoEnvioRps> EmitirAsync(DadosNfe dados, CancellationToken ct = default)
{
var rps = RpsBuilder
.New(
inscricaoPrestador: (InscricaoMunicipal)dados.InscricaoMunicipal,
tipoRps: TipoRps.Rps,
numeroRps: (Numero)dados.NumeroRps,
discriminacao: (Discriminacao)dados.Discriminacao,
serieRps: (SerieRps)dados.SerieRps)
.SetNFe(/* ... */)
.SetServico(/* ... */)
.SetIss(/* ... */)
.SetTomador(/* ... */)
.Build();
PedidoEnvio pedido = factory.NewCnpj((Cnpj)dados.Cnpj, rps);
return await envioRpsService.SendAsync(pedido, ct);
}
}
Consulta de CNPJ alfanumérico (V2)
public class ConsultaCnpjService(
Nfe.Paulistana.V2.Services.IConsultaCNPJService consultaService,
Nfe.Paulistana.V2.Builders.PedidoConsultaCNPJFactory factory)
{
public async Task<RetornoConsultaCNPJ> ConsultarAsync(string cnpjAlfa, CancellationToken ct = default)
{
var cnpj = new Nfe.Paulistana.V2.Models.DataTypes.Cnpj(cnpjAlfa);
var im = new Nfe.Paulistana.V2.Models.DataTypes.InscricaoMunicipal(39616924);
PedidoConsultaCNPJ pedido = factory.NewCnpj(cnpj, new CpfOrCnpj(cnpj), im);
return await consultaService.SendAsync(pedido, ct);
}
}
Consulta de informações de lote (V2)
public class InformacoesLoteService(
Nfe.Paulistana.V2.Services.IConsultaInformacoesLoteService infoService,
Nfe.Paulistana.V2.Builders.PedidoInformacoesLoteFactory factory)
{
public async Task<RetornoInformacoesLote> ConsultarAsync(long numeroLote, CancellationToken ct = default)
{
var cpf = new Cpf(46381819618L);
var im = new Nfe.Paulistana.V2.Models.DataTypes.InscricaoMunicipal(39616924);
PedidoInformacoesLote pedido = factory.NewCpf(cpf, im, new Numero(numeroLote));
return await infoService.SendAsync(pedido, ct);
}
}
Para exemplos end-to-end executáveis de todas as operações — envio de RPS (incluindo campos V2 como IBS/CBS), cancelamento e consultas — explore o projeto de amostra incluído neste repositório.
Tratamento de erros
Os serviços lançam exceções tipadas:
| Exceção | Causa |
|---|---|
ArgumentNullException |
Pedido nulo passado a SendAsync |
InvalidOperationException |
Falha na validação XSD do pedido, ou resposta sem payload válido |
HttpRequestException |
Falha de transporte HTTP (resolvida com política de resiliência) |
A resposta do webservice pode indicar falha de negócio através das coleções Erro e Alerta presentes em cada tipo Retorno*. Verifique Cabecalho.Sucesso antes de processar os dados:
RetornoConsultaCNPJ retorno = await consultaService.SendAsync(pedido, ct);
if (retorno.Cabecalho?.Sucesso == false)
{
foreach (var erro in retorno.Erro ?? [])
logger.LogError("NFS-e erro {Codigo}: {Descricao}", erro.Codigo, erro.Descricao);
}
Arquitetura
A biblioteca segue Clean Architecture com separação explícita entre domínio, aplicação e infraestrutura. Os pontos de destaque:
- Value Objects fortemente tipados — cada campo fiscal possui seu próprio tipo (
InscricaoMunicipal,Cnpj,CodigoNBS, etc.) com validação de invariantes no construtor. Erros de tipo são capturados em tempo de compilação. Para campos opcionais, todos os tipos de texto expõemParseIfPresent(string? value): retornanullpara entradas ausentes e lançaArgumentExceptionpara valores presentes mas inválidos. - Suporte dual V1/V2 — as duas versões do webservice da Prefeitura coexistem no mesmo pacote com namespaces isolados (
Nfe.Paulistana.V1/Nfe.Paulistana.V2), permitindo migração gradual. - Assinatura digital embutida — o pipeline de assinatura XML (xmldsig) é transparente ao consumidor; a biblioteca assina automaticamente RPS e pedidos de cancelamento usando o certificado configurado.
- Validação XSD embarcada — todos os schemas
.xsdsão recursos embarcados (EmbeddedResource); a validação ocorre antes do envio, sem dependência de arquivos externos. - Builder fluente — pedidos complexos são construídos via interface fluente (
RpsBuilder,EventoBuilder) que guia o consumidor pelos campos obrigatórios e opcionais.
Para detalhes de implementação e decisões de design, consulte:
| Documento | Conteúdo |
|---|---|
src/README.md |
Estrutura de pastas, namespaces e padrões da biblioteca |
docs/adr/0002 |
Decisão: Value Objects para campos fiscais |
docs/adr/0003 |
Decisão: suporte dual V1/V2 |
docs/adr/0004 |
Decisão: assinatura digital e validação XSD |
Otimizações de performance
Todas as otimizações descritas aqui são totalmente transparentes para o consumidor e não requerem configuração adicional.
Cache de XmlSerializer
A biblioteca implementa um cache estático de instâncias de XmlSerializer usando ConcurrentDictionary<Type, XmlSerializer>. Essa estratégia elimina três fontes de custo presentes na construção de serializadores por chamada:
- Alocação por chamada: Cada
new XmlSerializer(type)aloca um novo objeto no heap, mesmo quando o assembly de serialização já foi compilado pelo BCL. O cache garante que apenas uma instância por tipo existe durante todo o tempo de vida do processo. - Contenção de lock em alta concorrência: O cache interno do BCL usa um
Hashtableprotegido por lock. Em cenários com muitas threads simultâneas, cada construção disputa esse lock. OConcurrentDictionaryutiliza leituras lock-free em caminhos quentes, reduzindo a contenção. - Corrida no cold start: Múltiplas threads que chegam simultaneamente antes da primeira entrada no cache do BCL podem cada uma disparar uma compilação independente do assembly de serialização. O
GetOrAdddoConcurrentDictionarylimita isso a no máximo uma construção extra descartada, estabilizando imediatamente após.
Streaming de respostas e buffers poolados
Lotes de NFS-e com muitos itens e assinaturas embutidas podem gerar payloads SOAP de vários MB. Para evitar picos de memória nesses cenários, a biblioteca adota duas estratégias complementares:
- Deserialização incremental via stream: A resposta HTTP é lida com
ResponseHeadersReade entregue diretamente aoXmlSerializercomoStream. O envelope SOAP completo nunca é materializado comostring— a deserialização ocorre incrementalmente conforme os bytes chegam da rede. - Buffers poolados com
RecyclableMemoryStream: Todas as instâncias deMemoryStreamusadas na serialização do envelope de requisição e na serialização interna de conteúdo XML assinado são alocadas viaRecyclableMemoryStreamManager(backed porArrayPool<byte>), reduzindo a pressão no GC e evitando alocações no LOH em payloads grandes.
Contribuindo
Contribuições são bem-vindas. Antes de abrir um PR, leia:
docs/adr/— decisões arquiteturais registradas; entenda o porquê das escolhas antes de propor alteraçõestests/README.md— convenções de teste, estrutura da suíte em três níveis (Tier 1 unitários, Tier 2 integração estrutural, Tier 3 integração real) e como executar cada nível- Toda contribuição deve incluir testes correspondentes seguindo o padrão
Método_Estado_ResultadoEsperado
⚠️ Este repositório exige assinatura de CLA antes de aceitar contribuições externas.
Licença
Este projeto é licenciado sob a MIT License. Resumindo: você pode usar livremente, inclusive em projetos comerciais, desde que mantenha o aviso de copyright.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.Extensions.Configuration.Binder (>= 10.0.7)
- Microsoft.Extensions.DependencyInjection (>= 10.0.7)
- Microsoft.Extensions.Diagnostics (>= 10.0.7)
- Microsoft.Extensions.Http (>= 10.0.7)
- Microsoft.Extensions.Options (>= 10.0.7)
- Microsoft.IO.RecyclableMemoryStream (>= 3.0.1)
- System.Security.Cryptography.Pkcs (>= 10.0.7)
- System.Security.Cryptography.Xml (>= 10.0.7)
-
net8.0
- Microsoft.Extensions.Configuration.Binder (>= 10.0.7)
- Microsoft.Extensions.DependencyInjection (>= 10.0.7)
- Microsoft.Extensions.Diagnostics (>= 10.0.7)
- Microsoft.Extensions.Http (>= 10.0.7)
- Microsoft.Extensions.Options (>= 10.0.7)
- Microsoft.IO.RecyclableMemoryStream (>= 3.0.1)
- System.Security.Cryptography.Pkcs (>= 10.0.7)
- System.Security.Cryptography.Xml (>= 10.0.7)
-
net9.0
- Microsoft.Extensions.Configuration.Binder (>= 10.0.7)
- Microsoft.Extensions.DependencyInjection (>= 10.0.7)
- Microsoft.Extensions.Diagnostics (>= 10.0.7)
- Microsoft.Extensions.Http (>= 10.0.7)
- Microsoft.Extensions.Options (>= 10.0.7)
- Microsoft.IO.RecyclableMemoryStream (>= 3.0.1)
- System.Security.Cryptography.Pkcs (>= 10.0.7)
- System.Security.Cryptography.Xml (>= 10.0.7)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.1 | 128 | 4/28/2026 |
| 2.0.0 | 116 | 4/27/2026 |
| 1.0.0 | 110 | 4/9/2026 |
| 1.0.0-beta.2 | 74 | 4/8/2026 |
| 1.0.0-beta.1 | 85 | 4/6/2026 |