Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 153 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,147 @@ e este projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR

## [Não lançado]

## [6.0.1] - 2026-10-16

> **Patch de contrato e de documentação.** Dois bugs de código, provados por sonda ao vivo
> contra a API real (2026-10-14 a 10-16), e uma rodada de correções na documentação, que
> ensinava métodos sem caminho de sucesso e uma regra de chaves imprecisa. Nenhuma
> assinatura pública muda. Evidência versionada em `tests/fixtures/live-contracts/`.

### Corrigido

- **Quatro métodos da distribuição de NF-e chamavam uma rota que não existe.**
`inboundProductInvoices.getProductInvoiceDetails`, `getProductInvoiceEventDetails`,
`getJson` e `reprocessWebhook` montavam `/inbound/productinvoice/` (singular). A plataforma
serve só o plural, `/inbound/productinvoices/`. Medido em 2026-10-14: o singular responde
`404` de corpo vazio mesmo sem credencial, igual a um path inventado; o plural responde
`401` sem credencial, e `200` com ela nos dois métodos de leitura sondados
(`getProductInvoiceDetails` e `getJson`). A spec local `consulta-nfe-distribuicao-v1.yaml` ainda
diz singular; o upstream corrigiu em `nfe/docs@51d8816`. Cinco testes unitários afirmavam o
path errado e foram corrigidos junto.

O bug era latente. Em 30 dias de log de gateway (2026-09-14 a 10-14), o plural teve 28.543
respostas `200` e o singular, 9 respostas `404`, nenhuma com User-Agent do SDK. Evidência em
`inbound-distribution-routes.json`.

- **`municipalTaxes`, `stateTaxes` e `notifications` devolviam o envelope no lugar da
entidade.** A API responde `{ "municipalTax": {…} }`, `{ "stateTax": {…} }` e
`{ "notification": {…} }`, e o SDK repassava o corpo tipado como a entidade: `resultado.id`
era `undefined`, sem erro de compilação. Agora `create`, `retrieve` e `update` de
`municipalTaxes` e `stateTaxes`, e `notifications.retrieve`, devolvem a entidade.

O envelope foi medido no `retrieve` dos três em 2026-10-15. O `create` e o `update` não
foram sondados, porque exigiriam escrever num registro real; seguem a spec, que declara o
mesmo envelope. O desembrulho é defensivo: se a API responder a entidade direta, ela chega
intacta.

**Migração:** quem contornava o bug lendo `(resultado as any).municipalTax` (ou `.stateTax`,
`.notification`) passa a receber `undefined` nessa leitura. Use o resultado direto. Código
que precisa rodar nas duas versões pode ler `(resultado as any).municipalTax ?? resultado`.

Evidência em `response-envelopes.json`, que registra também três casos em que o SDK já
estava certo e a spec erra: `legalPeople.list`, `naturalPeople.retrieve` e
`addresses.lookupByPostalCode`.

### Documentação

- **`municipalTaxes.update` é um PUT que substitui a inscrição, e nada avisava.** Pela
documentação da plataforma (`nfe/docs@b9e7953`), omitir `taxNumber` apaga o número da
inscrição, e omitir `environment` volta para `Development`. `rpsNumber` e `lastRpsSent` são a
exceção: omitidos, preservam o contador de RPS; enviados, sobrescrevem o contador, inclusive
para trás. O JSDoc, `docs/recursos/municipal-taxes.md` e a skill agora mostram o roteiro: ler
com `retrieve`, alterar e reenviar sem os dois campos do contador. O SDK não sondou isso,
porque exigiria escrever sobre uma inscrição real.

- **A documentação ensinava como funcionais métodos que só respondem `404`.**
`municipalTaxes.getSeries()`/`updatePrefecture()` e
`consumerInvoiceQuery.retrieve()`/`downloadXml()` estão `@deprecated` desde a 6.0.0, porque
a plataforma não serve as rotas. Mesmo assim, `docs/recursos/`, o README, `docs/API.md`, a
skill e os exemplos ainda os mostravam como caminho de uso; agora aparecem como
descontinuados. O exemplo de webhook por empresa do README passou ao webhook de conta
(`createAccountWebhook`), e a seção v2 → v3 do `MIGRATION.md` ganhou o aviso de que os
métodos por empresa estão descontinuados.

`tests/unit/docs-drift.test.ts` passa a falhar quando um documento cita, sem aviso por
perto, um método `@deprecated` por rota não servida. O conjunto sai do código, não de uma
lista mantida à mão: entram os métodos com `@deprecated`, inclusive no nível da classe,
menos as depreciações funcionais declaradas com motivo (`companies.list`,
`webhooks.getAvailableEvents`).

- **Links quebrados (issue #47).** O README apontava para
`docs/MIGRATION-TO-GENERATED-TYPES.md`, e a entrada 3.0 deste CHANGELOG para
`RELEASE_NOTES_v3.md`; nenhum dos dois existe, e ambos passam ao `MIGRATION.md`.
`docs/API.md` e `docs/EXTENDING.md` linkavam arquivos fora de `docs/` (`../src/core/types.ts`,
`../CONTRIBUTING.md`, `../LICENSE`), que quebram no portal, onde `docs/` é espelhado sozinho;
passam a URLs absolutas do repositório. O `docs-drift.test.ts` agora confere os links
relativos de README, `MIGRATION.md`, CHANGELOG, `docs/**` e `skills/**`: o arquivo precisa
existir e, em `docs/**`, o link não pode sair de `docs/`.

- **Arquivo de licença (issue #47).** O repositório nunca teve um: o MIT existia só no campo
`license` do `package.json`, e a API do GitHub reportava o repositório como sem licença.
Entra o `LICENSE` com o texto MIT e a linha
`Copyright (c) 2026 NFE.IO HUB SERVICOS DE TECNOLOGIA LTDA`. O README, os cabeçalhos de
`src/` e o banner do bundle citam o mesmo titular. O banner usa `/*! … */`, que os
minificadores preservam quando a aplicação re-empacota o SDK. A licença cobre o SDK; o uso
da API segue o contrato da conta. `tests/unit/license.test.ts` compara o SPDX declarado com o
texto do arquivo e confere o arquivo no tarball.

- **A skill publicada ensinava assinaturas de antes da 6.0.0.** Comparada ao código, ela tinha
32 métodos com parâmetro, retorno ou nome errado, quase todos de quebras da 6.0.0 que não
chegaram a ela:
- downloads de `productInvoices`, `consumerInvoices`, `inboundProductInvoices` e
`transportationInvoices` descritos como `Buffer` ou string, quando devolvem um recurso com
URI;
- `productInvoices.sendCorrectionLetter`, `disable` e `disableRange`; o `cancel`, os
downloads e o `getStatus` de NFS-e; as leituras de NFC-e com um `environment` que não existe
mais;
- `transportationInvoices.retrieveEvent`, que não existe: o método é `getEvent`.

Também estavam errados o CRUD de webhook por empresa, que aparecia como funcional numa tabela,
o pitfall de chaves, que dizia que o cálculo de tributos usa a chave de dados, e o tipo
`NfeStateTax`. Tudo foi conferido contra o código: os usos que a skill ensina agora compilam em
modo estrito, e os que ela ensinava antes não compilam. Entraram também as notas desta release
(distribuição no plural, envelopes).

### Corrigido — registro, não código

**A regra de chaves da 6.0.0 vale por rota, não por host.** A entrada 6.0.0 diz que as
duas chaves "são complementares, e cada uma responde `403` no território da outra". Isso
generalizava uma sonda de 2026-09-01 que só testou rotas de documento fiscal em
`api.nfse.io`. A matriz foi refeita em 2026-10-14 sobre 16 rotas:

- a chave de dados é recusada (`403`) nas rotas de documento fiscal: tax-codes, NF-e, NFC-e,
distribuição, NFS-e v1 e, medido em 2026-10-16, o cálculo de tributos;
- a chave principal é recusada (`403`) nos hosts de consulta medidos: CNPJ, CEP e NF-e por
chave de acesso;
- na leitura, as rotas de cadastro da conta aceitam as duas: empresas (lista na v1, item e
lista na v2), inscrições municipais e estaduais, certificados, e webhooks nos dois hosts. A
escrita com a chave de dados não foi testada.

O SDK não tinha bug: ele envia a chave principal a todo recurso de `api.nfe.io` e
`api.nfse.io`, e ela funciona em todas essas rotas. A aceitação da chave de dados no cadastro
não é contrato documentado, e o SDK não depende dela. README, `MIGRATION.md`,
`docs/multi-host-routing.md`, a skill e os JSDoc agora descrevem a regra por rota.

Evidência em `api-key-route-matrix.json`. O fixture de 2026-09-01 continua como registro
histórico, com `supersededBy` apontando para o novo, e a entrada 6.0.0 abaixo não foi
reescrita.

### Manutenção

- **O `package-lock.json` estava na 5.2.0**, porque a 6.0.0 só atualizou o `package.json`.
Agora acompanha a versão, e `tests/unit/version.test.ts` falha se os dois divergirem.
- **O cabeçalho de `src/index.ts` ainda dizia `@version 5.1.0`.** A tag saiu, porque a versão
vive só em `src/version.ts`, e o `version.test.ts` passa a recusar `@version X.Y.Z` em
`src/`. A varredura de literais continua ignorando comentários, que citam versões antigas
como histórico.
- **Os testes de integração de `companies` que criam, alteram ou apagam empresas passam a
exigir `NFE_ALLOW_ACCOUNT_WRITES=true`.** Fora da CI, o `npm test` roda também a integração
sempre que há chave no `.env`, e cada rodada deixava sete empresas na conta: o
`companies.remove` responde com sucesso, mas a empresa continua lá, com status `Inactive`
(medido em 2026-10-16). Sem a variável, rodam só o `404` de empresa inexistente e a recusa
de corpo inválido, que não gravam nada. Ligue a variável só com uma conta descartável.

## [6.0.0] - 2026-09-03

> **Major de correção de contrato.** Nada aqui é funcionalidade nova: são bugs provados por
Expand Down Expand Up @@ -1168,7 +1309,7 @@ Todos os métodos mantêm a mesma assinatura básica, mas agora retornam Promise

Para migrar da v2 para v3, consulte:
- **Guia completo:** [MIGRATION.md](./MIGRATION.md)
- **Release notes:** [RELEASE_NOTES_v3.md](./RELEASE_NOTES_v3.md)
- **Release notes:** [Guia de migração v2 → v3](./MIGRATION.md#guia-de-migração-v2--v3)

**Checklist rápido:**
1. ✅ Atualizar Node.js para >= 18.0.0
Expand Down Expand Up @@ -1215,9 +1356,17 @@ SDK JavaScript legado com API baseada em callbacks.

## Links

[Unreleased]: https://github.com/nfe/client-nodejs/compare/v5.2.0...HEAD
[Não lançado]: https://github.com/nfe/client-nodejs/compare/v6.0.1...HEAD
[6.0.1]: https://github.com/nfe/client-nodejs/compare/v6.0.0...v6.0.1
[6.0.0]: https://github.com/nfe/client-nodejs/compare/v5.2.0...v6.0.0
[5.2.0]: https://github.com/nfe/client-nodejs/compare/v5.1.0...v5.2.0
[5.1.0]: https://github.com/nfe/client-nodejs/compare/v5.0.0...v5.1.0
[5.0.0]: https://github.com/nfe/client-nodejs/releases/tag/v5.0.0
[3.0.0]: https://github.com/nfe/client-nodejs/releases/tag/v3.0.0
[2.0.0]: https://github.com/nfe/client-nodejs/releases/tag/v2.0.0
[4.0.0]: https://github.com/nfe/client-nodejs/compare/v3.2.1...v4.0.0
[3.2.1]: https://github.com/nfe/client-nodejs/compare/v3.2.0...v3.2.1
[3.2.0]: https://github.com/nfe/client-nodejs/compare/v3.1.0...v3.2.0
[3.1.0]: https://github.com/nfe/client-nodejs/compare/v3.0.2...v3.1.0
[3.0.2]: https://github.com/nfe/client-nodejs/compare/3.0.1...v3.0.2
[3.0.1]: https://github.com/nfe/client-nodejs/compare/3.0.0...3.0.1
[3.0.0]: https://github.com/nfe/client-nodejs/releases/tag/3.0.0
[2.0.0]: https://www.npmjs.com/package/nfe-io/v/2.0.0
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ import { HttpClient } from 'nfe-io/dist/core/http/client';

## 📜 Licença

Ao contribuir, você concorda que suas contribuições serão licenciadas sob a mesma licença do projeto (MIT).
Ao contribuir, você concorda que suas contribuições serão licenciadas sob a mesma licença do projeto, a MIT, cujo texto está em [LICENSE](./LICENSE). Não há CLA: a contribuição entra sob a mesma licença com que o projeto é distribuído.

---

Expand Down
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 NFE.IO HUB SERVICOS DE TECNOLOGIA LTDA

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
14 changes: 11 additions & 3 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,12 @@ Empresa sem certificado responde `200` com lista vazia, não `404`.

### 6. A chave de dados não serve mais nos hosts fiscais

As duas chaves são **complementares**: cada uma responde `403` no território da outra. Nove
recursos de `api.nfse.io` resolviam a chave **de dados** num host **fiscal** e só funcionavam
por acidente, via o fallback `dataApiKey → apiKey`.
As duas chaves são **complementares**: a principal vale em `api.nfe.io` e `api.nfse.io`, e a de
dados, nos hosts de consulta. Nove recursos de `api.nfse.io` resolviam a chave **de dados**, que as
rotas de documento fiscal desse host recusam com `403`; eles só funcionavam por acidente, via o
fallback `dataApiKey → apiKey`. A v6 passou a enviar a principal a todos eles. (Correção de
2026-10-14: algumas rotas de cadastro da conta também aceitam a chave de dados, mas isso não é
contrato documentado, e o SDK não depende disso.)

**Como migrar:** se você usa `dataApiKey`, nada a fazer — os nove passam a funcionar. Se você
configurava **somente** `dataApiKey`, agora é preciso informar também `apiKey`: o acesso
Expand Down Expand Up @@ -593,6 +596,11 @@ await nfe.naturalPeople.delete('company-id', 'person-id');
### Webhooks

```javascript
// ⚠️ Desde a v5.1, os métodos de webhook por empresa estão descontinuados (@deprecated):
// a rota /v1/companies/{id}/webhooks responde 404. Use os métodos por conta —
// createAccountWebhook, listAccountWebhooks, retrieveAccountWebhook, updateAccountWebhook
// e deleteAccountWebhook. O bloco abaixo fica como registro histórico da v3.

// v2
nfe.webhooks.create(webhookData, callback);
nfe.webhooks.list(callback);
Expand Down
76 changes: 32 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -646,27 +646,13 @@ for (const event of result.events ?? []) {

> **Nota:** A API de Consulta NF-e usa um host separado (`nfe.api.nfe.io`). Você pode configurar uma chave API específica com `dataApiKey`, ou o SDK usará `apiKey` como fallback.

#### 🧾 Consulta de Cupom Fiscal Eletrônico - CFe-SAT (`nfe.consumerInvoiceQuery`)
#### 🧾 Consulta de Cupom Fiscal Eletrônico - CFe-SAT (`nfe.consumerInvoiceQuery`) — descontinuado

Consultar CFe-SAT (Cupom Fiscal Eletrônico) por chave de acesso. Recurso somente leitura sem necessidade de escopo de empresa:

```typescript
// Consultar dados completos do cupom fiscal
const coupon = await nfe.consumerInvoiceQuery.retrieve(
'35240112345678000190590000000012341234567890'
);
console.log('Status:', coupon.currentStatus); // 'Authorized'
console.log('Emissor:', coupon.issuer?.name);
console.log('Valor:', coupon.totals?.couponAmount);

// Baixar XML do CFe
const xml = await nfe.consumerInvoiceQuery.downloadXml(
'35240112345678000190590000000012341234567890'
);
fs.writeFileSync('cfe.xml', xml);
```

> **Nota:** A API de Consulta CFe-SAT usa o mesmo host (`nfe.api.nfe.io`) e chave de API que a consulta de NF-e.
> ⚠️ **Descontinuado: a plataforma não serve estas rotas.** `/v1/consumerinvoices/coupon/{chave}`
> e `.xml` estão declaradas na OpenAPI, mas respondem `404` igual ao de um caminho
> inexistente, inclusive sem credencial (medido em 2026-09-02). Os métodos `retrieve()` e
> `downloadXml()` estão marcados `@deprecated`: continuam emitindo a requisição, e o `404`
> vem com essa explicação. Não há substituto na API hoje.

#### 🏢 Consulta CNPJ / Pessoa Jurídica (`nfe.legalEntityLookup`)

Expand Down Expand Up @@ -969,40 +955,38 @@ emitirNotaFiscal().catch(console.error);
### Configuração de Webhook

```typescript
// Configurar webhook para receber eventos de notas fiscais
const webhook = await nfe.webhooks.create(empresaId, {
url: 'https://meuapp.com.br/api/webhooks/nfe',
events: [
'invoice.issued',
'invoice.cancelled',
'invoice.error'
],
active: true
// Webhooks são por conta (/v2/webhooks), sem companyId. Na criação, a NFE.io faz um
// ping na `uri`: o endpoint precisa estar no ar e responder 2xx.
const webhook = await nfe.webhooks.createAccountWebhook({
uri: 'https://meuapp.com.br/api/webhooks/nfe',
contentType: 'json',
secret: process.env.WEBHOOK_SECRET!, // 32 a 64 caracteres
filters: ['service_invoice.issued_successfully', 'service_invoice.cancelled_successfully'],
});

// No seu endpoint de webhook (capture o corpo cru: app.use(express.raw({ type: '*/*' })))
app.post('/api/webhooks/nfe', (req, res) => {
const assinatura = req.headers['x-hub-signature']; // header correto (HMAC-SHA1), não 'x-nfe-signature'
// No endpoint, valide a assinatura sobre o corpo bruto (Buffer), não sobre o JSON parseado
app.post('/api/webhooks/nfe', express.raw({ type: '*/*' }), (req, res) => {
const ehValido = nfe.webhooks.validateSignature(
req.body,
assinatura,
req.body, // Buffer bruto
req.headers['x-hub-signature'], // HMAC-SHA1
process.env.WEBHOOK_SECRET
);

if (!ehValido) {
return res.status(401).send('Assinatura inválida');
}

const { event, data } = req.body;

if (event === 'invoice.issued') {
console.log('Nota fiscal emitida:', data.id);
}


const evento = JSON.parse(req.body.toString('utf8'));
// ... processe o evento

res.status(200).send('OK');
});
```

> Os métodos de webhook por empresa (`webhooks.list/create/retrieve/update/delete/test`
> com `companyId`) estão descontinuados: a rota `/v1/companies/{id}/webhooks` responde
> `404`. Use os métodos por conta acima. Lista de eventos ao vivo: `nfe.webhooks.fetchEventTypes()`.

### Criação de Notas Fiscais em Lote

```typescript
Expand Down Expand Up @@ -1097,7 +1081,7 @@ npm run build

**Nota**: Arquivos gerados não devem ser editados manualmente. Edite as specs OpenAPI e regenere.

Para orientações de migração, veja [docs/MIGRATION-TO-GENERATED-TYPES.md](./docs/MIGRATION-TO-GENERATED-TYPES.md).
Para orientações de migração, veja [MIGRATION.md](./MIGRATION.md).

### Verificação de Tipos

Expand All @@ -1117,7 +1101,11 @@ Contribuições são bem-vindas! Por favor, veja [CONTRIBUTING.md](./CONTRIBUTIN

## 📄 Licença

MIT © [NFE.io](https://nfe.io)
O código deste SDK é distribuído sob a licença [MIT](./LICENSE).
Copyright (c) 2026 NFE.IO HUB SERVICOS DE TECNOLOGIA LTDA.

A licença cobre o SDK, não a API: o uso da API da NFE.io segue o contrato da sua conta
([Termos de Serviço](https://p.nfe.io/pt-br/termos-de-servico)).

## 🆘 Suporte

Expand Down
Loading
Loading