@useinvio/nfse-sdk 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/JSON_MAPPING.md +225 -0
- package/LICENSE +21 -0
- package/README.md +512 -0
- package/dist/client.d.ts +39 -0
- package/dist/client.js +77 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +13 -0
- package/dist/config.js.map +1 -0
- package/dist/dpsJson.d.ts +144 -0
- package/dist/dpsJson.js +252 -0
- package/dist/dpsJson.js.map +1 -0
- package/dist/emissaoNota.d.ts +31 -0
- package/dist/emissaoNota.js +65 -0
- package/dist/emissaoNota.js.map +1 -0
- package/dist/fiscalValidation.d.ts +6 -0
- package/dist/fiscalValidation.js +218 -0
- package/dist/fiscalValidation.js.map +1 -0
- package/dist/gzipB64.d.ts +4 -0
- package/dist/gzipB64.js +10 -0
- package/dist/gzipB64.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/loadPfx.d.ts +18 -0
- package/dist/loadPfx.js +41 -0
- package/dist/loadPfx.js.map +1 -0
- package/dist/sefinClient.d.ts +20 -0
- package/dist/sefinClient.js +117 -0
- package/dist/sefinClient.js.map +1 -0
- package/dist/signXml.d.ts +13 -0
- package/dist/signXml.js +52 -0
- package/dist/signXml.js.map +1 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
# @useinvio/nfse-sdk
|
|
2
|
+
|
|
3
|
+
SDK TypeScript para integração com a **NFS-e Nacional** (SEFIN).
|
|
4
|
+
|
|
5
|
+
Cuida de todo o protocolo de comunicação: certificado A1, assinatura XML (XMLDSIG), compactação GZip/Base64, mTLS, envio e consulta. A aplicação que consome o SDK continua responsável pelas regras de negócio — código de serviço, CST, retenções, numeração da DPS.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Índice
|
|
10
|
+
|
|
11
|
+
- [Instalação](#instalação)
|
|
12
|
+
- [Quick start](#quick-start)
|
|
13
|
+
- [Escolhendo o ponto de entrada](#escolhendo-o-ponto-de-entrada)
|
|
14
|
+
- [Cliente orientado a recursos](#cliente-orientado-a-recursos)
|
|
15
|
+
- [Certificado A1](#certificado-a1)
|
|
16
|
+
- [Emitir a partir de XML pronto](#emitir-a-partir-de-xml-pronto)
|
|
17
|
+
- [Emitir a partir de JSON declarativo](#emitir-a-partir-de-json-declarativo)
|
|
18
|
+
- [Inspecionar o XML antes de enviar](#inspecionar-o-xml-antes-de-enviar)
|
|
19
|
+
- [Tratar rejeições da SEFIN](#tratar-rejeições-da-sefin)
|
|
20
|
+
- [Consultar uma NFS-e emitida](#consultar-uma-nfs-e-emitida)
|
|
21
|
+
- [Enviar evento](#enviar-evento-ex-cancelamento)
|
|
22
|
+
- [Série e número da DPS](#série-e-número-da-dps)
|
|
23
|
+
- [Ambientes](#ambientes)
|
|
24
|
+
- [Referência da API](#referência-da-api)
|
|
25
|
+
- [Desenvolvimento](#desenvolvimento)
|
|
26
|
+
- [Publicação no npm](#publicação-no-npm)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Instalação
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @useinvio/nfse-sdk
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
> Node.js ≥ 20 é obrigatório.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
O caminho mais comum: emitir uma nota a partir de JSON declarativo.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { emitirNfse, loadPfx, EmitirNotaError } from '@useinvio/nfse-sdk';
|
|
46
|
+
|
|
47
|
+
const pfx = loadPfx('./certificado.pfx', process.env.PFX_PASSWORD!);
|
|
48
|
+
|
|
49
|
+
const nota = {
|
|
50
|
+
ambiente: 'restrita' as const,
|
|
51
|
+
prestador: {
|
|
52
|
+
cnpj: '12345678000195',
|
|
53
|
+
tpInsc: '2',
|
|
54
|
+
cLocEmi: '4106902',
|
|
55
|
+
serie: '1601',
|
|
56
|
+
opSimpNac: '1',
|
|
57
|
+
regEspTrib: '0',
|
|
58
|
+
},
|
|
59
|
+
servico: {
|
|
60
|
+
cTribNac: '010201',
|
|
61
|
+
xDescServ: 'Desenvolvimento de software',
|
|
62
|
+
cLocPrestacao: '4106902',
|
|
63
|
+
cNBS: '115022000',
|
|
64
|
+
},
|
|
65
|
+
emissao: {
|
|
66
|
+
nDPS: '1',
|
|
67
|
+
dhEmi: '2026-06-15T10:30:00-03:00',
|
|
68
|
+
dCompet: '2026-06-01',
|
|
69
|
+
valores: { vServ: '1500.00' },
|
|
70
|
+
tributacaoMunicipal: {
|
|
71
|
+
tribISSQN: '3',
|
|
72
|
+
cPaisResult: 'BR',
|
|
73
|
+
tpRetISSQN: '1',
|
|
74
|
+
},
|
|
75
|
+
tributacaoFederal: {
|
|
76
|
+
piscofins: { CST: '07' },
|
|
77
|
+
},
|
|
78
|
+
totTrib: {
|
|
79
|
+
pTotTribFed: '0.00',
|
|
80
|
+
pTotTribEst: '0.00',
|
|
81
|
+
pTotTribMun: '5.00',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const resultado = await emitirNfse(nota, pfx);
|
|
88
|
+
console.log(resultado.chaveAcesso); // chave de 50 dígitos
|
|
89
|
+
console.log(resultado.nfseXml); // XML autorizado pela SEFIN
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error instanceof EmitirNotaError) {
|
|
92
|
+
for (const rejeicao of error.erros) {
|
|
93
|
+
console.error(rejeicao.Codigo, rejeicao.Descricao);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Escolhendo o ponto de entrada
|
|
102
|
+
|
|
103
|
+
| Situação | O que usar |
|
|
104
|
+
|---|---|
|
|
105
|
+
| Quer uma API de cliente com `client.invoices.create(...)` | `new NfseClient(...)` |
|
|
106
|
+
| Outro sistema já gera o XML da DPS | `emitirNfse(xmlString, pfx)` |
|
|
107
|
+
| Você monta os dados e quer que a SDK construa o XML | `emitirNfse(notaJson, pfx)` |
|
|
108
|
+
| Precisa inspecionar ou salvar o XML antes de enviar | `buildDpsFromJson(nota)` |
|
|
109
|
+
| Fluxo customizado (assinar, compactar ou enviar separadamente) | funções de baixo nível |
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Cliente orientado a recursos
|
|
114
|
+
|
|
115
|
+
Use `NfseClient` quando preferir configurar ambiente e certificado uma vez e
|
|
116
|
+
emitir notas por recursos, como `client.invoices.create(...)`.
|
|
117
|
+
|
|
118
|
+
O cliente usa os mesmos campos do JSON declarativo (`prestador`, `servico` e
|
|
119
|
+
`emissao`). Os `defaults` seguem o mesmo formato e podem ser sobrescritos em
|
|
120
|
+
cada chamada.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { NfseClient } from '@useinvio/nfse-sdk';
|
|
124
|
+
|
|
125
|
+
const client = new NfseClient({
|
|
126
|
+
environment: 'sandbox',
|
|
127
|
+
certificate: {
|
|
128
|
+
content: process.env.NFSE_CERTIFICATE!, // PFX em base64
|
|
129
|
+
password: process.env.NFSE_CERTIFICATE_PASSWORD!,
|
|
130
|
+
},
|
|
131
|
+
defaults: {
|
|
132
|
+
prestador: {
|
|
133
|
+
cnpj: '12345678000195',
|
|
134
|
+
tpInsc: '2',
|
|
135
|
+
cLocEmi: '4106902',
|
|
136
|
+
serie: '1601',
|
|
137
|
+
opSimpNac: '1',
|
|
138
|
+
regEspTrib: '0',
|
|
139
|
+
},
|
|
140
|
+
servico: {
|
|
141
|
+
cTribNac: '010201',
|
|
142
|
+
xDescServ: 'Servico ficticio de desenvolvimento de software',
|
|
143
|
+
cLocPrestacao: '4106902',
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const invoice = await client.invoices.create({
|
|
149
|
+
emissao: {
|
|
150
|
+
nDPS: '1',
|
|
151
|
+
valores: { vServ: '1000.00' },
|
|
152
|
+
tomador: {
|
|
153
|
+
CPF: '00000000000',
|
|
154
|
+
xNome: 'Cliente Exemplo',
|
|
155
|
+
},
|
|
156
|
+
tributacaoMunicipal: {
|
|
157
|
+
tribISSQN: '3',
|
|
158
|
+
tpRetISSQN: '1',
|
|
159
|
+
},
|
|
160
|
+
tributacaoFederal: {
|
|
161
|
+
piscofins: { CST: '07' },
|
|
162
|
+
},
|
|
163
|
+
totTrib: {
|
|
164
|
+
pTotTribFed: '0.00',
|
|
165
|
+
pTotTribEst: '0.00',
|
|
166
|
+
pTotTribMun: '5.00',
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
console.log(invoice.chaveAcesso);
|
|
172
|
+
console.log(invoice.nfseXml);
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`environment: 'sandbox'` é alias de `restrita`, e `production` é alias de
|
|
176
|
+
`producao`. O campo `certificate.content` aceita o conteúdo binário do PFX em
|
|
177
|
+
`Buffer` ou o PFX codificado em base64, formato comum para variáveis de
|
|
178
|
+
ambiente.
|
|
179
|
+
|
|
180
|
+
Para inspecionar a DPS gerada sem assinar nem enviar:
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
const dps = client.invoices.buildDpsJson({
|
|
184
|
+
prestador: {
|
|
185
|
+
serie: '1701',
|
|
186
|
+
},
|
|
187
|
+
emissao: {
|
|
188
|
+
nDPS: '2',
|
|
189
|
+
valores: { vServ: '2500.00' },
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Certificado A1
|
|
197
|
+
|
|
198
|
+
### A partir de arquivo
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
import { loadPfx } from '@useinvio/nfse-sdk';
|
|
202
|
+
|
|
203
|
+
const pfx = loadPfx('./certificado.pfx', process.env.PFX_PASSWORD!);
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### A partir de buffer (ex.: certificado descriptografado do banco)
|
|
207
|
+
|
|
208
|
+
```ts
|
|
209
|
+
import { loadPfxFromBuffer } from '@useinvio/nfse-sdk';
|
|
210
|
+
|
|
211
|
+
const pfx = loadPfxFromBuffer(pfxBuffer, password);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
O objeto `pfx` retornado contém chave privada e certificado em PEM, pronto para assinar e fazer mTLS.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Emitir a partir de XML pronto
|
|
219
|
+
|
|
220
|
+
Use quando outro sistema (ERP, sistema legado) já gera o XML da DPS no layout nacional.
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import { emitirNfse, loadPfx } from '@useinvio/nfse-sdk';
|
|
224
|
+
|
|
225
|
+
const pfx = loadPfx('./certificado.pfx', process.env.PFX_PASSWORD!);
|
|
226
|
+
|
|
227
|
+
const resultado = await emitirNfse(dpsXml, pfx, { ambiente: 'restrita' });
|
|
228
|
+
|
|
229
|
+
console.log(resultado.chaveAcesso);
|
|
230
|
+
console.log(resultado.nfseXml);
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
> O XML precisa ter o atributo `Id` em `<infDPS>`. Se não tiver, passe `options.dpsId` manualmente.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
await emitirNfse(dpsXml, pfx, { dpsId: 'DPS0001', ambiente: 'restrita' });
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Emitir a partir de JSON declarativo
|
|
242
|
+
|
|
243
|
+
Use quando a aplicação mantém os perfis de prestador e serviço e quer que a SDK monte o XML.
|
|
244
|
+
|
|
245
|
+
Para a referência completa de campos aceitos no JSON, consulte [JSON_MAPPING.md](./JSON_MAPPING.md).
|
|
246
|
+
O builder valida formatos e combinações mínimas antes de gerar XML, mas não escolhe códigos fiscais por você. Campos como `tributacaoMunicipal` e `totTrib` precisam ser informados explicitamente; a SDK não assume tributação nem carga tributária por default.
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
import { emitirNfse, loadPfx, type DpsJsonRequest } from '@useinvio/nfse-sdk';
|
|
250
|
+
|
|
251
|
+
const nota: DpsJsonRequest = {
|
|
252
|
+
ambiente: 'restrita',
|
|
253
|
+
prestador: {
|
|
254
|
+
cnpj: '12345678000195',
|
|
255
|
+
tpInsc: '2',
|
|
256
|
+
cLocEmi: '4106902',
|
|
257
|
+
serie: '1601',
|
|
258
|
+
opSimpNac: '1',
|
|
259
|
+
regEspTrib: '0',
|
|
260
|
+
},
|
|
261
|
+
servico: {
|
|
262
|
+
cTribNac: '010201',
|
|
263
|
+
xDescServ: 'Desenvolvimento de software',
|
|
264
|
+
cLocPrestacao: '4106902',
|
|
265
|
+
cNBS: '115022000',
|
|
266
|
+
},
|
|
267
|
+
emissao: {
|
|
268
|
+
nDPS: '1',
|
|
269
|
+
dhEmi: '2026-06-15T10:30:00-03:00',
|
|
270
|
+
dCompet: '2026-06-01',
|
|
271
|
+
valores: { vServ: '1500.00' },
|
|
272
|
+
tributacaoMunicipal: {
|
|
273
|
+
tribISSQN: '3',
|
|
274
|
+
cPaisResult: 'BR',
|
|
275
|
+
tpRetISSQN: '1',
|
|
276
|
+
},
|
|
277
|
+
tributacaoFederal: {
|
|
278
|
+
piscofins: { CST: '07' },
|
|
279
|
+
},
|
|
280
|
+
totTrib: {
|
|
281
|
+
pTotTribFed: '0.00',
|
|
282
|
+
pTotTribEst: '0.00',
|
|
283
|
+
pTotTribMun: '5.00',
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const pfx = loadPfx('./certificado.pfx', process.env.PFX_PASSWORD!);
|
|
289
|
+
const resultado = await emitirNfse(nota, pfx);
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
> `pTotTribFed`, `pTotTribEst` e `pTotTribMun` são percentuais com duas casas decimais (ex.: `"5.00"`), diferentes das alíquotas com quatro casas (`pAliq`).
|
|
293
|
+
> O shape simplificado legado de `tribNac` é rejeitado: IBS/CBS no layout nacional `v1.01` exige o bloco RTC `IBSCBS`, ainda não implementado nesta SDK.
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Inspecionar o XML antes de enviar
|
|
298
|
+
|
|
299
|
+
Útil para comparar com um XML de referência, salvar snapshots ou depurar rejeições de schema.
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
import { buildDpsFromJson } from '@useinvio/nfse-sdk';
|
|
303
|
+
|
|
304
|
+
const { id, xml } = buildDpsFromJson(nota);
|
|
305
|
+
|
|
306
|
+
console.log(id); // ex.: "DPS..."
|
|
307
|
+
console.log(xml); // XML não assinado da DPS
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
## Tratar rejeições da SEFIN
|
|
313
|
+
|
|
314
|
+
`emitirNfse` lança `EmitirNotaError` quando a SEFIN retorna HTTP fora de 2xx ou resposta sem XML autorizado.
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
import { EmitirNotaError, emitirNfse } from '@useinvio/nfse-sdk';
|
|
318
|
+
|
|
319
|
+
try {
|
|
320
|
+
await emitirNfse(nota, pfx);
|
|
321
|
+
} catch (error) {
|
|
322
|
+
if (!(error instanceof EmitirNotaError)) throw error;
|
|
323
|
+
|
|
324
|
+
console.log('HTTP:', error.status);
|
|
325
|
+
console.log('DPS ID:', error.dpsId);
|
|
326
|
+
|
|
327
|
+
for (const rejeicao of error.erros) {
|
|
328
|
+
console.log(rejeicao.Codigo); // código oficial SEFIN
|
|
329
|
+
console.log(rejeicao.Descricao); // descrição da rejeição
|
|
330
|
+
console.log(rejeicao.Complemento); // detalhe adicional (quando presente)
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Consultar uma NFS-e emitida
|
|
338
|
+
|
|
339
|
+
```ts
|
|
340
|
+
import { consultarNfse } from '@useinvio/nfse-sdk';
|
|
341
|
+
|
|
342
|
+
const resposta = await consultarNfse(chaveAcesso, pfx, 'restrita');
|
|
343
|
+
|
|
344
|
+
if (resposta.status === 200) {
|
|
345
|
+
console.log(resposta.body);
|
|
346
|
+
} else {
|
|
347
|
+
console.error('Falha na consulta:', resposta.status, resposta.body);
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## Enviar evento (ex.: cancelamento)
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
import { enviarEvento, gzipBase64, signEnveloped } from '@useinvio/nfse-sdk';
|
|
357
|
+
|
|
358
|
+
const signedEventXml = signEnveloped(pedRegXml, pedRegId, 'infPedReg', pfx);
|
|
359
|
+
const pedRegXmlGZipB64 = gzipBase64(signedEventXml);
|
|
360
|
+
|
|
361
|
+
const resposta = await enviarEvento(pedRegXmlGZipB64, pfx, chaveAcesso, 'restrita');
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Série e número da DPS
|
|
367
|
+
|
|
368
|
+
A SEFIN identifica uma DPS pela combinação de prestador, município, `serie` e `nDPS`.
|
|
369
|
+
|
|
370
|
+
```ts
|
|
371
|
+
// série no perfil do prestador (padrão para todas as DPS)
|
|
372
|
+
prestador: {
|
|
373
|
+
serie: '1601',
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ou por emissão (sobrescreve prestador.serie apenas para esta DPS)
|
|
377
|
+
emissao: {
|
|
378
|
+
serie: '1601',
|
|
379
|
+
nDPS: '4',
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
O padrão usual é manter uma `serie` fixa e incrementar `nDPS` em cada nova DPS:
|
|
384
|
+
|
|
385
|
+
```
|
|
386
|
+
serie 1601, nDPS 1
|
|
387
|
+
serie 1601, nDPS 2
|
|
388
|
+
serie 1601, nDPS 3
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
> O Emissor Web usa séries como `70000`. Para emissão via API, use uma série válida para API (ex.: `1601`) e controle o `nDPS` na sua aplicação.
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
## Ambientes
|
|
396
|
+
|
|
397
|
+
| Chave | URL |
|
|
398
|
+
|---|---|
|
|
399
|
+
| `restrita` | `https://sefin.producaorestrita.nfse.gov.br/SefinNacional` |
|
|
400
|
+
| `producao` | `https://sefin.nfse.gov.br/SefinNacional` |
|
|
401
|
+
|
|
402
|
+
```ts
|
|
403
|
+
import { resolveSefinBaseUrl } from '@useinvio/nfse-sdk';
|
|
404
|
+
|
|
405
|
+
const baseUrl = resolveSefinBaseUrl('restrita');
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## Referência da API
|
|
411
|
+
|
|
412
|
+
### Funções principais
|
|
413
|
+
|
|
414
|
+
| Função | Descrição |
|
|
415
|
+
|---|---|
|
|
416
|
+
| `new NfseClient(options)` | Cria cliente com ambiente, certificado e defaults no mesmo formato do JSON declarativo. |
|
|
417
|
+
| `emitirNfse(input, pfx, options?)` | Emite uma NFS-e a partir de XML ou JSON. Retorna `ResultadoEmissaoNota`. |
|
|
418
|
+
| `consultarNfse(chaveAcesso, pfx, ambiente?)` | Consulta uma NFS-e pela chave de acesso. |
|
|
419
|
+
| `enviarEvento(xmlGzipB64, pfx, chaveAcesso, ambiente?)` | Envia evento fiscal (cancelamento, etc.). |
|
|
420
|
+
| `buildDpsFromJson(nota)` | Monta o XML da DPS sem assinar nem enviar. |
|
|
421
|
+
|
|
422
|
+
### Certificado
|
|
423
|
+
|
|
424
|
+
| Função | Descrição |
|
|
425
|
+
|---|---|
|
|
426
|
+
| `loadPfx(path, password)` | Carrega certificado A1 a partir de arquivo. |
|
|
427
|
+
| `loadPfxFromBuffer(buffer, password)` | Carrega certificado A1 a partir de buffer. |
|
|
428
|
+
|
|
429
|
+
### Funções de baixo nível
|
|
430
|
+
|
|
431
|
+
| Função | Descrição |
|
|
432
|
+
|---|---|
|
|
433
|
+
| `signDps(xml, dpsId, pfx)` | Assina XML de DPS com XMLDSIG. |
|
|
434
|
+
| `signEnveloped(xml, id, ref, pfx)` | Assina XML genérico (eventos). |
|
|
435
|
+
| `verifyDps(xml)` | Verifica assinatura de uma DPS. |
|
|
436
|
+
| `gzipBase64(xml)` | Compacta XML em GZip e codifica em Base64. |
|
|
437
|
+
| `gunzipBase64(b64)` | Descompacta resposta GZip/Base64. |
|
|
438
|
+
| `extrairErros(body)` | Normaliza erros oficiais de uma resposta da SEFIN. |
|
|
439
|
+
| `resolveSefinBaseUrl(ambiente)` | Retorna a URL base do ambiente. |
|
|
440
|
+
|
|
441
|
+
### Tipos exportados
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
import type {
|
|
445
|
+
Ambiente,
|
|
446
|
+
CreateInvoiceInput,
|
|
447
|
+
DpsJsonInput,
|
|
448
|
+
DpsJsonRequest,
|
|
449
|
+
EmitirNotaOptions,
|
|
450
|
+
NfseClientDpsDefaults,
|
|
451
|
+
NfseClientOptions,
|
|
452
|
+
NotaInput,
|
|
453
|
+
PfxMaterial,
|
|
454
|
+
PrestadorProfile,
|
|
455
|
+
ResultadoEmissaoNota,
|
|
456
|
+
SefinErro,
|
|
457
|
+
SefinResposta,
|
|
458
|
+
ServicoProfile,
|
|
459
|
+
} from '@useinvio/nfse-sdk';
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Constantes
|
|
463
|
+
|
|
464
|
+
```ts
|
|
465
|
+
import { SEFIN_BASE_URL, TP_AMB, DEFAULT_AMBIENTE } from '@useinvio/nfse-sdk';
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## Desenvolvimento
|
|
471
|
+
|
|
472
|
+
```bash
|
|
473
|
+
npm install
|
|
474
|
+
npm run typecheck # verifica tipos sem compilar
|
|
475
|
+
npm run build # compila para dist/
|
|
476
|
+
npm test # build + tipos + testes unitários
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
`npm test` também valida uma DPS gerada contra os XSD oficiais em `schemas/nfse/v1.01/Schemas/1.01`. Para compatibilidade com `xmllint`, o teste corrige em uma cópia temporária a âncora regex de `TSSerieDPS` presente no XSD oficial; os arquivos baixados do gov.br ficam preservados sem alteração.
|
|
480
|
+
|
|
481
|
+
### Uso local em outro projeto
|
|
482
|
+
|
|
483
|
+
```json
|
|
484
|
+
{
|
|
485
|
+
"dependencies": {
|
|
486
|
+
"@useinvio/nfse-sdk": "file:../nfse-sdk"
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## Publicação no npm
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
npm install
|
|
497
|
+
npm run typecheck
|
|
498
|
+
npm run build
|
|
499
|
+
npm pack --dry-run # verifica o que será publicado
|
|
500
|
+
npm publish
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
O pacote usa `publishConfig.access = public`, necessário para publicar um pacote escopado público no npm.
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## Princípios de design
|
|
508
|
+
|
|
509
|
+
- Sem dependência de banco de dados, config de aplicação ou estado de tenant.
|
|
510
|
+
- Funções pequenas, testáveis e reutilizáveis.
|
|
511
|
+
- Erros oficiais da SEFIN são preservados na íntegra, não engolidos.
|
|
512
|
+
- O SDK transporta declarações fiscais; não valida se estão contabilmente corretas.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Ambiente } from './config.js';
|
|
2
|
+
import type { DpsJsonInput, DpsJsonRequest, PrestadorProfile, ServicoProfile } from './dpsJson.js';
|
|
3
|
+
import { type ResultadoEmissaoNota } from './emissaoNota.js';
|
|
4
|
+
import { type PfxMaterial } from './loadPfx.js';
|
|
5
|
+
import { type SefinResposta } from './sefinClient.js';
|
|
6
|
+
export type NfseEnvironment = 'sandbox' | 'production' | Ambiente;
|
|
7
|
+
export interface NfseCertificateInput {
|
|
8
|
+
/** Conteudo bruto ou base64 do arquivo A1/PFX. */
|
|
9
|
+
content: string | Buffer;
|
|
10
|
+
password: string;
|
|
11
|
+
encoding?: 'base64' | 'binary' | 'utf8';
|
|
12
|
+
}
|
|
13
|
+
export interface NfseClientDpsDefaults {
|
|
14
|
+
ambiente?: Ambiente;
|
|
15
|
+
prestador?: Partial<PrestadorProfile>;
|
|
16
|
+
servico?: Partial<ServicoProfile>;
|
|
17
|
+
emissao?: Partial<DpsJsonInput>;
|
|
18
|
+
}
|
|
19
|
+
export type CreateInvoiceInput = NfseClientDpsDefaults;
|
|
20
|
+
export interface NfseClientOptions {
|
|
21
|
+
environment?: NfseEnvironment;
|
|
22
|
+
certificate: NfseCertificateInput | PfxMaterial;
|
|
23
|
+
defaults?: NfseClientDpsDefaults;
|
|
24
|
+
}
|
|
25
|
+
export interface InvoiceResource {
|
|
26
|
+
create(input: CreateInvoiceInput): Promise<ResultadoEmissaoNota>;
|
|
27
|
+
buildDpsJson(input: CreateInvoiceInput): DpsJsonRequest;
|
|
28
|
+
get(chaveAcesso: string): Promise<SefinResposta>;
|
|
29
|
+
}
|
|
30
|
+
export declare class NfseClient {
|
|
31
|
+
readonly invoices: InvoiceResource;
|
|
32
|
+
private readonly ambiente;
|
|
33
|
+
private readonly pfx;
|
|
34
|
+
private readonly defaults;
|
|
35
|
+
constructor(options: NfseClientOptions);
|
|
36
|
+
private buildInvoiceDpsJson;
|
|
37
|
+
private createInvoice;
|
|
38
|
+
private getInvoice;
|
|
39
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { DEFAULT_AMBIENTE } from './config.js';
|
|
2
|
+
import { emitirNfse } from './emissaoNota.js';
|
|
3
|
+
import { loadPfxFromBuffer } from './loadPfx.js';
|
|
4
|
+
import { consultarNfse } from './sefinClient.js';
|
|
5
|
+
function resolveEnvironment(environment) {
|
|
6
|
+
if (!environment || environment === 'sandbox')
|
|
7
|
+
return DEFAULT_AMBIENTE;
|
|
8
|
+
if (environment === 'production')
|
|
9
|
+
return 'producao';
|
|
10
|
+
return environment;
|
|
11
|
+
}
|
|
12
|
+
function isPfxMaterial(value) {
|
|
13
|
+
return 'privateKeyPem' in value && 'certPem' in value && 'pfxBuffer' in value;
|
|
14
|
+
}
|
|
15
|
+
function decodeCertificateContent(certificate) {
|
|
16
|
+
if (Buffer.isBuffer(certificate.content))
|
|
17
|
+
return certificate.content;
|
|
18
|
+
const content = certificate.content.trim();
|
|
19
|
+
const encoding = certificate.encoding ?? 'base64';
|
|
20
|
+
if (encoding === 'base64') {
|
|
21
|
+
const base64 = content.includes(',') ? content.slice(content.indexOf(',') + 1) : content;
|
|
22
|
+
return Buffer.from(base64.replace(/\s/g, ''), 'base64');
|
|
23
|
+
}
|
|
24
|
+
return Buffer.from(content, encoding);
|
|
25
|
+
}
|
|
26
|
+
function ensureObject(value, field) {
|
|
27
|
+
if (!value || Object.keys(value).length === 0) {
|
|
28
|
+
throw new Error(`Campo obrigatorio ausente para criar NFS-e: ${field}`);
|
|
29
|
+
}
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
export class NfseClient {
|
|
33
|
+
invoices;
|
|
34
|
+
ambiente;
|
|
35
|
+
pfx;
|
|
36
|
+
defaults;
|
|
37
|
+
constructor(options) {
|
|
38
|
+
this.ambiente = resolveEnvironment(options.environment);
|
|
39
|
+
this.pfx = isPfxMaterial(options.certificate)
|
|
40
|
+
? options.certificate
|
|
41
|
+
: loadPfxFromBuffer(decodeCertificateContent(options.certificate), options.certificate.password);
|
|
42
|
+
this.defaults = options.defaults ?? {};
|
|
43
|
+
this.invoices = {
|
|
44
|
+
create: (input) => this.createInvoice(input),
|
|
45
|
+
buildDpsJson: (input) => this.buildInvoiceDpsJson(input),
|
|
46
|
+
get: (chaveAcesso) => this.getInvoice(chaveAcesso),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
buildInvoiceDpsJson(input) {
|
|
50
|
+
const prestador = {
|
|
51
|
+
...this.defaults.prestador,
|
|
52
|
+
...input.prestador,
|
|
53
|
+
};
|
|
54
|
+
const servico = {
|
|
55
|
+
...this.defaults.servico,
|
|
56
|
+
...input.servico,
|
|
57
|
+
};
|
|
58
|
+
const emissao = {
|
|
59
|
+
...this.defaults.emissao,
|
|
60
|
+
...input.emissao,
|
|
61
|
+
};
|
|
62
|
+
return {
|
|
63
|
+
ambiente: input.ambiente ?? this.defaults.ambiente ?? this.ambiente,
|
|
64
|
+
prestador: ensureObject(prestador, 'prestador'),
|
|
65
|
+
servico: ensureObject(servico, 'servico'),
|
|
66
|
+
emissao: ensureObject(emissao, 'emissao'),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async createInvoice(input) {
|
|
70
|
+
const dpsJson = this.buildInvoiceDpsJson(input);
|
|
71
|
+
return emitirNfse(dpsJson, this.pfx, { ambiente: dpsJson.ambiente });
|
|
72
|
+
}
|
|
73
|
+
async getInvoice(chaveAcesso) {
|
|
74
|
+
return consultarNfse(chaveAcesso, this.pfx, this.ambiente);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../client.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type Ambiente = 'restrita' | 'producao';
|
|
2
|
+
export declare const SEFIN_BASE_URL: Record<Ambiente, string>;
|
|
3
|
+
export declare const TP_AMB: Record<Ambiente, '1' | '2'>;
|
|
4
|
+
export declare const DEFAULT_AMBIENTE: Ambiente;
|
|
5
|
+
export declare function resolveSefinBaseUrl(ambiente?: Ambiente): string;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const SEFIN_BASE_URL = {
|
|
2
|
+
producao: 'https://sefin.nfse.gov.br/SefinNacional',
|
|
3
|
+
restrita: 'https://sefin.producaorestrita.nfse.gov.br/SefinNacional',
|
|
4
|
+
};
|
|
5
|
+
export const TP_AMB = {
|
|
6
|
+
producao: '1',
|
|
7
|
+
restrita: '2',
|
|
8
|
+
};
|
|
9
|
+
export const DEFAULT_AMBIENTE = 'restrita';
|
|
10
|
+
export function resolveSefinBaseUrl(ambiente = DEFAULT_AMBIENTE) {
|
|
11
|
+
return SEFIN_BASE_URL[ambiente];
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../config.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,cAAc,GAA6B;IACtD,QAAQ,EAAE,yCAAyC;IACnD,QAAQ,EAAE,0DAA0D;CACrE,CAAC;AAEF,MAAM,CAAC,MAAM,MAAM,GAAgC;IACjD,QAAQ,EAAE,GAAG;IACb,QAAQ,EAAE,GAAG;CACd,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAa,UAAU,CAAC;AAErD,MAAM,UAAU,mBAAmB,CAAC,WAAqB,gBAAgB;IACvE,OAAO,cAAc,CAAC,QAAQ,CAAC,CAAC;AAClC,CAAC"}
|