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
@@ -1,196 +1,196 @@
1
- import { NFeXmlService } from './NFeXmlService.js';
2
- import { NFeSignService } from './NFeSignService.js';
3
- import { NFeSoapService } from './NFeSoapService.js';
4
- import { NFeEventoService } from './NFeEventoService.js';
5
- import { GhinfeError } from '../../errors/GhinfeError.js';
6
- import { gerarChaveAcesso, validarChaveAcesso } from '../../utils/chave.utils.js';
7
- import { SefazEndpointResolver } from '../../sefaz/SefazEndpointResolver.js';
8
-
9
- /**
10
- * @param {{
11
- * mock?: boolean,
12
- * uf?: string,
13
- * ambiente?: number,
14
- * sefazEndpoint?: string,
15
- * sefazEventoEndpoint?: string,
16
- * mockScenario?: string,
17
- * mockEventoScenario?: string,
18
- * }} options
19
- */
20
- function buildSoapOptions(options) {
21
- if (options.mock ?? true) {
22
- return {
23
- mock: true,
24
- endpoint: options.sefazEndpoint,
25
- eventoEndpoint: options.sefazEventoEndpoint,
26
- mockScenario: options.mockScenario,
27
- mockEventoScenario: options.mockEventoScenario,
28
- };
29
- }
30
-
31
- if (options.sefazEndpoint) {
32
- return {
33
- mock: false,
34
- endpoint: options.sefazEndpoint,
35
- eventoEndpoint: options.sefazEventoEndpoint,
36
- };
37
- }
38
-
39
- if (options.uf) {
40
- const urls = SefazEndpointResolver.paraNFe(options.uf, options.ambiente ?? 2);
41
- return { mock: false, endpoint: urls.endpoint, eventoEndpoint: urls.eventoEndpoint };
42
- }
43
-
44
- return { mock: true };
45
- }
46
-
47
- /**
48
- * Orquestrador principal da NF-e — une geração, assinatura e envio.
49
- */
50
- export class NFeService {
51
- /**
52
- * @param {{
53
- * mock?: boolean,
54
- * uf?: string,
55
- * ambiente?: number,
56
- * sefazEndpoint?: string,
57
- * sefazEventoEndpoint?: string,
58
- * mockScenario?: string,
59
- * mockEventoScenario?: string,
60
- * validarXsd?: boolean,
61
- * }} [options]
62
- */
63
- constructor(options = {}) {
64
- this.options = options;
65
- const soapOptions = buildSoapOptions(options);
66
-
67
- this.xmlService = new NFeXmlService({ validarXsd: options.validarXsd });
68
- this.signService = new NFeSignService();
69
- this.soapService = new NFeSoapService(soapOptions);
70
- this.eventoService = new NFeEventoService(soapOptions);
71
- }
72
-
73
- /** Resolve endpoints SEFAZ para a UF do documento. */
74
- configurarUf(uf, ambiente = 2) {
75
- const urls = SefazEndpointResolver.paraNFe(uf, ambiente);
76
- this.soapService.endpoint = urls.endpoint;
77
- this.soapService.eventoEndpoint = urls.eventoEndpoint;
78
- this.soapService.mock = false;
79
- this.eventoService.soapService.endpoint = urls.endpoint;
80
- this.eventoService.soapService.eventoEndpoint = urls.eventoEndpoint;
81
- this.eventoService.soapService.mock = false;
82
- return urls;
83
- }
84
-
85
- /** @param {Parameters<typeof gerarChaveAcesso>[0]} params */
86
- gerarChaveAcesso(params) {
87
- return gerarChaveAcesso(params);
88
- }
89
-
90
- /** @param {import('../types/NFeDocument.js').NFeDocument} documento */
91
- resolverDocumento(documento) {
92
- if (documento.chaveAcesso) {
93
- if (!validarChaveAcesso(documento.chaveAcesso)) {
94
- throw new GhinfeError('chaveAcesso com dígito verificador inválido', 'CHAVE_DV_INVALIDO');
95
- }
96
- return documento;
97
- }
98
-
99
- const chaveAcesso = gerarChaveAcesso({
100
- uf: documento.ide.uf,
101
- cnpj: documento.emitente.cnpj,
102
- modelo: '55',
103
- serie: documento.ide.serie ?? 1,
104
- numero: documento.ide.numero,
105
- tipoEmissao: documento.ide.tipoEmissao ?? 1,
106
- });
107
-
108
- return { ...documento, chaveAcesso };
109
- }
110
-
111
- async emitir(documento, certificado, idLote) {
112
- const doc = this.resolverDocumento(documento);
113
-
114
- if (!this.soapService.mock && this.options.uf === undefined && !this.options.sefazEndpoint) {
115
- this.configurarUf(doc.ide.uf, doc.ambiente ?? 2);
116
- }
117
-
118
- const xml = this.xmlService.gerarXml(doc);
119
- const xmlAssinado = this.signService.assinar(xml, certificado.pfx, certificado.senha);
120
- const retorno = await this.soapService.autorizar(xmlAssinado, doc.chaveAcesso, idLote);
121
-
122
- return { chaveAcesso: doc.chaveAcesso, xmlGerado: xml, xmlAssinado, retorno };
123
- }
124
-
125
- gerarXml(documento, options = {}) {
126
- const doc = this.resolverDocumento(documento);
127
- return this.xmlService.gerarXml(doc, options);
128
- }
129
-
130
- gerarXmlAssinado(documento, certificado) {
131
- const doc = this.resolverDocumento(documento);
132
- const xml = this.xmlService.gerarXml(doc);
133
- return this.signService.assinar(xml, certificado.pfx, certificado.senha);
134
- }
135
-
136
- async enviar(xmlAssinado, chaveAcesso) {
137
- return this.soapService.autorizar(xmlAssinado, chaveAcesso);
138
- }
139
-
140
- validarXsd(xml, options = {}) {
141
- return this.xmlService.validarXml(xml, options);
142
- }
143
-
144
- async cancelar(dados, certificado) {
145
- return this.eventoService.cancelar(dados, certificado);
146
- }
147
-
148
- async cartaCorrecao(dados, certificado) {
149
- return this.eventoService.cartaCorrecao(dados, certificado);
150
- }
151
-
152
- gerarXmlCancelamento(dados) {
153
- return this.eventoService.gerarXmlCancelamento(dados);
154
- }
155
-
156
- gerarXmlCartaCorrecao(dados) {
157
- return this.eventoService.gerarXmlCartaCorrecao(dados);
158
- }
159
-
160
- async enviarEventoMock(xmlEvento, chaveAcesso, tpEvento) {
161
- return this.eventoService.enviar(xmlEvento, chaveAcesso, tpEvento);
162
- }
163
-
164
- /**
165
- * Consulta status do webservice SEFAZ (NfeStatusServico4).
166
- * @param {string} [uf]
167
- * @param {number} [ambiente]
168
- */
169
- async consultarStatus(uf, ambiente) {
170
- const ufConsulta = uf ?? this.options.uf;
171
- if (!ufConsulta) {
172
- throw new GhinfeError('UF é obrigatória para consultar status SEFAZ', 'STATUS_UF_REQUIRED');
173
- }
174
- return this.soapService.consultarStatus(ufConsulta, ambiente ?? this.options.ambiente ?? 2);
175
- }
176
-
177
- toPersistencePayload(resultado) {
178
- if (!resultado.retorno.autorizado) {
179
- throw new GhinfeError('Não é possível persistir NF-e não autorizada', 'NFE_NOT_AUTHORIZED', resultado.retorno);
180
- }
181
-
182
- return {
183
- chave_acesso: resultado.chaveAcesso,
184
- xml_nfe: resultado.xmlAssinado,
185
- xml_protocolo: resultado.retorno.xmlProtocolo,
186
- protocolo: resultado.retorno.protocolo,
187
- status: resultado.retorno.cStat,
188
- motivo: resultado.retorno.xMotivo,
189
- autorizado_em: new Date(),
190
- };
191
- }
192
-
193
- toEventoPersistencePayload(resultado) {
194
- return this.eventoService.toPersistencePayload(resultado);
195
- }
196
- }
1
+ import { NFeXmlService } from './NFeXmlService.js';
2
+ import { NFeSignService } from './NFeSignService.js';
3
+ import { NFeSoapService } from './NFeSoapService.js';
4
+ import { NFeEventoService } from './NFeEventoService.js';
5
+ import { GhinfeError } from '../../errors/GhinfeError.js';
6
+ import { gerarChaveAcesso, validarChaveAcesso } from '../../utils/chave.utils.js';
7
+ import { SefazEndpointResolver } from '../../sefaz/SefazEndpointResolver.js';
8
+
9
+ /**
10
+ * @param {{
11
+ * mock?: boolean,
12
+ * uf?: string,
13
+ * ambiente?: number,
14
+ * sefazEndpoint?: string,
15
+ * sefazEventoEndpoint?: string,
16
+ * mockScenario?: string,
17
+ * mockEventoScenario?: string,
18
+ * }} options
19
+ */
20
+ function buildSoapOptions(options) {
21
+ if (options.mock ?? true) {
22
+ return {
23
+ mock: true,
24
+ endpoint: options.sefazEndpoint,
25
+ eventoEndpoint: options.sefazEventoEndpoint,
26
+ mockScenario: options.mockScenario,
27
+ mockEventoScenario: options.mockEventoScenario,
28
+ };
29
+ }
30
+
31
+ if (options.sefazEndpoint) {
32
+ return {
33
+ mock: false,
34
+ endpoint: options.sefazEndpoint,
35
+ eventoEndpoint: options.sefazEventoEndpoint,
36
+ };
37
+ }
38
+
39
+ if (options.uf) {
40
+ const urls = SefazEndpointResolver.paraNFe(options.uf, options.ambiente ?? 2);
41
+ return { mock: false, endpoint: urls.endpoint, eventoEndpoint: urls.eventoEndpoint };
42
+ }
43
+
44
+ return { mock: true };
45
+ }
46
+
47
+ /**
48
+ * Orquestrador principal da NF-e — une geração, assinatura e envio.
49
+ */
50
+ export class NFeService {
51
+ /**
52
+ * @param {{
53
+ * mock?: boolean,
54
+ * uf?: string,
55
+ * ambiente?: number,
56
+ * sefazEndpoint?: string,
57
+ * sefazEventoEndpoint?: string,
58
+ * mockScenario?: string,
59
+ * mockEventoScenario?: string,
60
+ * validarXsd?: boolean,
61
+ * }} [options]
62
+ */
63
+ constructor(options = {}) {
64
+ this.options = options;
65
+ const soapOptions = buildSoapOptions(options);
66
+
67
+ this.xmlService = new NFeXmlService({ validarXsd: options.validarXsd });
68
+ this.signService = new NFeSignService();
69
+ this.soapService = new NFeSoapService(soapOptions);
70
+ this.eventoService = new NFeEventoService(soapOptions);
71
+ }
72
+
73
+ /** Resolve endpoints SEFAZ para a UF do documento. */
74
+ configurarUf(uf, ambiente = 2) {
75
+ const urls = SefazEndpointResolver.paraNFe(uf, ambiente);
76
+ this.soapService.endpoint = urls.endpoint;
77
+ this.soapService.eventoEndpoint = urls.eventoEndpoint;
78
+ this.soapService.mock = false;
79
+ this.eventoService.soapService.endpoint = urls.endpoint;
80
+ this.eventoService.soapService.eventoEndpoint = urls.eventoEndpoint;
81
+ this.eventoService.soapService.mock = false;
82
+ return urls;
83
+ }
84
+
85
+ /** @param {Parameters<typeof gerarChaveAcesso>[0]} params */
86
+ gerarChaveAcesso(params) {
87
+ return gerarChaveAcesso(params);
88
+ }
89
+
90
+ /** @param {import('../types/NFeDocument.js').NFeDocument} documento */
91
+ resolverDocumento(documento) {
92
+ if (documento.chaveAcesso) {
93
+ if (!validarChaveAcesso(documento.chaveAcesso)) {
94
+ throw new GhinfeError('chaveAcesso com dígito verificador inválido', 'CHAVE_DV_INVALIDO');
95
+ }
96
+ return documento;
97
+ }
98
+
99
+ const chaveAcesso = gerarChaveAcesso({
100
+ uf: documento.ide.uf,
101
+ cnpj: documento.emitente.cnpj,
102
+ modelo: '55',
103
+ serie: documento.ide.serie ?? 1,
104
+ numero: documento.ide.numero,
105
+ tipoEmissao: documento.ide.tipoEmissao ?? 1,
106
+ });
107
+
108
+ return { ...documento, chaveAcesso };
109
+ }
110
+
111
+ async emitir(documento, certificado, idLote) {
112
+ const doc = this.resolverDocumento(documento);
113
+
114
+ if (!this.soapService.mock && this.options.uf === undefined && !this.options.sefazEndpoint) {
115
+ this.configurarUf(doc.ide.uf, doc.ambiente ?? 2);
116
+ }
117
+
118
+ const xml = this.xmlService.gerarXml(doc);
119
+ const xmlAssinado = this.signService.assinar(xml, certificado.pfx, certificado.senha);
120
+ const retorno = await this.soapService.autorizar(xmlAssinado, doc.chaveAcesso, idLote);
121
+
122
+ return { chaveAcesso: doc.chaveAcesso, xmlGerado: xml, xmlAssinado, retorno };
123
+ }
124
+
125
+ gerarXml(documento, options = {}) {
126
+ const doc = this.resolverDocumento(documento);
127
+ return this.xmlService.gerarXml(doc, options);
128
+ }
129
+
130
+ gerarXmlAssinado(documento, certificado) {
131
+ const doc = this.resolverDocumento(documento);
132
+ const xml = this.xmlService.gerarXml(doc);
133
+ return this.signService.assinar(xml, certificado.pfx, certificado.senha);
134
+ }
135
+
136
+ async enviar(xmlAssinado, chaveAcesso) {
137
+ return this.soapService.autorizar(xmlAssinado, chaveAcesso);
138
+ }
139
+
140
+ validarXsd(xml, options = {}) {
141
+ return this.xmlService.validarXml(xml, options);
142
+ }
143
+
144
+ async cancelar(dados, certificado) {
145
+ return this.eventoService.cancelar(dados, certificado);
146
+ }
147
+
148
+ async cartaCorrecao(dados, certificado) {
149
+ return this.eventoService.cartaCorrecao(dados, certificado);
150
+ }
151
+
152
+ gerarXmlCancelamento(dados) {
153
+ return this.eventoService.gerarXmlCancelamento(dados);
154
+ }
155
+
156
+ gerarXmlCartaCorrecao(dados) {
157
+ return this.eventoService.gerarXmlCartaCorrecao(dados);
158
+ }
159
+
160
+ async enviarEventoMock(xmlEvento, chaveAcesso, tpEvento) {
161
+ return this.eventoService.enviar(xmlEvento, chaveAcesso, tpEvento);
162
+ }
163
+
164
+ /**
165
+ * Consulta status do webservice SEFAZ (NfeStatusServico4).
166
+ * @param {string} [uf]
167
+ * @param {number} [ambiente]
168
+ */
169
+ async consultarStatus(uf, ambiente) {
170
+ const ufConsulta = uf ?? this.options.uf;
171
+ if (!ufConsulta) {
172
+ throw new GhinfeError('UF é obrigatória para consultar status SEFAZ', 'STATUS_UF_REQUIRED');
173
+ }
174
+ return this.soapService.consultarStatus(ufConsulta, ambiente ?? this.options.ambiente ?? 2);
175
+ }
176
+
177
+ toPersistencePayload(resultado) {
178
+ if (!resultado.retorno.autorizado) {
179
+ throw new GhinfeError('Não é possível persistir NF-e não autorizada', 'NFE_NOT_AUTHORIZED', resultado.retorno);
180
+ }
181
+
182
+ return {
183
+ chave_acesso: resultado.chaveAcesso,
184
+ xml_nfe: resultado.xmlAssinado,
185
+ xml_protocolo: resultado.retorno.xmlProtocolo,
186
+ protocolo: resultado.retorno.protocolo,
187
+ status: resultado.retorno.cStat,
188
+ motivo: resultado.retorno.xMotivo,
189
+ autorizado_em: new Date(),
190
+ };
191
+ }
192
+
193
+ toEventoPersistencePayload(resultado) {
194
+ return this.eventoService.toPersistencePayload(resultado);
195
+ }
196
+ }
@@ -1,111 +1,127 @@
1
- import { SignedXml } from 'xml-crypto';
2
- import { DOMParser } from '@xmldom/xmldom';
3
- import { loadPfxCertificate } from '../../utils/certificate.utils.js';
4
- import { GhinfeError } from '../../errors/GhinfeError.js';
5
-
6
- /**
7
- * Serviço de assinatura digital XML-DSig para NF-e (certificado A1 .pfx).
8
- * Assina o elemento infNFe com algoritmo exigido pela SEFAZ.
9
- */
10
- export class NFeSignService {
11
- /**
12
- * Assina o XML da NF-e usando certificado A1.
13
- * @param {string} xmlNFe XML da NF-e sem assinatura
14
- * @param {Buffer|string} pfxBuffer Conteúdo do arquivo .pfx
15
- * @param {string} pfxPassword Senha do certificado
16
- * @returns {string} XML assinado (NFe + Signature)
17
- */
18
- assinar(xmlNFe, pfxBuffer, pfxPassword) {
19
- return this.#assinarElemento(xmlNFe, 'infNFe', pfxBuffer, pfxPassword);
20
- }
21
-
22
- /**
23
- * Assina XML de evento NF-e (cancelamento, CC-e, etc.).
24
- * @param {string} xmlEvento
25
- * @param {Buffer|string} pfxBuffer
26
- * @param {string} pfxPassword
27
- * @returns {string}
28
- */
29
- assinarEvento(xmlEvento, pfxBuffer, pfxPassword) {
30
- return this.#assinarElemento(xmlEvento, 'infEvento', pfxBuffer, pfxPassword);
31
- }
32
-
33
- /**
34
- * Assina XML de RPS/NFS-e (elemento InfRps — ABRASF e derivados).
35
- * @param {string} xmlRps
36
- * @param {Buffer|string} pfxBuffer
37
- * @param {string} pfxPassword
38
- * @returns {string}
39
- */
40
- assinarRps(xmlRps, pfxBuffer, pfxPassword) {
41
- let tag = 'InfRps';
42
- if (xmlRps.includes('InfDeclaracaoPrestacaoServico')) tag = 'InfDeclaracaoPrestacaoServico';
43
- else if (xmlRps.includes('<infRps') || xmlRps.includes('infRps ')) tag = 'infRps';
44
- else if (xmlRps.includes('<InfRps') || xmlRps.includes('InfRps ')) tag = 'InfRps';
45
- return this.#assinarElemento(xmlRps, tag, pfxBuffer, pfxPassword);
46
- }
47
-
48
- /**
49
- * @param {string} xml
50
- * @param {string} tagName
51
- * @param {Buffer|string} pfxBuffer
52
- * @param {string} pfxPassword
53
- */
54
- #assinarElemento(xml, tagName, pfxBuffer, pfxPassword) {
55
- const { privateKeyPem, certificatePem } = loadPfxCertificate(pfxBuffer, pfxPassword);
56
-
57
- const doc = new DOMParser().parseFromString(xml, 'text/xml');
58
- const elemento = doc.getElementsByTagName(tagName)[0];
59
-
60
- if (!elemento) {
61
- throw new GhinfeError(`Elemento ${tagName} não encontrado no XML`, `SIGN_${tagName}_NOT_FOUND`);
62
- }
63
-
64
- const id = elemento.getAttribute('Id');
65
- if (!id) {
66
- throw new GhinfeError(`Atributo Id do ${tagName} é obrigatório para assinatura`, 'SIGN_MISSING_ID');
67
- }
68
-
69
- const signedXml = new SignedXml({
70
- privateKey: privateKeyPem,
71
- publicCert: certificatePem,
72
- signatureAlgorithm: 'http://www.w3.org/2000/09/xmldsig#rsa-sha1',
73
- canonicalizationAlgorithm: 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315',
74
- });
75
-
76
- signedXml.addReference({
77
- xpath: `//*[local-name()='${tagName}']`,
78
- transforms: [
79
- 'http://www.w3.org/2000/09/xmldsig#enveloped-signature',
80
- 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315',
81
- ],
82
- digestAlgorithm: 'http://www.w3.org/2000/09/xmldsig#sha1',
83
- uri: `#${id}`,
84
- });
85
-
86
- signedXml.computeSignature(xml, {
87
- location: { reference: `//*[local-name()='${tagName}']`, action: 'after' },
88
- });
89
-
90
- return signedXml.getSignedXml();
91
- }
92
-
93
- /**
94
- * Extrai informações do certificado para validação pré-envio.
95
- * @param {Buffer|string} pfxBuffer
96
- * @param {string} pfxPassword
97
- */
98
- obterInfoCertificado(pfxBuffer, pfxPassword) {
99
- const { certificate } = loadPfxCertificate(pfxBuffer, pfxPassword);
100
- const subject = certificate.subject.attributes
101
- .map((attr) => `${attr.shortName}=${attr.value}`)
102
- .join(', ');
103
-
104
- return {
105
- subject,
106
- validoDe: certificate.validity.notBefore,
107
- validoAte: certificate.validity.notAfter,
108
- serialNumber: certificate.serialNumber,
109
- };
110
- }
111
- }
1
+ import { SignedXml } from 'xml-crypto';
2
+ import { DOMParser } from '@xmldom/xmldom';
3
+ import { loadPfxCertificate } from '../../utils/certificate.utils.js';
4
+ import { GhinfeError } from '../../errors/GhinfeError.js';
5
+
6
+ /**
7
+ * Serviço de assinatura digital XML-DSig para NF-e (certificado A1 .pfx).
8
+ * Assina o elemento infNFe com algoritmo exigido pela SEFAZ.
9
+ */
10
+ export class NFeSignService {
11
+ /**
12
+ * Assina o XML da NF-e usando certificado A1.
13
+ * @param {string} xmlNFe XML da NF-e sem assinatura
14
+ * @param {Buffer|string} pfxBuffer Conteúdo do arquivo .pfx
15
+ * @param {string} pfxPassword Senha do certificado
16
+ * @returns {string} XML assinado (NFe + Signature)
17
+ */
18
+ assinar(xmlNFe, pfxBuffer, pfxPassword) {
19
+ return this.#assinarElemento(xmlNFe, 'infNFe', pfxBuffer, pfxPassword);
20
+ }
21
+
22
+ /**
23
+ * Assina XML de evento NF-e (cancelamento, CC-e, etc.).
24
+ * @param {string} xmlEvento
25
+ * @param {Buffer|string} pfxBuffer
26
+ * @param {string} pfxPassword
27
+ * @returns {string}
28
+ */
29
+ assinarEvento(xmlEvento, pfxBuffer, pfxPassword) {
30
+ return this.#assinarElemento(xmlEvento, 'infEvento', pfxBuffer, pfxPassword);
31
+ }
32
+
33
+ /**
34
+ * Assina XML da DC-e (elemento infDCe). A Signature é anexada como último
35
+ * filho de DCe, após infDCeSupl, conforme sequência do XSD.
36
+ * @param {string} xmlDCe
37
+ * @param {Buffer|string} pfxBuffer
38
+ * @param {string} pfxPassword
39
+ * @returns {string}
40
+ */
41
+ assinarDCe(xmlDCe, pfxBuffer, pfxPassword) {
42
+ return this.#assinarElemento(xmlDCe, 'infDCe', pfxBuffer, pfxPassword, {
43
+ reference: "/*[local-name()='DCe']",
44
+ action: 'append',
45
+ });
46
+ }
47
+
48
+ /**
49
+ * Assina XML de RPS/NFS-e (elemento InfRps — ABRASF e derivados).
50
+ * @param {string} xmlRps
51
+ * @param {Buffer|string} pfxBuffer
52
+ * @param {string} pfxPassword
53
+ * @returns {string}
54
+ */
55
+ assinarRps(xmlRps, pfxBuffer, pfxPassword) {
56
+ let tag = 'InfRps';
57
+ if (xmlRps.includes('InfDeclaracaoPrestacaoServico')) tag = 'InfDeclaracaoPrestacaoServico';
58
+ else if (xmlRps.includes('<infRps') || xmlRps.includes('infRps ')) tag = 'infRps';
59
+ else if (xmlRps.includes('<InfRps') || xmlRps.includes('InfRps ')) tag = 'InfRps';
60
+ return this.#assinarElemento(xmlRps, tag, pfxBuffer, pfxPassword);
61
+ }
62
+
63
+ /**
64
+ * @param {string} xml
65
+ * @param {string} tagName
66
+ * @param {Buffer|string} pfxBuffer
67
+ * @param {string} pfxPassword
68
+ * @param {{ reference: string, action: 'after'|'append' }} [location] Padrão: logo após o elemento assinado
69
+ */
70
+ #assinarElemento(xml, tagName, pfxBuffer, pfxPassword, location) {
71
+ const { privateKeyPem, certificatePem } = loadPfxCertificate(pfxBuffer, pfxPassword);
72
+
73
+ const doc = new DOMParser().parseFromString(xml, 'text/xml');
74
+ const elemento = doc.getElementsByTagName(tagName)[0];
75
+
76
+ if (!elemento) {
77
+ throw new GhinfeError(`Elemento ${tagName} não encontrado no XML`, `SIGN_${tagName}_NOT_FOUND`);
78
+ }
79
+
80
+ const id = elemento.getAttribute('Id');
81
+ if (!id) {
82
+ throw new GhinfeError(`Atributo Id do ${tagName} é obrigatório para assinatura`, 'SIGN_MISSING_ID');
83
+ }
84
+
85
+ const signedXml = new SignedXml({
86
+ privateKey: privateKeyPem,
87
+ publicCert: certificatePem,
88
+ signatureAlgorithm: 'http://www.w3.org/2000/09/xmldsig#rsa-sha1',
89
+ canonicalizationAlgorithm: 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315',
90
+ });
91
+
92
+ signedXml.addReference({
93
+ xpath: `//*[local-name()='${tagName}']`,
94
+ transforms: [
95
+ 'http://www.w3.org/2000/09/xmldsig#enveloped-signature',
96
+ 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315',
97
+ ],
98
+ digestAlgorithm: 'http://www.w3.org/2000/09/xmldsig#sha1',
99
+ uri: `#${id}`,
100
+ });
101
+
102
+ signedXml.computeSignature(xml, {
103
+ location: location ?? { reference: `//*[local-name()='${tagName}']`, action: 'after' },
104
+ });
105
+
106
+ return signedXml.getSignedXml();
107
+ }
108
+
109
+ /**
110
+ * Extrai informações do certificado para validação pré-envio.
111
+ * @param {Buffer|string} pfxBuffer
112
+ * @param {string} pfxPassword
113
+ */
114
+ obterInfoCertificado(pfxBuffer, pfxPassword) {
115
+ const { certificate } = loadPfxCertificate(pfxBuffer, pfxPassword);
116
+ const subject = certificate.subject.attributes
117
+ .map((attr) => `${attr.shortName}=${attr.value}`)
118
+ .join(', ');
119
+
120
+ return {
121
+ subject,
122
+ validoDe: certificate.validity.notBefore,
123
+ validoAte: certificate.validity.notAfter,
124
+ serialNumber: certificate.serialNumber,
125
+ };
126
+ }
127
+ }