foxnfe 1.3.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/README.md +236 -0
- package/dist/client.d.ts +34 -0
- package/dist/client.js +136 -0
- package/dist/distribuicao.d.ts +38 -0
- package/dist/distribuicao.js +53 -0
- package/dist/documents.d.ts +28 -0
- package/dist/documents.js +64 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +30 -0
- package/dist/events.d.ts +64 -0
- package/dist/events.js +109 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +45 -0
- package/dist/mcp.d.ts +22 -0
- package/dist/mcp.js +41 -0
- package/dist/nfe.d.ts +39 -0
- package/dist/nfe.js +73 -0
- package/dist/nfse.d.ts +32 -0
- package/dist/nfse.js +50 -0
- package/dist/reference.d.ts +51 -0
- package/dist/reference.js +52 -0
- package/dist/rtc.d.ts +45 -0
- package/dist/rtc.js +45 -0
- package/dist/support.d.ts +19 -0
- package/dist/support.js +31 -0
- package/dist/types.d.ts +139 -0
- package/dist/types.js +3 -0
- package/dist/webhook.d.ts +85 -0
- package/dist/webhook.js +59 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# foxnfe
|
|
2
|
+
|
|
3
|
+
SDK oficial FOX NF-e para Node.js — emissão NF-e, NFSe, cancelamento, consulta e integração MCP.
|
|
4
|
+
|
|
5
|
+
## Requisitos
|
|
6
|
+
|
|
7
|
+
- Node.js 18+
|
|
8
|
+
- TypeScript 5+ (opcional, para tipagem completa)
|
|
9
|
+
|
|
10
|
+
## Instalação
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install foxnfe
|
|
14
|
+
# ou
|
|
15
|
+
yarn add foxnfe
|
|
16
|
+
# ou
|
|
17
|
+
pnpm add foxnfe
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { Client } from 'foxnfe';
|
|
24
|
+
|
|
25
|
+
const client = new Client({ tenantSlug: 'minha-empresa' });
|
|
26
|
+
|
|
27
|
+
// Autenticar
|
|
28
|
+
const auth = await client.login('email@empresa.com', 'senha-segura');
|
|
29
|
+
console.log('Token:', auth.token);
|
|
30
|
+
|
|
31
|
+
// Ou usar token existente
|
|
32
|
+
const client2 = new Client({ tenantSlug: 'minha-empresa', token: 'seu-token-aqui' });
|
|
33
|
+
// Ou via withToken (retorna nova instância)
|
|
34
|
+
const authed = client.withToken('seu-token-aqui');
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## NF-e
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { Client, NfeEmitRequest } from 'foxnfe';
|
|
41
|
+
|
|
42
|
+
const client = new Client({ tenantSlug: 'minha-empresa', token: 'seu-token' });
|
|
43
|
+
const nfe = new Nfe(client);
|
|
44
|
+
|
|
45
|
+
// Emitir NF-e
|
|
46
|
+
const payload: NfeEmitRequest = {
|
|
47
|
+
ambiente: 2, // 2=homologação
|
|
48
|
+
certificate_id: 1,
|
|
49
|
+
tomador: {
|
|
50
|
+
cnpj: '12345678000190',
|
|
51
|
+
razao_social: 'Empresa Tomadora Ltda',
|
|
52
|
+
endereco: {
|
|
53
|
+
logradouro: 'Rua das Flores',
|
|
54
|
+
numero: '100',
|
|
55
|
+
municipio: 'São Paulo',
|
|
56
|
+
uf: 'SP',
|
|
57
|
+
cep: '01310100',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
itens: [{
|
|
61
|
+
codigo: 'SRV001',
|
|
62
|
+
descricao: 'Serviço de consultoria',
|
|
63
|
+
cfop: '5933',
|
|
64
|
+
quantidade: 1,
|
|
65
|
+
valor_unitario: 1000.00,
|
|
66
|
+
valor_total: 1000.00,
|
|
67
|
+
}],
|
|
68
|
+
pagamentos: [{ forma: '01', valor: 1000.00 }],
|
|
69
|
+
total: 1000.00,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const result = await nfe.emit(payload);
|
|
73
|
+
console.log('NF-e ID:', result.id);
|
|
74
|
+
|
|
75
|
+
// Aguardar autorização (polling automático)
|
|
76
|
+
const nfeAutorizada = await nfe.waitForAuthorization(result.id);
|
|
77
|
+
console.log('Status:', nfeAutorizada.status); // 'authorized'
|
|
78
|
+
|
|
79
|
+
// Baixar XML
|
|
80
|
+
const xml = await nfe.xml(result.id);
|
|
81
|
+
await fs.writeFile('nfe.xml', xml);
|
|
82
|
+
|
|
83
|
+
// Baixar DANFE PDF
|
|
84
|
+
const pdf = await nfe.pdf(result.id);
|
|
85
|
+
await fs.writeFile('danfe.pdf', pdf);
|
|
86
|
+
|
|
87
|
+
// Cancelar
|
|
88
|
+
await nfe.cancel(result.id, { justificativa: 'Cancelamento solicitado pelo cliente' });
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## NFSe
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { Client, Nfse, NfseEmitRequest } from 'foxnfe';
|
|
95
|
+
|
|
96
|
+
const nfse = new Nfse(client);
|
|
97
|
+
|
|
98
|
+
const payload: NfseEmitRequest = {
|
|
99
|
+
ambiente: 2,
|
|
100
|
+
certificate_id: 1,
|
|
101
|
+
prestador: {
|
|
102
|
+
cnpj: '12345678000190',
|
|
103
|
+
inscricao_municipal: '123456',
|
|
104
|
+
razao_social: 'Minha Empresa Ltda',
|
|
105
|
+
codigo_municipio: '3550308', // São Paulo (IBGE)
|
|
106
|
+
},
|
|
107
|
+
tomador: {
|
|
108
|
+
cnpj: '98765432000110',
|
|
109
|
+
nome: 'Cliente S.A.',
|
|
110
|
+
},
|
|
111
|
+
servico: {
|
|
112
|
+
codigo_tributacao_nacional: '01.01.00001',
|
|
113
|
+
descricao: 'Desenvolvimento de software',
|
|
114
|
+
data_competencia: '2026-05-01',
|
|
115
|
+
valor: 5000.00,
|
|
116
|
+
aliquota_iss: 2.0,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const result = await nfse.emit(payload);
|
|
121
|
+
const nfseData = await nfse.get(result.id);
|
|
122
|
+
console.log('Número NFSe:', nfseData.numero_nfse);
|
|
123
|
+
|
|
124
|
+
// Consultar por RPS ou chave
|
|
125
|
+
await nfse.consultByNumero('00000001');
|
|
126
|
+
await nfse.consultByChave('SP3550308202605010000000000001');
|
|
127
|
+
|
|
128
|
+
// Cancelar / Substituir
|
|
129
|
+
await nfse.cancel(result.id, { motivo: 'Erro nos dados' });
|
|
130
|
+
await nfse.substitute(result.id, { ...payload, motivo_cancelamento: 'Correção' });
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## MCP (Model Context Protocol)
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { Mcp } from 'foxnfe';
|
|
137
|
+
|
|
138
|
+
const mcp = new Mcp(client);
|
|
139
|
+
|
|
140
|
+
// Inicializar sessão MCP
|
|
141
|
+
const info = await mcp.initialize();
|
|
142
|
+
console.log('MCP Server:', info.serverInfo.name);
|
|
143
|
+
|
|
144
|
+
// Listar tools
|
|
145
|
+
const { tools } = await mcp.listTools();
|
|
146
|
+
tools.forEach(t => console.log(`${t.name}: ${t.description}`));
|
|
147
|
+
|
|
148
|
+
// Chamar uma tool
|
|
149
|
+
const result = await mcp.callTool('emitir_nfe', {
|
|
150
|
+
ambiente: 2,
|
|
151
|
+
certificate_id: 1,
|
|
152
|
+
// ...
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (result.is_error) {
|
|
156
|
+
console.error('Tool error:', result.content[0]?.text);
|
|
157
|
+
} else {
|
|
158
|
+
console.log('Tool result:', result.content[0]?.text);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## 1.3.0 — eventos, rejeições, homologação, RTC, cobertura NFS-e e suporte
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { NfeEvents, Nfe, Nfse, Rtc, Support } from 'foxnfe';
|
|
166
|
+
|
|
167
|
+
const ev = new NfeEvents(client);
|
|
168
|
+
await ev.atorInteressado(15, { documento: '11222333000181' }); // 110150
|
|
169
|
+
await ev.insucessoEntrega(15, { dh_tentativa: '2026-09-08T10:00:00-03:00', tp_motivo: 1 }); // 110192
|
|
170
|
+
await ev.inutilizar({ serie: 1, numero_inicial: 10, numero_final: 12, justificativa: 'Numeração pulada por falha do ERP' });
|
|
171
|
+
await ev.contratos(); // catálogo (conciliação financeira, RTC…)
|
|
172
|
+
await ev.registrarEvento(15, 'econf', { /* campos do contrato */ });
|
|
173
|
+
|
|
174
|
+
const nfe = new Nfe(client);
|
|
175
|
+
await nfe.rejeicao('539'); // categoria/ação/dica
|
|
176
|
+
await nfe.homologacaoRun(65); // amostras XML/PDF simuladas por cenário
|
|
177
|
+
|
|
178
|
+
await new Nfse(client).coberturaMunicipio('2304400'); // driver, operações e provas
|
|
179
|
+
const rtc = new Rtc(client);
|
|
180
|
+
await rtc.verifyResolution('550e8400-e29b-41d4-a716-446655440000'); // reproducible | output_drift | version_drift
|
|
181
|
+
await new Support(client).createCase({ subject: 'Webhook sem entrega desde ontem', priority: 'high' });
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Cada método valida localmente o que pode (ids, dígitos, tamanhos, enums) e deixa a regra fiscal para a API. A consulta de NF-e traz `rejection` quando houver rejeição SEFAZ.
|
|
185
|
+
|
|
186
|
+
## Tratamento de Erros
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { ApiException, AuthException, FoxNfeException } from 'foxnfe';
|
|
190
|
+
|
|
191
|
+
try {
|
|
192
|
+
await nfe.emit(payload);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
if (err instanceof AuthException) {
|
|
195
|
+
// Token inválido ou expirado (401/403)
|
|
196
|
+
console.error('Auth error:', err.message);
|
|
197
|
+
} else if (err instanceof ApiException) {
|
|
198
|
+
// Erro da API (422, 500, etc.)
|
|
199
|
+
console.error(`API error ${err.statusCode}:`, err.message);
|
|
200
|
+
console.error('Body:', err.responseBody);
|
|
201
|
+
} else if (err instanceof FoxNfeException) {
|
|
202
|
+
// Timeout, erro de rede, etc.
|
|
203
|
+
console.error('SDK error:', err.message);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Configuração avançada
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
const client = new Client({
|
|
212
|
+
tenantSlug: 'minha-empresa',
|
|
213
|
+
token: 'seu-token',
|
|
214
|
+
baseUrl: 'https://sandbox.centralfox.online/api/v1',
|
|
215
|
+
timeoutMs: 60_000,
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Estrutura do pacote
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
src/
|
|
223
|
+
├── index.ts # Exports públicos + createClient()
|
|
224
|
+
├── client.ts # Cliente HTTP principal
|
|
225
|
+
├── nfe.ts # Módulo NF-e
|
|
226
|
+
├── nfse.ts # Módulo NFSe
|
|
227
|
+
├── mcp.ts # Módulo MCP
|
|
228
|
+
├── types.ts # Tipos TypeScript exportados
|
|
229
|
+
└── errors.ts # Classes de erro
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Links
|
|
233
|
+
|
|
234
|
+
- [Documentação API](https://docs.centralfox.online)
|
|
235
|
+
- [Portal FOX NF-e](https://foxnfe.centralfox.online)
|
|
236
|
+
- [Suporte](mailto:suporte@centralfox.online)
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ClientOptions, LoginResponse } from './types.js';
|
|
2
|
+
export declare class Client {
|
|
3
|
+
private static readonly DEFAULT_BASE_URL;
|
|
4
|
+
readonly tenantSlug: string;
|
|
5
|
+
private token?;
|
|
6
|
+
private readonly baseUrl;
|
|
7
|
+
private readonly timeoutMs;
|
|
8
|
+
constructor(options: ClientOptions);
|
|
9
|
+
/** Autentica e armazena o token internamente. */
|
|
10
|
+
login(email: string, password: string): Promise<LoginResponse>;
|
|
11
|
+
/** Retorna uma nova instância com o token definido. */
|
|
12
|
+
withToken(token: string): Client;
|
|
13
|
+
logout(): Promise<void>;
|
|
14
|
+
me(): Promise<Record<string, unknown>>;
|
|
15
|
+
get<T = Record<string, unknown>>(path: string, params?: Record<string, string>): Promise<T>;
|
|
16
|
+
post<T = Record<string, unknown>>(path: string, body?: unknown): Promise<T>;
|
|
17
|
+
put<T = Record<string, unknown>>(path: string, body?: unknown): Promise<T>;
|
|
18
|
+
delete<T = Record<string, unknown>>(path: string): Promise<T>;
|
|
19
|
+
/** Retorna Buffer com conteúdo binário (XML, PDF). */
|
|
20
|
+
download(path: string): Promise<Buffer>;
|
|
21
|
+
private buildUrl;
|
|
22
|
+
/**
|
|
23
|
+
* Envio multipart (upload de XML). O Content-Type é definido pelo runtime com o
|
|
24
|
+
* boundary; nunca use JSON aqui.
|
|
25
|
+
*/
|
|
26
|
+
upload<T = Record<string, unknown>>(path: string, file: {
|
|
27
|
+
field: string;
|
|
28
|
+
filename: string;
|
|
29
|
+
content: Buffer | string;
|
|
30
|
+
contentType?: string;
|
|
31
|
+
}, fields?: Record<string, string>): Promise<T>;
|
|
32
|
+
private fetch;
|
|
33
|
+
private parseJson;
|
|
34
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Client = void 0;
|
|
4
|
+
const errors_js_1 = require("./errors.js");
|
|
5
|
+
class Client {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.tenantSlug = options.tenantSlug;
|
|
8
|
+
this.token = options.token;
|
|
9
|
+
this.baseUrl = options.baseUrl?.replace(/\/$/, '') ?? Client.DEFAULT_BASE_URL;
|
|
10
|
+
this.timeoutMs = options.timeoutMs ?? 30000;
|
|
11
|
+
}
|
|
12
|
+
/** Autentica e armazena o token internamente. */
|
|
13
|
+
async login(email, password) {
|
|
14
|
+
const data = await this.post('auth/login', { email, password });
|
|
15
|
+
if (!data.token)
|
|
16
|
+
throw new errors_js_1.AuthException('Token não retornado pela API.');
|
|
17
|
+
this.token = data.token;
|
|
18
|
+
return data;
|
|
19
|
+
}
|
|
20
|
+
/** Retorna uma nova instância com o token definido. */
|
|
21
|
+
withToken(token) {
|
|
22
|
+
return new Client({
|
|
23
|
+
tenantSlug: this.tenantSlug,
|
|
24
|
+
baseUrl: this.baseUrl,
|
|
25
|
+
timeoutMs: this.timeoutMs,
|
|
26
|
+
token,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async logout() {
|
|
30
|
+
await this.post('auth/logout');
|
|
31
|
+
this.token = undefined;
|
|
32
|
+
}
|
|
33
|
+
async me() {
|
|
34
|
+
return this.get('auth/me');
|
|
35
|
+
}
|
|
36
|
+
// ── HTTP helpers ────────────────────────────────────────────────────────
|
|
37
|
+
async get(path, params) {
|
|
38
|
+
const url = this.buildUrl(path, params);
|
|
39
|
+
const res = await this.fetch(url, { method: 'GET' });
|
|
40
|
+
return this.parseJson(res);
|
|
41
|
+
}
|
|
42
|
+
async post(path, body) {
|
|
43
|
+
const url = this.buildUrl(path);
|
|
44
|
+
const res = await this.fetch(url, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
47
|
+
});
|
|
48
|
+
return this.parseJson(res);
|
|
49
|
+
}
|
|
50
|
+
async put(path, body) {
|
|
51
|
+
const url = this.buildUrl(path);
|
|
52
|
+
const res = await this.fetch(url, {
|
|
53
|
+
method: 'PUT',
|
|
54
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
55
|
+
});
|
|
56
|
+
return this.parseJson(res);
|
|
57
|
+
}
|
|
58
|
+
async delete(path) {
|
|
59
|
+
const url = this.buildUrl(path);
|
|
60
|
+
const res = await this.fetch(url, { method: 'DELETE' });
|
|
61
|
+
return this.parseJson(res);
|
|
62
|
+
}
|
|
63
|
+
/** Retorna Buffer com conteúdo binário (XML, PDF). */
|
|
64
|
+
async download(path) {
|
|
65
|
+
const url = this.buildUrl(path);
|
|
66
|
+
const res = await this.fetch(url, { method: 'GET' });
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
const body = await res.json().catch(() => ({}));
|
|
69
|
+
throw errors_js_1.ApiException.fromResponse(res.status, body);
|
|
70
|
+
}
|
|
71
|
+
return Buffer.from(await res.arrayBuffer());
|
|
72
|
+
}
|
|
73
|
+
buildUrl(path, params) {
|
|
74
|
+
const url = new URL(`${this.baseUrl}/${path}`);
|
|
75
|
+
if (params) {
|
|
76
|
+
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
|
|
77
|
+
}
|
|
78
|
+
return url.toString();
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Envio multipart (upload de XML). O Content-Type é definido pelo runtime com o
|
|
82
|
+
* boundary; nunca use JSON aqui.
|
|
83
|
+
*/
|
|
84
|
+
async upload(path, file, fields = {}) {
|
|
85
|
+
const form = new FormData();
|
|
86
|
+
for (const [k, v] of Object.entries(fields))
|
|
87
|
+
form.set(k, v);
|
|
88
|
+
form.set(file.field, new Blob([file.content], { type: file.contentType ?? 'application/xml' }), file.filename);
|
|
89
|
+
const res = await this.fetch(this.buildUrl(path), { method: 'POST', body: form }, null);
|
|
90
|
+
return this.parseJson(res);
|
|
91
|
+
}
|
|
92
|
+
async fetch(url, init, contentType = 'application/json') {
|
|
93
|
+
const controller = new AbortController();
|
|
94
|
+
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
95
|
+
try {
|
|
96
|
+
return await fetch(url, {
|
|
97
|
+
...init,
|
|
98
|
+
signal: controller.signal,
|
|
99
|
+
headers: {
|
|
100
|
+
...(contentType ? { 'Content-Type': contentType } : {}),
|
|
101
|
+
'Accept': 'application/json',
|
|
102
|
+
'X-Tenant-ID': this.tenantSlug,
|
|
103
|
+
...(this.token ? { Authorization: `Bearer ${this.token}` } : {}),
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
if (err?.name === 'AbortError') {
|
|
109
|
+
throw new errors_js_1.FoxNfeException(`Timeout após ${this.timeoutMs}ms`);
|
|
110
|
+
}
|
|
111
|
+
throw new errors_js_1.FoxNfeException(`Erro de rede: ${err?.message ?? err}`);
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
clearTimeout(timer);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async parseJson(res) {
|
|
118
|
+
let body;
|
|
119
|
+
try {
|
|
120
|
+
body = await res.json();
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
throw new errors_js_1.FoxNfeException('Resposta JSON inválida.');
|
|
124
|
+
}
|
|
125
|
+
if (!res.ok) {
|
|
126
|
+
const err = body;
|
|
127
|
+
if (res.status === 401 || res.status === 403) {
|
|
128
|
+
throw new errors_js_1.AuthException(err.message ?? 'Não autorizado.');
|
|
129
|
+
}
|
|
130
|
+
throw errors_js_1.ApiException.fromResponse(res.status, err);
|
|
131
|
+
}
|
|
132
|
+
return body;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
exports.Client = Client;
|
|
136
|
+
Client.DEFAULT_BASE_URL = 'https://foxnfe.centralfox.online/api/v1';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Client } from './client.js';
|
|
2
|
+
export interface ManifestacaoRequest {
|
|
3
|
+
chave_acesso: string;
|
|
4
|
+
tipo_evento: 210200 | 210210 | 210220 | 210240;
|
|
5
|
+
n_seq_evento?: number;
|
|
6
|
+
justificativa?: string;
|
|
7
|
+
nfe_distribuicao_id?: number;
|
|
8
|
+
certificate_id?: number;
|
|
9
|
+
ambiente?: 1 | 2;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* NF-e recebidas (capacidade 10): distribuição DF-e, cursor NSU, captura
|
|
13
|
+
* automática (opt-in) e manifestação do destinatário (solicitar → aprovar →
|
|
14
|
+
* transmitir; `reconciliar` resolve uma transmissão ambígua por consulta real).
|
|
15
|
+
*/
|
|
16
|
+
export declare class Distribuicao {
|
|
17
|
+
private readonly client;
|
|
18
|
+
constructor(client: Client);
|
|
19
|
+
list(params?: Record<string, string | number | undefined>): Promise<Record<string, unknown>>;
|
|
20
|
+
cursor(): Promise<Record<string, unknown>>;
|
|
21
|
+
optins(): Promise<Record<string, unknown>>;
|
|
22
|
+
enableOptin(certificateId: number, ambiente: 1 | 2): Promise<Record<string, unknown>>;
|
|
23
|
+
disableOptin(id: number): Promise<Record<string, unknown>>;
|
|
24
|
+
/** Captura manual (bounded); `certificate_id`/`ambiente` opcionais. */
|
|
25
|
+
capturar(params?: {
|
|
26
|
+
certificate_id?: number;
|
|
27
|
+
ambiente?: 1 | 2;
|
|
28
|
+
}): Promise<Record<string, unknown>>;
|
|
29
|
+
manifestacoes(params?: Record<string, string | number | undefined>): Promise<Record<string, unknown>>;
|
|
30
|
+
manifestacao(id: number): Promise<Record<string, unknown>>;
|
|
31
|
+
solicitarManifestacao(body: ManifestacaoRequest): Promise<Record<string, unknown>>;
|
|
32
|
+
/** Aprovação vinculada ao payload_hash devolvido na solicitação (alteração invalida). */
|
|
33
|
+
aprovarManifestacao(id: number, payloadHash: string): Promise<Record<string, unknown>>;
|
|
34
|
+
transmitirManifestacao(id: number): Promise<Record<string, unknown>>;
|
|
35
|
+
reconciliarManifestacao(id: number): Promise<Record<string, unknown>>;
|
|
36
|
+
private static q;
|
|
37
|
+
private static id;
|
|
38
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Distribuicao = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* NF-e recebidas (capacidade 10): distribuição DF-e, cursor NSU, captura
|
|
6
|
+
* automática (opt-in) e manifestação do destinatário (solicitar → aprovar →
|
|
7
|
+
* transmitir; `reconciliar` resolve uma transmissão ambígua por consulta real).
|
|
8
|
+
*/
|
|
9
|
+
class Distribuicao {
|
|
10
|
+
constructor(client) {
|
|
11
|
+
this.client = client;
|
|
12
|
+
}
|
|
13
|
+
list(params = {}) { return this.client.get('nfe-distribuicao', Distribuicao.q(params)); }
|
|
14
|
+
cursor() { return this.client.get('nfe-distribuicao/cursor'); }
|
|
15
|
+
optins() { return this.client.get('nfe-distribuicao/captura-automatica'); }
|
|
16
|
+
enableOptin(certificateId, ambiente) {
|
|
17
|
+
Distribuicao.id(certificateId, 'certificateId');
|
|
18
|
+
return this.client.put('nfe-distribuicao/captura-automatica', { certificate_id: certificateId, ambiente });
|
|
19
|
+
}
|
|
20
|
+
disableOptin(id) { Distribuicao.id(id, 'id'); return this.client.delete(`nfe-distribuicao/captura-automatica/${id}`); }
|
|
21
|
+
/** Captura manual (bounded); `certificate_id`/`ambiente` opcionais. */
|
|
22
|
+
capturar(params = {}) { return this.client.post('nfe-distribuicao/capturar', params); }
|
|
23
|
+
manifestacoes(params = {}) { return this.client.get('nfe-distribuicao/manifestacoes', Distribuicao.q(params)); }
|
|
24
|
+
manifestacao(id) { Distribuicao.id(id, 'id'); return this.client.get(`nfe-distribuicao/manifestacoes/${id}`); }
|
|
25
|
+
solicitarManifestacao(body) {
|
|
26
|
+
if (!/^[0-9]{6}[0-9A-Z]{12}[0-9]{26}$/.test(body.chave_acesso))
|
|
27
|
+
throw new TypeError('chave_acesso inválida (44 posições).');
|
|
28
|
+
if (![210200, 210210, 210220, 210240].includes(body.tipo_evento))
|
|
29
|
+
throw new TypeError('tipo_evento inválido.');
|
|
30
|
+
return this.client.post('nfe-distribuicao/manifestacoes', body);
|
|
31
|
+
}
|
|
32
|
+
/** Aprovação vinculada ao payload_hash devolvido na solicitação (alteração invalida). */
|
|
33
|
+
aprovarManifestacao(id, payloadHash) {
|
|
34
|
+
Distribuicao.id(id, 'id');
|
|
35
|
+
if (!/^[0-9a-f]{64}$/.test(payloadHash))
|
|
36
|
+
throw new TypeError('payloadHash deve ser sha256 hex minúsculo.');
|
|
37
|
+
return this.client.post(`nfe-distribuicao/manifestacoes/${id}/aprovar`, { payload_hash: payloadHash });
|
|
38
|
+
}
|
|
39
|
+
transmitirManifestacao(id) { Distribuicao.id(id, 'id'); return this.client.post(`nfe-distribuicao/manifestacoes/${id}/transmitir`); }
|
|
40
|
+
reconciliarManifestacao(id) { Distribuicao.id(id, 'id'); return this.client.post(`nfe-distribuicao/manifestacoes/${id}/reconciliar`); }
|
|
41
|
+
static q(params) {
|
|
42
|
+
const p = {};
|
|
43
|
+
for (const [k, v] of Object.entries(params))
|
|
44
|
+
if (v !== undefined)
|
|
45
|
+
p[k] = String(v);
|
|
46
|
+
return p;
|
|
47
|
+
}
|
|
48
|
+
static id(id, label) {
|
|
49
|
+
if (!Number.isInteger(id) || id < 1)
|
|
50
|
+
throw new TypeError(`${label} deve ser inteiro positivo.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.Distribuicao = Distribuicao;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Client } from './client.js';
|
|
2
|
+
export interface DocumentListParams {
|
|
3
|
+
[key: string]: string | number | undefined;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Central de Documentos (capacidades 14/15/16): consulta unificada (emitidas,
|
|
7
|
+
* recebidas, importadas), detalhe, importação de XML de terceiros com prévia
|
|
8
|
+
* (sem valor fiscal próprio) e exportação ZIP assíncrona.
|
|
9
|
+
*/
|
|
10
|
+
export declare class Documents {
|
|
11
|
+
private readonly client;
|
|
12
|
+
constructor(client: Client);
|
|
13
|
+
/** Consulta unificada paginada (filtros: período, origem, tipo, status, participante…). */
|
|
14
|
+
list(params?: DocumentListParams): Promise<Record<string, unknown>>;
|
|
15
|
+
/** Detalhe por origem (`nfes`, `nfses`, `nfe_distribuicoes`, `imported_documents`) e id. */
|
|
16
|
+
show(source: string, id: number): Promise<Record<string, unknown>>;
|
|
17
|
+
/** Prévia de importação: valida e vincula SEM persistir; devolve integrity.raw_sha256 para o commit. */
|
|
18
|
+
importPreview(xml: Buffer | string, filename?: string): Promise<Record<string, unknown>>;
|
|
19
|
+
/** Importa o XML (mesmos bytes da prévia quando previewSha256 é informado). */
|
|
20
|
+
import(xml: Buffer | string, previewSha256?: string, filename?: string): Promise<Record<string, unknown>>;
|
|
21
|
+
/** Solicita exportação ZIP assíncrona com os mesmos filtros da consulta. */
|
|
22
|
+
exportRequest(filters?: DocumentListParams): Promise<Record<string, unknown>>;
|
|
23
|
+
exportStatus(requestId: string): Promise<Record<string, unknown>>;
|
|
24
|
+
/** ZIP com manifesto de hashes; confira a integridade antes de arquivar. */
|
|
25
|
+
exportDownload(requestId: string): Promise<Buffer>;
|
|
26
|
+
private static id;
|
|
27
|
+
private static uuid;
|
|
28
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Documents = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Central de Documentos (capacidades 14/15/16): consulta unificada (emitidas,
|
|
6
|
+
* recebidas, importadas), detalhe, importação de XML de terceiros com prévia
|
|
7
|
+
* (sem valor fiscal próprio) e exportação ZIP assíncrona.
|
|
8
|
+
*/
|
|
9
|
+
class Documents {
|
|
10
|
+
constructor(client) {
|
|
11
|
+
this.client = client;
|
|
12
|
+
}
|
|
13
|
+
/** Consulta unificada paginada (filtros: período, origem, tipo, status, participante…). */
|
|
14
|
+
list(params = {}) {
|
|
15
|
+
const p = {};
|
|
16
|
+
for (const [k, v] of Object.entries(params))
|
|
17
|
+
if (v !== undefined)
|
|
18
|
+
p[k] = String(v);
|
|
19
|
+
return this.client.get('documents', p);
|
|
20
|
+
}
|
|
21
|
+
/** Detalhe por origem (`nfes`, `nfses`, `nfe_distribuicoes`, `imported_documents`) e id. */
|
|
22
|
+
show(source, id) {
|
|
23
|
+
if (!/^[a-z_]+$/.test(source))
|
|
24
|
+
throw new TypeError('source inválido.');
|
|
25
|
+
Documents.id(id);
|
|
26
|
+
return this.client.get(`documents/${source}/${id}`);
|
|
27
|
+
}
|
|
28
|
+
/** Prévia de importação: valida e vincula SEM persistir; devolve integrity.raw_sha256 para o commit. */
|
|
29
|
+
importPreview(xml, filename = 'nota.xml') {
|
|
30
|
+
return this.client.upload('documents/imports/preview', { field: 'file', filename, content: xml });
|
|
31
|
+
}
|
|
32
|
+
/** Importa o XML (mesmos bytes da prévia quando previewSha256 é informado). */
|
|
33
|
+
import(xml, previewSha256, filename = 'nota.xml') {
|
|
34
|
+
const fields = {};
|
|
35
|
+
if (previewSha256 !== undefined) {
|
|
36
|
+
if (!/^[0-9a-f]{64}$/.test(previewSha256))
|
|
37
|
+
throw new TypeError('previewSha256 deve ser sha256 hex minúsculo.');
|
|
38
|
+
fields.preview_sha256 = previewSha256;
|
|
39
|
+
}
|
|
40
|
+
return this.client.upload('documents/imports', { field: 'file', filename, content: xml }, fields);
|
|
41
|
+
}
|
|
42
|
+
/** Solicita exportação ZIP assíncrona com os mesmos filtros da consulta. */
|
|
43
|
+
exportRequest(filters = {}) {
|
|
44
|
+
return this.client.post('documents/exports', filters);
|
|
45
|
+
}
|
|
46
|
+
exportStatus(requestId) {
|
|
47
|
+
Documents.uuid(requestId);
|
|
48
|
+
return this.client.get(`documents/exports/${requestId}`);
|
|
49
|
+
}
|
|
50
|
+
/** ZIP com manifesto de hashes; confira a integridade antes de arquivar. */
|
|
51
|
+
exportDownload(requestId) {
|
|
52
|
+
Documents.uuid(requestId);
|
|
53
|
+
return this.client.download(`documents/exports/${requestId}/download`);
|
|
54
|
+
}
|
|
55
|
+
static id(id) {
|
|
56
|
+
if (!Number.isInteger(id) || id < 1)
|
|
57
|
+
throw new TypeError('id deve ser inteiro positivo.');
|
|
58
|
+
}
|
|
59
|
+
static uuid(v) {
|
|
60
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v))
|
|
61
|
+
throw new TypeError('requestId deve ser um UUID válido.');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.Documents = Documents;
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ApiErrorResponse } from './types.js';
|
|
2
|
+
export declare class FoxNfeException extends Error {
|
|
3
|
+
readonly context?: unknown | undefined;
|
|
4
|
+
constructor(message: string, context?: unknown | undefined);
|
|
5
|
+
}
|
|
6
|
+
export declare class ApiException extends FoxNfeException {
|
|
7
|
+
readonly statusCode: number;
|
|
8
|
+
readonly responseBody?: ApiErrorResponse | undefined;
|
|
9
|
+
constructor(statusCode: number, message: string, responseBody?: ApiErrorResponse | undefined);
|
|
10
|
+
static fromResponse(statusCode: number, body: ApiErrorResponse): ApiException;
|
|
11
|
+
}
|
|
12
|
+
export declare class AuthException extends FoxNfeException {
|
|
13
|
+
constructor(message: string);
|
|
14
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AuthException = exports.ApiException = exports.FoxNfeException = void 0;
|
|
4
|
+
class FoxNfeException extends Error {
|
|
5
|
+
constructor(message, context) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.context = context;
|
|
8
|
+
this.name = 'FoxNfeException';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.FoxNfeException = FoxNfeException;
|
|
12
|
+
class ApiException extends FoxNfeException {
|
|
13
|
+
constructor(statusCode, message, responseBody) {
|
|
14
|
+
super(message, responseBody);
|
|
15
|
+
this.statusCode = statusCode;
|
|
16
|
+
this.responseBody = responseBody;
|
|
17
|
+
this.name = 'ApiException';
|
|
18
|
+
}
|
|
19
|
+
static fromResponse(statusCode, body) {
|
|
20
|
+
return new ApiException(statusCode, body.message ?? `HTTP ${statusCode}`, body);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.ApiException = ApiException;
|
|
24
|
+
class AuthException extends FoxNfeException {
|
|
25
|
+
constructor(message) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = 'AuthException';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.AuthException = AuthException;
|