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