ghinfe 0.6.0 → 0.6.2

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 (110) hide show
  1. package/.env.example +24 -15
  2. package/LICENSE +21 -21
  3. package/README.md +491 -413
  4. package/docs/developer-guide.html +421 -421
  5. package/docs/openapi.yaml +428 -428
  6. package/examples/dce-homologacao.js +141 -0
  7. package/examples/emitir-nfce.js +51 -51
  8. package/examples/emitir-nfe.js +83 -83
  9. package/examples/emitir-nfse.js +46 -46
  10. package/examples/endpoints-sefaz.js +14 -14
  11. package/examples/eventos-nfe.js +49 -49
  12. package/examples/gerar-chave.js +25 -25
  13. package/examples/gerar-dace.js +59 -0
  14. package/examples/gerar-danfe.js +75 -75
  15. package/examples/gerar-dce.js +58 -0
  16. package/examples/layouts-nfse.js +51 -51
  17. package/examples/mock-sefaz.js +52 -52
  18. package/examples/nfse-eventos.js +57 -57
  19. package/examples/reforma-ibscbs.js +27 -27
  20. package/examples/reforma-nfe-xml.js +52 -52
  21. package/examples/status-sefaz.js +12 -12
  22. package/examples/validar-xsd.js +48 -48
  23. package/package.json +59 -68
  24. package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/consSitDCeTiposBasico_v1.00.xsd +123 -0
  25. package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/consSitDCe_v1.00.xsd +10 -0
  26. package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/consStatServDCeTiposBasico_v1.00.xsd +110 -0
  27. package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/consStatServDCe_v1.00.xsd +10 -0
  28. package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/dceTiposBasico_v1.00.xsd +1388 -0
  29. package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/retConsSitDCe_v1.00.xsd +10 -0
  30. package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/retConsStatServDCe_v1.00.xsd +10 -0
  31. package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/tiposGeralDCe_v1.00.xsd +712 -0
  32. package/schemas/dce/PL_DCe_100a_NT2024.001/consulta/xmldsig-core-schema_v1.01.xsd +98 -0
  33. package/schemas/dce/PL_DCe_100a_NT2024.001/dceTiposBasico_v1.00.xsd +1410 -0
  34. package/schemas/dce/PL_DCe_100a_NT2024.001/dce_v1.00.xsd +10 -0
  35. package/schemas/dce/PL_DCe_100a_NT2024.001/evento/evCancDCe_v1.00.xsd +36 -0
  36. package/schemas/dce/PL_DCe_100a_NT2024.001/evento/eventoDCeTiposBasico_v1.00.xsd +283 -0
  37. package/schemas/dce/PL_DCe_100a_NT2024.001/evento/eventoDCe_v1.00.xsd +9 -0
  38. package/schemas/dce/PL_DCe_100a_NT2024.001/evento/procEventoDCe_v1.00.xsd +10 -0
  39. package/schemas/dce/PL_DCe_100a_NT2024.001/evento/retEventoDCe_v1.00.xsd +9 -0
  40. package/schemas/dce/PL_DCe_100a_NT2024.001/evento/tiposGeralDCe_v1.00.xsd +712 -0
  41. package/schemas/dce/PL_DCe_100a_NT2024.001/evento/xmldsig-core-schema_v1.01.xsd +98 -0
  42. package/schemas/dce/PL_DCe_100a_NT2024.001/procDCe_v1.00.xsd +37 -0
  43. package/schemas/dce/PL_DCe_100a_NT2024.001/retDCe_v1.00.xsd +9 -0
  44. package/schemas/dce/PL_DCe_100a_NT2024.001/tiposGeralDCe_v1.00.xsd +712 -0
  45. package/schemas/dce/PL_DCe_100a_NT2024.001/xmldsig-core-schema_v1.01.xsd +98 -0
  46. package/schemas/nfe/PL_009_V4/enviNFe_v3.10.xsd +8 -8
  47. package/schemas/nfe/PL_009_V4/xmldsig-core-schema_v1.01.xsd +97 -97
  48. package/scripts/download-schemas.js +39 -39
  49. package/scripts/test-xsd.js +34 -34
  50. package/src/api/server.js +316 -316
  51. package/src/constants/sefaz.js +69 -69
  52. package/src/danfe/DanfeService.js +205 -205
  53. package/src/danfe/xml-extract.js +69 -69
  54. package/src/dce/builders/DCeEventoXmlBuilder.js +86 -0
  55. package/src/dce/builders/DCeXmlBuilder.js +345 -0
  56. package/src/dce/constants/dce.js +87 -0
  57. package/src/dce/dace/DaceService.js +356 -0
  58. package/src/dce/dace/dace-extract.js +114 -0
  59. package/src/dce/services/DCeService.js +263 -0
  60. package/src/dce/services/DCeSoapService.js +235 -0
  61. package/src/dce/services/DCeXmlService.js +67 -0
  62. package/src/dce/types/DCeDocument.js +106 -0
  63. package/src/dce/utils/qrcode.utils.js +59 -0
  64. package/src/errors/GhinfeError.js +13 -13
  65. package/src/errors/SefazError.js +25 -25
  66. package/src/errors/XsdValidationError.js +15 -15
  67. package/src/index.js +84 -60
  68. package/src/nfce/builders/NFCeXmlBuilder.js +178 -178
  69. package/src/nfce/constants/qrcode.js +26 -26
  70. package/src/nfce/services/NFCeService.js +188 -188
  71. package/src/nfce/services/NFCeXmlService.js +38 -38
  72. package/src/nfce/types/NFCeDocument.js +42 -42
  73. package/src/nfce/utils/qrcode.utils.js +92 -92
  74. package/src/nfe/builders/NFeEventoXmlBuilder.js +110 -110
  75. package/src/nfe/builders/NFeXmlBuilder.js +215 -215
  76. package/src/nfe/mocks/sefaz-responses.js +195 -195
  77. package/src/nfe/services/NFeEventoService.js +95 -95
  78. package/src/nfe/services/NFeService.js +196 -196
  79. package/src/nfe/services/NFeSignService.js +127 -111
  80. package/src/nfe/services/NFeSoapService.js +437 -437
  81. package/src/nfe/services/NFeXmlService.js +41 -41
  82. package/src/nfe/services/NFeXsdService.js +56 -56
  83. package/src/nfe/types/NFeDocument.js +92 -92
  84. package/src/nfe/types/NFeEvento.js +44 -44
  85. package/src/nfse/NFSeMunicipioResolver.js +42 -42
  86. package/src/nfse/builders/NFSeEventoXmlBuilder.js +92 -92
  87. package/src/nfse/builders/NFSeXmlBuilder.js +16 -16
  88. package/src/nfse/constants/municipios.js +82 -82
  89. package/src/nfse/layouts/adapters.js +202 -202
  90. package/src/nfse/mocks/nfse-responses.js +142 -142
  91. package/src/nfse/services/NFSeService.js +191 -191
  92. package/src/nfse/services/NFSeSoapService.js +232 -232
  93. package/src/nfse/services/NFSeXmlService.js +12 -12
  94. package/src/nfse/types/NFSeDocument.js +54 -54
  95. package/src/queue/NFCeQueue.js +63 -63
  96. package/src/queue/NFSeQueue.js +59 -59
  97. package/src/queue/NFeQueue.js +76 -76
  98. package/src/reforma/ibscbs.js +175 -175
  99. package/src/sefaz/SefazEndpointResolver.js +95 -95
  100. package/src/sefaz/ca/icp-brasil-raiz-v10.pem +38 -0
  101. package/src/sefaz/endpoints.registry.js +205 -205
  102. package/src/sefaz/httpsAgent.js +33 -0
  103. package/src/shared/fiscal.helpers.js +53 -53
  104. package/src/utils/certificate.utils.js +69 -47
  105. package/src/utils/chave.utils.js +205 -117
  106. package/src/utils/code128.utils.js +67 -0
  107. package/src/utils/date.utils.js +26 -26
  108. package/src/utils/xml.utils.js +44 -44
  109. package/src/validation/XsdValidator.js +133 -114
  110. package/src/validation/schemas.js +18 -14
@@ -0,0 +1,263 @@
1
+ import { DCeXmlService } from './DCeXmlService.js';
2
+ import { DCeSoapService } from './DCeSoapService.js';
3
+ import { DCeEventoXmlBuilder } from '../builders/DCeEventoXmlBuilder.js';
4
+ import { DaceService } from '../dace/DaceService.js';
5
+ import { NFeSignService } from '../../nfe/services/NFeSignService.js';
6
+ import { GhinfeError } from '../../errors/GhinfeError.js';
7
+ import { gerarChaveAcessoDCe, parseChaveAcessoDCe } from '../../utils/chave.utils.js';
8
+ import { assinarChaveQrCodeDCe, gerarUrlQrCodeDCe } from '../utils/qrcode.utils.js';
9
+ import { DCE_MODELO, DCE_TIPO_EMITENTE, DCE_TIPO_EMISSAO } from '../constants/dce.js';
10
+
11
+ /**
12
+ * Orquestrador da DC-e (Declaração de Conteúdo eletrônica, modelo 99).
13
+ */
14
+ export class DCeService {
15
+ /**
16
+ * @param {{
17
+ * certificado?: { pfx: Buffer|string, senha: string },
18
+ * ambiente?: number,
19
+ * timeout?: number,
20
+ * compactar?: boolean,
21
+ * validarXsd?: boolean,
22
+ * qrCodeUrl?: string,
23
+ * urlChave?: string,
24
+ * }} [options]
25
+ * certificado: obrigatório para comunicação com a SEFAZ (TLS mútuo e assinatura)
26
+ * compactar: envia a DC-e via dceAutorizacaoZip (GZip + Base64)
27
+ */
28
+ constructor(options = {}) {
29
+ this.options = options;
30
+ this.ambiente = options.ambiente ?? 2;
31
+ this.xmlService = new DCeXmlService({ validarXsd: options.validarXsd });
32
+ this.signService = new NFeSignService();
33
+ this.eventoBuilder = new DCeEventoXmlBuilder();
34
+ this.soapService = options.certificado
35
+ ? new DCeSoapService({ certificado: options.certificado, ambiente: this.ambiente, timeout: options.timeout })
36
+ : null;
37
+ }
38
+
39
+ #certificado() {
40
+ if (!this.options.certificado || !this.soapService) {
41
+ throw new GhinfeError(
42
+ 'Informe options.certificado no DCeService para comunicar com a SEFAZ',
43
+ 'DCE_CERTIFICADO_AUSENTE'
44
+ );
45
+ }
46
+ return this.options.certificado;
47
+ }
48
+
49
+ /**
50
+ * CNPJ que compõe a chave: de quem emite/assina a DC-e.
51
+ * @param {import('../types/DCeDocument.js').DCeDocumento} documento
52
+ */
53
+ #cnpjChave(documento) {
54
+ const tpEmit = documento.ide.tipoEmitente;
55
+ const origem = {
56
+ [DCE_TIPO_EMITENTE.FISCO]: ['fisco', documento.fisco],
57
+ [DCE_TIPO_EMITENTE.MARKETPLACE]: ['marketplace', documento.marketplace],
58
+ [DCE_TIPO_EMITENTE.EMISSOR_PROPRIO]: ['emitente', documento.emitente],
59
+ [DCE_TIPO_EMITENTE.TRANSPORTADORA]: ['transportadora', documento.transportadora],
60
+ [DCE_TIPO_EMITENTE.ECT]: ['ect', documento.ect],
61
+ }[tpEmit];
62
+
63
+ if (!origem) {
64
+ throw new GhinfeError(`ide.tipoEmitente inválido: ${tpEmit}`, 'DCE_TP_EMIT_INVALIDO');
65
+ }
66
+ const [campo, parte] = origem;
67
+ if (!parte?.cnpj) {
68
+ throw new GhinfeError(`${campo}.cnpj é obrigatório para tipoEmitente=${tpEmit}`, 'DCE_CNPJ_CHAVE_AUSENTE');
69
+ }
70
+ return parte.cnpj;
71
+ }
72
+
73
+ /**
74
+ * Gera a chave de acesso (se ausente) e fixa a data de emissão usada no AAMM e no dhEmi.
75
+ * @param {import('../types/DCeDocument.js').DCeDocumento} documento
76
+ */
77
+ resolverDocumento(documento) {
78
+ const ambiente = documento.ambiente ?? this.ambiente;
79
+
80
+ if (documento.chaveAcesso) {
81
+ const chave = parseChaveAcessoDCe(documento.chaveAcesso);
82
+ if (!chave.valida) {
83
+ throw new GhinfeError('chaveAcesso com dígito verificador inválido', 'CHAVE_DV_INVALIDO');
84
+ }
85
+ if (chave.modelo !== DCE_MODELO) {
86
+ throw new GhinfeError('chaveAcesso não é de DC-e (modelo 99)', 'DCE_MODELO_INVALIDO');
87
+ }
88
+ return { ...documento, ambiente };
89
+ }
90
+
91
+ const dataEmissao = documento.ide.dataEmissao ?? new Date();
92
+ const chaveAcesso = gerarChaveAcessoDCe({
93
+ uf: documento.ide.uf,
94
+ cnpj: this.#cnpjChave(documento),
95
+ serie: documento.ide.serie ?? 1,
96
+ numero: documento.ide.numero,
97
+ tipoEmitente: documento.ide.tipoEmitente,
98
+ tipoEmissao: documento.ide.tipoEmissao,
99
+ siteAutorizador: documento.ide.siteAutorizador,
100
+ codigoNumerico: documento.ide.codigoNumerico,
101
+ dataEmissao,
102
+ });
103
+
104
+ return { ...documento, ambiente, ide: { ...documento.ide, dataEmissao }, chaveAcesso };
105
+ }
106
+
107
+ /**
108
+ * Monta a URL do QR Code. Em contingência off-line exige o certificado para o parâmetro sign.
109
+ * @param {import('../types/DCeDocument.js').DCeDocumento} documento Já resolvido (com chaveAcesso)
110
+ * @param {{ pfx: Buffer|string, senha: string }} [certificado]
111
+ */
112
+ gerarQrCode(documento, certificado) {
113
+ const { tipoEmissao } = parseChaveAcessoDCe(documento.chaveAcesso);
114
+ const offline = tipoEmissao === DCE_TIPO_EMISSAO.CONTINGENCIA_OFFLINE;
115
+
116
+ if (offline && !certificado) {
117
+ throw new GhinfeError(
118
+ 'Certificado obrigatório para gerar o QR Code em contingência off-line',
119
+ 'DCE_QRCODE_CERTIFICADO_AUSENTE'
120
+ );
121
+ }
122
+
123
+ return gerarUrlQrCodeDCe({
124
+ chaveAcesso: documento.chaveAcesso,
125
+ ambiente: documento.ambiente ?? 2,
126
+ tipoEmissao,
127
+ emitente: documento.emitente,
128
+ sign: offline ? assinarChaveQrCodeDCe(documento.chaveAcesso, certificado.pfx, certificado.senha) : undefined,
129
+ baseUrl: this.options.qrCodeUrl,
130
+ });
131
+ }
132
+
133
+ /**
134
+ * Gera XML completo (com infDCeSupl) sem assinatura.
135
+ * Em contingência off-line use gerarXmlAssinado — o QR Code depende do certificado.
136
+ * @param {import('../types/DCeDocument.js').DCeDocumento} documento
137
+ * @param {{ validarXsd?: boolean }} [options]
138
+ */
139
+ gerarXml(documento, options = {}) {
140
+ const doc = this.resolverDocumento(documento);
141
+ return this.xmlService.gerarXml(doc, {
142
+ validarXsd: options.validarXsd,
143
+ qrCode: this.gerarQrCode(doc),
144
+ urlChave: this.options.urlChave ?? this.options.qrCodeUrl,
145
+ });
146
+ }
147
+
148
+ /**
149
+ * @param {import('../types/DCeDocument.js').DCeDocumento} documento
150
+ * @param {{ pfx: Buffer|string, senha: string }} certificado
151
+ * @param {{ validarXsd?: boolean }} [options]
152
+ */
153
+ gerarXmlAssinado(documento, certificado, options = {}) {
154
+ const doc = this.resolverDocumento(documento);
155
+ const qrCode = this.gerarQrCode(doc, certificado);
156
+
157
+ const xml = this.xmlService.gerarXml(doc, {
158
+ validarXsd: false,
159
+ qrCode,
160
+ urlChave: this.options.urlChave ?? this.options.qrCodeUrl,
161
+ });
162
+ const xmlAssinado = this.signService.assinarDCe(xml, certificado.pfx, certificado.senha);
163
+
164
+ if (options.validarXsd ?? this.xmlService.validarXsd) {
165
+ this.xmlService.validarXml(xmlAssinado);
166
+ }
167
+
168
+ return { chaveAcesso: doc.chaveAcesso, xml: xmlAssinado, qrCode };
169
+ }
170
+
171
+ /** @param {string} xml */
172
+ validarXsd(xml) {
173
+ return this.xmlService.validarXml(xml);
174
+ }
175
+
176
+ /**
177
+ * Consulta status do web service de autorização (DCeStatusServico).
178
+ * @param {string} [uf]
179
+ */
180
+ async consultarStatus(uf) {
181
+ this.#certificado();
182
+ return this.soapService.consultarStatus(uf);
183
+ }
184
+
185
+ /**
186
+ * Gera, assina, valida (XSD) e envia a DC-e para autorização.
187
+ * Lança SefazError em caso de rejeição.
188
+ * @param {import('../types/DCeDocument.js').DCeDocumento} documento
189
+ * @param {{ compactar?: boolean }} [options]
190
+ */
191
+ async emitir(documento, options = {}) {
192
+ const certificado = this.#certificado();
193
+ const doc = this.resolverDocumento(documento);
194
+
195
+ if (doc.ambiente !== this.ambiente) {
196
+ throw new GhinfeError(
197
+ `documento.ambiente (${doc.ambiente}) difere do ambiente do serviço (${this.ambiente})`,
198
+ 'DCE_AMBIENTE_DIVERGENTE'
199
+ );
200
+ }
201
+
202
+ const { xml, qrCode } = this.gerarXmlAssinado(doc, certificado, { validarXsd: true });
203
+ const retorno = await this.soapService.autorizar(xml, {
204
+ compactar: options.compactar ?? this.options.compactar,
205
+ });
206
+
207
+ return { chaveAcesso: doc.chaveAcesso, qrCode, xmlAssinado: xml, retorno };
208
+ }
209
+
210
+ /**
211
+ * Consulta situação da DC-e na SEFAZ (DCeConsulta).
212
+ * @param {string} chaveAcesso
213
+ */
214
+ async consultar(chaveAcesso) {
215
+ this.#certificado();
216
+ return this.soapService.consultar(chaveAcesso);
217
+ }
218
+
219
+ /**
220
+ * Cancela a DC-e (evento 110111). Prazo: até 24h após a autorização.
221
+ * @param {{
222
+ * chaveAcesso: string,
223
+ * protocolo: string,
224
+ * justificativa: string,
225
+ * emitente: { cnpj?: string, cpf?: string, idOutros?: string },
226
+ * sequencia?: number,
227
+ * dataEvento?: Date,
228
+ * }} params emitente: mesmo usuário emitente (grupo emit) da DC-e
229
+ */
230
+ async cancelar(params) {
231
+ const certificado = this.#certificado();
232
+ const xmlEvento = this.eventoBuilder.buildCancelamento({ ...params, ambiente: this.ambiente });
233
+ const xmlAssinado = this.signService.assinarEvento(xmlEvento, certificado.pfx, certificado.senha);
234
+ this.xmlService.validarEvento(xmlAssinado);
235
+
236
+ const retorno = await this.soapService.enviarEvento(xmlAssinado);
237
+ return { chaveAcesso: params.chaveAcesso, xmlEvento: xmlAssinado, retorno };
238
+ }
239
+
240
+ /**
241
+ * Gera a DACE em HTML a partir do dceProc (ou do resultado de emitir()).
242
+ * @param {string|Awaited<ReturnType<DCeService['emitir']>>} xmlOuResultado
243
+ * @param {Parameters<DaceService['gerar']>[1]} [opcoes]
244
+ */
245
+ async gerarDace(xmlOuResultado, opcoes) {
246
+ const xml = typeof xmlOuResultado === 'string' ? xmlOuResultado : xmlOuResultado.retorno.xmlProc;
247
+ return new DaceService().gerar(xml, opcoes);
248
+ }
249
+
250
+ /** @param {Awaited<ReturnType<DCeService['emitir']>>} resultado */
251
+ toPersistencePayload(resultado) {
252
+ return {
253
+ chave_acesso: resultado.chaveAcesso,
254
+ modelo: DCE_MODELO,
255
+ xml_dce: resultado.retorno.xmlProc,
256
+ protocolo: resultado.retorno.protocolo,
257
+ qrcode_url: resultado.qrCode,
258
+ status: resultado.retorno.cStat,
259
+ motivo: resultado.retorno.xMotivo,
260
+ autorizado_em: resultado.retorno.dhRecbto ? new Date(resultado.retorno.dhRecbto) : new Date(),
261
+ };
262
+ }
263
+ }
@@ -0,0 +1,235 @@
1
+ import zlib from 'zlib';
2
+ import axios from 'axios';
3
+ import { criarHttpsAgentSefaz } from '../../sefaz/httpsAgent.js';
4
+ import { GhinfeError } from '../../errors/GhinfeError.js';
5
+ import { SefazError } from '../../errors/SefazError.js';
6
+ import { UF_IBGE } from '../../constants/sefaz.js';
7
+ import { DCE_NAMESPACE, DCE_VERSAO, DCE_WEBSERVICES, DCE_STATUS } from '../constants/dce.js';
8
+
9
+ const SERVICOS = {
10
+ autorizacao: { wsdl: 'DCeAutorizacao', metodo: 'dceAutorizacao' },
11
+ consulta: { wsdl: 'DCeConsulta', metodo: 'dceConsulta' },
12
+ status: { wsdl: 'DCeStatusServico', metodo: 'dceStatusServico' },
13
+ evento: { wsdl: 'DCeRecepcaoEvento', metodo: 'dceRecepcaoEvento' },
14
+ };
15
+
16
+ // O WSDL de DCeConsulta declara dceResultMsg, mas o servidor responde dceConsultaResult
17
+ const RESULTADO_SOAP = /<(dce\w*Result(?:Msg)?)(?:\s[^>]*)?>([\s\S]*?)<\/\1>/;
18
+
19
+ const semDeclaracao = (xml) => xml.replace(/^\s*<\?xml[^>]*\?>\s*/, '');
20
+
21
+ /** @param {string} xml @param {string} nome */
22
+ function tag(xml, nome) {
23
+ return xml?.match(new RegExp(`<${nome}(?:\\s[^>]*)?>([^<]*)</${nome}>`))?.[1];
24
+ }
25
+
26
+ /** @param {string} xml @param {string} nome */
27
+ function grupo(xml, nome) {
28
+ return xml?.match(new RegExp(`<${nome}[\\s>][\\s\\S]*?</${nome}>`))?.[0];
29
+ }
30
+
31
+ /**
32
+ * Cliente SOAP 1.2 dos web services da DC-e, com TLS mútuo (certificado A1).
33
+ */
34
+ export class DCeSoapService {
35
+ /**
36
+ * @param {{
37
+ * certificado: { pfx: Buffer|string, senha: string },
38
+ * ambiente?: number,
39
+ * timeout?: number,
40
+ * endpoints?: Partial<Record<keyof typeof SERVICOS, string>>,
41
+ * ca?: string|string[],
42
+ * }} options
43
+ */
44
+ constructor(options) {
45
+ if (!options?.certificado) {
46
+ throw new GhinfeError('Certificado A1 obrigatório para comunicação com a SEFAZ', 'DCE_CERTIFICADO_AUSENTE');
47
+ }
48
+ this.ambiente = options.ambiente ?? 2;
49
+ this.timeout = options.timeout ?? 30000;
50
+ this.endpoints = { ...DCE_WEBSERVICES[this.ambiente], ...options.endpoints };
51
+ this.httpsAgent = criarHttpsAgentSefaz(options.certificado, { ca: options.ca });
52
+ }
53
+
54
+ /**
55
+ * @param {keyof typeof SERVICOS} servico
56
+ * @param {string} xmlDados
57
+ * @param {{ compactar?: boolean }} [options]
58
+ * @returns {Promise<string>} XML de retorno (conteúdo do elemento *Result)
59
+ */
60
+ async #chamar(servico, xmlDados, options = {}) {
61
+ const { wsdl, metodo } = SERVICOS[servico];
62
+ const ns = `http://www.portalfiscal.inf.br/dce/wsdl/${wsdl}`;
63
+ const acao = options.compactar ? `${metodo}Zip` : metodo;
64
+
65
+ const corpo = options.compactar
66
+ ? `<dceDadosMsgZip xmlns="${ns}">${zlib.gzipSync(Buffer.from(semDeclaracao(xmlDados), 'utf8')).toString('base64')}</dceDadosMsgZip>`
67
+ : `<dceDadosMsg xmlns="${ns}">${semDeclaracao(xmlDados)}</dceDadosMsg>`;
68
+
69
+ const envelope =
70
+ '<?xml version="1.0" encoding="utf-8"?>' +
71
+ '<soap12:Envelope xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">' +
72
+ `<soap12:Body>${corpo}</soap12:Body></soap12:Envelope>`;
73
+
74
+ let response;
75
+ try {
76
+ response = await axios.post(this.endpoints[servico], envelope, {
77
+ httpsAgent: this.httpsAgent,
78
+ timeout: this.timeout,
79
+ responseType: 'text',
80
+ headers: { 'Content-Type': `application/soap+xml; charset=utf-8; action="${ns}/${acao}"` },
81
+ });
82
+ } catch (error) {
83
+ if (axios.isAxiosError(error)) {
84
+ throw new GhinfeError(
85
+ `Falha na comunicação SOAP (${wsdl}): ${error.message}`,
86
+ 'SEFAZ_HTTP_ERROR',
87
+ { status: error.response?.status, data: error.response?.data }
88
+ );
89
+ }
90
+ throw error;
91
+ }
92
+
93
+ const conteudo = response.data.match(RESULTADO_SOAP)?.[2];
94
+ if (!conteudo) {
95
+ throw new GhinfeError(`Resposta SOAP de ${wsdl} sem elemento de resultado`, 'SEFAZ_RESPOSTA_INVALIDA', {
96
+ data: response.data,
97
+ });
98
+ }
99
+ return conteudo;
100
+ }
101
+
102
+ /**
103
+ * Consulta status do serviço (DCeStatusServico).
104
+ * @param {string} [uf] UF consultada (o autorizador responde por todas)
105
+ */
106
+ async consultarStatus(uf = 'PR') {
107
+ const cUF = UF_IBGE[uf.toUpperCase()];
108
+ const xml =
109
+ `<consStatServDCe xmlns="${DCE_NAMESPACE}" versao="${DCE_VERSAO}">` +
110
+ `<tpAmb>${this.ambiente}</tpAmb><cUF>${cUF}</cUF><xServ>STATUS</xServ></consStatServDCe>`;
111
+
112
+ const retorno = await this.#chamar('status', xml);
113
+ const cStat = tag(retorno, 'cStat');
114
+ return {
115
+ emOperacao: cStat === DCE_STATUS.SERVICO_EM_OPERACAO,
116
+ cStat,
117
+ xMotivo: tag(retorno, 'xMotivo'),
118
+ cUF: tag(retorno, 'cUF'),
119
+ dhRecbto: tag(retorno, 'dhRecbto'),
120
+ tMed: tag(retorno, 'tMed'),
121
+ xObs: tag(retorno, 'xObs'),
122
+ xmlRetorno: retorno,
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Envia DC-e assinada para autorização (síncrono). Lança SefazError se não autorizada.
128
+ * @param {string} xmlAssinado
129
+ * @param {{ compactar?: boolean }} [options] compactar: usa dceAutorizacaoZip (GZip + Base64)
130
+ */
131
+ async autorizar(xmlAssinado, options = {}) {
132
+ const retorno = await this.#chamar('autorizacao', xmlAssinado, options);
133
+ const protDCe = grupo(retorno, 'protDCe');
134
+ const fonte = protDCe ?? retorno;
135
+ const cStat = tag(fonte, 'cStat');
136
+ const xMotivo = tag(fonte, 'xMotivo');
137
+
138
+ if (cStat !== DCE_STATUS.AUTORIZADO) {
139
+ throw new SefazError(cStat ?? '999', xMotivo ?? 'Retorno não identificado', {
140
+ cStatRetorno: tag(retorno, 'cStat'),
141
+ xmlRetorno: retorno,
142
+ });
143
+ }
144
+
145
+ return {
146
+ autorizado: true,
147
+ cStat,
148
+ xMotivo,
149
+ protocolo: tag(protDCe, 'nProt'),
150
+ chaveAcesso: tag(protDCe, 'chDCe'),
151
+ dhRecbto: tag(protDCe, 'dhRecbto'),
152
+ digVal: tag(protDCe, 'digVal'),
153
+ xmlProtocolo: protDCe,
154
+ xmlProc: this.montarDCeProc(xmlAssinado, protDCe),
155
+ xmlRetorno: retorno,
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Consulta situação da DC-e (DCeConsulta). Não lança erro para cStat de rejeição.
161
+ * @param {string} chaveAcesso
162
+ */
163
+ async consultar(chaveAcesso) {
164
+ const xml =
165
+ `<consSitDCe xmlns="${DCE_NAMESPACE}" versao="${DCE_VERSAO}">` +
166
+ `<tpAmb>${this.ambiente}</tpAmb><xServ>CONSULTAR</xServ><chDCe>${chaveAcesso}</chDCe></consSitDCe>`;
167
+
168
+ const retorno = await this.#chamar('consulta', xml);
169
+ const protDCe = grupo(retorno, 'protDCe');
170
+ const cStat = tag(retorno, 'cStat');
171
+
172
+ return {
173
+ cStat,
174
+ xMotivo: tag(retorno, 'xMotivo'),
175
+ autorizada: cStat === DCE_STATUS.AUTORIZADO,
176
+ cancelada: cStat === DCE_STATUS.CANCELADO,
177
+ protocolo: tag(protDCe, 'nProt'),
178
+ xmlProtocolo: protDCe,
179
+ eventos: [...retorno.matchAll(/<procEventoDCe[\s>][\s\S]*?<\/procEventoDCe>/g)].map((m) => m[0]),
180
+ xmlRetorno: retorno,
181
+ };
182
+ }
183
+
184
+ /**
185
+ * Envia evento assinado (DCeRecepcaoEvento). Lança SefazError se não registrado.
186
+ * @param {string} xmlEventoAssinado
187
+ */
188
+ async enviarEvento(xmlEventoAssinado) {
189
+ const retorno = await this.#chamar('evento', xmlEventoAssinado);
190
+ const infEvento = grupo(retorno, 'infEvento') ?? retorno;
191
+ const cStat = tag(infEvento, 'cStat');
192
+ const xMotivo = tag(infEvento, 'xMotivo');
193
+ const sucesso = [
194
+ DCE_STATUS.EVENTO_REGISTRADO,
195
+ DCE_STATUS.EVENTO_REGISTRADO_SEM_VINCULO,
196
+ DCE_STATUS.CANCELAMENTO_FORA_PRAZO,
197
+ ];
198
+
199
+ if (!sucesso.includes(cStat)) {
200
+ throw new SefazError(cStat ?? '999', xMotivo ?? 'Retorno não identificado', { xmlRetorno: retorno });
201
+ }
202
+
203
+ const retEvento = grupo(retorno, 'retEventoDCe');
204
+ return {
205
+ registrado: true,
206
+ cStat,
207
+ xMotivo,
208
+ protocolo: tag(infEvento, 'nProt'),
209
+ tpEvento: tag(infEvento, 'tpEvento'),
210
+ dhRegEvento: tag(infEvento, 'dhRegEvento'),
211
+ xmlRetorno: retorno,
212
+ xmlProc: retEvento
213
+ ? `<?xml version="1.0" encoding="UTF-8"?><procEventoDCe xmlns="${DCE_NAMESPACE}" versao="${DCE_VERSAO}">` +
214
+ `${semDeclaracao(xmlEventoAssinado)}${retEvento}</procEventoDCe>`
215
+ : undefined,
216
+ };
217
+ }
218
+
219
+ /**
220
+ * Monta dceProc (DC-e assinada + protocolo de autorização) para armazenamento.
221
+ * @param {string} xmlAssinado
222
+ * @param {string} protDCe
223
+ */
224
+ montarDCeProc(xmlAssinado, protDCe) {
225
+ // A SEFA-PR devolve protDCe sem o atributo versao, obrigatório em procDCe_v1.00.xsd
226
+ const prot = /^<protDCe[^>]*\sversao=/.test(protDCe)
227
+ ? protDCe
228
+ : protDCe.replace(/^<protDCe/, `<protDCe versao="${DCE_VERSAO}"`);
229
+
230
+ return (
231
+ `<?xml version="1.0" encoding="UTF-8"?><dceProc xmlns="${DCE_NAMESPACE}" versao="${DCE_VERSAO}">` +
232
+ `${semDeclaracao(xmlAssinado)}${prot}</dceProc>`
233
+ );
234
+ }
235
+ }
@@ -0,0 +1,67 @@
1
+ import { DCeXmlBuilder } from '../builders/DCeXmlBuilder.js';
2
+ import { XsdValidator } from '../../validation/XsdValidator.js';
3
+ import { XSD_SCHEMAS } from '../../validation/schemas.js';
4
+
5
+ /**
6
+ * Serviço de geração e validação XSD da DC-e.
7
+ */
8
+ export class DCeXmlService {
9
+ /**
10
+ * @param {{ validarXsd?: boolean }} [options]
11
+ */
12
+ constructor(options = {}) {
13
+ this.builder = new DCeXmlBuilder();
14
+ this.validarXsd = options.validarXsd ?? false;
15
+ }
16
+
17
+ /**
18
+ * @param {import('../types/DCeDocument.js').DCeDocumento} documento
19
+ * @param {{ validarXsd?: boolean, qrCode?: string, urlChave?: string, sign?: string, qrCodeBaseUrl?: string }} [options]
20
+ */
21
+ gerarXml(documento, options = {}) {
22
+ const { validarXsd, ...buildOptions } = options;
23
+ const xml = this.builder.build(documento, buildOptions);
24
+
25
+ if (validarXsd ?? this.validarXsd) {
26
+ this.validarXml(xml);
27
+ }
28
+
29
+ return xml;
30
+ }
31
+
32
+ /**
33
+ * Valida contra dce_v1.00.xsd. XML sem assinatura recebe Signature stub.
34
+ * @param {string} xml
35
+ */
36
+ validarXml(xml) {
37
+ return XsdValidator.validar(xml, XSD_SCHEMAS.DCE, {
38
+ injetarAssinaturaStub: !xml.includes('</Signature>'),
39
+ modelo: '99',
40
+ });
41
+ }
42
+
43
+ /**
44
+ * Valida evento assinado (eventoDCe) e o detalhe do cancelamento (evCancDCe).
45
+ * @param {string} xmlEventoAssinado
46
+ */
47
+ validarEvento(xmlEventoAssinado) {
48
+ XsdValidator.validar(xmlEventoAssinado, XSD_SCHEMAS.DCE_EVENTO);
49
+
50
+ const detalhe = xmlEventoAssinado.match(/<evCancDCe>[\s\S]*?<\/evCancDCe>/)?.[0];
51
+ if (detalhe) {
52
+ XsdValidator.validar(
53
+ detalhe.replace('<evCancDCe>', '<evCancDCe xmlns="http://www.portalfiscal.inf.br/dce">'),
54
+ XSD_SCHEMAS.DCE_EVENTO_CANCELAMENTO
55
+ );
56
+ }
57
+ return { valido: true, avisos: [] };
58
+ }
59
+
60
+ /** @param {string} xml */
61
+ tentarValidar(xml) {
62
+ return XsdValidator.tentarValidar(xml, XSD_SCHEMAS.DCE, {
63
+ injetarAssinaturaStub: !xml.includes('</Signature>'),
64
+ modelo: '99',
65
+ });
66
+ }
67
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * @typedef {Object} DCeEndereco
3
+ * @property {string} logradouro
4
+ * @property {string} numero
5
+ * @property {string} [complemento]
6
+ * @property {string} bairro
7
+ * @property {string} codigoMunicipio IBGE 7 dígitos
8
+ * @property {string} municipio
9
+ * @property {string} uf
10
+ * @property {string} cep
11
+ * @property {string} [codigoPais] Padrão 1058 (Brasil)
12
+ * @property {string} [pais] Padrão "BRASIL"
13
+ * @property {string} [telefone]
14
+ * @property {string} [email] Somente destinatário
15
+ */
16
+
17
+ /**
18
+ * Usuário emitente (quem remete o bem). Informe apenas um entre cnpj, cpf e idOutros.
19
+ * @typedef {Object} DCeParte
20
+ * @property {string} [cnpj]
21
+ * @property {string} [cpf]
22
+ * @property {string} [idOutros] Estrangeiro, indígena, quilombola etc.
23
+ * @property {string} razaoSocial
24
+ * @property {DCeEndereco} endereco
25
+ */
26
+
27
+ /**
28
+ * @typedef {Object} DCeFisco
29
+ * @property {string} cnpj
30
+ * @property {string} orgao
31
+ * @property {string} uf
32
+ */
33
+
34
+ /**
35
+ * @typedef {Object} DCeMarketplace
36
+ * @property {string} cnpj
37
+ * @property {string} razaoSocial
38
+ * @property {string} site
39
+ */
40
+
41
+ /**
42
+ * Transportadora ou ECT (Correios) emitente da DC-e.
43
+ * @typedef {Object} DCeEmpresaEmissora
44
+ * @property {string} cnpj
45
+ * @property {string} razaoSocial
46
+ */
47
+
48
+ /**
49
+ * @typedef {Object} DCeItem
50
+ * @property {string} descricao
51
+ * @property {string} ncm Capítulo (2 dígitos) ou NCM completo (8). "00000000" se não classificável
52
+ * @property {number} quantidade
53
+ * @property {number} valorUnitario
54
+ * @property {number} [valorTotal] Calculado (quantidade × valorUnitario) se omitido
55
+ * @property {string} [informacoesAdicionais]
56
+ */
57
+
58
+ /**
59
+ * @typedef {Object} DCeObservacao
60
+ * @property {string} campo Até 20 caracteres
61
+ * @property {string} texto Até 60 caracteres
62
+ */
63
+
64
+ /**
65
+ * @typedef {Object} DCeInformacoesAdicionais
66
+ * @property {string} [fisco]
67
+ * @property {string} [complementares]
68
+ * @property {string} [marketplace]
69
+ * @property {string} [ect]
70
+ * @property {DCeObservacao[]} [obsEmitente]
71
+ * @property {DCeObservacao[]} [obsFisco]
72
+ * @property {DCeObservacao[]} [obsMarketplace] Também usado por transportadoras
73
+ * @property {DCeObservacao[]} [obsECT]
74
+ */
75
+
76
+ /**
77
+ * @typedef {Object} DCeIde
78
+ * @property {string} uf UF do usuário emitente
79
+ * @property {number} [serie] 0 a 999 (padrão 1)
80
+ * @property {number} numero 1 a 999999999
81
+ * @property {number} tipoEmitente 0=Fisco, 1=Marketplace, 2=Emissor próprio, 3=Transportadora, 4=ECT
82
+ * @property {number} [tipoEmissao] 1=Normal, 2=Contingência off-line
83
+ * @property {number} [siteAutorizador] 0 quando o autorizador tem site único
84
+ * @property {string} [codigoNumerico] cDC (6 dígitos) — gerado se omitido
85
+ * @property {Date} [dataEmissao]
86
+ */
87
+
88
+ /**
89
+ * @typedef {Object} DCeDocumento
90
+ * @property {string} [chaveAcesso] 44 dígitos — gerada automaticamente se omitida
91
+ * @property {number} [ambiente] 1=Produção, 2=Homologação
92
+ * @property {DCeIde} ide
93
+ * @property {DCeParte} emitente
94
+ * @property {DCeFisco} [fisco] Obrigatório se tipoEmitente=0
95
+ * @property {DCeMarketplace} [marketplace] Obrigatório se tipoEmitente=1
96
+ * @property {DCeEmpresaEmissora} [transportadora] Obrigatório se tipoEmitente=3
97
+ * @property {DCeEmpresaEmissora} [ect] Obrigatório se tipoEmitente=4
98
+ * @property {DCeParte} destinatario
99
+ * @property {Array<{ cnpj?: string, cpf?: string }>} [autorizadosXml] Até 10
100
+ * @property {DCeItem[]} itens
101
+ * @property {{ modalidade: number, cnpjTransportadora?: string }} transporte 0=Correios, 1=Próprio, 2=Transportadora
102
+ * @property {DCeInformacoesAdicionais} [informacoesAdicionais]
103
+ * @property {string} [solicitacaoFisco] JSON do pedido no app do Fisco (somente tipoEmitente=0)
104
+ */
105
+
106
+ export {};