@deuriib/better-dgii-ecf 0.0.0-stage → 0.0.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.es.md +159 -0
- package/README.md +196 -2
- package/dist/client.cjs +11 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +31 -0
- package/dist/client.d.ts +31 -0
- package/dist/client.js +9 -0
- package/dist/client.js.map +1 -0
- package/dist/index.cjs +1337 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +40 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +1307 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin-BVYTmUJU.d.cts +1058 -0
- package/dist/plugin-BVYTmUJU.d.ts +1058 -0
- package/package.json +89 -4
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1337 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var api = require('better-auth/api');
|
|
4
|
+
var error = require('@better-auth/core/error');
|
|
5
|
+
var dgiiEcf$1 = require('@deuriib/dgii-ecf');
|
|
6
|
+
var errorCodes = require('@better-auth/core/utils/error-codes');
|
|
7
|
+
var crypto = require('crypto');
|
|
8
|
+
var z = require('zod');
|
|
9
|
+
|
|
10
|
+
function _interopNamespace(e) {
|
|
11
|
+
if (e && e.__esModule) return e;
|
|
12
|
+
var n = Object.create(null);
|
|
13
|
+
if (e) {
|
|
14
|
+
Object.keys(e).forEach(function (k) {
|
|
15
|
+
if (k !== 'default') {
|
|
16
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
17
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
get: function () { return e[k]; }
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
n.default = e;
|
|
25
|
+
return Object.freeze(n);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
var z__namespace = /*#__PURE__*/_interopNamespace(z);
|
|
29
|
+
|
|
30
|
+
// src/client-provider.ts
|
|
31
|
+
var DGII_ECF_ERROR_CODES = errorCodes.defineErrorCodes({
|
|
32
|
+
DGII_SESSION_REQUIRED: "A session is required to call a DGII e-CF endpoint",
|
|
33
|
+
DGII_TAXPAYER_ID_REQUIRED: "No taxpayer id (RNC) could be resolved: send `taxPayerId`, set the organization field, or configure `resolveTaxPayerId`",
|
|
34
|
+
DGII_TAXPAYER_ID_INVALID: "The taxpayer id is not a valid DGII RNC (9 digits) or c\xE9dula (11 digits)",
|
|
35
|
+
DGII_TAXPAYER_ID_MISMATCH: "The signed document was issued under a taxpayer id other than the one resolved for this request",
|
|
36
|
+
DGII_TAXPAYER_ID_FORBIDDEN: "This session is not allowed to act on behalf of the requested taxpayer id",
|
|
37
|
+
DGII_ORGANIZATION_NOT_FOUND: "The organization in scope was not found",
|
|
38
|
+
DGII_ORGANIZATION_REQUIRED: "This request needs an organization: send `organizationId` or select an active organization",
|
|
39
|
+
DGII_NOT_A_MEMBER: "The session user is not a member of the organization in scope",
|
|
40
|
+
DGII_INSUFFICIENT_ROLE: "The session user does not hold a role allowed to perform this DGII operation",
|
|
41
|
+
DGII_CREDENTIALS_UNAVAILABLE: "No DGII credentials could be resolved for this taxpayer id",
|
|
42
|
+
DGII_CLIENT_NOT_CONFIGURED: "The plugin was configured without `credentials` or `getClient`, so it cannot reach DGII",
|
|
43
|
+
DGII_STATUS_API_KEY_REQUIRED: "The DGII status service needs an API key: configure `status.apiKey`",
|
|
44
|
+
DGII_DOCUMENT_UNREADABLE: "The signed document carries no issuer taxpayer id (RNCEmisor / RncEmisor)",
|
|
45
|
+
DGII_SIGNING_FAILED: "The document could not be signed with the organization key",
|
|
46
|
+
DGII_REQUEST_FAILED: "The DGII request failed",
|
|
47
|
+
DGII_SUBMISSION_NOT_FOUND: "No stored submission matches this id",
|
|
48
|
+
DGII_PERSISTENCE_UNAVAILABLE: "Submission persistence is disabled, so this operation is unavailable",
|
|
49
|
+
DGII_IDEMPOTENCY_IN_FLIGHT: "A submission with this idempotency key is still in flight or has an unknown outcome; reconcile it before retrying"
|
|
50
|
+
});
|
|
51
|
+
var STATUS_BY_DGII_CODE = {
|
|
52
|
+
INVALID_XML: "BAD_REQUEST",
|
|
53
|
+
SIGNING: "BAD_REQUEST",
|
|
54
|
+
VALIDATION: "BAD_REQUEST",
|
|
55
|
+
CERTIFICATE: "INTERNAL_SERVER_ERROR",
|
|
56
|
+
IDEMPOTENCY: "CONFLICT",
|
|
57
|
+
AUTH: "BAD_GATEWAY",
|
|
58
|
+
HTTP: "BAD_GATEWAY",
|
|
59
|
+
TIMEOUT: "BAD_GATEWAY",
|
|
60
|
+
CIRCUIT_OPEN: "BAD_GATEWAY",
|
|
61
|
+
UNKNOWN: "BAD_GATEWAY"
|
|
62
|
+
};
|
|
63
|
+
var toFailureDetail = (error) => ({
|
|
64
|
+
code: error.code,
|
|
65
|
+
status: error.status,
|
|
66
|
+
statusText: error.statusText,
|
|
67
|
+
resource: error.resource,
|
|
68
|
+
method: error.method,
|
|
69
|
+
mensajes: error.mensajes
|
|
70
|
+
});
|
|
71
|
+
var badRequest = (error, message) => new api.APIError("BAD_REQUEST", {
|
|
72
|
+
message: message ?? error.message,
|
|
73
|
+
code: error.code
|
|
74
|
+
});
|
|
75
|
+
var forbidden = (error, message) => new api.APIError("FORBIDDEN", {
|
|
76
|
+
message: message ?? error.message,
|
|
77
|
+
code: error.code
|
|
78
|
+
});
|
|
79
|
+
var conflict = (error, message) => new api.APIError("CONFLICT", {
|
|
80
|
+
message: message ?? error.message,
|
|
81
|
+
code: error.code
|
|
82
|
+
});
|
|
83
|
+
var serverError = (error, message) => new api.APIError("INTERNAL_SERVER_ERROR", {
|
|
84
|
+
message: message ?? error.message,
|
|
85
|
+
code: error.code
|
|
86
|
+
});
|
|
87
|
+
var toApiError = (error) => new api.APIError(STATUS_BY_DGII_CODE[error.code], {
|
|
88
|
+
message: error.message,
|
|
89
|
+
code: DGII_ECF_ERROR_CODES.DGII_REQUEST_FAILED.code,
|
|
90
|
+
dgii: toFailureDetail(error)
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// src/client-provider.ts
|
|
94
|
+
var DEFAULT_MAX_SIZE = 50;
|
|
95
|
+
var DEFAULT_TTL_MS = 10 * 60 * 1e3;
|
|
96
|
+
var cacheKey = (scope) => `${scope.environment}\0${scope.taxPayerId}`;
|
|
97
|
+
var unavailable = (taxPayerId) => new api.APIError("INTERNAL_SERVER_ERROR", {
|
|
98
|
+
message: `No DGII credentials were resolved for taxpayer ${taxPayerId}`,
|
|
99
|
+
code: DGII_ECF_ERROR_CODES.DGII_CREDENTIALS_UNAVAILABLE.code
|
|
100
|
+
});
|
|
101
|
+
var createClientProvider = (options, now = Date.now) => {
|
|
102
|
+
const getClient = options.getClient;
|
|
103
|
+
if (getClient !== void 0) {
|
|
104
|
+
return {
|
|
105
|
+
async get(scope) {
|
|
106
|
+
const client = await getClient(scope);
|
|
107
|
+
if (client === null) {
|
|
108
|
+
throw unavailable(scope.taxPayerId);
|
|
109
|
+
}
|
|
110
|
+
return client;
|
|
111
|
+
},
|
|
112
|
+
invalidate() {
|
|
113
|
+
},
|
|
114
|
+
size: 0,
|
|
115
|
+
constructsClients: false
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
const resolveCredentials = options.credentials;
|
|
119
|
+
if (resolveCredentials === void 0) {
|
|
120
|
+
throw new error.BetterAuthError(
|
|
121
|
+
"[better-dgii-ecf] Configure `credentials` or `getClient` \u2014 the plugin cannot reach DGII without one of them."
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
const maxSize = Math.max(1, options.clientCache?.maxSize ?? DEFAULT_MAX_SIZE);
|
|
125
|
+
const ttlMs = Math.max(0, options.clientCache?.ttlMs ?? DEFAULT_TTL_MS);
|
|
126
|
+
const cache = /* @__PURE__ */ new Map();
|
|
127
|
+
const evictExpired = (at) => {
|
|
128
|
+
for (const [key, entry] of cache) {
|
|
129
|
+
if (entry.expiresAt <= at) {
|
|
130
|
+
cache.delete(key);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
return {
|
|
135
|
+
async get(scope) {
|
|
136
|
+
const at = now();
|
|
137
|
+
evictExpired(at);
|
|
138
|
+
const key = cacheKey(scope);
|
|
139
|
+
const cached = cache.get(key);
|
|
140
|
+
if (cached !== void 0 && cached.expiresAt > at) {
|
|
141
|
+
cache.delete(key);
|
|
142
|
+
cache.set(key, cached);
|
|
143
|
+
return cached.client;
|
|
144
|
+
}
|
|
145
|
+
const material = await resolveCredentials(scope);
|
|
146
|
+
if (material === null) {
|
|
147
|
+
throw unavailable(scope.taxPayerId);
|
|
148
|
+
}
|
|
149
|
+
if (material.key === void 0 || material.cert === void 0) {
|
|
150
|
+
throw new api.APIError("INTERNAL_SERVER_ERROR", {
|
|
151
|
+
message: `The credentials resolved for taxpayer ${scope.taxPayerId} carry no key/cert PEM pair`,
|
|
152
|
+
code: DGII_ECF_ERROR_CODES.DGII_CREDENTIALS_UNAVAILABLE.code
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const statusApiKey = options.status?.apiKey;
|
|
156
|
+
const client = new dgiiEcf$1.ECF(material, scope.environment, {
|
|
157
|
+
...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs },
|
|
158
|
+
...statusApiKey === void 0 ? {} : { statusApiKey }
|
|
159
|
+
});
|
|
160
|
+
if (ttlMs > 0) {
|
|
161
|
+
while (cache.size >= maxSize) {
|
|
162
|
+
const oldest = cache.keys().next();
|
|
163
|
+
if (oldest.done === true) {
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
cache.delete(oldest.value);
|
|
167
|
+
}
|
|
168
|
+
cache.set(key, { client, expiresAt: at + ttlMs });
|
|
169
|
+
}
|
|
170
|
+
return client;
|
|
171
|
+
},
|
|
172
|
+
invalidate(taxPayerId) {
|
|
173
|
+
if (taxPayerId === void 0) {
|
|
174
|
+
cache.clear();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
for (const key of cache.keys()) {
|
|
178
|
+
if (key.endsWith(`\0${taxPayerId}`)) {
|
|
179
|
+
cache.delete(key);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
get size() {
|
|
184
|
+
return cache.size;
|
|
185
|
+
},
|
|
186
|
+
constructsClients: true
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
var SUBMISSION_MODEL = "dgiiEcfSubmission";
|
|
190
|
+
var DEFAULT_MAX_RESPONSE_LENGTH = 8192;
|
|
191
|
+
var hashDocument = (xml) => crypto.createHash("sha256").update(xml, "utf8").digest("hex");
|
|
192
|
+
var serializeResponse = (value, maxLength) => {
|
|
193
|
+
if (value === void 0 || value === null) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
let serialized;
|
|
197
|
+
try {
|
|
198
|
+
serialized = JSON.stringify(value);
|
|
199
|
+
} catch {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
if (serialized === void 0) {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
return serialized.length > maxLength ? serialized.slice(0, maxLength) : serialized;
|
|
206
|
+
};
|
|
207
|
+
var disabledStore = {
|
|
208
|
+
enabled: false,
|
|
209
|
+
async findReplay() {
|
|
210
|
+
return null;
|
|
211
|
+
},
|
|
212
|
+
async begin() {
|
|
213
|
+
return null;
|
|
214
|
+
},
|
|
215
|
+
async complete() {
|
|
216
|
+
},
|
|
217
|
+
async list() {
|
|
218
|
+
throw new api.APIError("NOT_IMPLEMENTED", {
|
|
219
|
+
message: DGII_ECF_ERROR_CODES.DGII_PERSISTENCE_UNAVAILABLE.message,
|
|
220
|
+
code: DGII_ECF_ERROR_CODES.DGII_PERSISTENCE_UNAVAILABLE.code
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
var createSubmissionStore = (ctx, options) => {
|
|
225
|
+
if (options?.enabled === false) {
|
|
226
|
+
return disabledStore;
|
|
227
|
+
}
|
|
228
|
+
const adapter = ctx.context.adapter;
|
|
229
|
+
const storeResponses = options?.storeResponses ?? true;
|
|
230
|
+
const maxResponseLength = options?.maxResponseLength ?? DEFAULT_MAX_RESPONSE_LENGTH;
|
|
231
|
+
return {
|
|
232
|
+
enabled: true,
|
|
233
|
+
async findReplay(taxPayerId, idempotencyKey) {
|
|
234
|
+
const rows = await adapter.findMany({
|
|
235
|
+
model: SUBMISSION_MODEL,
|
|
236
|
+
where: [
|
|
237
|
+
{ field: "taxPayerId", value: taxPayerId },
|
|
238
|
+
{ field: "idempotencyKey", value: idempotencyKey }
|
|
239
|
+
],
|
|
240
|
+
limit: 1,
|
|
241
|
+
sortBy: { field: "createdAt", direction: "desc" }
|
|
242
|
+
});
|
|
243
|
+
return rows[0] ?? null;
|
|
244
|
+
},
|
|
245
|
+
async begin(input) {
|
|
246
|
+
const created = await adapter.create({
|
|
247
|
+
model: SUBMISSION_MODEL,
|
|
248
|
+
data: {
|
|
249
|
+
organizationId: input.organizationId,
|
|
250
|
+
taxPayerId: input.taxPayerId,
|
|
251
|
+
userId: input.userId,
|
|
252
|
+
environment: input.environment,
|
|
253
|
+
operation: input.operation,
|
|
254
|
+
status: "pending",
|
|
255
|
+
idempotencyKey: input.idempotencyKey,
|
|
256
|
+
encf: input.encf,
|
|
257
|
+
trackId: null,
|
|
258
|
+
documentHash: input.documentHash,
|
|
259
|
+
errorCode: null,
|
|
260
|
+
response: null
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
return created.id;
|
|
264
|
+
},
|
|
265
|
+
async complete(id, outcome) {
|
|
266
|
+
if (id === null) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
await adapter.update({
|
|
270
|
+
model: SUBMISSION_MODEL,
|
|
271
|
+
where: [{ field: "id", value: id }],
|
|
272
|
+
update: {
|
|
273
|
+
status: outcome.status,
|
|
274
|
+
trackId: outcome.trackId ?? null,
|
|
275
|
+
errorCode: outcome.errorCode ?? null,
|
|
276
|
+
response: storeResponses ? serializeResponse(outcome.response, maxResponseLength) : null
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
},
|
|
280
|
+
async list({ organizationId, taxPayerId, limit }) {
|
|
281
|
+
const where = [];
|
|
282
|
+
if (organizationId !== null) {
|
|
283
|
+
where.push({ field: "organizationId", value: organizationId });
|
|
284
|
+
}
|
|
285
|
+
if (taxPayerId !== null) {
|
|
286
|
+
where.push({ field: "taxPayerId", value: taxPayerId });
|
|
287
|
+
}
|
|
288
|
+
return adapter.findMany({
|
|
289
|
+
model: SUBMISSION_MODEL,
|
|
290
|
+
...where.length > 0 ? { where } : {},
|
|
291
|
+
limit,
|
|
292
|
+
sortBy: { field: "createdAt", direction: "desc" }
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
};
|
|
297
|
+
var planReplay = (record) => {
|
|
298
|
+
if (record === null) {
|
|
299
|
+
return { kind: "proceed" };
|
|
300
|
+
}
|
|
301
|
+
if (record.status === "succeeded") {
|
|
302
|
+
return { kind: "replay", record };
|
|
303
|
+
}
|
|
304
|
+
if (record.status === "pending") {
|
|
305
|
+
return { kind: "conflict" };
|
|
306
|
+
}
|
|
307
|
+
return { kind: "proceed" };
|
|
308
|
+
};
|
|
309
|
+
var parseStoredResponse = (record) => {
|
|
310
|
+
const raw = record.response;
|
|
311
|
+
if (typeof raw !== "string" || raw === "") {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
try {
|
|
315
|
+
return JSON.parse(raw);
|
|
316
|
+
} catch {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// src/tax-payer.ts
|
|
322
|
+
var SEPARATORS = /[\s.\-–—]/g;
|
|
323
|
+
var SHAPE = /^(?:\d{9}|\d{11})$/;
|
|
324
|
+
var normalizeTaxPayerId = (value) => value.replace(SEPARATORS, "").trim();
|
|
325
|
+
var isTaxPayerIdShape = (value) => SHAPE.test(value);
|
|
326
|
+
var TaxPayerIdError = class extends Error {
|
|
327
|
+
reason;
|
|
328
|
+
constructor(reason, message) {
|
|
329
|
+
super(message);
|
|
330
|
+
this.name = "TaxPayerIdError";
|
|
331
|
+
this.reason = reason;
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
var applyTaxPayerIdValidation = async (raw, validation) => {
|
|
335
|
+
const normalized = normalizeTaxPayerId(raw);
|
|
336
|
+
if (normalized === "") {
|
|
337
|
+
throw new TaxPayerIdError("shape", "The taxpayer id is empty");
|
|
338
|
+
}
|
|
339
|
+
if (validation === "off") {
|
|
340
|
+
return normalized;
|
|
341
|
+
}
|
|
342
|
+
if (validation === "format") {
|
|
343
|
+
if (!isTaxPayerIdShape(normalized)) {
|
|
344
|
+
throw new TaxPayerIdError(
|
|
345
|
+
"shape",
|
|
346
|
+
`"${raw}" is not a DGII RNC (9 digits) or c\xE9dula (11 digits)`
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
return normalized;
|
|
350
|
+
}
|
|
351
|
+
const verdict = await validation(normalized);
|
|
352
|
+
if (verdict === false) {
|
|
353
|
+
throw new TaxPayerIdError("custom", `"${raw}" was rejected by the validator`);
|
|
354
|
+
}
|
|
355
|
+
if (typeof verdict === "string") {
|
|
356
|
+
return normalizeTaxPayerId(verdict);
|
|
357
|
+
}
|
|
358
|
+
return normalized;
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// src/resolution.ts
|
|
362
|
+
var ORGANIZATION_MODEL = "organization";
|
|
363
|
+
var MEMBER_MODEL = "member";
|
|
364
|
+
var DEFAULT_ROLES = ["owner", "admin"];
|
|
365
|
+
var hasOrganizationPlugin = (ctx) => (ctx.context.options.plugins ?? []).some((plugin) => plugin.id === "organization");
|
|
366
|
+
var readOrganizationField = (row, field) => {
|
|
367
|
+
const raw = row[field] ?? row.taxPayerId ?? row.tax_payer_id;
|
|
368
|
+
if (typeof raw !== "string") {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
const normalized = normalizeTaxPayerId(raw);
|
|
372
|
+
return normalized === "" ? null : normalized;
|
|
373
|
+
};
|
|
374
|
+
var readActiveOrganizationId = (session) => {
|
|
375
|
+
const raw = session.activeOrganizationId;
|
|
376
|
+
return typeof raw === "string" && raw !== "" ? raw : null;
|
|
377
|
+
};
|
|
378
|
+
var rolesOf = (member) => String(member.role ?? "").split(",").map((role) => role.trim()).filter((role) => role !== "");
|
|
379
|
+
var environmentOf = async (options, scope) => {
|
|
380
|
+
const configured = options.environment ?? dgiiEcf$1.Environment.Dev;
|
|
381
|
+
if (typeof configured === "function") {
|
|
382
|
+
return configured({
|
|
383
|
+
taxPayerId: scope.taxPayerId,
|
|
384
|
+
organizationId: scope.organizationId,
|
|
385
|
+
userId: scope.user.id
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
return configured;
|
|
389
|
+
};
|
|
390
|
+
var resolveRequestScope = async ({
|
|
391
|
+
ctx,
|
|
392
|
+
options,
|
|
393
|
+
input
|
|
394
|
+
}) => {
|
|
395
|
+
const authenticated = ctx.context.session;
|
|
396
|
+
if (authenticated === null) {
|
|
397
|
+
throw new api.APIError("UNAUTHORIZED", {
|
|
398
|
+
message: DGII_ECF_ERROR_CODES.DGII_SESSION_REQUIRED.message,
|
|
399
|
+
code: DGII_ECF_ERROR_CODES.DGII_SESSION_REQUIRED.code
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
const { user, session } = authenticated;
|
|
403
|
+
const withOrganization = hasOrganizationPlugin(ctx);
|
|
404
|
+
const organizationId = input.organizationId ?? (withOrganization ? readActiveOrganizationId(session) : null);
|
|
405
|
+
let organization = null;
|
|
406
|
+
let organizationTaxPayerId = null;
|
|
407
|
+
if (withOrganization && organizationId !== null) {
|
|
408
|
+
organization = await ctx.context.adapter.findOne({
|
|
409
|
+
model: ORGANIZATION_MODEL,
|
|
410
|
+
where: [{ field: "id", value: organizationId }]
|
|
411
|
+
});
|
|
412
|
+
if (organization === null) {
|
|
413
|
+
throw new api.APIError("NOT_FOUND", {
|
|
414
|
+
message: DGII_ECF_ERROR_CODES.DGII_ORGANIZATION_NOT_FOUND.message,
|
|
415
|
+
code: DGII_ECF_ERROR_CODES.DGII_ORGANIZATION_NOT_FOUND.code
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
organizationTaxPayerId = readOrganizationField(
|
|
419
|
+
organization,
|
|
420
|
+
options.organization?.field ?? "taxPayerId"
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
const validation = options.validateTaxPayerId ?? "format";
|
|
424
|
+
const toCanonical = async (raw) => {
|
|
425
|
+
try {
|
|
426
|
+
return await applyTaxPayerIdValidation(raw, validation);
|
|
427
|
+
} catch (error) {
|
|
428
|
+
if (error instanceof TaxPayerIdError) {
|
|
429
|
+
throw badRequest(DGII_ECF_ERROR_CODES.DGII_TAXPAYER_ID_INVALID, error.message);
|
|
430
|
+
}
|
|
431
|
+
throw error;
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
let taxPayerId = null;
|
|
435
|
+
let source = "request";
|
|
436
|
+
if (input.taxPayerId !== void 0) {
|
|
437
|
+
taxPayerId = await toCanonical(input.taxPayerId);
|
|
438
|
+
} else if (organizationTaxPayerId !== null) {
|
|
439
|
+
taxPayerId = await toCanonical(organizationTaxPayerId);
|
|
440
|
+
source = "organization";
|
|
441
|
+
} else {
|
|
442
|
+
const resolver = options.resolveTaxPayerId;
|
|
443
|
+
const fromResolver = resolver === void 0 ? null : await resolver({ user, session, organizationId });
|
|
444
|
+
if (fromResolver !== null) {
|
|
445
|
+
taxPayerId = await toCanonical(fromResolver);
|
|
446
|
+
source = "resolver";
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (taxPayerId === null) {
|
|
450
|
+
throw badRequest(DGII_ECF_ERROR_CODES.DGII_TAXPAYER_ID_REQUIRED);
|
|
451
|
+
}
|
|
452
|
+
await authorize({
|
|
453
|
+
ctx,
|
|
454
|
+
options,
|
|
455
|
+
user,
|
|
456
|
+
session,
|
|
457
|
+
taxPayerId,
|
|
458
|
+
organizationId: withOrganization ? organizationId : null,
|
|
459
|
+
organization,
|
|
460
|
+
organizationTaxPayerId,
|
|
461
|
+
source,
|
|
462
|
+
withOrganization
|
|
463
|
+
});
|
|
464
|
+
const resolved = {
|
|
465
|
+
taxPayerId,
|
|
466
|
+
organizationId: withOrganization ? organizationId : null,
|
|
467
|
+
organizationTaxPayerId,
|
|
468
|
+
source,
|
|
469
|
+
userId: user.id,
|
|
470
|
+
user,
|
|
471
|
+
session
|
|
472
|
+
};
|
|
473
|
+
return { ...resolved, environment: await environmentOf(options, resolved) };
|
|
474
|
+
};
|
|
475
|
+
var authorize = async (input) => {
|
|
476
|
+
const custom = input.options.authorize;
|
|
477
|
+
if (custom !== void 0) {
|
|
478
|
+
const allowed = await custom({
|
|
479
|
+
user: input.user,
|
|
480
|
+
session: input.session,
|
|
481
|
+
taxPayerId: input.taxPayerId,
|
|
482
|
+
organizationId: input.organizationId,
|
|
483
|
+
organizationTaxPayerId: input.organizationTaxPayerId,
|
|
484
|
+
source: input.source
|
|
485
|
+
});
|
|
486
|
+
if (!allowed) {
|
|
487
|
+
throw forbidden(DGII_ECF_ERROR_CODES.DGII_TAXPAYER_ID_FORBIDDEN);
|
|
488
|
+
}
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
if (!input.withOrganization) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (input.organizationId === null || input.organization === null) {
|
|
495
|
+
if (input.source === "request" && input.options.allowUntrustedTaxPayerId === true) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
if (input.source === "resolver") {
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
throw badRequest(DGII_ECF_ERROR_CODES.DGII_ORGANIZATION_REQUIRED);
|
|
502
|
+
}
|
|
503
|
+
const member = await input.ctx.context.adapter.findOne({
|
|
504
|
+
model: MEMBER_MODEL,
|
|
505
|
+
where: [
|
|
506
|
+
{ field: "organizationId", value: input.organizationId },
|
|
507
|
+
{ field: "userId", value: input.user.id }
|
|
508
|
+
]
|
|
509
|
+
});
|
|
510
|
+
if (member === null) {
|
|
511
|
+
throw forbidden(DGII_ECF_ERROR_CODES.DGII_NOT_A_MEMBER);
|
|
512
|
+
}
|
|
513
|
+
const allowedRoles = input.options.roles ?? DEFAULT_ROLES;
|
|
514
|
+
const memberRoles = rolesOf(member);
|
|
515
|
+
if (!memberRoles.some((role) => allowedRoles.includes(role))) {
|
|
516
|
+
throw forbidden(
|
|
517
|
+
DGII_ECF_ERROR_CODES.DGII_INSUFFICIENT_ROLE,
|
|
518
|
+
`${DGII_ECF_ERROR_CODES.DGII_INSUFFICIENT_ROLE.message} (required one of: ${allowedRoles.join(", ")})`
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
if (input.organizationTaxPayerId !== null && input.organizationTaxPayerId !== input.taxPayerId) {
|
|
522
|
+
throw forbidden(
|
|
523
|
+
DGII_ECF_ERROR_CODES.DGII_TAXPAYER_ID_FORBIDDEN,
|
|
524
|
+
`Organization ${input.organizationId} issues under ${input.organizationTaxPayerId}, not ${input.taxPayerId}`
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
// src/xml.ts
|
|
530
|
+
var PATTERN_CACHE = /* @__PURE__ */ new Map();
|
|
531
|
+
var ATTRIBUTE_SPAN = `(?:[^>"']|"[^"]*"|'[^']*')*`;
|
|
532
|
+
var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
533
|
+
var elementPattern = (tag) => {
|
|
534
|
+
const cached = PATTERN_CACHE.get(tag);
|
|
535
|
+
if (cached !== void 0) {
|
|
536
|
+
return cached;
|
|
537
|
+
}
|
|
538
|
+
const name = escapeRegExp(tag);
|
|
539
|
+
const prefix = "(?:[\\w.-]+:)?";
|
|
540
|
+
const pattern = new RegExp(
|
|
541
|
+
`<${prefix}${name}${ATTRIBUTE_SPAN}>([\\s\\S]*?)<\\/${prefix}${name}\\s*>`
|
|
542
|
+
);
|
|
543
|
+
PATTERN_CACHE.set(tag, pattern);
|
|
544
|
+
return pattern;
|
|
545
|
+
};
|
|
546
|
+
var skipNonElement = (xml, open) => {
|
|
547
|
+
if (xml.startsWith("<!--", open)) {
|
|
548
|
+
const end = xml.indexOf("-->", open + 4);
|
|
549
|
+
return end === -1 ? xml.length : end + 3;
|
|
550
|
+
}
|
|
551
|
+
if (xml.startsWith("<?", open)) {
|
|
552
|
+
const end = xml.indexOf("?>", open + 2);
|
|
553
|
+
return end === -1 ? xml.length : end + 2;
|
|
554
|
+
}
|
|
555
|
+
if (xml.startsWith("<!", open)) {
|
|
556
|
+
let depth = 0;
|
|
557
|
+
for (let cursor = open + 2; cursor < xml.length; cursor += 1) {
|
|
558
|
+
const character = xml[cursor];
|
|
559
|
+
if (character === "[") {
|
|
560
|
+
depth += 1;
|
|
561
|
+
} else if (character === "]") {
|
|
562
|
+
depth = Math.max(0, depth - 1);
|
|
563
|
+
} else if (character === ">" && depth === 0) {
|
|
564
|
+
return cursor + 1;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return xml.length;
|
|
568
|
+
}
|
|
569
|
+
return -1;
|
|
570
|
+
};
|
|
571
|
+
var withoutNonElementMarkup = (xml) => {
|
|
572
|
+
const parts = [];
|
|
573
|
+
let index = 0;
|
|
574
|
+
for (; ; ) {
|
|
575
|
+
const open = xml.indexOf("<", index);
|
|
576
|
+
if (open === -1) {
|
|
577
|
+
parts.push(xml.slice(index));
|
|
578
|
+
return parts.join("");
|
|
579
|
+
}
|
|
580
|
+
parts.push(xml.slice(index, open));
|
|
581
|
+
const end = skipNonElement(xml, open);
|
|
582
|
+
if (end === -1) {
|
|
583
|
+
parts.push("<");
|
|
584
|
+
index = open + 1;
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
index = end;
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
var elementText = (xml, tag) => {
|
|
591
|
+
const text = elementPattern(tag).exec(withoutNonElementMarkup(xml))?.[1]?.trim();
|
|
592
|
+
return text === void 0 || text === "" ? void 0 : text;
|
|
593
|
+
};
|
|
594
|
+
var ROOT_PATTERN = /<\s*(?:[A-Za-z_][\w.-]*:)?([A-Za-z_][\w.-]*)[\s/>]/;
|
|
595
|
+
var rootElementName = (xml) => ROOT_PATTERN.exec(withoutNonElementMarkup(xml))?.[1];
|
|
596
|
+
var ISSUER_TAGS = ["RNCEmisor", "RncEmisor"];
|
|
597
|
+
var issuerTaxPayerId = (xml) => {
|
|
598
|
+
for (const tag of ISSUER_TAGS) {
|
|
599
|
+
const value = elementText(xml, tag);
|
|
600
|
+
if (value !== void 0) {
|
|
601
|
+
return value;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
return void 0;
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
// src/endpoints/shared.ts
|
|
608
|
+
var prepare = async (deps, ctx, input) => {
|
|
609
|
+
const scope = await resolveRequestScope({ ctx, options: deps.options, input });
|
|
610
|
+
const client = await deps.provider.get(scope);
|
|
611
|
+
return { scope, client };
|
|
612
|
+
};
|
|
613
|
+
var unwrap = (result) => {
|
|
614
|
+
if (result.isErr()) {
|
|
615
|
+
throw toApiError(result.error);
|
|
616
|
+
}
|
|
617
|
+
return result.value;
|
|
618
|
+
};
|
|
619
|
+
var assertIssuer = (xml, taxPayerId) => {
|
|
620
|
+
const issuer = issuerTaxPayerId(xml);
|
|
621
|
+
if (issuer === void 0) {
|
|
622
|
+
throw badRequest(DGII_ECF_ERROR_CODES.DGII_DOCUMENT_UNREADABLE);
|
|
623
|
+
}
|
|
624
|
+
if (normalizeTaxPayerId(issuer) !== taxPayerId) {
|
|
625
|
+
throw forbidden(
|
|
626
|
+
DGII_ECF_ERROR_CODES.DGII_TAXPAYER_ID_MISMATCH,
|
|
627
|
+
`The document is issued by ${issuer} but this request acts for ${taxPayerId}`
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
var signDocument = async (deps, scope, xml) => {
|
|
632
|
+
const resolver = deps.options.credentials;
|
|
633
|
+
if (resolver === void 0) {
|
|
634
|
+
throw serverError(
|
|
635
|
+
DGII_ECF_ERROR_CODES.DGII_CREDENTIALS_UNAVAILABLE,
|
|
636
|
+
"Server-side signing needs the `credentials` resolver; `getClient` hands the key to the host."
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
const material = await resolver(scope);
|
|
640
|
+
if (material === null) {
|
|
641
|
+
throw serverError(
|
|
642
|
+
DGII_ECF_ERROR_CODES.DGII_CREDENTIALS_UNAVAILABLE,
|
|
643
|
+
`No credentials were resolved for taxpayer ${scope.taxPayerId}`
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
const { key, cert } = material;
|
|
647
|
+
if (key === void 0 || cert === void 0) {
|
|
648
|
+
throw serverError(
|
|
649
|
+
DGII_ECF_ERROR_CODES.DGII_CREDENTIALS_UNAVAILABLE,
|
|
650
|
+
"The resolved credentials carry no `key`/`cert` PEM pair, so the document cannot be signed"
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
const signature = new dgiiEcf$1.Signature(key, cert);
|
|
654
|
+
const root = rootElementName(xml);
|
|
655
|
+
const signed = root === void 0 ? signature.signXml(xml) : signature.signXml(xml, root);
|
|
656
|
+
if (signed.isErr()) {
|
|
657
|
+
throw new api.APIError("BAD_REQUEST", {
|
|
658
|
+
message: signed.error.message,
|
|
659
|
+
code: DGII_ECF_ERROR_CODES.DGII_SIGNING_FAILED.code,
|
|
660
|
+
dgii: { code: signed.error.code }
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
return signed.value;
|
|
664
|
+
};
|
|
665
|
+
var documentEncf = (xml) => elementText(xml, "eNCF") ?? elementText(xml, "SecuenciaeNCFDesde") ?? elementText(xml, "RangoNCF") ?? null;
|
|
666
|
+
var trackIdOf = (value) => {
|
|
667
|
+
if (typeof value !== "object" || value === null) {
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
const candidate = value.trackId;
|
|
671
|
+
return typeof candidate === "string" && candidate !== "" ? candidate : null;
|
|
672
|
+
};
|
|
673
|
+
var runSubmission = async ({
|
|
674
|
+
ctx,
|
|
675
|
+
deps,
|
|
676
|
+
scope,
|
|
677
|
+
operation,
|
|
678
|
+
xml,
|
|
679
|
+
idempotencyKey,
|
|
680
|
+
send
|
|
681
|
+
}) => {
|
|
682
|
+
const store = createSubmissionStore(ctx, deps.options.persistence);
|
|
683
|
+
const documentHash = hashDocument(xml);
|
|
684
|
+
const replayKey = idempotencyKey ?? documentHash;
|
|
685
|
+
if (store.enabled) {
|
|
686
|
+
const existing = await store.findReplay(scope.taxPayerId, replayKey);
|
|
687
|
+
const decision = planReplay(existing);
|
|
688
|
+
if (decision.kind === "conflict") {
|
|
689
|
+
throw conflict(
|
|
690
|
+
DGII_ECF_ERROR_CODES.DGII_IDEMPOTENCY_IN_FLIGHT,
|
|
691
|
+
`A submission for this document is still in flight (record ${existing?.id ?? "unknown"}). Reconcile it by e-NCF before retrying.`
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
if (decision.kind === "replay") {
|
|
695
|
+
return {
|
|
696
|
+
result: parseStoredResponse(decision.record),
|
|
697
|
+
replayed: true,
|
|
698
|
+
submissionId: decision.record.id
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
const submissionId = await store.begin({
|
|
703
|
+
organizationId: scope.organizationId,
|
|
704
|
+
taxPayerId: scope.taxPayerId,
|
|
705
|
+
userId: scope.userId,
|
|
706
|
+
environment: scope.environment,
|
|
707
|
+
operation,
|
|
708
|
+
idempotencyKey: replayKey,
|
|
709
|
+
encf: documentEncf(xml),
|
|
710
|
+
documentHash
|
|
711
|
+
});
|
|
712
|
+
let result;
|
|
713
|
+
try {
|
|
714
|
+
result = unwrap(await send());
|
|
715
|
+
} catch (error) {
|
|
716
|
+
const errorCode = error instanceof api.APIError ? String(error.body?.code ?? error.status) : error instanceof Error ? error.name : "UNKNOWN";
|
|
717
|
+
await store.complete(submissionId, { status: "failed", errorCode });
|
|
718
|
+
throw error;
|
|
719
|
+
}
|
|
720
|
+
await store.complete(submissionId, {
|
|
721
|
+
status: "succeeded",
|
|
722
|
+
trackId: trackIdOf(result),
|
|
723
|
+
response: result
|
|
724
|
+
});
|
|
725
|
+
return { result, replayed: false, submissionId };
|
|
726
|
+
};
|
|
727
|
+
var scopeEcho = (scope) => ({
|
|
728
|
+
taxPayerId: scope.taxPayerId,
|
|
729
|
+
organizationId: scope.organizationId,
|
|
730
|
+
environment: scope.environment
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
// src/endpoints/documents.ts
|
|
734
|
+
var scopeShape = {
|
|
735
|
+
taxPayerId: z__namespace.string().min(1).optional().meta({
|
|
736
|
+
description: "Taxpayer id (RNC / c\xE9dula) to act for. Overrides the organization field for this request only."
|
|
737
|
+
}),
|
|
738
|
+
organizationId: z__namespace.string().min(1).optional().meta({
|
|
739
|
+
description: "Organization to act for. Defaults to the session active organization."
|
|
740
|
+
})
|
|
741
|
+
};
|
|
742
|
+
var documentShape = {
|
|
743
|
+
...scopeShape,
|
|
744
|
+
xml: z__namespace.string().min(1).meta({ description: "The DGII document, as XML." }),
|
|
745
|
+
fileName: z__namespace.string().min(1).max(255).optional().meta({
|
|
746
|
+
description: "Multipart file name sent to DGII. Defaults to the document e-NCF."
|
|
747
|
+
}),
|
|
748
|
+
sign: z__namespace.boolean().optional().meta({
|
|
749
|
+
description: "Sign the XML server-side with the organization key before transmitting. Requires the `credentials` resolver."
|
|
750
|
+
}),
|
|
751
|
+
idempotencyKey: z__namespace.string().min(1).max(200).optional().meta({
|
|
752
|
+
description: "Replay guard key. Defaults to the SHA-256 of the document, so retrying the same document never double-issues."
|
|
753
|
+
})
|
|
754
|
+
};
|
|
755
|
+
var fileNameFor = (xml, fallback) => {
|
|
756
|
+
const encf = documentEncf(xml);
|
|
757
|
+
return encf === null ? `${fallback}.xml` : `${encf}.xml`;
|
|
758
|
+
};
|
|
759
|
+
var sendDocument = (deps) => api.createAuthEndpoint(
|
|
760
|
+
"/dgii-ecf/documents",
|
|
761
|
+
{
|
|
762
|
+
method: "POST",
|
|
763
|
+
body: z__namespace.object({
|
|
764
|
+
...documentShape,
|
|
765
|
+
buyerHost: z__namespace.string().url().optional().meta({
|
|
766
|
+
description: "Deliver directly to the buyer Sender\u2194Receiver host instead of DGII."
|
|
767
|
+
})
|
|
768
|
+
}),
|
|
769
|
+
use: [api.sessionMiddleware]
|
|
770
|
+
},
|
|
771
|
+
async (ctx) => {
|
|
772
|
+
const { scope, client } = await prepare(deps, ctx, ctx.body);
|
|
773
|
+
const xml = ctx.body.sign === true ? await signDocument(deps, scope, ctx.body.xml) : ctx.body.xml;
|
|
774
|
+
assertIssuer(xml, scope.taxPayerId);
|
|
775
|
+
const outcome = await runSubmission({
|
|
776
|
+
ctx,
|
|
777
|
+
deps,
|
|
778
|
+
scope,
|
|
779
|
+
operation: "sendDocument",
|
|
780
|
+
xml,
|
|
781
|
+
idempotencyKey: ctx.body.idempotencyKey,
|
|
782
|
+
send: () => client.sendDocument(
|
|
783
|
+
xml,
|
|
784
|
+
ctx.body.fileName ?? fileNameFor(xml, "document"),
|
|
785
|
+
ctx.body.buyerHost
|
|
786
|
+
)
|
|
787
|
+
});
|
|
788
|
+
return ctx.json({
|
|
789
|
+
...scopeEcho(scope),
|
|
790
|
+
submissionId: outcome.submissionId,
|
|
791
|
+
replayed: outcome.replayed,
|
|
792
|
+
result: outcome.result
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
);
|
|
796
|
+
var sendElectronicDocument = (deps) => api.createAuthEndpoint(
|
|
797
|
+
"/dgii-ecf/documents/electronic",
|
|
798
|
+
{
|
|
799
|
+
method: "POST",
|
|
800
|
+
body: z__namespace.object({
|
|
801
|
+
...documentShape,
|
|
802
|
+
buyerHost: z__namespace.string().url().optional()
|
|
803
|
+
}),
|
|
804
|
+
use: [api.sessionMiddleware]
|
|
805
|
+
},
|
|
806
|
+
async (ctx) => {
|
|
807
|
+
const { scope, client } = await prepare(deps, ctx, ctx.body);
|
|
808
|
+
const xml = ctx.body.sign === true ? await signDocument(deps, scope, ctx.body.xml) : ctx.body.xml;
|
|
809
|
+
assertIssuer(xml, scope.taxPayerId);
|
|
810
|
+
const outcome = await runSubmission({
|
|
811
|
+
ctx,
|
|
812
|
+
deps,
|
|
813
|
+
scope,
|
|
814
|
+
operation: "sendElectronicDocument",
|
|
815
|
+
xml,
|
|
816
|
+
idempotencyKey: ctx.body.idempotencyKey,
|
|
817
|
+
send: () => client.sendElectronicDocument(
|
|
818
|
+
xml,
|
|
819
|
+
ctx.body.fileName ?? fileNameFor(xml, "ecf"),
|
|
820
|
+
ctx.body.buyerHost
|
|
821
|
+
)
|
|
822
|
+
});
|
|
823
|
+
return ctx.json({
|
|
824
|
+
...scopeEcho(scope),
|
|
825
|
+
submissionId: outcome.submissionId,
|
|
826
|
+
replayed: outcome.replayed,
|
|
827
|
+
result: outcome.result
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
);
|
|
831
|
+
var sendSummary = (deps) => api.createAuthEndpoint(
|
|
832
|
+
"/dgii-ecf/documents/summary",
|
|
833
|
+
{
|
|
834
|
+
method: "POST",
|
|
835
|
+
body: z__namespace.object(documentShape),
|
|
836
|
+
use: [api.sessionMiddleware]
|
|
837
|
+
},
|
|
838
|
+
async (ctx) => {
|
|
839
|
+
const { scope, client } = await prepare(deps, ctx, ctx.body);
|
|
840
|
+
const xml = ctx.body.sign === true ? await signDocument(deps, scope, ctx.body.xml) : ctx.body.xml;
|
|
841
|
+
assertIssuer(xml, scope.taxPayerId);
|
|
842
|
+
const outcome = await runSubmission({
|
|
843
|
+
ctx,
|
|
844
|
+
deps,
|
|
845
|
+
scope,
|
|
846
|
+
operation: "sendSummary",
|
|
847
|
+
xml,
|
|
848
|
+
idempotencyKey: ctx.body.idempotencyKey,
|
|
849
|
+
send: () => client.sendSummary(xml, ctx.body.fileName ?? fileNameFor(xml, "summary"))
|
|
850
|
+
});
|
|
851
|
+
return ctx.json({
|
|
852
|
+
...scopeEcho(scope),
|
|
853
|
+
submissionId: outcome.submissionId,
|
|
854
|
+
replayed: outcome.replayed,
|
|
855
|
+
result: outcome.result
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
);
|
|
859
|
+
var sendCommercialApproval = (deps) => api.createAuthEndpoint(
|
|
860
|
+
"/dgii-ecf/documents/commercial-approval",
|
|
861
|
+
{
|
|
862
|
+
method: "POST",
|
|
863
|
+
body: z__namespace.object({
|
|
864
|
+
...documentShape,
|
|
865
|
+
buyerHost: z__namespace.string().url().optional().meta({
|
|
866
|
+
description: "Deliver the approval directly to the issuer host."
|
|
867
|
+
})
|
|
868
|
+
}),
|
|
869
|
+
use: [api.sessionMiddleware]
|
|
870
|
+
},
|
|
871
|
+
async (ctx) => {
|
|
872
|
+
const { scope, client } = await prepare(deps, ctx, ctx.body);
|
|
873
|
+
const xml = ctx.body.sign === true ? await signDocument(deps, scope, ctx.body.xml) : ctx.body.xml;
|
|
874
|
+
assertIssuer(xml, scope.taxPayerId);
|
|
875
|
+
const buyerHost = ctx.body.buyerHost;
|
|
876
|
+
const outcome = await runSubmission({
|
|
877
|
+
ctx,
|
|
878
|
+
deps,
|
|
879
|
+
scope,
|
|
880
|
+
operation: "sendCommercialApproval",
|
|
881
|
+
xml,
|
|
882
|
+
idempotencyKey: ctx.body.idempotencyKey,
|
|
883
|
+
send: () => {
|
|
884
|
+
const fileName = ctx.body.fileName ?? fileNameFor(xml, "approval");
|
|
885
|
+
return buyerHost === void 0 ? client.sendCommercialApproval(xml, fileName) : client.sendCommercialApproval(xml, fileName, buyerHost);
|
|
886
|
+
}
|
|
887
|
+
});
|
|
888
|
+
return ctx.json({
|
|
889
|
+
...scopeEcho(scope),
|
|
890
|
+
submissionId: outcome.submissionId,
|
|
891
|
+
replayed: outcome.replayed,
|
|
892
|
+
result: outcome.result
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
);
|
|
896
|
+
var voidENCF = (deps) => api.createAuthEndpoint(
|
|
897
|
+
"/dgii-ecf/documents/void-range",
|
|
898
|
+
{
|
|
899
|
+
method: "POST",
|
|
900
|
+
body: z__namespace.object(documentShape),
|
|
901
|
+
use: [api.sessionMiddleware]
|
|
902
|
+
},
|
|
903
|
+
async (ctx) => {
|
|
904
|
+
const { scope, client } = await prepare(deps, ctx, ctx.body);
|
|
905
|
+
const xml = ctx.body.sign === true ? await signDocument(deps, scope, ctx.body.xml) : ctx.body.xml;
|
|
906
|
+
assertIssuer(xml, scope.taxPayerId);
|
|
907
|
+
const outcome = await runSubmission({
|
|
908
|
+
ctx,
|
|
909
|
+
deps,
|
|
910
|
+
scope,
|
|
911
|
+
operation: "voidENCF",
|
|
912
|
+
xml,
|
|
913
|
+
idempotencyKey: ctx.body.idempotencyKey,
|
|
914
|
+
send: () => client.voidENCF(xml, ctx.body.fileName ?? fileNameFor(xml, "void-range"))
|
|
915
|
+
});
|
|
916
|
+
return ctx.json({
|
|
917
|
+
...scopeEcho(scope),
|
|
918
|
+
submissionId: outcome.submissionId,
|
|
919
|
+
replayed: outcome.replayed,
|
|
920
|
+
result: outcome.result
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
);
|
|
924
|
+
var reconcileSubmission = (deps) => api.createAuthEndpoint(
|
|
925
|
+
"/dgii-ecf/documents/reconcile",
|
|
926
|
+
{
|
|
927
|
+
method: "POST",
|
|
928
|
+
body: z__namespace.object({
|
|
929
|
+
...scopeShape,
|
|
930
|
+
xml: z__namespace.string().min(1).meta({ description: "The signed document, as XML." })
|
|
931
|
+
}),
|
|
932
|
+
use: [api.sessionMiddleware]
|
|
933
|
+
},
|
|
934
|
+
async (ctx) => {
|
|
935
|
+
const { scope, client } = await prepare(deps, ctx, ctx.body);
|
|
936
|
+
assertIssuer(ctx.body.xml, scope.taxPayerId);
|
|
937
|
+
const result = unwrap(await client.reconcileSubmission(ctx.body.xml));
|
|
938
|
+
return ctx.json({ ...scopeEcho(scope), result });
|
|
939
|
+
}
|
|
940
|
+
);
|
|
941
|
+
var scopeShape2 = {
|
|
942
|
+
taxPayerId: z__namespace.string().min(1).optional().meta({
|
|
943
|
+
description: "Taxpayer id (RNC / c\xE9dula) to act for. Overrides the organization field."
|
|
944
|
+
}),
|
|
945
|
+
organizationId: z__namespace.string().min(1).optional().meta({
|
|
946
|
+
description: "Organization to act for. Defaults to the session active organization."
|
|
947
|
+
})
|
|
948
|
+
};
|
|
949
|
+
var trackStatus = (deps) => api.createAuthEndpoint(
|
|
950
|
+
"/dgii-ecf/tracks",
|
|
951
|
+
{
|
|
952
|
+
method: "GET",
|
|
953
|
+
query: z__namespace.object({
|
|
954
|
+
...scopeShape2,
|
|
955
|
+
trackId: z__namespace.string().min(1).meta({ description: "DGII track id." })
|
|
956
|
+
}),
|
|
957
|
+
use: [api.sessionMiddleware]
|
|
958
|
+
},
|
|
959
|
+
async (ctx) => {
|
|
960
|
+
const { scope, client } = await prepare(deps, ctx, ctx.query);
|
|
961
|
+
const result = unwrap(await client.statusTrackId(ctx.query.trackId));
|
|
962
|
+
return ctx.json({ ...scopeEcho(scope), result });
|
|
963
|
+
}
|
|
964
|
+
);
|
|
965
|
+
var inquiryStatus = (deps) => api.createAuthEndpoint(
|
|
966
|
+
"/dgii-ecf/inquiries/status",
|
|
967
|
+
{
|
|
968
|
+
method: "POST",
|
|
969
|
+
body: z__namespace.object({
|
|
970
|
+
...scopeShape2,
|
|
971
|
+
ncf: z__namespace.string().min(1).meta({ description: "The e-NCF to look up." }),
|
|
972
|
+
buyerTaxPayerId: z__namespace.string().min(1).optional().meta({ description: "Buyer RNC, when DGII needs it to disambiguate." }),
|
|
973
|
+
securityCode: z__namespace.string().min(1).optional().meta({ description: "Six-digit security code of the document." })
|
|
974
|
+
}),
|
|
975
|
+
use: [api.sessionMiddleware]
|
|
976
|
+
},
|
|
977
|
+
async (ctx) => {
|
|
978
|
+
const { scope, client } = await prepare(deps, ctx, ctx.body);
|
|
979
|
+
const result = unwrap(
|
|
980
|
+
await client.inquiryStatus(
|
|
981
|
+
scope.taxPayerId,
|
|
982
|
+
ctx.body.ncf,
|
|
983
|
+
ctx.body.buyerTaxPayerId,
|
|
984
|
+
ctx.body.securityCode
|
|
985
|
+
)
|
|
986
|
+
);
|
|
987
|
+
return ctx.json({ ...scopeEcho(scope), result });
|
|
988
|
+
}
|
|
989
|
+
);
|
|
990
|
+
var trackStatuses = (deps) => api.createAuthEndpoint(
|
|
991
|
+
"/dgii-ecf/inquiries/tracks",
|
|
992
|
+
{
|
|
993
|
+
method: "POST",
|
|
994
|
+
body: z__namespace.object({
|
|
995
|
+
...scopeShape2,
|
|
996
|
+
encf: z__namespace.string().min(1).meta({ description: "The e-NCF to list tracks for." })
|
|
997
|
+
}),
|
|
998
|
+
use: [api.sessionMiddleware]
|
|
999
|
+
},
|
|
1000
|
+
async (ctx) => {
|
|
1001
|
+
const { scope, client } = await prepare(deps, ctx, ctx.body);
|
|
1002
|
+
const result = unwrap(
|
|
1003
|
+
await client.trackStatuses(scope.taxPayerId, ctx.body.encf)
|
|
1004
|
+
);
|
|
1005
|
+
return ctx.json({ ...scopeEcho(scope), result });
|
|
1006
|
+
}
|
|
1007
|
+
);
|
|
1008
|
+
var summaryInvoiceInquiry = (deps) => api.createAuthEndpoint(
|
|
1009
|
+
"/dgii-ecf/inquiries/summary",
|
|
1010
|
+
{
|
|
1011
|
+
method: "POST",
|
|
1012
|
+
body: z__namespace.object({
|
|
1013
|
+
...scopeShape2,
|
|
1014
|
+
encf: z__namespace.string().min(1).meta({ description: "The RFCE e-NCF to look up." }),
|
|
1015
|
+
securityCode: z__namespace.string().min(1).meta({ description: "Six-digit security code of the summary invoice." })
|
|
1016
|
+
}),
|
|
1017
|
+
use: [api.sessionMiddleware]
|
|
1018
|
+
},
|
|
1019
|
+
async (ctx) => {
|
|
1020
|
+
const { scope, client } = await prepare(deps, ctx, ctx.body);
|
|
1021
|
+
const result = unwrap(
|
|
1022
|
+
await client.getSummaryInvoiceInquiry(
|
|
1023
|
+
scope.taxPayerId,
|
|
1024
|
+
ctx.body.encf,
|
|
1025
|
+
ctx.body.securityCode
|
|
1026
|
+
)
|
|
1027
|
+
);
|
|
1028
|
+
return ctx.json({ ...scopeEcho(scope), result });
|
|
1029
|
+
}
|
|
1030
|
+
);
|
|
1031
|
+
var customerDirectory = (deps) => api.createAuthEndpoint(
|
|
1032
|
+
"/dgii-ecf/directories/customers",
|
|
1033
|
+
{
|
|
1034
|
+
method: "GET",
|
|
1035
|
+
query: z__namespace.object({
|
|
1036
|
+
...scopeShape2,
|
|
1037
|
+
rnc: z__namespace.string().min(1).optional().meta({
|
|
1038
|
+
description: "RNC to look up. Defaults to the taxpayer this request acts for."
|
|
1039
|
+
})
|
|
1040
|
+
}),
|
|
1041
|
+
use: [api.sessionMiddleware]
|
|
1042
|
+
},
|
|
1043
|
+
async (ctx) => {
|
|
1044
|
+
const { scope, client } = await prepare(deps, ctx, ctx.query);
|
|
1045
|
+
const result = unwrap(
|
|
1046
|
+
await client.getCustomerDirectory(ctx.query.rnc ?? scope.taxPayerId)
|
|
1047
|
+
);
|
|
1048
|
+
return ctx.json({ ...scopeEcho(scope), result: result ?? [] });
|
|
1049
|
+
}
|
|
1050
|
+
);
|
|
1051
|
+
var directoryListing = (deps) => api.createAuthEndpoint(
|
|
1052
|
+
"/dgii-ecf/directories/all",
|
|
1053
|
+
{
|
|
1054
|
+
method: "GET",
|
|
1055
|
+
query: z__namespace.object(scopeShape2),
|
|
1056
|
+
use: [api.sessionMiddleware]
|
|
1057
|
+
},
|
|
1058
|
+
async (ctx) => {
|
|
1059
|
+
const { scope, client } = await prepare(deps, ctx, ctx.query);
|
|
1060
|
+
const result = unwrap(await client.getDirectoryListing());
|
|
1061
|
+
return ctx.json({ ...scopeEcho(scope), result });
|
|
1062
|
+
}
|
|
1063
|
+
);
|
|
1064
|
+
var scopeShape3 = {
|
|
1065
|
+
taxPayerId: z__namespace.string().min(1).optional().meta({
|
|
1066
|
+
description: "Taxpayer id (RNC / c\xE9dula) to act for. Overrides the organization field."
|
|
1067
|
+
}),
|
|
1068
|
+
organizationId: z__namespace.string().min(1).optional().meta({
|
|
1069
|
+
description: "Organization to act for. Defaults to the session active organization."
|
|
1070
|
+
})
|
|
1071
|
+
};
|
|
1072
|
+
var STATUS_ENVIRONMENTS = {
|
|
1073
|
+
precertification: dgiiEcf$1.StatusEnvironment.PreCertification,
|
|
1074
|
+
production: dgiiEcf$1.StatusEnvironment.Production,
|
|
1075
|
+
certification: dgiiEcf$1.StatusEnvironment.Certification
|
|
1076
|
+
};
|
|
1077
|
+
var requireStatusKey = (deps) => {
|
|
1078
|
+
if (deps.provider.constructsClients && deps.options.status?.apiKey === void 0) {
|
|
1079
|
+
throw serverError(DGII_ECF_ERROR_CODES.DGII_STATUS_API_KEY_REQUIRED);
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
var authenticate = (deps) => api.createAuthEndpoint(
|
|
1083
|
+
"/dgii-ecf/authenticate",
|
|
1084
|
+
{
|
|
1085
|
+
method: "POST",
|
|
1086
|
+
body: z__namespace.object({
|
|
1087
|
+
...scopeShape3,
|
|
1088
|
+
buyerHost: z__namespace.string().url().optional().meta({ description: "Authenticate against a buyer Sender\u2194Receiver host." })
|
|
1089
|
+
}),
|
|
1090
|
+
use: [api.sessionMiddleware]
|
|
1091
|
+
},
|
|
1092
|
+
async (ctx) => {
|
|
1093
|
+
const { scope, client } = await prepare(deps, ctx, ctx.body);
|
|
1094
|
+
const token = unwrap(await client.authenticate(ctx.body.buyerHost));
|
|
1095
|
+
return ctx.json({
|
|
1096
|
+
...scopeEcho(scope),
|
|
1097
|
+
issuedAt: token.expedido,
|
|
1098
|
+
expiresAt: token.expira
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
);
|
|
1102
|
+
var serviceStatus = (deps) => api.createAuthEndpoint(
|
|
1103
|
+
"/dgii-ecf/service-status",
|
|
1104
|
+
{
|
|
1105
|
+
method: "GET",
|
|
1106
|
+
query: z__namespace.object(scopeShape3),
|
|
1107
|
+
use: [api.sessionMiddleware]
|
|
1108
|
+
},
|
|
1109
|
+
async (ctx) => {
|
|
1110
|
+
requireStatusKey(deps);
|
|
1111
|
+
const { scope, client } = await prepare(deps, ctx, ctx.query);
|
|
1112
|
+
const services = unwrap(await client.getServiceStatus());
|
|
1113
|
+
return ctx.json({ ...scopeEcho(scope), services });
|
|
1114
|
+
}
|
|
1115
|
+
);
|
|
1116
|
+
var maintenanceWindows = (deps) => api.createAuthEndpoint(
|
|
1117
|
+
"/dgii-ecf/maintenance-windows",
|
|
1118
|
+
{
|
|
1119
|
+
method: "GET",
|
|
1120
|
+
query: z__namespace.object(scopeShape3),
|
|
1121
|
+
use: [api.sessionMiddleware]
|
|
1122
|
+
},
|
|
1123
|
+
async (ctx) => {
|
|
1124
|
+
requireStatusKey(deps);
|
|
1125
|
+
const { scope, client } = await prepare(deps, ctx, ctx.query);
|
|
1126
|
+
const windows = unwrap(await client.getMaintenanceWindows());
|
|
1127
|
+
return ctx.json({ ...scopeEcho(scope), windows });
|
|
1128
|
+
}
|
|
1129
|
+
);
|
|
1130
|
+
var serviceAvailability = (deps) => api.createAuthEndpoint(
|
|
1131
|
+
"/dgii-ecf/service-availability",
|
|
1132
|
+
{
|
|
1133
|
+
method: "GET",
|
|
1134
|
+
query: z__namespace.object({
|
|
1135
|
+
...scopeShape3,
|
|
1136
|
+
environment: z__namespace.enum(["precertification", "production", "certification"]).meta({ description: "DGII environment to query." })
|
|
1137
|
+
}),
|
|
1138
|
+
use: [api.sessionMiddleware]
|
|
1139
|
+
},
|
|
1140
|
+
async (ctx) => {
|
|
1141
|
+
requireStatusKey(deps);
|
|
1142
|
+
const { scope, client } = await prepare(deps, ctx, ctx.query);
|
|
1143
|
+
const result = unwrap(
|
|
1144
|
+
await client.checkServiceAvailability(
|
|
1145
|
+
STATUS_ENVIRONMENTS[ctx.query.environment]
|
|
1146
|
+
)
|
|
1147
|
+
);
|
|
1148
|
+
return ctx.json({
|
|
1149
|
+
...scopeEcho(scope),
|
|
1150
|
+
environmentChecked: ctx.query.environment,
|
|
1151
|
+
estado: result.estado
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
);
|
|
1155
|
+
var scopeShape4 = {
|
|
1156
|
+
taxPayerId: z__namespace.string().min(1).optional().meta({
|
|
1157
|
+
description: "Taxpayer id (RNC / c\xE9dula) to act for. Overrides the organization field."
|
|
1158
|
+
}),
|
|
1159
|
+
organizationId: z__namespace.string().min(1).optional().meta({
|
|
1160
|
+
description: "Organization to act for. Defaults to the session active organization."
|
|
1161
|
+
})
|
|
1162
|
+
};
|
|
1163
|
+
var resolveTaxPayer = (deps) => api.createAuthEndpoint(
|
|
1164
|
+
"/dgii-ecf/taxpayer",
|
|
1165
|
+
{
|
|
1166
|
+
method: "GET",
|
|
1167
|
+
query: z__namespace.object(scopeShape4),
|
|
1168
|
+
use: [api.sessionMiddleware]
|
|
1169
|
+
},
|
|
1170
|
+
async (ctx) => {
|
|
1171
|
+
const { scope } = await prepare(deps, ctx, ctx.query);
|
|
1172
|
+
return ctx.json({
|
|
1173
|
+
taxPayerId: scope.taxPayerId,
|
|
1174
|
+
organizationId: scope.organizationId,
|
|
1175
|
+
organizationTaxPayerId: scope.organizationTaxPayerId,
|
|
1176
|
+
source: scope.source,
|
|
1177
|
+
environment: scope.environment
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
);
|
|
1181
|
+
var listSubmissions = (deps) => api.createAuthEndpoint(
|
|
1182
|
+
"/dgii-ecf/submissions",
|
|
1183
|
+
{
|
|
1184
|
+
method: "GET",
|
|
1185
|
+
query: z__namespace.object({
|
|
1186
|
+
...scopeShape4,
|
|
1187
|
+
limit: z__namespace.coerce.number().int().min(1).max(100).optional().meta({
|
|
1188
|
+
description: "Maximum number of records to return. Defaults to 20."
|
|
1189
|
+
}),
|
|
1190
|
+
status: z__namespace.enum(["pending", "succeeded", "failed"]).optional().meta({ description: "Filter by record status." })
|
|
1191
|
+
}),
|
|
1192
|
+
use: [api.sessionMiddleware]
|
|
1193
|
+
},
|
|
1194
|
+
async (ctx) => {
|
|
1195
|
+
const { scope } = await prepare(deps, ctx, ctx.query);
|
|
1196
|
+
const store = createSubmissionStore(ctx, deps.options.persistence);
|
|
1197
|
+
const records = await store.list({
|
|
1198
|
+
organizationId: scope.organizationId,
|
|
1199
|
+
taxPayerId: scope.organizationId === null ? scope.taxPayerId : null,
|
|
1200
|
+
limit: Math.min(ctx.query.limit ?? 20, 100)
|
|
1201
|
+
});
|
|
1202
|
+
const filtered = ctx.query.status === void 0 ? records : records.filter((record) => record.status === ctx.query.status);
|
|
1203
|
+
return ctx.json({
|
|
1204
|
+
...scopeEcho(scope),
|
|
1205
|
+
submissions: filtered.map((record) => ({
|
|
1206
|
+
id: record.id,
|
|
1207
|
+
operation: record.operation,
|
|
1208
|
+
status: record.status,
|
|
1209
|
+
taxPayerId: record.taxPayerId,
|
|
1210
|
+
organizationId: record.organizationId ?? null,
|
|
1211
|
+
environment: record.environment,
|
|
1212
|
+
encf: record.encf ?? null,
|
|
1213
|
+
trackId: record.trackId ?? null,
|
|
1214
|
+
documentHash: record.documentHash ?? null,
|
|
1215
|
+
errorCode: record.errorCode ?? null,
|
|
1216
|
+
createdAt: record.createdAt ?? null
|
|
1217
|
+
}))
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
);
|
|
1221
|
+
|
|
1222
|
+
// src/plugin.ts
|
|
1223
|
+
var readonlyTaxPayerIdField = () => ({
|
|
1224
|
+
type: "string",
|
|
1225
|
+
required: false,
|
|
1226
|
+
input: false,
|
|
1227
|
+
returned: true,
|
|
1228
|
+
unique: false
|
|
1229
|
+
});
|
|
1230
|
+
var dgiiEcfOrganizationSchema = (field = "taxPayerId") => ({
|
|
1231
|
+
additionalFields: {
|
|
1232
|
+
[field]: {
|
|
1233
|
+
type: "string",
|
|
1234
|
+
required: false,
|
|
1235
|
+
// Writable: this fragment is declared by the organization plugin, which
|
|
1236
|
+
// is what makes `organization.create` / `organization.update` accept it.
|
|
1237
|
+
input: true,
|
|
1238
|
+
returned: true,
|
|
1239
|
+
unique: false
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
});
|
|
1243
|
+
var dgiiEcf = (options = {}) => {
|
|
1244
|
+
const provider = createClientProvider(options);
|
|
1245
|
+
const deps = { options, provider };
|
|
1246
|
+
const schema = {
|
|
1247
|
+
[SUBMISSION_MODEL]: {
|
|
1248
|
+
modelName: SUBMISSION_MODEL,
|
|
1249
|
+
fields: {
|
|
1250
|
+
organizationId: { type: "string", required: false },
|
|
1251
|
+
taxPayerId: { type: "string", required: true },
|
|
1252
|
+
userId: { type: "string", required: true },
|
|
1253
|
+
environment: { type: "string", required: true },
|
|
1254
|
+
operation: { type: "string", required: true },
|
|
1255
|
+
status: { type: "string", required: true },
|
|
1256
|
+
idempotencyKey: { type: "string", required: false },
|
|
1257
|
+
encf: { type: "string", required: false },
|
|
1258
|
+
trackId: { type: "string", required: false },
|
|
1259
|
+
documentHash: { type: "string", required: false },
|
|
1260
|
+
errorCode: { type: "string", required: false },
|
|
1261
|
+
response: { type: "string", required: false },
|
|
1262
|
+
// Better Auth does not add timestamps implicitly: a plugin table that
|
|
1263
|
+
// wants them declares them. `createdAt` orders the replay lookup and the
|
|
1264
|
+
// audit listing; `updatedAt` records when a submission was closed.
|
|
1265
|
+
createdAt: { type: "date", required: true, defaultValue: () => /* @__PURE__ */ new Date() },
|
|
1266
|
+
updatedAt: { type: "date", required: false, onUpdate: () => /* @__PURE__ */ new Date() }
|
|
1267
|
+
},
|
|
1268
|
+
indexes: [
|
|
1269
|
+
// The replay lookup: taxpayer + key must be a cheap read.
|
|
1270
|
+
{ fields: ["taxPayerId", "idempotencyKey"] },
|
|
1271
|
+
// The audit listing: "everything this organization submitted".
|
|
1272
|
+
{ fields: ["organizationId", "createdAt"] }
|
|
1273
|
+
]
|
|
1274
|
+
}
|
|
1275
|
+
};
|
|
1276
|
+
if (options.organization?.extendSchema === true) {
|
|
1277
|
+
schema.organization = {
|
|
1278
|
+
fields: {
|
|
1279
|
+
[options.organization.field ?? "taxPayerId"]: readonlyTaxPayerIdField()
|
|
1280
|
+
}
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
const plugin = {
|
|
1284
|
+
id: "dgii-ecf",
|
|
1285
|
+
$ERROR_CODES: DGII_ECF_ERROR_CODES,
|
|
1286
|
+
schema,
|
|
1287
|
+
endpoints: {
|
|
1288
|
+
// Identity
|
|
1289
|
+
dgiiEcfResolveTaxPayer: resolveTaxPayer(deps),
|
|
1290
|
+
// Session / token
|
|
1291
|
+
dgiiEcfAuthenticate: authenticate(deps),
|
|
1292
|
+
// Documents
|
|
1293
|
+
dgiiEcfSendDocument: sendDocument(deps),
|
|
1294
|
+
dgiiEcfSendElectronicDocument: sendElectronicDocument(deps),
|
|
1295
|
+
dgiiEcfSendSummary: sendSummary(deps),
|
|
1296
|
+
dgiiEcfSendCommercialApproval: sendCommercialApproval(deps),
|
|
1297
|
+
dgiiEcfVoidENCF: voidENCF(deps),
|
|
1298
|
+
dgiiEcfReconcileSubmission: reconcileSubmission(deps),
|
|
1299
|
+
// Queries
|
|
1300
|
+
dgiiEcfTrackStatus: trackStatus(deps),
|
|
1301
|
+
dgiiEcfInquiryStatus: inquiryStatus(deps),
|
|
1302
|
+
dgiiEcfTrackStatuses: trackStatuses(deps),
|
|
1303
|
+
dgiiEcfSummaryInvoiceInquiry: summaryInvoiceInquiry(deps),
|
|
1304
|
+
dgiiEcfCustomerDirectory: customerDirectory(deps),
|
|
1305
|
+
dgiiEcfDirectoryListing: directoryListing(deps),
|
|
1306
|
+
// DGII operations
|
|
1307
|
+
dgiiEcfServiceStatus: serviceStatus(deps),
|
|
1308
|
+
dgiiEcfMaintenanceWindows: maintenanceWindows(deps),
|
|
1309
|
+
dgiiEcfServiceAvailability: serviceAvailability(deps),
|
|
1310
|
+
// Audit
|
|
1311
|
+
dgiiEcfListSubmissions: listSubmissions(deps)
|
|
1312
|
+
},
|
|
1313
|
+
rateLimit: [
|
|
1314
|
+
{
|
|
1315
|
+
// Fiscal transmissions are metered by DGII and expensive to retry, so
|
|
1316
|
+
// the whole surface gets a conservative per-window budget.
|
|
1317
|
+
window: 60,
|
|
1318
|
+
max: 60,
|
|
1319
|
+
pathMatcher: (path) => path.startsWith("/dgii-ecf")
|
|
1320
|
+
}
|
|
1321
|
+
]
|
|
1322
|
+
};
|
|
1323
|
+
return Object.assign(plugin, { clientProvider: provider });
|
|
1324
|
+
};
|
|
1325
|
+
|
|
1326
|
+
Object.defineProperty(exports, "Environment", {
|
|
1327
|
+
enumerable: true,
|
|
1328
|
+
get: function () { return dgiiEcf$1.Environment; }
|
|
1329
|
+
});
|
|
1330
|
+
exports.DGII_ECF_ERROR_CODES = DGII_ECF_ERROR_CODES;
|
|
1331
|
+
exports.SUBMISSION_MODEL = SUBMISSION_MODEL;
|
|
1332
|
+
exports.dgiiEcf = dgiiEcf;
|
|
1333
|
+
exports.dgiiEcfOrganizationSchema = dgiiEcfOrganizationSchema;
|
|
1334
|
+
exports.isTaxPayerIdShape = isTaxPayerIdShape;
|
|
1335
|
+
exports.normalizeTaxPayerId = normalizeTaxPayerId;
|
|
1336
|
+
//# sourceMappingURL=index.cjs.map
|
|
1337
|
+
//# sourceMappingURL=index.cjs.map
|