@emitia/node 0.1.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/LICENSE +21 -0
- package/README.md +168 -0
- package/dist/index.d.ts +3587 -0
- package/dist/index.js +888 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,888 @@
|
|
|
1
|
+
import { createHmac, timingSafeEqual } from 'crypto';
|
|
2
|
+
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
5
|
+
|
|
6
|
+
// src/config.ts
|
|
7
|
+
var DEFAULT_BASE_URL = "https://api.emitia.co";
|
|
8
|
+
function validateConfig(apiKey, opts) {
|
|
9
|
+
if (!apiKey.startsWith("sk_test_") && !apiKey.startsWith("sk_live_")) {
|
|
10
|
+
throw new Error(`Invalid API key "${apiKey.slice(0, 10)}...": must start with "sk_test_" or "sk_live_".`);
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
apiKey,
|
|
14
|
+
baseURL: opts?.baseURL ?? DEFAULT_BASE_URL,
|
|
15
|
+
timeout: opts?.timeout ?? 3e4,
|
|
16
|
+
maxRetries: opts?.maxRetries ?? 2,
|
|
17
|
+
fetch: opts?.fetch
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
__name(validateConfig, "validateConfig");
|
|
21
|
+
|
|
22
|
+
// src/errors.ts
|
|
23
|
+
function isErrorEnvelope(value) {
|
|
24
|
+
if (value === null || typeof value !== "object") return false;
|
|
25
|
+
const v = value;
|
|
26
|
+
const inner = v["error"];
|
|
27
|
+
if (inner === null || typeof inner !== "object") return false;
|
|
28
|
+
const e = inner;
|
|
29
|
+
return typeof e["type"] === "string" && typeof e["code"] === "string" && typeof e["message"] === "string";
|
|
30
|
+
}
|
|
31
|
+
__name(isErrorEnvelope, "isErrorEnvelope");
|
|
32
|
+
var EmitiaError = class extends Error {
|
|
33
|
+
static {
|
|
34
|
+
__name(this, "EmitiaError");
|
|
35
|
+
}
|
|
36
|
+
type;
|
|
37
|
+
code;
|
|
38
|
+
requestId;
|
|
39
|
+
statusCode;
|
|
40
|
+
docUrl;
|
|
41
|
+
details;
|
|
42
|
+
constructor(opts) {
|
|
43
|
+
super(opts.message);
|
|
44
|
+
this.name = this.constructor.name;
|
|
45
|
+
this.type = opts.type;
|
|
46
|
+
this.code = opts.code;
|
|
47
|
+
this.requestId = opts.requestId;
|
|
48
|
+
this.statusCode = opts.statusCode;
|
|
49
|
+
this.docUrl = opts.docUrl;
|
|
50
|
+
this.details = opts.details;
|
|
51
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var AuthenticationError = class extends EmitiaError {
|
|
55
|
+
static {
|
|
56
|
+
__name(this, "AuthenticationError");
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var PermissionError = class extends EmitiaError {
|
|
60
|
+
static {
|
|
61
|
+
__name(this, "PermissionError");
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var NotFoundError = class extends EmitiaError {
|
|
65
|
+
static {
|
|
66
|
+
__name(this, "NotFoundError");
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
var ValidationError = class extends EmitiaError {
|
|
70
|
+
static {
|
|
71
|
+
__name(this, "ValidationError");
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var RateLimitError = class extends EmitiaError {
|
|
75
|
+
static {
|
|
76
|
+
__name(this, "RateLimitError");
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var IdempotencyError = class extends EmitiaError {
|
|
80
|
+
static {
|
|
81
|
+
__name(this, "IdempotencyError");
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var DianRejectedError = class extends EmitiaError {
|
|
85
|
+
static {
|
|
86
|
+
__name(this, "DianRejectedError");
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
var DianUnavailableError = class extends EmitiaError {
|
|
90
|
+
static {
|
|
91
|
+
__name(this, "DianUnavailableError");
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
var CertificateError = class extends EmitiaError {
|
|
95
|
+
static {
|
|
96
|
+
__name(this, "CertificateError");
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
var NumberingError = class extends EmitiaError {
|
|
100
|
+
static {
|
|
101
|
+
__name(this, "NumberingError");
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
var ApiError = class extends EmitiaError {
|
|
105
|
+
static {
|
|
106
|
+
__name(this, "ApiError");
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
var TYPE_TO_CLASS = {
|
|
110
|
+
authentication_error: AuthenticationError,
|
|
111
|
+
permission_error: PermissionError,
|
|
112
|
+
not_found_error: NotFoundError,
|
|
113
|
+
validation_error: ValidationError,
|
|
114
|
+
rate_limit_error: RateLimitError,
|
|
115
|
+
idempotency_error: IdempotencyError,
|
|
116
|
+
dian_rejected: DianRejectedError,
|
|
117
|
+
dian_unavailable: DianUnavailableError,
|
|
118
|
+
certificate_error: CertificateError,
|
|
119
|
+
numbering_error: NumberingError
|
|
120
|
+
};
|
|
121
|
+
var HTTP_STATUS_TO_CLASS = {
|
|
122
|
+
401: AuthenticationError,
|
|
123
|
+
403: PermissionError,
|
|
124
|
+
404: NotFoundError,
|
|
125
|
+
422: ValidationError,
|
|
126
|
+
429: RateLimitError
|
|
127
|
+
};
|
|
128
|
+
function fromPayload(envelope, httpStatus, requestId) {
|
|
129
|
+
const { type, code, message, doc_url, details } = envelope.error;
|
|
130
|
+
const opts = {
|
|
131
|
+
type,
|
|
132
|
+
code,
|
|
133
|
+
message,
|
|
134
|
+
requestId,
|
|
135
|
+
statusCode: httpStatus,
|
|
136
|
+
docUrl: doc_url,
|
|
137
|
+
details
|
|
138
|
+
};
|
|
139
|
+
const ByType = TYPE_TO_CLASS[type];
|
|
140
|
+
if (ByType !== void 0) {
|
|
141
|
+
return new ByType(opts);
|
|
142
|
+
}
|
|
143
|
+
const ByStatus = HTTP_STATUS_TO_CLASS[httpStatus];
|
|
144
|
+
if (ByStatus !== void 0) {
|
|
145
|
+
return new ByStatus(opts);
|
|
146
|
+
}
|
|
147
|
+
return new ApiError(opts);
|
|
148
|
+
}
|
|
149
|
+
__name(fromPayload, "fromPayload");
|
|
150
|
+
|
|
151
|
+
// src/version.ts
|
|
152
|
+
var SDK_VERSION = "0.1.0";
|
|
153
|
+
|
|
154
|
+
// src/http.ts
|
|
155
|
+
var RETRY_BASE_MS = 500;
|
|
156
|
+
var RETRY_CAP_MS = 8e3;
|
|
157
|
+
function sleep(ms, signal) {
|
|
158
|
+
return new Promise((resolve, reject) => {
|
|
159
|
+
if (signal?.aborted === true) {
|
|
160
|
+
reject(new Error("AbortError"));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const id = setTimeout(resolve, ms);
|
|
164
|
+
const onAbort = /* @__PURE__ */ __name(() => {
|
|
165
|
+
clearTimeout(id);
|
|
166
|
+
reject(new Error("AbortError"));
|
|
167
|
+
}, "onAbort");
|
|
168
|
+
signal?.addEventListener("abort", onAbort, {
|
|
169
|
+
once: true
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
__name(sleep, "sleep");
|
|
174
|
+
function jitteredDelay(attempt) {
|
|
175
|
+
const exp = RETRY_BASE_MS * 2 ** attempt;
|
|
176
|
+
const capped = Math.min(exp, RETRY_CAP_MS);
|
|
177
|
+
return Math.floor(Math.random() * capped);
|
|
178
|
+
}
|
|
179
|
+
__name(jitteredDelay, "jitteredDelay");
|
|
180
|
+
function parseRetryAfter(header, nowMs) {
|
|
181
|
+
if (header === null) return null;
|
|
182
|
+
const asSeconds = Number(header);
|
|
183
|
+
if (Number.isFinite(asSeconds)) {
|
|
184
|
+
return Math.min(Math.max(asSeconds, 0) * 1e3, RETRY_CAP_MS);
|
|
185
|
+
}
|
|
186
|
+
const asDate = Date.parse(header);
|
|
187
|
+
if (Number.isNaN(asDate)) return null;
|
|
188
|
+
return Math.min(Math.max(asDate - nowMs, 0), RETRY_CAP_MS);
|
|
189
|
+
}
|
|
190
|
+
__name(parseRetryAfter, "parseRetryAfter");
|
|
191
|
+
function shouldRetry(status) {
|
|
192
|
+
return status === 429 || status >= 500 && status <= 599;
|
|
193
|
+
}
|
|
194
|
+
__name(shouldRetry, "shouldRetry");
|
|
195
|
+
var HttpClient = class {
|
|
196
|
+
static {
|
|
197
|
+
__name(this, "HttpClient");
|
|
198
|
+
}
|
|
199
|
+
config;
|
|
200
|
+
constructor(config) {
|
|
201
|
+
this.config = config;
|
|
202
|
+
}
|
|
203
|
+
async request(opts) {
|
|
204
|
+
const fetchFn = this.config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
205
|
+
const idempotencyKey = opts.idempotencyKey ?? (opts.autoIdempotency === true ? crypto.randomUUID() : void 0);
|
|
206
|
+
let lastError;
|
|
207
|
+
for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
|
|
208
|
+
const url = this.buildUrl(opts.path, opts.query);
|
|
209
|
+
const headers = {
|
|
210
|
+
authorization: `Bearer ${this.config.apiKey}`,
|
|
211
|
+
"user-agent": `emitia-node/${SDK_VERSION} node/${process.version}`,
|
|
212
|
+
accept: "application/json"
|
|
213
|
+
};
|
|
214
|
+
if (opts.body !== void 0) {
|
|
215
|
+
headers["content-type"] = "application/json";
|
|
216
|
+
}
|
|
217
|
+
if (idempotencyKey !== void 0) {
|
|
218
|
+
headers["idempotency-key"] = idempotencyKey;
|
|
219
|
+
}
|
|
220
|
+
const response = await fetchFn(url, {
|
|
221
|
+
method: opts.method,
|
|
222
|
+
headers,
|
|
223
|
+
body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0,
|
|
224
|
+
signal: opts.signal
|
|
225
|
+
});
|
|
226
|
+
const requestId = response.headers.get("x-request-id") ?? "";
|
|
227
|
+
if (response.ok) {
|
|
228
|
+
return await response.json();
|
|
229
|
+
}
|
|
230
|
+
let bodyText;
|
|
231
|
+
try {
|
|
232
|
+
bodyText = await response.text();
|
|
233
|
+
} catch {
|
|
234
|
+
bodyText = "";
|
|
235
|
+
}
|
|
236
|
+
let parsedBody;
|
|
237
|
+
try {
|
|
238
|
+
parsedBody = bodyText.length > 0 ? JSON.parse(bodyText) : {};
|
|
239
|
+
} catch {
|
|
240
|
+
parsedBody = {};
|
|
241
|
+
}
|
|
242
|
+
const responseError = isErrorEnvelope(parsedBody) ? fromPayload(parsedBody, response.status, requestId) : new Error(`HTTP ${response.status}: ${bodyText}`);
|
|
243
|
+
if (shouldRetry(response.status) && attempt < this.config.maxRetries) {
|
|
244
|
+
const retryAfter = parseRetryAfter(response.headers.get("retry-after"), Date.now());
|
|
245
|
+
const delayMs = retryAfter ?? jitteredDelay(attempt);
|
|
246
|
+
await sleep(delayMs, opts.signal);
|
|
247
|
+
lastError = responseError;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
throw responseError;
|
|
251
|
+
}
|
|
252
|
+
throw lastError ?? new Error("Request failed after retries");
|
|
253
|
+
}
|
|
254
|
+
buildUrl(path, query) {
|
|
255
|
+
const base = this.config.baseURL.replace(/\/$/, "");
|
|
256
|
+
const url = `${base}${path}`;
|
|
257
|
+
if (query === void 0 || Object.keys(query).length === 0) return url;
|
|
258
|
+
const params = new URLSearchParams();
|
|
259
|
+
for (const [key, value] of Object.entries(query)) {
|
|
260
|
+
params.set(key, String(value));
|
|
261
|
+
}
|
|
262
|
+
return `${url}?${params.toString()}`;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
// src/resources/base.ts
|
|
267
|
+
var BaseResource = class {
|
|
268
|
+
static {
|
|
269
|
+
__name(this, "BaseResource");
|
|
270
|
+
}
|
|
271
|
+
_http;
|
|
272
|
+
constructor(http) {
|
|
273
|
+
this._http = http;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
// src/resources/account.ts
|
|
278
|
+
var AccountResource = class extends BaseResource {
|
|
279
|
+
static {
|
|
280
|
+
__name(this, "AccountResource");
|
|
281
|
+
}
|
|
282
|
+
retrieve() {
|
|
283
|
+
return this._http.request({
|
|
284
|
+
method: "GET",
|
|
285
|
+
path: "/v1/account"
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// src/resources/balance.ts
|
|
291
|
+
var BalanceResource = class extends BaseResource {
|
|
292
|
+
static {
|
|
293
|
+
__name(this, "BalanceResource");
|
|
294
|
+
}
|
|
295
|
+
retrieve() {
|
|
296
|
+
return this._http.request({
|
|
297
|
+
method: "GET",
|
|
298
|
+
path: "/v1/balance"
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// src/resources/catalogs.ts
|
|
304
|
+
var CatalogsResource = class extends BaseResource {
|
|
305
|
+
static {
|
|
306
|
+
__name(this, "CatalogsResource");
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* GET /v1/catalogs/{slug}
|
|
310
|
+
* Returns all items in a named DIAN catalog.
|
|
311
|
+
*/
|
|
312
|
+
retrieve(slug) {
|
|
313
|
+
return this._http.request({
|
|
314
|
+
method: "GET",
|
|
315
|
+
path: `/v1/catalogs/${slug}`
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* GET /v1/catalogs/municipios
|
|
320
|
+
* Returns Colombian municipalities. Optionally filtered by 2-digit department code.
|
|
321
|
+
*/
|
|
322
|
+
municipios(query) {
|
|
323
|
+
return this._http.request({
|
|
324
|
+
method: "GET",
|
|
325
|
+
path: "/v1/catalogs/municipios",
|
|
326
|
+
query: query?.department !== void 0 ? {
|
|
327
|
+
department: query.department
|
|
328
|
+
} : void 0
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// src/resources/certificates.ts
|
|
334
|
+
var CertificatesResource = class extends BaseResource {
|
|
335
|
+
static {
|
|
336
|
+
__name(this, "CertificatesResource");
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* GET /v1/certificates/{id}
|
|
340
|
+
* Returns certificate metadata by ID. 404 if not found or cross-tenant.
|
|
341
|
+
*/
|
|
342
|
+
retrieve(id) {
|
|
343
|
+
return this._http.request({
|
|
344
|
+
method: "GET",
|
|
345
|
+
path: `/v1/certificates/${id}`
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* GET /v1/certificates
|
|
350
|
+
* Returns a cursor-paginated list of certificate metadata.
|
|
351
|
+
* Soft-deleted certificates are excluded.
|
|
352
|
+
*/
|
|
353
|
+
list(params) {
|
|
354
|
+
const query = {};
|
|
355
|
+
if (params?.limit !== void 0) query["limit"] = params.limit;
|
|
356
|
+
if (params?.starting_after !== void 0) query["starting_after"] = params.starting_after;
|
|
357
|
+
return this._http.request({
|
|
358
|
+
method: "GET",
|
|
359
|
+
path: "/v1/certificates",
|
|
360
|
+
query: Object.keys(query).length > 0 ? query : void 0
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* POST /v1/certificates — Upload a new .p12 certificate.
|
|
365
|
+
* Returns 201. No auto-idempotency (openapi.json: no Idempotency-Key header required).
|
|
366
|
+
* Explicit override accepted via opts.idempotencyKey.
|
|
367
|
+
*/
|
|
368
|
+
create(body, opts) {
|
|
369
|
+
return this._http.request({
|
|
370
|
+
method: "POST",
|
|
371
|
+
path: "/v1/certificates",
|
|
372
|
+
body,
|
|
373
|
+
idempotencyKey: opts?.idempotencyKey
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* DELETE /v1/certificates/{id} — Soft-delete a certificate.
|
|
378
|
+
* Returns 200 with the deleted CertificateResponse (not 204). No idempotency-key.
|
|
379
|
+
*/
|
|
380
|
+
del(id) {
|
|
381
|
+
return this._http.request({
|
|
382
|
+
method: "DELETE",
|
|
383
|
+
path: `/v1/certificates/${id}`
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* POST /v1/certificates/{id}/activate — Set this certificate as the active signing cert.
|
|
388
|
+
* Returns 200 with updated CertificateResponse. No idempotency-key.
|
|
389
|
+
*/
|
|
390
|
+
activate(id) {
|
|
391
|
+
return this._http.request({
|
|
392
|
+
method: "POST",
|
|
393
|
+
path: `/v1/certificates/${id}/activate`
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// src/resources/credit-notes.ts
|
|
399
|
+
var CreditNotesResource = class extends BaseResource {
|
|
400
|
+
static {
|
|
401
|
+
__name(this, "CreditNotesResource");
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* POST /v1/credit-notes — Create a draft credit note.
|
|
405
|
+
* Returns 202. Auto-generates idempotency-key (R03).
|
|
406
|
+
*/
|
|
407
|
+
create(body, opts) {
|
|
408
|
+
return this._http.request({
|
|
409
|
+
method: "POST",
|
|
410
|
+
path: "/v1/credit-notes",
|
|
411
|
+
body,
|
|
412
|
+
idempotencyKey: opts?.idempotencyKey,
|
|
413
|
+
autoIdempotency: opts?.idempotencyKey === void 0
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* GET /v1/credit-notes/{id} — Retrieve credit note by ID (detail including DIAN response).
|
|
418
|
+
* No idempotency-key.
|
|
419
|
+
*/
|
|
420
|
+
retrieve(id) {
|
|
421
|
+
return this._http.request({
|
|
422
|
+
method: "GET",
|
|
423
|
+
path: `/v1/credit-notes/${id}`
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* GET /v1/credit-notes — List credit notes (cursor-paginated, filterable).
|
|
428
|
+
* No idempotency-key.
|
|
429
|
+
*/
|
|
430
|
+
list(params) {
|
|
431
|
+
const query = {};
|
|
432
|
+
if (params?.limit !== void 0) query["limit"] = params.limit;
|
|
433
|
+
if (params?.starting_after !== void 0) query["starting_after"] = params.starting_after;
|
|
434
|
+
if (params?.status !== void 0) query["status"] = params.status;
|
|
435
|
+
if (params?.["created[gte]"] !== void 0) query["created[gte]"] = params["created[gte]"];
|
|
436
|
+
if (params?.["created[lt]"] !== void 0) query["created[lt]"] = params["created[lt]"];
|
|
437
|
+
return this._http.request({
|
|
438
|
+
method: "GET",
|
|
439
|
+
path: "/v1/credit-notes",
|
|
440
|
+
query: Object.keys(query).length > 0 ? query : void 0
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* POST /v1/credit-notes/{id}/send-email — Manually resend the credit note email.
|
|
445
|
+
* Returns 202. Auto-generates idempotency-key (R03).
|
|
446
|
+
*/
|
|
447
|
+
sendEmail(id, body, opts) {
|
|
448
|
+
return this._http.request({
|
|
449
|
+
method: "POST",
|
|
450
|
+
path: `/v1/credit-notes/${id}/send-email`,
|
|
451
|
+
body: body ?? {},
|
|
452
|
+
idempotencyKey: opts?.idempotencyKey,
|
|
453
|
+
autoIdempotency: opts?.idempotencyKey === void 0
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* GET /v1/credit-notes/{id}/email-content — Get email content.
|
|
458
|
+
* No idempotency-key.
|
|
459
|
+
*/
|
|
460
|
+
emailContent(id) {
|
|
461
|
+
return this._http.request({
|
|
462
|
+
method: "GET",
|
|
463
|
+
path: `/v1/credit-notes/${id}/email-content`
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* GET /v1/credit-notes/{id}/emails — List email delivery attempts.
|
|
468
|
+
* No idempotency-key.
|
|
469
|
+
*/
|
|
470
|
+
listEmails(id) {
|
|
471
|
+
return this._http.request({
|
|
472
|
+
method: "GET",
|
|
473
|
+
path: `/v1/credit-notes/${id}/emails`
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// src/resources/customers.ts
|
|
479
|
+
var CustomersResource = class extends BaseResource {
|
|
480
|
+
static {
|
|
481
|
+
__name(this, "CustomersResource");
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* GET /v1/customers/{id}
|
|
485
|
+
* Returns a customer by public ID. 404 if not found or soft-deleted.
|
|
486
|
+
*/
|
|
487
|
+
retrieve(id) {
|
|
488
|
+
return this._http.request({
|
|
489
|
+
method: "GET",
|
|
490
|
+
path: `/v1/customers/${id}`
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* GET /v1/customers
|
|
495
|
+
* Returns a cursor-paginated list of customers.
|
|
496
|
+
*/
|
|
497
|
+
list(params) {
|
|
498
|
+
const query = {};
|
|
499
|
+
if (params?.limit !== void 0) query["limit"] = params.limit;
|
|
500
|
+
if (params?.starting_after !== void 0) query["starting_after"] = params.starting_after;
|
|
501
|
+
return this._http.request({
|
|
502
|
+
method: "GET",
|
|
503
|
+
path: "/v1/customers",
|
|
504
|
+
query: Object.keys(query).length > 0 ? query : void 0
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* POST /v1/customers — Create a customer.
|
|
509
|
+
* Returns 201. Auto-generates idempotency-key (R03 — openapi.json marks Idempotency-Key required).
|
|
510
|
+
*/
|
|
511
|
+
create(body, opts) {
|
|
512
|
+
return this._http.request({
|
|
513
|
+
method: "POST",
|
|
514
|
+
path: "/v1/customers",
|
|
515
|
+
body,
|
|
516
|
+
idempotencyKey: opts?.idempotencyKey,
|
|
517
|
+
autoIdempotency: opts?.idempotencyKey === void 0
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
/**
|
|
521
|
+
* PATCH /v1/customers/{id} — Update a customer.
|
|
522
|
+
* Returns 200 with updated CustomerResponse. No idempotency-key.
|
|
523
|
+
*/
|
|
524
|
+
update(id, body) {
|
|
525
|
+
return this._http.request({
|
|
526
|
+
method: "PATCH",
|
|
527
|
+
path: `/v1/customers/${id}`,
|
|
528
|
+
body
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* DELETE /v1/customers/{id} — Soft-delete a customer.
|
|
533
|
+
* Returns 200 with the deleted CustomerResponse (not 204). No idempotency-key.
|
|
534
|
+
*/
|
|
535
|
+
del(id) {
|
|
536
|
+
return this._http.request({
|
|
537
|
+
method: "DELETE",
|
|
538
|
+
path: `/v1/customers/${id}`
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
// src/resources/debit-notes.ts
|
|
544
|
+
var DebitNotesResource = class extends BaseResource {
|
|
545
|
+
static {
|
|
546
|
+
__name(this, "DebitNotesResource");
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* POST /v1/debit-notes — Create a draft debit note.
|
|
550
|
+
* Returns 202. Auto-generates idempotency-key (R03).
|
|
551
|
+
*/
|
|
552
|
+
create(body, opts) {
|
|
553
|
+
return this._http.request({
|
|
554
|
+
method: "POST",
|
|
555
|
+
path: "/v1/debit-notes",
|
|
556
|
+
body,
|
|
557
|
+
idempotencyKey: opts?.idempotencyKey,
|
|
558
|
+
autoIdempotency: opts?.idempotencyKey === void 0
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* GET /v1/debit-notes/{id} — Retrieve debit note by ID (detail including DIAN response).
|
|
563
|
+
* No idempotency-key.
|
|
564
|
+
*/
|
|
565
|
+
retrieve(id) {
|
|
566
|
+
return this._http.request({
|
|
567
|
+
method: "GET",
|
|
568
|
+
path: `/v1/debit-notes/${id}`
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* GET /v1/debit-notes — List debit notes (cursor-paginated, filterable).
|
|
573
|
+
* No idempotency-key.
|
|
574
|
+
*/
|
|
575
|
+
list(params) {
|
|
576
|
+
const query = {};
|
|
577
|
+
if (params?.limit !== void 0) query["limit"] = params.limit;
|
|
578
|
+
if (params?.starting_after !== void 0) query["starting_after"] = params.starting_after;
|
|
579
|
+
if (params?.status !== void 0) query["status"] = params.status;
|
|
580
|
+
if (params?.["created[gte]"] !== void 0) query["created[gte]"] = params["created[gte]"];
|
|
581
|
+
if (params?.["created[lt]"] !== void 0) query["created[lt]"] = params["created[lt]"];
|
|
582
|
+
return this._http.request({
|
|
583
|
+
method: "GET",
|
|
584
|
+
path: "/v1/debit-notes",
|
|
585
|
+
query: Object.keys(query).length > 0 ? query : void 0
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* POST /v1/debit-notes/{id}/send-email — Manually resend the debit note email.
|
|
590
|
+
* Returns 202. Auto-generates idempotency-key (R03).
|
|
591
|
+
*/
|
|
592
|
+
sendEmail(id, body, opts) {
|
|
593
|
+
return this._http.request({
|
|
594
|
+
method: "POST",
|
|
595
|
+
path: `/v1/debit-notes/${id}/send-email`,
|
|
596
|
+
body: body ?? {},
|
|
597
|
+
idempotencyKey: opts?.idempotencyKey,
|
|
598
|
+
autoIdempotency: opts?.idempotencyKey === void 0
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* GET /v1/debit-notes/{id}/email-content — Get email content.
|
|
603
|
+
* No idempotency-key.
|
|
604
|
+
*/
|
|
605
|
+
emailContent(id) {
|
|
606
|
+
return this._http.request({
|
|
607
|
+
method: "GET",
|
|
608
|
+
path: `/v1/debit-notes/${id}/email-content`
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* GET /v1/debit-notes/{id}/emails — List email delivery attempts.
|
|
613
|
+
* No idempotency-key.
|
|
614
|
+
*/
|
|
615
|
+
listEmails(id) {
|
|
616
|
+
return this._http.request({
|
|
617
|
+
method: "GET",
|
|
618
|
+
path: `/v1/debit-notes/${id}/emails`
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
// src/resources/invoices.ts
|
|
624
|
+
var InvoicesResource = class extends BaseResource {
|
|
625
|
+
static {
|
|
626
|
+
__name(this, "InvoicesResource");
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* POST /v1/invoices — Create a draft invoice.
|
|
630
|
+
* Returns 202. Auto-generates idempotency-key (R03).
|
|
631
|
+
*/
|
|
632
|
+
create(body, opts) {
|
|
633
|
+
return this._http.request({
|
|
634
|
+
method: "POST",
|
|
635
|
+
path: "/v1/invoices",
|
|
636
|
+
body,
|
|
637
|
+
idempotencyKey: opts?.idempotencyKey,
|
|
638
|
+
autoIdempotency: opts?.idempotencyKey === void 0
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* GET /v1/invoices/{id} — Retrieve invoice by ID (detail including lines).
|
|
643
|
+
* No idempotency-key.
|
|
644
|
+
*/
|
|
645
|
+
retrieve(id) {
|
|
646
|
+
return this._http.request({
|
|
647
|
+
method: "GET",
|
|
648
|
+
path: `/v1/invoices/${id}`
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* GET /v1/invoices — List invoices (cursor-paginated, filterable).
|
|
653
|
+
* No idempotency-key.
|
|
654
|
+
*/
|
|
655
|
+
list(params) {
|
|
656
|
+
const query = {};
|
|
657
|
+
if (params?.limit !== void 0) query["limit"] = params.limit;
|
|
658
|
+
if (params?.starting_after !== void 0) query["starting_after"] = params.starting_after;
|
|
659
|
+
if (params?.status !== void 0) query["status"] = params.status;
|
|
660
|
+
if (params?.customer !== void 0) query["customer"] = params.customer;
|
|
661
|
+
if (params?.["created[gte]"] !== void 0) query["created[gte]"] = params["created[gte]"];
|
|
662
|
+
if (params?.["created[lt]"] !== void 0) query["created[lt]"] = params["created[lt]"];
|
|
663
|
+
if (params?.number_prefix !== void 0) query["number_prefix"] = params.number_prefix;
|
|
664
|
+
if (params?.cufe !== void 0) query["cufe"] = params.cufe;
|
|
665
|
+
return this._http.request({
|
|
666
|
+
method: "GET",
|
|
667
|
+
path: "/v1/invoices",
|
|
668
|
+
query: Object.keys(query).length > 0 ? query : void 0
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* POST /v1/invoices/{id}/send-email — Manually resend the invoice email.
|
|
673
|
+
* Returns 202. Auto-generates idempotency-key (R03).
|
|
674
|
+
*/
|
|
675
|
+
sendEmail(id, body, opts) {
|
|
676
|
+
return this._http.request({
|
|
677
|
+
method: "POST",
|
|
678
|
+
path: `/v1/invoices/${id}/send-email`,
|
|
679
|
+
body: body ?? {},
|
|
680
|
+
idempotencyKey: opts?.idempotencyKey,
|
|
681
|
+
autoIdempotency: opts?.idempotencyKey === void 0
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* GET /v1/invoices/{id}/email-content — Get email content (recipient, subject, AttachedDoc).
|
|
686
|
+
* No idempotency-key.
|
|
687
|
+
*/
|
|
688
|
+
emailContent(id) {
|
|
689
|
+
return this._http.request({
|
|
690
|
+
method: "GET",
|
|
691
|
+
path: `/v1/invoices/${id}/email-content`
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* GET /v1/invoices/{id}/emails — List email delivery attempts.
|
|
696
|
+
* No idempotency-key.
|
|
697
|
+
*/
|
|
698
|
+
listEmails(id) {
|
|
699
|
+
return this._http.request({
|
|
700
|
+
method: "GET",
|
|
701
|
+
path: `/v1/invoices/${id}/emails`
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
};
|
|
705
|
+
|
|
706
|
+
// src/resources/numbering-resolutions.ts
|
|
707
|
+
var NumberingResolutionsResource = class extends BaseResource {
|
|
708
|
+
static {
|
|
709
|
+
__name(this, "NumberingResolutionsResource");
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* GET /v1/numbering-resolutions/{id}
|
|
713
|
+
* Returns a numbering resolution by ID. 404 if not found or cross-tenant.
|
|
714
|
+
*/
|
|
715
|
+
retrieve(id) {
|
|
716
|
+
return this._http.request({
|
|
717
|
+
method: "GET",
|
|
718
|
+
path: `/v1/numbering-resolutions/${id}`
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* GET /v1/numbering-resolutions
|
|
723
|
+
* Returns a cursor-paginated list of numbering resolutions.
|
|
724
|
+
*/
|
|
725
|
+
list(params) {
|
|
726
|
+
const query = {};
|
|
727
|
+
if (params?.limit !== void 0) query["limit"] = params.limit;
|
|
728
|
+
if (params?.starting_after !== void 0) query["starting_after"] = params.starting_after;
|
|
729
|
+
return this._http.request({
|
|
730
|
+
method: "GET",
|
|
731
|
+
path: "/v1/numbering-resolutions",
|
|
732
|
+
query: Object.keys(query).length > 0 ? query : void 0
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* POST /v1/numbering-resolutions — Register a new DIAN resolution.
|
|
737
|
+
* Returns 201. Auto-generates idempotency-key (R03 — openapi.json marks Idempotency-Key required).
|
|
738
|
+
*/
|
|
739
|
+
create(body, opts) {
|
|
740
|
+
return this._http.request({
|
|
741
|
+
method: "POST",
|
|
742
|
+
path: "/v1/numbering-resolutions",
|
|
743
|
+
body,
|
|
744
|
+
idempotencyKey: opts?.idempotencyKey,
|
|
745
|
+
autoIdempotency: opts?.idempotencyKey === void 0
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* POST /v1/numbering-resolutions/{id}/activate — Mark this resolution as active.
|
|
750
|
+
* Returns 200 with updated NumberingResolutionResponse. No idempotency-key.
|
|
751
|
+
*/
|
|
752
|
+
activate(id) {
|
|
753
|
+
return this._http.request({
|
|
754
|
+
method: "POST",
|
|
755
|
+
path: `/v1/numbering-resolutions/${id}/activate`
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
// src/resources/test-resource.ts
|
|
761
|
+
var TestResource = class extends BaseResource {
|
|
762
|
+
static {
|
|
763
|
+
__name(this, "TestResource");
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* POST /v1/test/trigger-event — Manually fire a webhook event in test mode.
|
|
767
|
+
* Returns 201 with TriggerEventResponse.
|
|
768
|
+
* Throws PermissionError (403) if called with a live API key.
|
|
769
|
+
* No idempotency-key.
|
|
770
|
+
*/
|
|
771
|
+
triggerEvent(body) {
|
|
772
|
+
return this._http.request({
|
|
773
|
+
method: "POST",
|
|
774
|
+
path: "/v1/test/trigger-event",
|
|
775
|
+
body
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
|
|
780
|
+
// src/client.ts
|
|
781
|
+
var Emitia = class {
|
|
782
|
+
static {
|
|
783
|
+
__name(this, "Emitia");
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Private HTTP transport.
|
|
787
|
+
* Using a true private field (#http) so it cannot be accessed externally.
|
|
788
|
+
*/
|
|
789
|
+
#http;
|
|
790
|
+
// ── Read-only resources (PR2 — fully wired) ───────────────────────────────────
|
|
791
|
+
account;
|
|
792
|
+
balance;
|
|
793
|
+
catalogs;
|
|
794
|
+
// ── Write resources (PR3b — read + write fully wired) ────────────────────────
|
|
795
|
+
certificates;
|
|
796
|
+
numberingResolutions;
|
|
797
|
+
customers;
|
|
798
|
+
// ── Document resources (PR3a — fully wired) ──────────────────────────────────
|
|
799
|
+
invoices;
|
|
800
|
+
creditNotes;
|
|
801
|
+
debitNotes;
|
|
802
|
+
// ── Test resource (PR3b) ──────────────────────────────────────────────────────
|
|
803
|
+
test;
|
|
804
|
+
constructor(apiKey, opts) {
|
|
805
|
+
const config = validateConfig(apiKey, opts);
|
|
806
|
+
this.#http = new HttpClient(config);
|
|
807
|
+
this.account = new AccountResource(this.#http);
|
|
808
|
+
this.balance = new BalanceResource(this.#http);
|
|
809
|
+
this.catalogs = new CatalogsResource(this.#http);
|
|
810
|
+
this.certificates = new CertificatesResource(this.#http);
|
|
811
|
+
this.numberingResolutions = new NumberingResolutionsResource(this.#http);
|
|
812
|
+
this.customers = new CustomersResource(this.#http);
|
|
813
|
+
this.invoices = new InvoicesResource(this.#http);
|
|
814
|
+
this.creditNotes = new CreditNotesResource(this.#http);
|
|
815
|
+
this.debitNotes = new DebitNotesResource(this.#http);
|
|
816
|
+
this.test = new TestResource(this.#http);
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
var SignatureVerificationError = class extends Error {
|
|
820
|
+
static {
|
|
821
|
+
__name(this, "SignatureVerificationError");
|
|
822
|
+
}
|
|
823
|
+
reason;
|
|
824
|
+
constructor(reason, message) {
|
|
825
|
+
super(message);
|
|
826
|
+
this.name = "SignatureVerificationError";
|
|
827
|
+
this.reason = reason;
|
|
828
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
function parseSignatureHeader(header) {
|
|
832
|
+
if (!header) return null;
|
|
833
|
+
const parts = header.split(",");
|
|
834
|
+
let t;
|
|
835
|
+
const v1Values = [];
|
|
836
|
+
for (const part of parts) {
|
|
837
|
+
const trimmed = part.trim();
|
|
838
|
+
if (trimmed.startsWith("t=")) {
|
|
839
|
+
const raw = trimmed.slice(2);
|
|
840
|
+
if (!/^\d+$/.test(raw)) {
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
t = Number(raw);
|
|
844
|
+
} else if (trimmed.startsWith("v1=")) {
|
|
845
|
+
const value = trimmed.slice(3);
|
|
846
|
+
if (value) v1Values.push(value);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
if (t === void 0 || v1Values.length === 0) return null;
|
|
850
|
+
return {
|
|
851
|
+
t,
|
|
852
|
+
v1Values
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
__name(parseSignatureHeader, "parseSignatureHeader");
|
|
856
|
+
function constructEvent(payload, signatureHeader, secret, toleranceSec = 300, nowSec = () => Math.floor(Date.now() / 1e3)) {
|
|
857
|
+
const parsed = parseSignatureHeader(signatureHeader);
|
|
858
|
+
if (parsed === null) {
|
|
859
|
+
throw new SignatureVerificationError("no_signature", `Invalid or missing Emitia-Signature header: "${signatureHeader}". Expected format: t=<unix_seconds>,v1=<hex_hmac>`);
|
|
860
|
+
}
|
|
861
|
+
const { t, v1Values } = parsed;
|
|
862
|
+
const now = nowSec();
|
|
863
|
+
const drift = Math.abs(now - t);
|
|
864
|
+
if (drift > toleranceSec) {
|
|
865
|
+
throw new SignatureVerificationError("timestamp_out_of_tolerance", `Webhook timestamp is too old or too far in the future. Timestamp: ${t}, now: ${now}, drift: ${drift}s, tolerance: ${toleranceSec}s. Ensure your server clock is synchronized and that this event is not a replay.`);
|
|
866
|
+
}
|
|
867
|
+
const payloadString = Buffer.isBuffer(payload) ? payload.toString("utf8") : payload;
|
|
868
|
+
const signedPayload = `${t}.${payloadString}`;
|
|
869
|
+
const expectedHmac = createHmac("sha256", secret).update(signedPayload).digest();
|
|
870
|
+
const matched = v1Values.some((v1) => {
|
|
871
|
+
const v1Buffer = Buffer.from(v1, "hex");
|
|
872
|
+
if (v1Buffer.length !== expectedHmac.length) return false;
|
|
873
|
+
return timingSafeEqual(v1Buffer, expectedHmac);
|
|
874
|
+
});
|
|
875
|
+
if (!matched) {
|
|
876
|
+
throw new SignatureVerificationError("signature_mismatch", "Webhook signature verification failed. The computed HMAC does not match any v1 signature in the header. Verify you are using the correct signing secret and that the raw (unparsed) request body is passed to constructEvent().");
|
|
877
|
+
}
|
|
878
|
+
try {
|
|
879
|
+
return JSON.parse(payloadString);
|
|
880
|
+
} catch {
|
|
881
|
+
throw new SignatureVerificationError("invalid_payload", "Webhook signature verified, but the request body is not valid JSON.");
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
__name(constructEvent, "constructEvent");
|
|
885
|
+
|
|
886
|
+
export { ApiError, AuthenticationError, CertificateError, DianRejectedError, DianUnavailableError, Emitia, EmitiaError, IdempotencyError, NotFoundError, NumberingError, PermissionError, RateLimitError, SDK_VERSION, SignatureVerificationError, ValidationError, constructEvent, fromPayload, isErrorEnvelope };
|
|
887
|
+
//# sourceMappingURL=index.js.map
|
|
888
|
+
//# sourceMappingURL=index.js.map
|