@sinete/nfe 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +11 -0
  3. package/README.md +147 -1
  4. package/dist/build/build.d.ts +127 -0
  5. package/dist/build/build.d.ts.map +1 -0
  6. package/dist/build/destinatario.d.ts +19 -0
  7. package/dist/build/destinatario.d.ts.map +1 -0
  8. package/dist/build/ibscbs.d.ts +27 -0
  9. package/dist/build/ibscbs.d.ts.map +1 -0
  10. package/dist/build/icms.d.ts +42 -0
  11. package/dist/build/icms.d.ts.map +1 -0
  12. package/dist/build/nfce.d.ts +122 -0
  13. package/dist/build/nfce.d.ts.map +1 -0
  14. package/dist/build/pl.d.ts +19 -0
  15. package/dist/build/pl.d.ts.map +1 -0
  16. package/dist/build/rejeicoes.d.ts +52 -0
  17. package/dist/build/rejeicoes.d.ts.map +1 -0
  18. package/dist/build/tributos.d.ts +45 -0
  19. package/dist/build/tributos.d.ts.map +1 -0
  20. package/dist/build/values.d.ts +48 -0
  21. package/dist/build/values.d.ts.map +1 -0
  22. package/dist/decimal.d.ts +66 -0
  23. package/dist/decimal.d.ts.map +1 -0
  24. package/dist/format.d.ts +48 -0
  25. package/dist/format.d.ts.map +1 -0
  26. package/dist/ibs-cbs.d.ts +14 -0
  27. package/dist/ibs-cbs.d.ts.map +1 -0
  28. package/dist/ibs-cbs.js +43 -0
  29. package/dist/ibs-cbs.js.map +10 -0
  30. package/dist/index.d.ts +37 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +4370 -0
  33. package/dist/index.js.map +33 -0
  34. package/dist/issues.d.ts +24 -0
  35. package/dist/issues.d.ts.map +1 -0
  36. package/dist/model.d.ts +861 -0
  37. package/dist/model.d.ts.map +1 -0
  38. package/dist/ports.d.ts +109 -0
  39. package/dist/ports.d.ts.map +1 -0
  40. package/dist/rotulo.d.ts +12 -0
  41. package/dist/rotulo.d.ts.map +1 -0
  42. package/dist/rtc.d.ts +65 -0
  43. package/dist/rtc.d.ts.map +1 -0
  44. package/dist/services/client.d.ts +273 -0
  45. package/dist/services/client.d.ts.map +1 -0
  46. package/dist/services/gzip.d.ts +7 -0
  47. package/dist/services/gzip.d.ts.map +1 -0
  48. package/dist/services/index.d.ts +14 -0
  49. package/dist/services/index.d.ts.map +1 -0
  50. package/dist/services/outcome.d.ts +15 -0
  51. package/dist/services/outcome.d.ts.map +1 -0
  52. package/dist/services/proc.d.ts +41 -0
  53. package/dist/services/proc.d.ts.map +1 -0
  54. package/dist/services/recuperar.d.ts +28 -0
  55. package/dist/services/recuperar.d.ts.map +1 -0
  56. package/dist/services/resolver.d.ts +88 -0
  57. package/dist/services/resolver.d.ts.map +1 -0
  58. package/dist/services/soap.d.ts +43 -0
  59. package/dist/services/soap.d.ts.map +1 -0
  60. package/dist/time.d.ts +12 -0
  61. package/dist/time.d.ts.map +1 -0
  62. package/dist/versao-gerada.d.ts +3 -0
  63. package/dist/versao-gerada.d.ts.map +1 -0
  64. package/package.json +58 -3
  65. package/src/build/build.ts +1486 -0
  66. package/src/build/destinatario.ts +212 -0
  67. package/src/build/ibscbs.ts +131 -0
  68. package/src/build/icms.ts +645 -0
  69. package/src/build/nfce.ts +425 -0
  70. package/src/build/pl.ts +73 -0
  71. package/src/build/rejeicoes.ts +142 -0
  72. package/src/build/tributos.ts +217 -0
  73. package/src/build/values.ts +126 -0
  74. package/src/data/arredondamento.json +30 -0
  75. package/src/data/cstat.json +36 -0
  76. package/src/data/fusos.json +17 -0
  77. package/src/data/nfce-urls.json +170 -0
  78. package/src/data/produtor-rural.json +17 -0
  79. package/src/data/reforma.json +6 -0
  80. package/src/data/resp-tec.json +13 -0
  81. package/src/data/servicos.json +44 -0
  82. package/src/data/suframa.json +7 -0
  83. package/src/decimal.ts +231 -0
  84. package/src/format.ts +64 -0
  85. package/src/ibs-cbs.ts +89 -0
  86. package/src/index.ts +153 -0
  87. package/src/issues.ts +80 -0
  88. package/src/model.ts +1033 -0
  89. package/src/ports.ts +96 -0
  90. package/src/rotulo.ts +137 -0
  91. package/src/rtc.ts +305 -0
  92. package/src/services/client.ts +1085 -0
  93. package/src/services/gzip.ts +26 -0
  94. package/src/services/index.ts +43 -0
  95. package/src/services/outcome.ts +50 -0
  96. package/src/services/proc.ts +164 -0
  97. package/src/services/recuperar.ts +91 -0
  98. package/src/services/resolver.ts +127 -0
  99. package/src/services/soap.ts +133 -0
  100. package/src/time.ts +23 -0
  101. package/src/versao-gerada.ts +3 -0
@@ -0,0 +1,1085 @@
1
+ /**
2
+ * Serviços da NF-e 4.00 sobre o `@sinete/transport` e a assinatura do `@sinete/core/xml`: status, autorização síncrona e
3
+ * assíncrona (recibo e consulta do recibo com política de espera), consulta protocolo, eventos (cancelamento,
4
+ * cancelamento por substituição, CC-e, manifestação do destinatário no AN), inutilização, consulta cadastro e
5
+ * Distribuição DF-e. Contingência SVC por dado: o autorizador SVC-AN ou SVC-RS de cada UF vem do `@sinete/transport`.
6
+ *
7
+ * Todo desfecho que chega a uma resposta da SEFAZ é um `SefazOutcome` do core (autorizado, rejeitado, denegado,
8
+ * pendente), com a dica do `@sinete/rejeicoes` na rejeição. Os documentos processados (`nfeProc`, `procEventoNFe`,
9
+ * `ProcInutNFe`) são montados por splice: o XML assinado entra byte a byte, o protocolo entra como fatia da resposta.
10
+ *
11
+ * Idempotência da autorização (MOC 7.0, Visão Geral; RV de duplicidade 204 e 539): grave o XML assinado antes de
12
+ * enviar; se o envio ficar sem resposta (timeout, conexão caída), nunca gere outro `cNF` nem outro `dhEmi` para o mesmo
13
+ * número. Consulte a chave (`resolverEnvioSemResposta`) e, se ela não constar, reenvie exatamente os mesmos bytes.
14
+ */
15
+
16
+ import type { Ambiente, Clock, CUf, Logger, SefazOutcome, Signer, Uf } from '@sinete/core';
17
+ import {
18
+ authorized,
19
+ ConfigError,
20
+ denied,
21
+ noopLogger,
22
+ ProtocolError,
23
+ pending,
24
+ tpAmbOf,
25
+ ufByCUf,
26
+ ufBySigla,
27
+ ValidationError,
28
+ } from '@sinete/core';
29
+ import type { XmlDocument, XmlElement } from '@sinete/core/xml';
30
+ import { childElements, firstChild, parseXml, signXml, textOf } from '@sinete/core/xml';
31
+ import type { ComplexType, SchemaIssue } from '@sinete/schemas';
32
+ import { decode, decodeXml, serialize, serializeRoot, validate } from '@sinete/schemas';
33
+ import type { TRetConsCad_infCons_infCad } from '@sinete/schemas/nfe/consulta-cadastro/PL_010d';
34
+ import { ConsCadElement, TRetConsCad } from '@sinete/schemas/nfe/consulta-cadastro/PL_010d';
35
+ import { consSitNFeElement, TProtNFe, TRetConsSitNFe } from '@sinete/schemas/nfe/consulta-protocolo/PL_010d';
36
+ import type { distDFeInt, resEvento, resNFe } from '@sinete/schemas/nfe/dist-dfe/PL_NFeDistDFe_104';
37
+ import {
38
+ distDFeIntElement,
39
+ resEventoElement,
40
+ resNFeElement,
41
+ retDistDFeInt,
42
+ } from '@sinete/schemas/nfe/dist-dfe/PL_NFeDistDFe_104';
43
+ import { TEvento_infEvento as TEventoCanc, TRetEnvEvento } from '@sinete/schemas/nfe/evento-cancelamento/PL_010d';
44
+ import type { TEvento_infEvento_detEvento as DetCancSubst } from '@sinete/schemas/nfe/evento-cancelamento-substituicao/PL_010d';
45
+ import { TEvento_infEvento as TEventoCancSubst } from '@sinete/schemas/nfe/evento-cancelamento-substituicao/PL_010d';
46
+ import type { TEvento_infEvento_detEvento as DetCce } from '@sinete/schemas/nfe/evento-cce/PL_010d';
47
+ import { TEvento_infEvento as TEventoCce } from '@sinete/schemas/nfe/evento-cce/PL_010d';
48
+ import { TEvento_infEvento as TEventoCiencia } from '@sinete/schemas/nfe/evento-ciencia-operacao/PL_010d';
49
+ import { TEvento_infEvento as TEventoConfirmacao } from '@sinete/schemas/nfe/evento-confirmacao-operacao/PL_010d';
50
+ import { TEvento_infEvento as TEventoDesconhecimento } from '@sinete/schemas/nfe/evento-desconhecimento-operacao/PL_010d';
51
+ import { TEvento_infEvento as TEventoNaoRealizada } from '@sinete/schemas/nfe/evento-operacao-nao-realizada/PL_010d';
52
+ import { TInutNFe_infInut, TRetInutNFe } from '@sinete/schemas/nfe/inutilizacao/PL_010d';
53
+ import { TRetConsReciNFe, TRetEnviNFe } from '@sinete/schemas/nfe/PL_010f';
54
+ import { consStatServElement, TRetConsStatServ } from '@sinete/schemas/nfe/status-servico/PL_009q';
55
+ import type { EndpointRef, NfeServico, Transport } from '@sinete/transport';
56
+ import { nfceEndpoint, nfeContingenciaDaUf, nfeEndpoint } from '@sinete/transport';
57
+ import type { ChaveAcesso } from '@sinete/validators';
58
+ import { parseChaveAcesso, parseCnpj, parseCpf } from '@sinete/validators';
59
+ import { formatDh, offsetDaUf } from '../time.ts';
60
+ import { gunzipBase64 } from './gzip.ts';
61
+ import { cstatEm, rejeitado } from './outcome.ts';
62
+ import type { DocumentoAssinado } from './proc.ts';
63
+ import { documentoAssinado, envelope, NFE_NS, sliceElement } from './proc.ts';
64
+ import type { RespostaSoap } from './soap.ts';
65
+ import { chamar } from './soap.ts';
66
+
67
+ // ---------------------------------------------------------------------------------------------------------------
68
+ // Configuração
69
+ // ---------------------------------------------------------------------------------------------------------------
70
+
71
+ /** CNPJ ou CPF de quem assina um evento ou consulta a distribuição. */
72
+ export type AutorDocumento =
73
+ | { readonly CNPJ: string; readonly CPF?: never }
74
+ | { readonly CPF: string; readonly CNPJ?: never };
75
+
76
+ /** Espera injetável (testes passam uma que não dorme). */
77
+ export type Sleep = (ms: number, signal?: AbortSignal) => Promise<void>;
78
+
79
+ export interface NfeClientOptions {
80
+ readonly transport: Transport;
81
+ /** Assina NF-e, eventos e inutilização (A1 WebCrypto, A3 via PKCS#11, HSM). */
82
+ readonly signer: Signer;
83
+ readonly ambiente: Ambiente;
84
+ /**
85
+ * UF dos serviços que não partem de um documento: status do serviço, inutilização, recibo consultado sem a NF-e,
86
+ * `cUFAutor` padrão da distribuição e fuso da manifestação. Autorização, consulta, recibo com a NF-e e eventos da
87
+ * própria nota vão ao autorizador da chave (cUF e tpEmis), seja qual for esta UF; sem ela, os serviços que precisam
88
+ * dela lançam `ConfigError`.
89
+ */
90
+ readonly uf?: Uf;
91
+ /** Relógio de emissão: `dhEvento`, número do lote. */
92
+ readonly clock: Clock;
93
+ readonly logger?: Logger;
94
+ /** Prazo por requisição; padrão o do transporte. */
95
+ readonly timeoutMs?: number;
96
+ /** Fuso do emitente em minutos; padrão o da UF da chave nos eventos e o de `uf` na manifestação (`data/fusos.json`). */
97
+ readonly offsetMinutes?: number;
98
+ /**
99
+ * `svc`: o status do serviço e o recibo consultado sem a NF-e vão ao SVC-AN ou SVC-RS de `uf`. Não vale para o que
100
+ * parte de um documento: a NF-e assinada em SVC (tpEmis 6 ou 7, na chave) vai ao SVC que a chave diz, e a assinada
101
+ * fora dele vai à UF, com ou sem esta opção.
102
+ */
103
+ readonly contingencia?: 'svc';
104
+ /** CNPJ ou CPF do titular do certificado: autor da manifestação, interessado da distribuição, emitente da inutilização. */
105
+ readonly autor?: AutorDocumento;
106
+ readonly sleep?: Sleep;
107
+ /**
108
+ * Sobrepõe o endpoint da NFC-e (modelo 65) por serviço e UF. Sem ela, vale a tabela da NFC-e do `@sinete/transport`
109
+ * (`nfceEndpoint`), que em várias UFs (SP, MG, PR, RS, SVRS) é outro host que o da NF-e. A NFC-e não tem SVC: com
110
+ * `contingencia: 'svc'`, o documento modelo 65 continua indo ao autorizador normal da NFC-e.
111
+ */
112
+ readonly nfceEndpoint?: (servico: NfeServico, uf: Uf) => EndpointRef;
113
+ /** Gerador de `idLote` (até 15 dígitos); padrão: os milissegundos do relógio. */
114
+ readonly idLote?: () => string;
115
+ }
116
+
117
+ /** Opções da consulta de um recibo. */
118
+ export interface ConsultaReciboOpcoes {
119
+ /** Cancela a requisição em curso. */
120
+ readonly signal?: AbortSignal;
121
+ /**
122
+ * Modelo do lote, para escolher o serviço (NFC-e vai pelo `nfceEndpoint`). Com a NF-e assinada o modelo vem da
123
+ * chave e este campo, se vier, tem de bater; sem ela, é obrigatório para NFC-e e o padrão é 55.
124
+ */
125
+ readonly mod?: '55' | '65';
126
+ }
127
+
128
+ /** Política de consulta do recibo (autorização assíncrona). */
129
+ export interface PoliticaRecibo extends ConsultaReciboOpcoes {
130
+ /** Consultas no máximo. Padrão 10. */
131
+ readonly maxTentativas?: number;
132
+ /** Espera antes da primeira consulta e mínima entre consultas. Padrão 2 000 ms (o MOC pede aguardar o tMed). */
133
+ readonly esperaMinimaMs?: number;
134
+ /** Fator de crescimento da espera. Padrão 1,5. */
135
+ readonly multiplicador?: number;
136
+ /** Teto da espera. Padrão 30 000 ms. */
137
+ readonly esperaMaximaMs?: number;
138
+ }
139
+
140
+ // ---------------------------------------------------------------------------------------------------------------
141
+ // Valores dos desfechos
142
+ // ---------------------------------------------------------------------------------------------------------------
143
+
144
+ /** Status do serviço (cStat 107). */
145
+ export interface StatusServico {
146
+ readonly cUF: string;
147
+ readonly verAplic: string;
148
+ readonly dhRecbto: string;
149
+ /** Tempo médio de resposta, em segundos. */
150
+ readonly tMed?: string;
151
+ readonly dhRetorno?: string;
152
+ readonly xObs?: string;
153
+ }
154
+
155
+ /** Protocolo de uma NF-e (autorização ou denegação). */
156
+ export interface ProtocoloNfe {
157
+ readonly chNFe: string;
158
+ readonly cStat: string;
159
+ readonly xMotivo: string;
160
+ readonly nProt?: string;
161
+ readonly dhRecbto: string;
162
+ readonly digVal?: string;
163
+ readonly verAplic: string;
164
+ /** `protNFe` como veio na resposta (fatia do texto). */
165
+ readonly protNFe: string;
166
+ /**
167
+ * `nfeProc` com a NF-e assinada byte a byte e o `protNFe`; presente quando a NF-e assinada é conhecida e o `digVal`
168
+ * do protocolo confere com o DigestValue dela.
169
+ */
170
+ readonly nfeProc?: string;
171
+ }
172
+
173
+ /** Desfecho da autorização: autorizada, denegada (número consumido), rejeitada ou pendente (recibo em `ref`). */
174
+ export type AutorizacaoOutcome = SefazOutcome<ProtocoloNfe, ProtocoloNfe>;
175
+
176
+ /** Situação da NF-e na consulta protocolo. */
177
+ export interface ConsultaNfe {
178
+ readonly chNFe: string;
179
+ readonly situacao: 'autorizada' | 'cancelada' | 'denegada';
180
+ readonly protocolo?: ProtocoloNfe;
181
+ /** `procEventoNFe` devolvidos, como fatias da resposta. */
182
+ readonly eventos: readonly string[];
183
+ /** Com a NF-e assinada dada: o `digVal` do protocolo é o DigestValue dela. */
184
+ readonly digValConfere?: boolean;
185
+ }
186
+
187
+ export type ConsultaOutcome = SefazOutcome<ConsultaNfe, ConsultaNfe>;
188
+
189
+ /** Evento registrado (cStat 135, 136 ou 155). */
190
+ export interface EventoRegistrado {
191
+ readonly chNFe: string;
192
+ readonly tpEvento: string;
193
+ readonly nSeqEvento: string;
194
+ readonly nProt?: string;
195
+ readonly dhRegEvento: string;
196
+ /** `retEvento` como veio na resposta. */
197
+ readonly retEvento: string;
198
+ /** Evento assinado + `retEvento`. */
199
+ readonly procEventoNFe: string;
200
+ }
201
+
202
+ export type EventoOutcome = SefazOutcome<EventoRegistrado, never>;
203
+
204
+ /** Inutilização homologada (cStat 102). */
205
+ export interface Inutilizacao {
206
+ readonly nProt?: string;
207
+ readonly dhRecbto: string;
208
+ readonly retInutNFe: string;
209
+ readonly procInutNFe: string;
210
+ }
211
+
212
+ export type InutilizacaoOutcome = SefazOutcome<Inutilizacao, never>;
213
+
214
+ export interface Cadastro {
215
+ readonly UF: string;
216
+ readonly dhCons: string;
217
+ readonly infCad: readonly TRetConsCad_infCons_infCad[];
218
+ }
219
+
220
+ /** Documento devolvido pela Distribuição DF-e, já descompactado. */
221
+ export interface DocumentoDistribuido {
222
+ readonly NSU: string;
223
+ /** Schema que o AN informa (`resNFe_v1.01.xsd`, `procNFe_v4.00.xsd`...). */
224
+ readonly schema: string;
225
+ readonly tipo: 'resNFe' | 'resEvento' | 'procNFe' | 'procEventoNFe' | 'outro';
226
+ /** XML como veio dentro do gzip. Guarde este texto; não reserialize. */
227
+ readonly xml: string;
228
+ readonly resNFe?: resNFe;
229
+ readonly resEvento?: resEvento;
230
+ }
231
+
232
+ export interface Distribuicao {
233
+ readonly ultNSU: string;
234
+ readonly maxNSU: string;
235
+ readonly dhResp: string;
236
+ readonly documentos: readonly DocumentoDistribuido[];
237
+ }
238
+
239
+ export type ManifestacaoTipo = 'ciencia' | 'confirmacao' | 'desconhecimento' | 'nao-realizada';
240
+
241
+ export interface AutorizarOpcoes {
242
+ /** `indSinc` 1 (padrão) ou 0 (lote assíncrono com recibo). */
243
+ readonly sincrono?: boolean;
244
+ /** Cancela a requisição em curso. */
245
+ readonly signal?: AbortSignal;
246
+ }
247
+
248
+ export interface CancelamentoPedido {
249
+ readonly chave: string;
250
+ readonly nProt: string;
251
+ readonly xJust: string;
252
+ /** Padrão: o CNPJ ou CPF do emitente na chave. */
253
+ readonly autor?: AutorDocumento;
254
+ }
255
+
256
+ export interface CancelamentoSubstituicaoPedido {
257
+ readonly chave: string;
258
+ readonly nProt: string;
259
+ readonly xJust: string;
260
+ /** Chave da NFC-e que substitui a cancelada. */
261
+ readonly chNFeRef: string;
262
+ readonly cOrgaoAutor: string;
263
+ readonly tpAutor?: '1';
264
+ readonly verAplic: string;
265
+ readonly autor?: AutorDocumento;
266
+ }
267
+
268
+ export interface CartaCorrecaoPedido {
269
+ readonly chave: string;
270
+ readonly xCorrecao: string;
271
+ /** 1 a 20; cada CC-e nova substitui a anterior e leva o sequencial seguinte. */
272
+ readonly nSeqEvento: number;
273
+ readonly autor?: AutorDocumento;
274
+ }
275
+
276
+ export interface ManifestacaoPedido {
277
+ readonly chave: string;
278
+ readonly tipo: ManifestacaoTipo;
279
+ /** Obrigatória na operação não realizada (15 a 255 caracteres). */
280
+ readonly xJust?: string;
281
+ readonly autor?: AutorDocumento;
282
+ }
283
+
284
+ export interface InutilizacaoPedido {
285
+ /** Ano com 2 ou 4 dígitos. */
286
+ readonly ano: number | string;
287
+ readonly serie: number | string;
288
+ readonly nNFIni: number | string;
289
+ readonly nNFFin: number | string;
290
+ readonly xJust: string;
291
+ readonly mod?: '55' | '65';
292
+ readonly autor?: AutorDocumento;
293
+ }
294
+
295
+ export type CadastroPedido = { readonly uf: Uf } & (
296
+ | { readonly CNPJ: string }
297
+ | { readonly CPF: string }
298
+ | { readonly IE: string }
299
+ );
300
+
301
+ export type DistribuicaoConsulta =
302
+ | { readonly ultNSU: string | number }
303
+ | { readonly NSU: string | number }
304
+ | { readonly chNFe: string };
305
+
306
+ export interface DistribuicaoOpcoes {
307
+ /** UF do interessado; padrão a UF do cliente. */
308
+ readonly cUFAutor?: string;
309
+ readonly autor?: AutorDocumento;
310
+ }
311
+
312
+ export interface NfeClient {
313
+ readonly options: NfeClientOptions;
314
+ /**
315
+ * Status do serviço de autorização na UF das opções (MOC 7.0, tabela 4.4.1: 107 em operação, 108 e 109 paralisado).
316
+ * Com `mod: '65'`, o do autorizador da NFC-e, que em várias UFs é outro host.
317
+ */
318
+ statusServico(opcoes?: { readonly mod?: '55' | '65' }): Promise<SefazOutcome<StatusServico, never>>;
319
+ /** Envia uma NF-e assinada (a string devolvida pela assinatura, sem outra alteração). Padrão síncrono. */
320
+ autorizar(nfeAssinada: string, opcoes?: AutorizarOpcoes): Promise<AutorizacaoOutcome>;
321
+ /** Consulta o recibo de um lote assíncrono; com a NF-e assinada, monta o `nfeProc`. */
322
+ consultarRecibo(nRec: string, nfeAssinada?: string, opcoes?: ConsultaReciboOpcoes): Promise<AutorizacaoOutcome>;
323
+ /** Consulta o recibo até sair de pendente ou esgotar a política. */
324
+ aguardarRecibo(nRec: string, nfeAssinada?: string, politica?: PoliticaRecibo): Promise<AutorizacaoOutcome>;
325
+ consultar(chave: string, nfeAssinada?: string): Promise<ConsultaOutcome>;
326
+ cancelar(p: CancelamentoPedido): Promise<EventoOutcome>;
327
+ /** Cancelamento por substituição (110112): só NFC-e (modelo 65). */
328
+ cancelarPorSubstituicao(p: CancelamentoSubstituicaoPedido): Promise<EventoOutcome>;
329
+ cartaCorrecao(p: CartaCorrecaoPedido): Promise<EventoOutcome>;
330
+ /** Manifestação do destinatário, registrada no Ambiente Nacional (cOrgao 91). */
331
+ manifestar(p: ManifestacaoPedido): Promise<EventoOutcome>;
332
+ inutilizar(p: InutilizacaoPedido): Promise<InutilizacaoOutcome>;
333
+ consultarCadastro(p: CadastroPedido): Promise<SefazOutcome<Cadastro, never>>;
334
+ distribuicaoDFe(
335
+ consulta: DistribuicaoConsulta,
336
+ opcoes?: DistribuicaoOpcoes,
337
+ ): Promise<SefazOutcome<Distribuicao, never>>;
338
+ }
339
+
340
+ // ---------------------------------------------------------------------------------------------------------------
341
+ // Utilitários
342
+ // ---------------------------------------------------------------------------------------------------------------
343
+
344
+ /** Autorizador SVC da UF e o `tpEmis` que a NF-e emitida nele leva (6 SVC-AN, 7 SVC-RS; MOC 7.0, B22). */
345
+ export function autorizadorContingencia(
346
+ uf: Uf,
347
+ ambiente: Ambiente,
348
+ ): { readonly autorizador: 'SVC-AN' | 'SVC-RS'; readonly tpEmis: '6' | '7' } {
349
+ const autorizador = nfeContingenciaDaUf(uf, ambiente);
350
+ return { autorizador, tpEmis: autorizador === 'SVC-AN' ? '6' : '7' };
351
+ }
352
+
353
+ const defaultSleep: Sleep = (ms: number, signal?: AbortSignal): Promise<void> =>
354
+ new Promise<void>((resolve, reject) => {
355
+ if (signal?.aborted) {
356
+ reject(signal.reason);
357
+ return;
358
+ }
359
+ // O listener sai também quando o prazo vence: um signal longo reaproveitado entre esperas não acumula listeners.
360
+ const onAbort = (): void => {
361
+ clearTimeout(t);
362
+ reject(signal?.reason);
363
+ };
364
+ const t = setTimeout(() => {
365
+ signal?.removeEventListener('abort', onAbort);
366
+ resolve();
367
+ }, ms);
368
+ signal?.addEventListener('abort', onAbort, { once: true });
369
+ });
370
+
371
+ function chaveValida(chave: string, path: string): ChaveAcesso {
372
+ const r = parseChaveAcesso(chave, { path });
373
+ if (!r.ok) throw new ValidationError(`chave de acesso inválida: ${r.error.message}`, [r.error]);
374
+ return r.value;
375
+ }
376
+
377
+ function schemaIssues(what: string, issues: readonly SchemaIssue[]): void {
378
+ if (issues.length > 0) throw new ValidationError(`${what} não confere com o schema`, issues);
379
+ }
380
+
381
+ function documentoAutor(a: AutorDocumento, path: string): { CNPJ: string } | { CPF: string } {
382
+ if (a.CNPJ !== undefined) {
383
+ const r = parseCnpj(a.CNPJ, { path: `${path}.CNPJ` });
384
+ if (!r.ok) throw new ValidationError('CNPJ do autor inválido', [r.error]);
385
+ return { CNPJ: r.value };
386
+ }
387
+ const r = parseCpf(a.CPF ?? '', { path: `${path}.CPF` });
388
+ if (!r.ok) throw new ValidationError('CPF do autor inválido', [r.error]);
389
+ return { CPF: r.value };
390
+ }
391
+
392
+ function autorDaChave(c: ChaveAcesso): { CNPJ: string } | { CPF: string } {
393
+ // parseChaveAcesso já exigiu CNPJ ou "000" + CPF válido nas 14 posições do emitente.
394
+ return c.cnpj !== undefined ? { CNPJ: c.cnpj } : { CPF: c.cpf ?? c.emitente.slice(3) };
395
+ }
396
+
397
+ /**
398
+ * Autor de um evento do emitente (cancelamento, cancelamento por substituição, CC-e): o informado tem de ser o CNPJ ou
399
+ * o CPF do emitente na chave, senão a SEFAZ rejeita com 574 (MOC 7.0 Visão Geral, RV P12-44); sem autor, é o da chave.
400
+ */
401
+ function autorDoEmitente(a: AutorDocumento | undefined, c: ChaveAcesso): { CNPJ: string } | { CPF: string } {
402
+ const daChave = autorDaChave(c);
403
+ if (a === undefined) return daChave;
404
+ const autor = documentoAutor(a, 'autor');
405
+ const [campo, valor] = 'CNPJ' in autor ? ['CNPJ', autor.CNPJ] : ['CPF', autor.CPF];
406
+ if (valor !== ('CNPJ' in daChave ? daChave.CNPJ : daChave.CPF) || campo !== ('CNPJ' in daChave ? 'CNPJ' : 'CPF')) {
407
+ throw new ValidationError('o autor do evento não é o emitente da NF-e', [
408
+ {
409
+ path: `autor.${campo}`,
410
+ code: 'autor_difere_do_emitente',
411
+ message: `o autor de um evento do emitente é o ${'CNPJ' in daChave ? 'CNPJ' : 'CPF'} da chave (P12-44, rejeição 574)`,
412
+ origem: 'entrada',
413
+ },
414
+ ]);
415
+ }
416
+ return autor;
417
+ }
418
+
419
+ /**
420
+ * Autorizador SVC pelo tpEmis da chave (MOC 7.0, B22: 6 SVC-AN, 7 SVC-RS). A nota assinada em contingência é
421
+ * autorizada, consultada e cancelada no SVC que a autorizou, seja qual for a contingência de agora (NT 2013.007).
422
+ */
423
+ const SVC_DO_TPEMIS: Readonly<Record<string, 'SVC-AN' | 'SVC-RS'>> = { '6': 'SVC-AN', '7': 'SVC-RS' };
424
+
425
+ /** Chave do documento assinado, lida para rotear: o emitente é conferido pela SEFAZ, não aqui. */
426
+ function chaveDoDocumento(a: DocumentoAssinado): ChaveAcesso {
427
+ const r = parseChaveAcesso(a.id.slice(3), { path: 'infNFe.Id', checkEmitente: false });
428
+ if (!r.ok) throw new ConfigError(`Id da NF-e assinada não é uma chave de acesso: ${r.error.message}`);
429
+ return r.value;
430
+ }
431
+
432
+ /** Fatia autossuficiente (com o `xmlns` do elemento), para devolver ao chamador fora de um envelope. */
433
+ function avulso(doc: XmlDocument, el: XmlElement): string {
434
+ return sliceElement(doc, el, '');
435
+ }
436
+
437
+ /** Protocolo lido e a fatia sem `xmlns` redundante, para entrar no `nfeProc`. */
438
+ interface ProtocoloLido {
439
+ readonly p: Omit<ProtocoloNfe, 'nfeProc'>;
440
+ readonly embutido: string;
441
+ }
442
+
443
+ /** Monta o protocolo a partir do `protNFe` da resposta. */
444
+ function lerProtocolo(doc: XmlDocument, el: XmlElement): ProtocoloLido {
445
+ const { value } = decode(TProtNFe, el, doc.source);
446
+ const inf = value.infProt;
447
+ if (!inf) throw new ProtocolError('protNFe sem infProt');
448
+ const p = {
449
+ chNFe: inf.chNFe,
450
+ cStat: inf.cStat,
451
+ xMotivo: inf.xMotivo,
452
+ dhRecbto: inf.dhRecbto,
453
+ verAplic: inf.verAplic,
454
+ protNFe: avulso(doc, el),
455
+ ...(inf.nProt === undefined ? {} : { nProt: inf.nProt }),
456
+ ...(inf.digVal === undefined ? {} : { digVal: inf.digVal }),
457
+ };
458
+ return { p, embutido: sliceElement(doc, el) };
459
+ }
460
+
461
+ /**
462
+ * Protocolo de uma NF-e que este cliente enviou: o `digVal` tem de ser o DigestValue da nota assinada, senão o
463
+ * protocolo é de outro conteúdo e nenhum `nfeProc` é montado (`ProtocolError`). Sem `digVal` não há como provar que o
464
+ * protocolo é deste conteúdo: o desfecho volta sem `nfeProc` (confirme com `consultar`).
465
+ */
466
+ function desfechoDoProtocolo({ p, embutido }: ProtocoloLido, a: DocumentoAssinado | undefined): AutorizacaoOutcome {
467
+ const status = { cStat: p.cStat, xMotivo: p.xMotivo };
468
+ const autorizada = cstatEm(p.cStat, 'autorizada');
469
+ const denegada = cstatEm(p.cStat, 'denegada');
470
+ if (!autorizada && !denegada) return rejeitado(status);
471
+ let full: ProtocoloNfe = p;
472
+ if (a) {
473
+ if (`NFe${p.chNFe}` !== a.id) {
474
+ throw new ProtocolError('protocolo de outra chave de acesso', { details: { chNFe: p.chNFe } });
475
+ }
476
+ if (p.digVal !== undefined && p.digVal !== a.digestValue) {
477
+ throw new ProtocolError('digVal do protocolo difere do DigestValue da NF-e enviada', {
478
+ details: { chNFe: p.chNFe, cStat: p.cStat },
479
+ });
480
+ }
481
+ if (p.digVal !== undefined) full = { ...p, nfeProc: envelope('nfeProc', '4.00', [a.xml, embutido]) };
482
+ }
483
+ return autorizada ? authorized(status, full) : denied(status, full);
484
+ }
485
+
486
+ function protNFeDaChave(parent: XmlElement, a: DocumentoAssinado | undefined): XmlElement | undefined {
487
+ const prots = childElements(parent).filter((e) => e.local === 'protNFe' && e.ns === NFE_NS);
488
+ if (!a) return prots[0];
489
+ const chave = a.id.slice(3);
490
+ return (
491
+ prots.find((e) => {
492
+ const inf = firstChild(e, 'infProt', NFE_NS);
493
+ const ch = inf && firstChild(inf, 'chNFe', NFE_NS);
494
+ return ch !== undefined && textOf(ch).trim() === chave;
495
+ }) ?? (prots.length === 1 ? prots[0] : undefined)
496
+ );
497
+ }
498
+
499
+ // ---------------------------------------------------------------------------------------------------------------
500
+ // Cliente
501
+ // ---------------------------------------------------------------------------------------------------------------
502
+
503
+ const EVENTO_VERSAO = '1.00';
504
+ /** Texto fixo das condições de uso da CC-e (schema e110110, primeira forma do enum). */
505
+ const XCONDUSO: DetCce['xCondUso'] =
506
+ 'A Carta de Correção é disciplinada pelo § 1º-A do art. 7º do Convênio S/N, de 15 de dezembro de 1970 e pode ser utilizada para regularização de erro ocorrido na emissão de documento fiscal, desde que o erro não esteja relacionado com: I - as variáveis que determinam o valor do imposto tais como: base de cálculo, alíquota, diferença de preço, quantidade, valor da operação ou da prestação; II - a correção de dados cadastrais que implique mudança do remetente ou do destinatário; III - a data de emissão ou de saída.';
507
+
508
+ /** Manifestação do destinatário (NT 2012.002): tipo de evento, descrição e schema do `detEvento`. */
509
+ const MANIFESTACOES: Readonly<
510
+ Record<ManifestacaoTipo, { readonly tpEvento: string; readonly descEvento: string; readonly ct: ComplexType }>
511
+ > = {
512
+ confirmacao: { tpEvento: '210200', descEvento: 'Confirmacao da Operacao', ct: TEventoConfirmacao },
513
+ ciencia: { tpEvento: '210210', descEvento: 'Ciencia da Operacao', ct: TEventoCiencia },
514
+ desconhecimento: { tpEvento: '210220', descEvento: 'Desconhecimento da Operacao', ct: TEventoDesconhecimento },
515
+ 'nao-realizada': { tpEvento: '210240', descEvento: 'Operacao nao Realizada', ct: TEventoNaoRealizada },
516
+ };
517
+
518
+ interface EventoPedido {
519
+ readonly ct: ComplexType;
520
+ readonly tpEvento: string;
521
+ readonly chave: string;
522
+ readonly nSeqEvento: number;
523
+ readonly cOrgao: string;
524
+ readonly autor: { CNPJ: string } | { CPF: string };
525
+ readonly detEvento: Readonly<Record<string, unknown>>;
526
+ readonly endpoint: EndpointRef;
527
+ /** Fuso do `dhEvento`. */
528
+ readonly offsetMinutes: number;
529
+ }
530
+
531
+ /** Cria o cliente dos serviços da NF-e. */
532
+ export function createNfeClient(options: NfeClientOptions): NfeClient {
533
+ const logger = (options.logger ?? noopLogger).child({ modulo: 'nfe', ambiente: options.ambiente });
534
+ const sleep = options.sleep ?? defaultSleep;
535
+ const tpAmb = tpAmbOf(options.ambiente);
536
+ const ufInfo = options.uf === undefined ? undefined : ufBySigla(options.uf);
537
+ if (options.uf !== undefined && !ufInfo) throw new ConfigError(`UF inválida: ${String(options.uf)}`);
538
+ /** UF dos serviços sem documento; sem `options.uf`, `ConfigError` nomeando o serviço. */
539
+ const ufPadrao = (servico: string): { readonly uf: Uf; readonly cUF: CUf } => {
540
+ if (options.uf === undefined || ufInfo === undefined) {
541
+ throw new ConfigError(`${servico} precisa da UF: informe NfeClientOptions.uf`);
542
+ }
543
+ return { uf: options.uf, cUF: ufInfo.cUF };
544
+ };
545
+ /** Fuso de um evento pela UF de quem o registra. */
546
+ const offsetDe = (uf: Uf): number => options.offsetMinutes ?? offsetDaUf(uf);
547
+
548
+ const idLote = (): string => {
549
+ const id = options.idLote ? options.idLote() : String(options.clock.now().getTime()).slice(-15);
550
+ if (!/^[0-9]{1,15}$/.test(id)) throw new ConfigError(`idLote inválido: ${id}`);
551
+ return id;
552
+ };
553
+
554
+ /** Endpoint da NFC-e: a opção `nfceEndpoint` ou a tabela da NFC-e do transporte, sempre no autorizador normal. */
555
+ const endpointNfce = (servico: NfeServico, uf: Uf): EndpointRef =>
556
+ options.nfceEndpoint
557
+ ? options.nfceEndpoint(servico, uf)
558
+ : nfceEndpoint({ ambiente: options.ambiente, servico, uf });
559
+
560
+ /**
561
+ * Endpoint de um serviço sem documento, pela UF das opções: NF-e com SVC quando `contingencia` pede; NFC-e no
562
+ * autorizador normal.
563
+ */
564
+ const endpoint = (servico: NfeServico, mod = '55'): EndpointRef => {
565
+ const { uf } = ufPadrao(servico);
566
+ if (mod === '65') return endpointNfce(servico, uf);
567
+ return nfeEndpoint({
568
+ ambiente: options.ambiente,
569
+ servico,
570
+ uf,
571
+ ...(options.contingencia === undefined ? {} : { contingencia: options.contingencia }),
572
+ });
573
+ };
574
+
575
+ /**
576
+ * Endpoint de um serviço sobre um documento, pelo autorizador da chave: a UF do cUF e, no tpEmis 6 ou 7, o SVC que
577
+ * autorizou a nota. `naUf` força o autorizador da UF (a CC-e não existe no SVC). A NFC-e vai sempre ao autorizador
578
+ * normal da NFC-e.
579
+ */
580
+ const endpointDaChave = (servico: NfeServico, c: ChaveAcesso, naUf = false): EndpointRef => {
581
+ if (c.mod === '65') return endpointNfce(servico, c.uf);
582
+ const svc = naUf || c.tpEmis === undefined ? undefined : SVC_DO_TPEMIS[c.tpEmis];
583
+ return nfeEndpoint({
584
+ ambiente: options.ambiente,
585
+ servico,
586
+ ...(svc === undefined ? { uf: c.uf } : { autorizador: svc }),
587
+ });
588
+ };
589
+
590
+ const call = (
591
+ ep: EndpointRef,
592
+ servico: NfeServico,
593
+ mensagem: string,
594
+ retorno: string,
595
+ signal?: AbortSignal,
596
+ ): Promise<RespostaSoap> =>
597
+ chamar({
598
+ transport: options.transport,
599
+ endpoint: ep,
600
+ servico,
601
+ mensagem,
602
+ retorno,
603
+ logger,
604
+ ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
605
+ ...(signal === undefined ? {} : { signal }),
606
+ });
607
+
608
+ const autorPadrao = (a: AutorDocumento | undefined, path: string): { CNPJ: string } | { CPF: string } => {
609
+ const autor = a ?? options.autor;
610
+ if (!autor) throw new ConfigError(`informe o CNPJ ou CPF do autor (${path} ou NfeClientOptions.autor)`);
611
+ return documentoAutor(autor, path);
612
+ };
613
+
614
+ async function consultarRecibo(
615
+ nRec: string,
616
+ nfeAssinada?: string,
617
+ opcoes: ConsultaReciboOpcoes = {},
618
+ ): Promise<AutorizacaoOutcome> {
619
+ if (!/^[0-9]{15}$/.test(nRec)) throw new ConfigError(`número de recibo inválido: ${nRec}`);
620
+ const a = nfeAssinada === undefined ? undefined : documentoAssinado(nfeAssinada, 'NFe', 'infNFe');
621
+ const c = a === undefined ? undefined : chaveDoDocumento(a);
622
+ if (c !== undefined && opcoes.mod !== undefined && opcoes.mod !== c.mod) {
623
+ throw new ConfigError(`mod ${opcoes.mod} não é o da NF-e assinada (${c.mod})`);
624
+ }
625
+ const msg = envelope('consReciNFe', '4.00', [`<tpAmb>${tpAmb}</tpAmb><nRec>${nRec}</nRec>`]);
626
+ // O recibo é do autorizador que recebeu o lote: com a NF-e, o da chave; sem ela, o das opções.
627
+ const ep =
628
+ c === undefined ? endpoint('NFeRetAutorizacao', opcoes.mod ?? '55') : endpointDaChave('NFeRetAutorizacao', c);
629
+ const r = await call(ep, 'NFeRetAutorizacao', msg, 'retConsReciNFe', opcoes.signal);
630
+ const v = decode(TRetConsReciNFe, r.ret, r.doc.source).value;
631
+ if (v.nRec !== undefined && v.nRec !== nRec) {
632
+ throw new ProtocolError('retorno de outro recibo', { details: { nRec: v.nRec } });
633
+ }
634
+ const status = { cStat: v.cStat, xMotivo: v.xMotivo };
635
+ logger.info('nfe.recibo', { nRec, cStat: v.cStat });
636
+ if (cstatEm(v.cStat, 'loteEmProcessamento') || cstatEm(v.cStat, 'loteRecebido'))
637
+ return pending(status, { ref: nRec });
638
+ if (cstatEm(v.cStat, 'loteProcessado')) {
639
+ const el = protNFeDaChave(r.ret, a);
640
+ if (!el) throw new ProtocolError('lote processado sem o protNFe da NF-e', { details: { nRec } });
641
+ return desfechoDoProtocolo(lerProtocolo(r.doc, el), a);
642
+ }
643
+ return rejeitado(status);
644
+ }
645
+
646
+ async function consultar(chave: string, nfeAssinada?: string): Promise<ConsultaOutcome> {
647
+ const c = chaveValida(chave, 'chNFe');
648
+ const a = nfeAssinada === undefined ? undefined : documentoAssinado(nfeAssinada, 'NFe', 'infNFe');
649
+ if (a && a.id !== `NFe${c.chave}`) throw new ConfigError('a NF-e assinada não é a da chave consultada');
650
+ const msg = serializeRoot(consSitNFeElement, { versao: '4.00', tpAmb, xServ: 'CONSULTAR', chNFe: c.chave });
651
+ const ep = endpointDaChave('NfeConsultaProtocolo', c);
652
+ const r = await call(ep, 'NfeConsultaProtocolo', msg, 'retConsSitNFe');
653
+ const v = decode(TRetConsSitNFe, r.ret, r.doc.source).value;
654
+ const status = { cStat: v.cStat, xMotivo: v.xMotivo };
655
+ logger.info('nfe.consulta', { chNFe: c.chave, cStat: v.cStat });
656
+ const situacao = cstatEm(v.cStat, 'autorizada')
657
+ ? 'autorizada'
658
+ : cstatEm(v.cStat, 'cancelada')
659
+ ? 'cancelada'
660
+ : cstatEm(v.cStat, 'denegada')
661
+ ? 'denegada'
662
+ : undefined;
663
+ if (situacao === undefined) return rejeitado(status);
664
+ const protEl = firstChild(r.ret, 'protNFe', NFE_NS);
665
+ const eventos = childElements(r.ret)
666
+ .filter((e) => e.local === 'procEventoNFe' && e.ns === NFE_NS)
667
+ .map((e) => avulso(r.doc, e));
668
+ let protocolo: ProtocoloNfe | undefined;
669
+ let digValConfere: boolean | undefined;
670
+ if (protEl) {
671
+ const { p, embutido } = lerProtocolo(r.doc, protEl);
672
+ if (p.chNFe !== c.chave) {
673
+ throw new ProtocolError('protocolo de outra chave de acesso na consulta', { details: { chNFe: p.chNFe } });
674
+ }
675
+ protocolo = p;
676
+ if (a) {
677
+ digValConfere = p.digVal !== undefined && p.digVal === a.digestValue;
678
+ if (digValConfere) protocolo = { ...p, nfeProc: envelope('nfeProc', '4.00', [a.xml, embutido]) };
679
+ }
680
+ }
681
+ const value: ConsultaNfe = {
682
+ chNFe: c.chave,
683
+ situacao,
684
+ eventos,
685
+ ...(protocolo === undefined ? {} : { protocolo }),
686
+ ...(digValConfere === undefined ? {} : { digValConfere }),
687
+ };
688
+ return situacao === 'denegada' ? denied(status, value) : authorized(status, value);
689
+ }
690
+
691
+ async function enviarEvento(p: EventoPedido): Promise<EventoOutcome> {
692
+ const nSeq = p.nSeqEvento;
693
+ if (!Number.isInteger(nSeq) || nSeq < 1 || nSeq > 99) throw new ConfigError(`nSeqEvento inválido: ${nSeq}`);
694
+ const nSeqEvento = String(nSeq);
695
+ // Id = "ID" + tpEvento + chave + nSeqEvento com 2 dígitos (leiaute do evento, atributo Id de infEvento).
696
+ const id = `ID${p.tpEvento}${p.chave}${nSeqEvento.padStart(2, '0')}`;
697
+ const inf = {
698
+ Id: id,
699
+ cOrgao: p.cOrgao,
700
+ tpAmb,
701
+ ...p.autor,
702
+ chNFe: p.chave,
703
+ dhEvento: formatDh(options.clock.now(), p.offsetMinutes),
704
+ tpEvento: p.tpEvento,
705
+ nSeqEvento,
706
+ verEvento: EVENTO_VERSAO,
707
+ detEvento: p.detEvento,
708
+ };
709
+ const infXml = serialize(p.ct, 'infEvento', inf, NFE_NS);
710
+ const evento = `<evento xmlns="${NFE_NS}" versao="${EVENTO_VERSAO}">${infXml}</evento>`;
711
+ const infEl = firstChild(parseXml(evento).root, 'infEvento', NFE_NS) as XmlElement;
712
+ schemaIssues('evento', validate(p.ct, infEl));
713
+ const assinado = await signXml(evento, { id }, options.signer);
714
+ const msg = envelope('envEvento', EVENTO_VERSAO, [`<idLote>${idLote()}</idLote>`, assinado]);
715
+ const r = await call(p.endpoint, 'RecepcaoEvento', msg, 'retEnvEvento');
716
+ const v = decode(TRetEnvEvento, r.ret, r.doc.source).value;
717
+ logger.info('nfe.evento', { chNFe: p.chave, tpEvento: p.tpEvento, cStat: v.cStat });
718
+ if (!cstatEm(v.cStat, 'loteEventoProcessado')) return rejeitado({ cStat: v.cStat, xMotivo: v.xMotivo });
719
+ const retEl = childElements(r.ret).find((e) => e.local === 'retEvento' && e.ns === NFE_NS);
720
+ const ret = v.retEvento?.[0]?.infEvento;
721
+ if (!retEl || !ret) throw new ProtocolError('lote de evento processado sem retEvento');
722
+ // O retorno tem de ser deste evento: chave, tipo e sequência, quando vierem, iguais aos enviados.
723
+ const divergentes = (
724
+ [
725
+ ['chNFe', ret.chNFe, p.chave],
726
+ ['tpEvento', ret.tpEvento, p.tpEvento],
727
+ ['nSeqEvento', ret.nSeqEvento === undefined ? undefined : String(Number(ret.nSeqEvento)), nSeqEvento],
728
+ ] as const
729
+ ).filter(([, veio, enviado]) => veio !== undefined && veio !== enviado);
730
+ if (divergentes.length > 0) {
731
+ throw new ProtocolError('retEvento de outro evento', {
732
+ details: Object.fromEntries(divergentes.map(([k, veio]) => [k, veio])),
733
+ });
734
+ }
735
+ const status = { cStat: ret.cStat, xMotivo: ret.xMotivo };
736
+ if (!cstatEm(ret.cStat, 'eventoRegistrado')) return rejeitado(status);
737
+ return authorized(status, {
738
+ chNFe: p.chave,
739
+ tpEvento: p.tpEvento,
740
+ nSeqEvento,
741
+ dhRegEvento: ret.dhRegEvento,
742
+ retEvento: avulso(r.doc, retEl),
743
+ procEventoNFe: envelope('procEventoNFe', EVENTO_VERSAO, [assinado, sliceElement(r.doc, retEl)]),
744
+ ...(ret.nProt === undefined ? {} : { nProt: ret.nProt }),
745
+ });
746
+ }
747
+
748
+ const client: NfeClient = {
749
+ options,
750
+
751
+ async statusServico(opcoes?: { readonly mod?: '55' | '65' }): Promise<SefazOutcome<StatusServico, never>> {
752
+ const { cUF } = ufPadrao('NfeStatusServico');
753
+ const msg = serializeRoot(consStatServElement, { versao: '4.00', tpAmb, cUF, xServ: 'STATUS' });
754
+ const r = await call(endpoint('NfeStatusServico', opcoes?.mod), 'NfeStatusServico', msg, 'retConsStatServ');
755
+ const v = decode(TRetConsStatServ, r.ret, r.doc.source).value;
756
+ const status = { cStat: v.cStat, xMotivo: v.xMotivo };
757
+ logger.info('nfe.status', { cStat: v.cStat });
758
+ if (!cstatEm(v.cStat, 'servicoEmOperacao')) return rejeitado(status);
759
+ return authorized(status, {
760
+ cUF: v.cUF,
761
+ verAplic: v.verAplic,
762
+ dhRecbto: v.dhRecbto,
763
+ ...(v.tMed === undefined ? {} : { tMed: v.tMed }),
764
+ ...(v.dhRetorno === undefined ? {} : { dhRetorno: v.dhRetorno }),
765
+ ...(v.xObs === undefined ? {} : { xObs: v.xObs }),
766
+ });
767
+ },
768
+
769
+ async autorizar(nfeAssinada: string, opcoes: AutorizarOpcoes = {}): Promise<AutorizacaoOutcome> {
770
+ const a = documentoAssinado(nfeAssinada, 'NFe', 'infNFe');
771
+ const sincrono = opcoes.sincrono ?? true;
772
+ const msg = envelope('enviNFe', '4.00', [
773
+ `<idLote>${idLote()}</idLote><indSinc>${sincrono ? '1' : '0'}</indSinc>`,
774
+ a.xml,
775
+ ]);
776
+ // O autorizador é o do documento: a UF do cUF e, assinada em SVC (tpEmis 6 ou 7), o SVC da chave.
777
+ const ep = endpointDaChave('NFeAutorizacao', chaveDoDocumento(a));
778
+ const r = await call(ep, 'NFeAutorizacao', msg, 'retEnviNFe', opcoes.signal);
779
+ const v = decode(TRetEnviNFe, r.ret, r.doc.source).value;
780
+ const status = { cStat: v.cStat, xMotivo: v.xMotivo };
781
+ logger.info('nfe.autorizacao', { chNFe: a.id.slice(3), cStat: v.cStat, sincrono });
782
+ if (cstatEm(v.cStat, 'loteRecebido')) {
783
+ const nRec = v.infRec?.nRec;
784
+ if (!nRec) throw new ProtocolError('lote recebido sem número de recibo');
785
+ const tMed = Number(v.infRec?.tMed ?? '0');
786
+ return pending(status, { ref: nRec, retryAfterMs: Math.max(1, tMed) * 1000 });
787
+ }
788
+ const protEl = protNFeDaChave(r.ret, a);
789
+ if (protEl) return desfechoDoProtocolo(lerProtocolo(r.doc, protEl), a);
790
+ return rejeitado(status);
791
+ },
792
+
793
+ consultarRecibo,
794
+
795
+ async aguardarRecibo(
796
+ nRec: string,
797
+ nfeAssinada?: string,
798
+ politica: PoliticaRecibo = {},
799
+ ): Promise<AutorizacaoOutcome> {
800
+ const max = politica.maxTentativas ?? 10;
801
+ const minimo = politica.esperaMinimaMs ?? 2000;
802
+ const mult = politica.multiplicador ?? 1.5;
803
+ const teto = politica.esperaMaximaMs ?? 30_000;
804
+ if (!(max >= 1) || !(minimo >= 0) || !(mult >= 1) || !(teto >= minimo)) {
805
+ throw new ConfigError('política de consulta do recibo inválida', { details: { max, minimo, mult, teto } });
806
+ }
807
+ let espera = minimo;
808
+ let ultimo: AutorizacaoOutcome | undefined;
809
+ for (let i = 0; i < max; i++) {
810
+ await sleep(espera, politica.signal);
811
+ // O mesmo signal cancela a espera e a requisição em curso.
812
+ ultimo = await consultarRecibo(nRec, nfeAssinada, politica);
813
+ if (ultimo.status !== 'pending') return ultimo;
814
+ espera = Math.min(teto, Math.max(minimo, Math.round(espera * mult), ultimo.retryAfterMs ?? 0));
815
+ }
816
+ return ultimo as AutorizacaoOutcome;
817
+ },
818
+
819
+ consultar,
820
+
821
+ async cancelar(p: CancelamentoPedido): Promise<EventoOutcome> {
822
+ const c = chaveValida(p.chave, 'chave');
823
+ return enviarEvento({
824
+ ct: TEventoCanc,
825
+ tpEvento: '110111',
826
+ chave: c.chave,
827
+ nSeqEvento: 1,
828
+ cOrgao: c.cUF,
829
+ autor: autorDoEmitente(p.autor, c),
830
+ detEvento: { versao: EVENTO_VERSAO, descEvento: 'Cancelamento', nProt: p.nProt, xJust: p.xJust },
831
+ // No autorizador da nota: o SVC só cancela a NF-e que ele autorizou, e a da UF só se cancela na UF.
832
+ endpoint: endpointDaChave('RecepcaoEvento', c),
833
+ offsetMinutes: offsetDe(c.uf),
834
+ });
835
+ },
836
+
837
+ async cancelarPorSubstituicao(p: CancelamentoSubstituicaoPedido): Promise<EventoOutcome> {
838
+ const c = chaveValida(p.chave, 'chave');
839
+ if (c.mod !== '65') {
840
+ throw new ValidationError('cancelamento por substituição só existe para NFC-e', [
841
+ {
842
+ path: 'chave',
843
+ code: 'modelo_nao_suportado',
844
+ message: 'O evento 110112 é exclusivo da NFC-e (modelo 65)',
845
+ },
846
+ ]);
847
+ }
848
+ const ref = chaveValida(p.chNFeRef, 'chNFeRef');
849
+ // detEvento do e110112_v1.00.xsd (Evento_CancSubst_v1.01, NT 2018.004), validado com o envelope.
850
+ const detEvento: DetCancSubst = {
851
+ versao: EVENTO_VERSAO,
852
+ descEvento: 'Cancelamento por substituicao',
853
+ cOrgaoAutor: p.cOrgaoAutor as DetCancSubst['cOrgaoAutor'],
854
+ tpAutor: p.tpAutor ?? '1',
855
+ verAplic: p.verAplic,
856
+ nProt: p.nProt,
857
+ xJust: p.xJust,
858
+ chNFeRef: ref.chave,
859
+ };
860
+ return enviarEvento({
861
+ ct: TEventoCancSubst,
862
+ tpEvento: '110112',
863
+ chave: c.chave,
864
+ nSeqEvento: 1,
865
+ cOrgao: c.cUF,
866
+ autor: autorDoEmitente(p.autor, c),
867
+ detEvento,
868
+ endpoint: endpointDaChave('RecepcaoEvento', c),
869
+ offsetMinutes: offsetDe(c.uf),
870
+ });
871
+ },
872
+
873
+ async cartaCorrecao(p: CartaCorrecaoPedido): Promise<EventoOutcome> {
874
+ const c = chaveValida(p.chave, 'chave');
875
+ if (!Number.isInteger(p.nSeqEvento) || p.nSeqEvento < 1 || p.nSeqEvento > 20) {
876
+ throw new ConfigError(`nSeqEvento da CC-e vai de 1 a 20: ${p.nSeqEvento}`);
877
+ }
878
+ return enviarEvento({
879
+ ct: TEventoCce,
880
+ tpEvento: '110110',
881
+ chave: c.chave,
882
+ nSeqEvento: p.nSeqEvento,
883
+ cOrgao: c.cUF,
884
+ autor: autorDoEmitente(p.autor, c),
885
+ detEvento: {
886
+ versao: EVENTO_VERSAO,
887
+ descEvento: 'Carta de Correção',
888
+ xCorrecao: p.xCorrecao,
889
+ xCondUso: XCONDUSO,
890
+ },
891
+ // A CC-e vai sempre à UF: o SVC só recebe o cancelamento (NT 2013.007).
892
+ endpoint: endpointDaChave('RecepcaoEvento', c, true),
893
+ offsetMinutes: offsetDe(c.uf),
894
+ });
895
+ },
896
+
897
+ async manifestar(p: ManifestacaoPedido): Promise<EventoOutcome> {
898
+ const c = chaveValida(p.chave, 'chave');
899
+ const m = MANIFESTACOES[p.tipo];
900
+ if (!m) throw new ConfigError(`tipo de manifestação desconhecido: ${String(p.tipo)}`);
901
+ return enviarEvento({
902
+ ct: m.ct,
903
+ tpEvento: m.tpEvento,
904
+ chave: c.chave,
905
+ nSeqEvento: 1,
906
+ // Manifestação do destinatário: registrada no Ambiente Nacional, cOrgao 91 (NT 2012.002).
907
+ cOrgao: '91',
908
+ autor: autorPadrao(p.autor, 'autor'),
909
+ detEvento: {
910
+ versao: EVENTO_VERSAO,
911
+ descEvento: m.descEvento,
912
+ ...(p.xJust === undefined ? {} : { xJust: p.xJust }),
913
+ },
914
+ endpoint: nfeEndpoint({ ambiente: options.ambiente, servico: 'RecepcaoEvento', autorizador: 'AN' }),
915
+ // Quem manifesta é o destinatário: o fuso é o da UF dele, quando informada.
916
+ offsetMinutes: offsetDe(options.uf ?? c.uf),
917
+ });
918
+ },
919
+
920
+ async inutilizar(p: InutilizacaoPedido): Promise<InutilizacaoOutcome> {
921
+ const autor = autorPadrao(p.autor, 'autor');
922
+ // NT 2018.001 v1.10, item 6.1: o controle de inutilização não se aplica ao emitente pessoa física, e o leiaute do
923
+ // pedido não prevê o CPF; a série 910 a 969 (emitente CPF) é rejeitada com 266 (regra I02a, item 6.2).
924
+ if (!('CNPJ' in autor)) {
925
+ throw new ValidationError('inutilização não se aplica ao emitente pessoa física', [
926
+ {
927
+ path: 'autor.CPF',
928
+ code: 'inutilizacao_emitente_cpf',
929
+ message: 'O pedido de inutilização só existe para emitente CNPJ (NT 2018.001 v1.10, item 6.1)',
930
+ },
931
+ ]);
932
+ }
933
+ const cnpj = autor.CNPJ;
934
+ const { uf, cUF } = ufPadrao('NfeInutilizacao');
935
+ const anoTxt = String(p.ano);
936
+ const ano = anoTxt.length === 4 ? anoTxt.slice(2) : anoTxt.padStart(2, '0');
937
+ const mod = p.mod ?? '55';
938
+ const serie = String(p.serie);
939
+ const ini = String(p.nNFIni);
940
+ const fin = String(p.nNFFin);
941
+ for (const [k, x, re] of [
942
+ ['ano', ano, /^[0-9]{2}$/],
943
+ ['serie', serie, /^(0|[1-9][0-9]{0,2})$/],
944
+ ['nNFIni', ini, /^[1-9][0-9]{0,8}$/],
945
+ ['nNFFin', fin, /^[1-9][0-9]{0,8}$/],
946
+ ] as const) {
947
+ if (!re.test(x)) throw new ConfigError(`${k} inválido para inutilização: ${x}`);
948
+ }
949
+ if (Number(fin) < Number(ini)) throw new ConfigError('nNFFin menor que nNFIni');
950
+ if (Number(serie) >= 910 && Number(serie) <= 969) {
951
+ throw new ValidationError('série de emitente pessoa física não se inutiliza', [
952
+ {
953
+ path: 'serie',
954
+ code: 'inutilizacao_serie_cpf',
955
+ message: 'Série 910 a 969 identifica emitente CPF: a SEFAZ rejeita com 266 (NT 2018.001 v1.10, regra I02a)',
956
+ },
957
+ ]);
958
+ }
959
+ // Id = "ID" + cUF + ano + CNPJ + mod + série (3) + número inicial (9) + número final (9) (leiaute infInut).
960
+ const id = `ID${cUF}${ano}${cnpj}${mod}${serie.padStart(3, '0')}${ini.padStart(9, '0')}${fin.padStart(9, '0')}`;
961
+ const inf = {
962
+ Id: id,
963
+ tpAmb,
964
+ xServ: 'INUTILIZAR' as const,
965
+ cUF,
966
+ ano,
967
+ CNPJ: cnpj,
968
+ mod,
969
+ serie,
970
+ nNFIni: ini,
971
+ nNFFin: fin,
972
+ xJust: p.xJust,
973
+ };
974
+ const infXml = serialize(TInutNFe_infInut, 'infInut', inf, NFE_NS);
975
+ const inut = envelope('inutNFe', '4.00', [infXml]);
976
+ const infEl = firstChild(parseXml(inut).root, 'infInut', NFE_NS) as XmlElement;
977
+ schemaIssues('pedido de inutilização', validate(TInutNFe_infInut, infEl));
978
+ const assinado = await signXml(inut, { id }, options.signer);
979
+ // A inutilização não existe no SVC (o SVC-RS nem publica o serviço): vai sempre ao autorizador normal da UF.
980
+ const ep =
981
+ mod === '65'
982
+ ? endpointNfce('NfeInutilizacao', uf)
983
+ : nfeEndpoint({ ambiente: options.ambiente, servico: 'NfeInutilizacao', uf });
984
+ const r = await call(ep, 'NfeInutilizacao', assinado, 'retInutNFe');
985
+ const v = decode(TRetInutNFe, r.ret, r.doc.source).value;
986
+ const status = { cStat: v.infInut.cStat, xMotivo: v.infInut.xMotivo };
987
+ logger.info('nfe.inutilizacao', { id, cStat: status.cStat });
988
+ if (!cstatEm(status.cStat, 'inutilizacaoHomologada')) return rejeitado(status);
989
+ // A homologação tem de ser desta faixa: os campos que o retorno trouxer, iguais aos do pedido.
990
+ const r0 = v.infInut;
991
+ const n = (x: string | undefined): string | undefined => (x === undefined ? undefined : String(Number(x)));
992
+ const confere: readonly (readonly [string, string | undefined, string])[] = [
993
+ ['ano', r0.ano, ano],
994
+ ['CNPJ', r0.CNPJ ?? (r0 as { CPF?: string }).CPF, cnpj],
995
+ ['mod', r0.mod, mod],
996
+ ['serie', n(r0.serie), String(Number(serie))],
997
+ ['nNFIni', n(r0.nNFIni), String(Number(ini))],
998
+ ['nNFFin', n(r0.nNFFin), String(Number(fin))],
999
+ ];
1000
+ const fora = confere.filter(([, veio, pedido]) => veio !== undefined && veio !== pedido);
1001
+ if (fora.length > 0) {
1002
+ throw new ProtocolError('retInutNFe de outra faixa', {
1003
+ details: Object.fromEntries(fora.map(([k, v]) => [k, v])),
1004
+ });
1005
+ }
1006
+ return authorized(status, {
1007
+ dhRecbto: v.infInut.dhRecbto,
1008
+ retInutNFe: avulso(r.doc, r.ret),
1009
+ procInutNFe: envelope('ProcInutNFe', '4.00', [assinado, sliceElement(r.doc, r.ret)]),
1010
+ ...(v.infInut.nProt === undefined ? {} : { nProt: v.infInut.nProt }),
1011
+ });
1012
+ },
1013
+
1014
+ async consultarCadastro(p: CadastroPedido): Promise<SefazOutcome<Cadastro, never>> {
1015
+ const doc =
1016
+ 'CNPJ' in p
1017
+ ? documentoAutor({ CNPJ: p.CNPJ }, 'CNPJ')
1018
+ : 'CPF' in p
1019
+ ? documentoAutor({ CPF: p.CPF }, 'CPF')
1020
+ : { IE: p.IE.replace(/[^0-9A-Za-z]/g, '').toUpperCase() };
1021
+ const msg = serializeRoot(ConsCadElement, { versao: '2.00', infCons: { xServ: 'CONS-CAD', UF: p.uf, ...doc } });
1022
+ const ep = nfeEndpoint({ ambiente: options.ambiente, servico: 'NfeConsultaCadastro', uf: p.uf });
1023
+ const r = await call(ep, 'NfeConsultaCadastro', msg, 'retConsCad');
1024
+ const v = decode(TRetConsCad, r.ret, r.doc.source).value;
1025
+ const i = v.infCons;
1026
+ const status = { cStat: i.cStat, xMotivo: i.xMotivo };
1027
+ logger.info('nfe.cadastro', { uf: p.uf, cStat: i.cStat });
1028
+ if (!cstatEm(i.cStat, 'cadastroEncontrado')) return rejeitado(status);
1029
+ return authorized(status, { UF: i.UF, dhCons: i.dhCons, infCad: i.infCad ?? [] });
1030
+ },
1031
+
1032
+ async distribuicaoDFe(
1033
+ consulta: DistribuicaoConsulta,
1034
+ opcoes: DistribuicaoOpcoes = {},
1035
+ ): Promise<SefazOutcome<Distribuicao, never>> {
1036
+ const autor = autorPadrao(opcoes.autor, 'autor');
1037
+ const nsu = (x: string | number, k: string): string => {
1038
+ const s = String(x);
1039
+ if (!/^[0-9]{1,15}$/.test(s)) throw new ConfigError(`${k} inválido: ${s}`);
1040
+ return s.padStart(15, '0');
1041
+ };
1042
+ const grupo =
1043
+ 'ultNSU' in consulta
1044
+ ? { distNSU: { ultNSU: nsu(consulta.ultNSU, 'ultNSU') } }
1045
+ : 'NSU' in consulta
1046
+ ? { consNSU: { NSU: nsu(consulta.NSU, 'NSU') } }
1047
+ : { consChNFe: { chNFe: chaveValida(consulta.chNFe, 'chNFe').chave } };
1048
+ const cUFAutor = opcoes.cUFAutor ?? ufPadrao('NFeDistribuicaoDFe').cUF;
1049
+ if (ufByCUf(cUFAutor) === undefined) throw new ConfigError(`cUFAutor inválido: ${cUFAutor}`);
1050
+ const msg = serializeRoot(distDFeIntElement, {
1051
+ versao: '1.01',
1052
+ tpAmb,
1053
+ cUFAutor: cUFAutor as CUf,
1054
+ ...autor,
1055
+ ...grupo,
1056
+ } as distDFeInt);
1057
+ const ep = nfeEndpoint({ ambiente: options.ambiente, servico: 'NFeDistribuicaoDFe' });
1058
+ const r = await call(ep, 'NFeDistribuicaoDFe', msg, 'retDistDFeInt');
1059
+ const v = decode(retDistDFeInt, r.ret, r.doc.source).value;
1060
+ const status = { cStat: v.cStat, xMotivo: v.xMotivo };
1061
+ logger.info('nfe.distribuicao', { cStat: v.cStat, ultNSU: v.ultNSU, maxNSU: v.maxNSU });
1062
+ const base = { ultNSU: v.ultNSU, maxNSU: v.maxNSU, dhResp: v.dhResp };
1063
+ if (cstatEm(v.cStat, 'distribuicaoNenhumDocumento')) return authorized(status, { ...base, documentos: [] });
1064
+ if (!cstatEm(v.cStat, 'distribuicaoDocumentos')) return rejeitado(status);
1065
+ const documentos: DocumentoDistribuido[] = [];
1066
+ for (const z of v.loteDistDFeInt?.docZip ?? []) {
1067
+ const xml = await gunzipBase64(z.$text);
1068
+ documentos.push(documentoDistribuido(z.NSU ?? '', z.schema, xml));
1069
+ }
1070
+ return authorized(status, { ...base, documentos });
1071
+ },
1072
+ };
1073
+ return client;
1074
+ }
1075
+
1076
+ /** Classifica e, nos resumos, decodifica o documento da distribuição pelo nome do schema. */
1077
+ function documentoDistribuido(NSU: string, schema: string, xml: string): DocumentoDistribuido {
1078
+ const nome = schema.replace(/_v?\d.*$/, '');
1079
+ const base = { NSU, schema, xml };
1080
+ if (nome === 'resNFe') return { ...base, tipo: 'resNFe', resNFe: decodeXml(resNFeElement, xml).value };
1081
+ if (nome === 'resEvento') return { ...base, tipo: 'resEvento', resEvento: decodeXml(resEventoElement, xml).value };
1082
+ if (nome === 'procNFe') return { ...base, tipo: 'procNFe' };
1083
+ if (nome === 'procEventoNFe') return { ...base, tipo: 'procEventoNFe' };
1084
+ return { ...base, tipo: 'outro' };
1085
+ }