node-sped-nfe 1.1.1 → 1.1.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.
- package/dist/utils/tools.d.ts +1 -0
- package/dist/utils/tools.js +8 -1
- package/package.json +1 -3
- package/saida.txt +0 -0
- package/testes/nfe.xml +285 -0
- package/testes/nfe_sign.xml +1 -0
- package/{exemplos/nfe.js → testes/teste.js} +12 -0
- package/xmlvalid.json +1 -0
- package/docs/README.md +0 -60
- package/docs/Tools.md +0 -105
- package/docs/requisitos.md +0 -26
- package/docs/xml.md +0 -1653
- package/exemplos/consulta.js +0 -19
- package/exemplos/nfce.js +0 -165
- package/exemplos/status.js +0 -22
- package/src/index.ts +0 -4
- package/src/utils/eventos.ts +0 -1487
- package/src/utils/extras.ts +0 -298
- package/src/utils/make.ts +0 -712
- package/src/utils/tools.ts +0 -436
package/src/utils/tools.ts
DELETED
@@ -1,436 +0,0 @@
|
|
1
|
-
import { XMLParser, XMLBuilder } from "fast-xml-parser";
|
2
|
-
import https from "https";
|
3
|
-
import { spawnSync, SpawnSyncReturns } from "child_process"
|
4
|
-
import tmp from "tmp"
|
5
|
-
import crypto from "crypto";
|
6
|
-
import { urlEventos } from "./eventos.js"
|
7
|
-
import fs from "fs"
|
8
|
-
import path from 'path';
|
9
|
-
import { fileURLToPath } from 'url';
|
10
|
-
import pem from 'pem';
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
const __filename = fileURLToPath(import.meta.url);
|
15
|
-
const __dirname = path.dirname(__filename);
|
16
|
-
|
17
|
-
class Tools {
|
18
|
-
#cert: {
|
19
|
-
pfx: string;
|
20
|
-
senha: string;
|
21
|
-
};
|
22
|
-
#xmlTools: {
|
23
|
-
XMLBuilder: XMLBuilder;
|
24
|
-
XMLParser: XMLParser;
|
25
|
-
} = {
|
26
|
-
XMLBuilder: {} as XMLBuilder,
|
27
|
-
XMLParser: {} as XMLParser
|
28
|
-
};
|
29
|
-
#pem: {
|
30
|
-
key: string;
|
31
|
-
cert: string;
|
32
|
-
ca: string[]; // <- define que pode ser uma lista de strings
|
33
|
-
} = {
|
34
|
-
key: "", // A chave privada extraída do PKCS#12, em formato PEM
|
35
|
-
cert: "", // O certificado extraído, em formato PEM
|
36
|
-
ca: [] // Uma lista de certificados da cadeia (se houver), ou null
|
37
|
-
};
|
38
|
-
#config: {
|
39
|
-
mod: string;
|
40
|
-
xmllint: string;
|
41
|
-
UF: string;
|
42
|
-
tpAmb: number;
|
43
|
-
CSC: string;
|
44
|
-
CSCid: string;
|
45
|
-
versao: string;
|
46
|
-
timeout: number;
|
47
|
-
};
|
48
|
-
|
49
|
-
constructor(config = { mod: "", xmllint: 'xmllint', UF: '', tpAmb: 2, CSC: "", CSCid: "", versao: "4.00", timeout: 30 }, certificado = { pfx: "", senha: "" }) {
|
50
|
-
if (typeof config != "object") throw "Tools({config},{}): Config deve ser um objecto!";
|
51
|
-
if (typeof config.UF == "undefined") throw "Tools({...,UF:?},{}): UF não definida!";
|
52
|
-
if (typeof config.tpAmb == "undefined") throw "Tools({...,tpAmb:?},{}): tpAmb não definida!";
|
53
|
-
if (typeof config.versao == "undefined") throw "Tools({...,versao:?},{}): versao não definida!";
|
54
|
-
|
55
|
-
if (typeof config.timeout == "undefined") config.timeout = 30;
|
56
|
-
if (typeof config.xmllint == "undefined") config.xmllint = 'xmllint';
|
57
|
-
|
58
|
-
//Configurar certificado
|
59
|
-
this.#config = config;
|
60
|
-
this.#cert = certificado;
|
61
|
-
this.#xmlTools.XMLBuilder = new XMLBuilder({
|
62
|
-
ignoreAttributes: false,
|
63
|
-
attributeNamePrefix: "@",
|
64
|
-
});
|
65
|
-
this.#xmlTools.XMLParser = new XMLParser({
|
66
|
-
ignoreAttributes: false,
|
67
|
-
attributeNamePrefix: "@",
|
68
|
-
parseTagValue: false, // Evita conversão automática de valores
|
69
|
-
});
|
70
|
-
}
|
71
|
-
|
72
|
-
sefazEnviaLote(xml: string, data: any = { idLote: 1, indSinc: 0, compactar: false }): Promise<string> {
|
73
|
-
return new Promise(async (resvol, reject) => {
|
74
|
-
if (typeof data.idLote == "undefined") data.idLote = 1;
|
75
|
-
if (typeof data.indSinc == "undefined") data.indSinc = 0;
|
76
|
-
if (typeof data.compactar == "undefined") data.compactar = false;
|
77
|
-
|
78
|
-
await this.#certTools();
|
79
|
-
let jsonXmlLote = {
|
80
|
-
"soap:Envelope": {
|
81
|
-
"@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
82
|
-
"@xmlns:xsd": "http://www.w3.org/2001/XMLSchema",
|
83
|
-
"@xmlns:soap": "http://www.w3.org/2003/05/soap-envelope",
|
84
|
-
"soap:Body": {
|
85
|
-
"nfeDadosMsg": {
|
86
|
-
"@xmlns": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeAutorizacao4",
|
87
|
-
"enviNFe": {
|
88
|
-
...{
|
89
|
-
"@xmlns": "http://www.portalfiscal.inf.br/nfe",
|
90
|
-
"@versao": "4.00",
|
91
|
-
"idLote": data.idLote,
|
92
|
-
"indSinc": data.indSinc,
|
93
|
-
},
|
94
|
-
...(await this.xml2json(xml))
|
95
|
-
}
|
96
|
-
}
|
97
|
-
}
|
98
|
-
},
|
99
|
-
}
|
100
|
-
let xmlLote = await this.json2xml(jsonXmlLote);
|
101
|
-
try {
|
102
|
-
let tempUF = urlEventos(this.#config.UF, this.#config.versao);
|
103
|
-
const req = https.request(tempUF[`mod${this.#config.mod}`][(this.#config.tpAmb == 1 ? "producao" : "homologacao")].NFeAutorizacao, {
|
104
|
-
...{
|
105
|
-
method: 'POST',
|
106
|
-
headers: {
|
107
|
-
'Content-Type': 'application/soap+xml; charset=utf-8',
|
108
|
-
'Content-Length': xmlLote.length,
|
109
|
-
},
|
110
|
-
rejectUnauthorized: false
|
111
|
-
},
|
112
|
-
...this.#pem
|
113
|
-
}, (res) => {
|
114
|
-
let data = '';
|
115
|
-
|
116
|
-
res.on('data', (chunk) => {
|
117
|
-
data += chunk;
|
118
|
-
});
|
119
|
-
|
120
|
-
res.on('end', () => {
|
121
|
-
resvol(data);
|
122
|
-
});
|
123
|
-
});
|
124
|
-
|
125
|
-
req.setTimeout(this.#config.timeout * 1000, () => {
|
126
|
-
reject({
|
127
|
-
name: 'TimeoutError',
|
128
|
-
message: 'The operation was aborted due to timeout'
|
129
|
-
});
|
130
|
-
req.destroy(); // cancela a requisição
|
131
|
-
});
|
132
|
-
req.on('error', (erro) => {
|
133
|
-
reject(erro);
|
134
|
-
});
|
135
|
-
req.write(xmlLote);
|
136
|
-
req.end();
|
137
|
-
} catch (erro) {
|
138
|
-
reject(erro);
|
139
|
-
}
|
140
|
-
})
|
141
|
-
}
|
142
|
-
|
143
|
-
async xmlSign(xmlJSON: string, data: any = { tag: "infNFe" }): Promise<string> {
|
144
|
-
return new Promise(async (resvol, reject) => {
|
145
|
-
if (data.tag === undefined) data.tag = "infNFe";
|
146
|
-
|
147
|
-
//Obter a tag que ira ser assinada
|
148
|
-
let xml = await this.xml2json(xmlJSON) as any;
|
149
|
-
let tempPem = await this.#certTools() as any;
|
150
|
-
|
151
|
-
const sign = crypto.createSign('RSA-SHA1'); // Correção: Alterado para RSA-SHA1
|
152
|
-
let signedInfo = {
|
153
|
-
"SignedInfo": {
|
154
|
-
"@xmlns": "http://www.w3.org/2000/09/xmldsig#",
|
155
|
-
"CanonicalizationMethod": {
|
156
|
-
"@Algorithm": "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
|
157
|
-
},
|
158
|
-
"SignatureMethod": {
|
159
|
-
"@Algorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1" // Mantém SHA1
|
160
|
-
},
|
161
|
-
"Reference": {
|
162
|
-
"@URI": `#${xml.NFe.infNFe['@Id']}`,
|
163
|
-
"Transforms": {
|
164
|
-
"Transform": [
|
165
|
-
{
|
166
|
-
"@Algorithm": "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
|
167
|
-
},
|
168
|
-
{
|
169
|
-
"@Algorithm": "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"
|
170
|
-
},
|
171
|
-
]
|
172
|
-
},
|
173
|
-
"DigestMethod": {
|
174
|
-
"@Algorithm": "http://www.w3.org/2000/09/xmldsig#sha1" // Mantém SHA1
|
175
|
-
},
|
176
|
-
"DigestValue": crypto.createHash('sha1').update(await this.json2xml({ infNFe: xml.NFe.infNFe }), 'utf8') // Mantém Hash SHA1
|
177
|
-
.digest('base64')
|
178
|
-
|
179
|
-
}
|
180
|
-
}
|
181
|
-
}
|
182
|
-
sign.update(await this.json2xml(signedInfo), 'utf8');
|
183
|
-
xml.NFe.Signature = {
|
184
|
-
...signedInfo,
|
185
|
-
...{
|
186
|
-
"SignatureValue": sign.sign(tempPem.key, 'base64'),
|
187
|
-
"KeyInfo": {
|
188
|
-
"X509Data": {
|
189
|
-
"X509Certificate": tempPem.cert.replace(/-----BEGIN CERTIFICATE-----/g, '').replace(/-----END CERTIFICATE-----/g, '').replace(/\r\n/g, '')
|
190
|
-
}
|
191
|
-
},
|
192
|
-
"@xmlns": "http://www.w3.org/2000/09/xmldsig#"
|
193
|
-
}
|
194
|
-
}
|
195
|
-
|
196
|
-
|
197
|
-
if (xml.NFe.infNFe.ide.mod == 65) {
|
198
|
-
xml.NFe.infNFeSupl.qrCode = this.#gerarQRCodeNFCe(xml.NFe, "2", this.#config.CSCid, this.#config.CSC)
|
199
|
-
}
|
200
|
-
|
201
|
-
this.json2xml(xml).then(async res => {
|
202
|
-
this.#xmlValido(res, `nfe_v${this.#config.versao}`);
|
203
|
-
resvol(res);
|
204
|
-
}).catch(err => {
|
205
|
-
reject(err)
|
206
|
-
})
|
207
|
-
})
|
208
|
-
}
|
209
|
-
|
210
|
-
//Gerar QRCode da NFCe
|
211
|
-
#gerarQRCodeNFCe(NFe: any, versaoQRCode: string = "2", idCSC: string, CSC: string): string {
|
212
|
-
let s = '|',
|
213
|
-
concat,
|
214
|
-
hash;
|
215
|
-
if (NFe.infNFe.ide.tpEmis == 1) {
|
216
|
-
concat = [NFe.infNFe['@Id'].replace("NFe", ""), versaoQRCode, NFe.infNFe.ide.tpAmb, Number(idCSC)].join(s);
|
217
|
-
} else {
|
218
|
-
let hexDigestValue = Buffer.from(NFe.Signature.SignedInfo.Reference.DigestValue).toString('hex');
|
219
|
-
concat = [NFe.infNFe['@Id'].replace("NFe", ""), versaoQRCode, NFe.infNFe.ide.tpAmb, NFe.infNFe.ide.dhEmi, NFe.infNFe.total.ICMSTot.vNF, hexDigestValue, Number(idCSC)].join(s);
|
220
|
-
}
|
221
|
-
hash = crypto.createHash('sha1').update(concat + CSC).digest('hex');
|
222
|
-
return NFe.infNFeSupl.qrCode + '?p=' + concat + s + hash;
|
223
|
-
}
|
224
|
-
|
225
|
-
async xml2json(xml: string): Promise<object> {
|
226
|
-
return new Promise((resvol, reject) => {
|
227
|
-
resvol(this.#xmlTools.XMLParser.parse(xml))
|
228
|
-
})
|
229
|
-
}
|
230
|
-
|
231
|
-
async json2xml(obj: object): Promise<string> {
|
232
|
-
return new Promise((resvol, reject) => {
|
233
|
-
resvol(this.#xmlTools.XMLBuilder.build(obj))
|
234
|
-
})
|
235
|
-
}
|
236
|
-
|
237
|
-
//Obter certificado
|
238
|
-
async getCertificado(): Promise<object> {
|
239
|
-
return new Promise(async (resvol, reject) => {
|
240
|
-
this.#certTools().then(resvol).catch(reject)
|
241
|
-
})
|
242
|
-
}
|
243
|
-
|
244
|
-
//Consulta NFe
|
245
|
-
consultarNFe(chNFe: string): Promise<string> {
|
246
|
-
return new Promise(async (resolve, reject) => {
|
247
|
-
if (!chNFe || chNFe.length !== 44) {
|
248
|
-
return reject("consultarNFe(chNFe) -> chave inválida!");
|
249
|
-
}
|
250
|
-
|
251
|
-
if (typeof this.#config.UF === "undefined") throw "consultarNFe({...UF}) -> não definido!";
|
252
|
-
if (typeof this.#config.tpAmb === "undefined") throw "consultarNFe({...tpAmb}) -> não definido!";
|
253
|
-
if (typeof this.#config.mod === "undefined") throw "consultarNFe({...mod}) -> não definido!";
|
254
|
-
|
255
|
-
let consSitNFe = {
|
256
|
-
"@xmlns": "http://www.portalfiscal.inf.br/nfe",
|
257
|
-
"@versao": "4.00",
|
258
|
-
"tpAmb": this.#config.tpAmb,
|
259
|
-
"xServ": "CONSULTAR",
|
260
|
-
"chNFe": chNFe
|
261
|
-
};
|
262
|
-
|
263
|
-
let xmlObj = {
|
264
|
-
"soap:Envelope": {
|
265
|
-
"@xmlns:soap": "http://www.w3.org/2003/05/soap-envelope",
|
266
|
-
"@xmlns:nfe": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeConsultaProtocolo4",
|
267
|
-
"soap:Body": {
|
268
|
-
"nfe:nfeDadosMsg": {
|
269
|
-
"consSitNFe": consSitNFe
|
270
|
-
}
|
271
|
-
}
|
272
|
-
}
|
273
|
-
};
|
274
|
-
|
275
|
-
try {
|
276
|
-
const builder = new XMLBuilder({
|
277
|
-
ignoreAttributes: false,
|
278
|
-
attributeNamePrefix: "@"
|
279
|
-
});
|
280
|
-
|
281
|
-
// Validação do XML interno (opcional)
|
282
|
-
this.#xmlValido(builder.build({ consSitNFe }), `consSitNFe_v${this.#config.versao}`);
|
283
|
-
|
284
|
-
const xml = builder.build(xmlObj);
|
285
|
-
|
286
|
-
let tempUF = urlEventos(this.#config.UF, this.#config.versao);
|
287
|
-
|
288
|
-
const url = tempUF[`mod${this.#config.mod}`][(this.#config.tpAmb == 1 ? "producao" : "homologacao")].NFeConsultaProtocolo;
|
289
|
-
|
290
|
-
const req = https.request(url, {
|
291
|
-
method: 'POST',
|
292
|
-
headers: {
|
293
|
-
'Content-Type': 'application/soap+xml; charset=utf-8',
|
294
|
-
'Content-Length': xml.length,
|
295
|
-
},
|
296
|
-
rejectUnauthorized: false,
|
297
|
-
...await this.#certTools()
|
298
|
-
}, (res) => {
|
299
|
-
let data = '';
|
300
|
-
res.on('data', (chunk) => data += chunk);
|
301
|
-
res.on('end', () => resolve(data));
|
302
|
-
});
|
303
|
-
|
304
|
-
req.setTimeout(this.#config.timeout * 1000, () => {
|
305
|
-
reject({
|
306
|
-
name: 'TimeoutError',
|
307
|
-
message: 'The operation was aborted due to timeout'
|
308
|
-
});
|
309
|
-
req.destroy(); // cancela a requisição
|
310
|
-
});
|
311
|
-
req.on('error', (err) => reject(err));
|
312
|
-
req.write(xml);
|
313
|
-
req.end();
|
314
|
-
} catch (err) {
|
315
|
-
reject(err);
|
316
|
-
}
|
317
|
-
});
|
318
|
-
}
|
319
|
-
|
320
|
-
//Consulta status sefaz
|
321
|
-
async sefazStatus(): Promise<string> {
|
322
|
-
return new Promise(async (resvol, reject) => {
|
323
|
-
|
324
|
-
if (typeof this.#config.UF == "undefined") throw "sefazStatus({...UF}) -> não definido!";
|
325
|
-
if (typeof this.#config.tpAmb == "undefined") throw "sefazStatus({...tpAmb}) -> não definido!";
|
326
|
-
if (typeof this.#config.mod == "undefined") throw "sefazStatus({...mod}) -> não definido!";
|
327
|
-
|
328
|
-
let tempUF = urlEventos(this.#config.UF, this.#config.versao);
|
329
|
-
|
330
|
-
//Separado para validar o corpo da consulta
|
331
|
-
let consStatServ = {
|
332
|
-
"@versao": "4.00",
|
333
|
-
"@xmlns": "http://www.portalfiscal.inf.br/nfe",
|
334
|
-
"tpAmb": this.#config.tpAmb,
|
335
|
-
"cUF": tempUF.cUF,
|
336
|
-
"xServ": "STATUS"
|
337
|
-
}
|
338
|
-
|
339
|
-
let xmlObj = {
|
340
|
-
"soap:Envelope": {
|
341
|
-
"@xmlns:soap": "http://www.w3.org/2003/05/soap-envelope",
|
342
|
-
"@xmlns:nfe": "http://www.portalfiscal.inf.br/nfe/wsdl/NFeStatusServico4",
|
343
|
-
"soap:Body": {
|
344
|
-
"nfe:nfeDadosMsg": {
|
345
|
-
consStatServ
|
346
|
-
}
|
347
|
-
}
|
348
|
-
}
|
349
|
-
}
|
350
|
-
|
351
|
-
try {
|
352
|
-
let tempBuild = new XMLBuilder({
|
353
|
-
ignoreAttributes: false,
|
354
|
-
attributeNamePrefix: "@"
|
355
|
-
});
|
356
|
-
|
357
|
-
//Validação
|
358
|
-
this.#xmlValido(tempBuild.build({ consStatServ }), `consStatServ_v${this.#config.versao}`);
|
359
|
-
let tempUF = urlEventos(this.#config.UF, this.#config.versao);
|
360
|
-
let xml = tempBuild.build(xmlObj);
|
361
|
-
const req = https.request(tempUF[`mod${this.#config.mod}`][(this.#config.tpAmb == 1 ? "producao" : "homologacao")].NFeStatusServico, {
|
362
|
-
...{
|
363
|
-
method: 'POST',
|
364
|
-
headers: {
|
365
|
-
'Content-Type': 'application/soap+xml; charset=utf-8',
|
366
|
-
'Content-Length': xml.length,
|
367
|
-
},
|
368
|
-
rejectUnauthorized: false
|
369
|
-
},
|
370
|
-
...await this.#certTools()
|
371
|
-
}, (res) => {
|
372
|
-
let data = '';
|
373
|
-
|
374
|
-
res.on('data', (chunk) => {
|
375
|
-
data += chunk;
|
376
|
-
});
|
377
|
-
|
378
|
-
res.on('end', () => {
|
379
|
-
resvol(data);
|
380
|
-
});
|
381
|
-
});
|
382
|
-
|
383
|
-
req.setTimeout(this.#config.timeout * 1000, () => {
|
384
|
-
reject({
|
385
|
-
name: 'TimeoutError',
|
386
|
-
message: 'The operation was aborted due to timeout'
|
387
|
-
});
|
388
|
-
req.destroy(); // cancela a requisição
|
389
|
-
});
|
390
|
-
req.on('error', (erro) => {
|
391
|
-
reject(erro);
|
392
|
-
});
|
393
|
-
|
394
|
-
req.write(xml);
|
395
|
-
req.end();
|
396
|
-
} catch (erro) {
|
397
|
-
reject(erro);
|
398
|
-
}
|
399
|
-
})
|
400
|
-
}
|
401
|
-
|
402
|
-
|
403
|
-
//Validar XML da NFe, somente apos assinar
|
404
|
-
async #xmlValido(xml: string, xsd: string) {
|
405
|
-
const xmlFile = tmp.fileSync({ mode: 0o644, prefix: 'xml-', postfix: '.xml' });
|
406
|
-
fs.writeFileSync(xmlFile.name, xml, { encoding: 'utf8' });
|
407
|
-
const schemaPath = path.resolve(__dirname, `../../schemas/${xsd}.xsd`);
|
408
|
-
const verif: SpawnSyncReturns<string> = spawnSync(
|
409
|
-
this.#config.xmllint,
|
410
|
-
['--noout', '--schema', schemaPath, xmlFile.name],
|
411
|
-
{ encoding: 'utf8' }
|
412
|
-
);
|
413
|
-
|
414
|
-
xmlFile.removeCallback();
|
415
|
-
|
416
|
-
// Aqui, usamos o operador de encadeamento opcional (?.)
|
417
|
-
if (verif.error) {
|
418
|
-
throw new Error("Biblioteca xmllint não encontrada!");
|
419
|
-
} else if (!verif.stderr.includes(".xml validates")) {
|
420
|
-
throw new Error(verif.stderr);
|
421
|
-
}
|
422
|
-
return 1;
|
423
|
-
}
|
424
|
-
|
425
|
-
#certTools(): Promise<object> {
|
426
|
-
return new Promise(async (resvol, reject) => {
|
427
|
-
if (this.#pem.key != "") resvol(this.#pem);
|
428
|
-
pem.readPkcs12(this.#cert.pfx, { p12Password: this.#cert.senha }, (err, myPem) => {
|
429
|
-
if (err) return reject(err); // <-- importante!
|
430
|
-
this.#pem = myPem;
|
431
|
-
resvol(this.#pem);
|
432
|
-
});
|
433
|
-
})
|
434
|
-
}
|
435
|
-
}
|
436
|
-
export { Tools }
|