facturas 0.7.1 → 0.9.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 +799 -27
- package/dist/{chunk-IOKZX6CA.mjs → chunk-4QVFXT35.mjs} +2 -2
- package/dist/chunk-76WU5BVI.mjs +109 -0
- package/dist/chunk-76WU5BVI.mjs.map +1 -0
- package/dist/{chunk-EDY3PNKJ.mjs → chunk-7LEKQ4P7.mjs} +2 -2
- package/dist/{chunk-OHHXHYLV.mjs → chunk-RCM4NAAG.mjs} +244 -48
- package/dist/chunk-RCM4NAAG.mjs.map +1 -0
- package/dist/{chunk-A3C3Y5PI.mjs → chunk-XNBD5NVL.mjs} +32 -31
- package/dist/chunk-XNBD5NVL.mjs.map +1 -0
- package/dist/{chunk-MBWOFO67.mjs → chunk-ZOSNASXB.mjs} +14 -2
- package/dist/chunk-ZOSNASXB.mjs.map +1 -0
- package/dist/constants.d.ts +49 -1
- package/dist/constants.mjs +9 -1
- package/dist/errors.d.ts +2 -2
- package/dist/errors.mjs +5 -3
- package/dist/{fiscal-evidence-BQuBbJ3h.d.ts → fiscal-evidence-DBzakqBE.d.ts} +11 -3
- package/dist/{index-CVpygFb0.d.ts → index-CVGQyyYM.d.ts} +1 -1
- package/dist/index.d.ts +238 -9
- package/dist/index.mjs +1343 -30
- package/dist/index.mjs.map +1 -1
- package/dist/padron.d.ts +2 -2
- package/dist/padron.mjs +2 -2
- package/dist/{types-BXq0da71.d.ts → types-BiznC1-X.d.ts} +12 -1
- package/dist/types.d.ts +1 -1
- package/dist/wsfe-DF0jAE2k.d.ts +325 -0
- package/dist/wsfe.d.ts +4 -309
- package/dist/wsfe.mjs +4 -4
- package/dist/wsmtxca.d.ts +14 -5
- package/dist/wsmtxca.mjs +3 -3
- package/package.json +1 -1
- package/dist/chunk-A3C3Y5PI.mjs.map +0 -1
- package/dist/chunk-MBWOFO67.mjs.map +0 -1
- package/dist/chunk-OHHXHYLV.mjs.map +0 -1
- package/dist/chunk-VVY2LZIZ.mjs +0 -61
- package/dist/chunk-VVY2LZIZ.mjs.map +0 -1
- /package/dist/{chunk-IOKZX6CA.mjs.map → chunk-4QVFXT35.mjs.map} +0 -0
- /package/dist/{chunk-EDY3PNKJ.mjs.map → chunk-7LEKQ4P7.mjs.map} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -1,16 +1,29 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createPadronService
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-7LEKQ4P7.mjs";
|
|
4
4
|
import {
|
|
5
5
|
buildFacturaB,
|
|
6
6
|
buildFacturaC,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
calculateWsfeAmounts,
|
|
8
|
+
createWsfeService,
|
|
9
|
+
normalizeArcaAmountToMinorUnits,
|
|
10
|
+
normalizeWsfeDateInput,
|
|
11
|
+
normalizeWsfeVoucherInput,
|
|
12
|
+
serializeArcaExchangeRate
|
|
13
|
+
} from "./chunk-RCM4NAAG.mjs";
|
|
14
|
+
import {
|
|
15
|
+
ARCA_CURRENCY_IDS,
|
|
16
|
+
ARCA_DOCUMENT_TYPES,
|
|
17
|
+
ARCA_FINAL_CONSUMER_IDENTIFICATION_THRESHOLD_MINOR_UNITS,
|
|
18
|
+
ARCA_INVOICE_CLASS_BY_ISSUER,
|
|
19
|
+
ARCA_ISSUER_CONDITION_IDS,
|
|
20
|
+
ARCA_RECEIVER_CONDITION_IDS,
|
|
21
|
+
ARCA_VOUCHER_TYPES
|
|
22
|
+
} from "./chunk-76WU5BVI.mjs";
|
|
10
23
|
import {
|
|
11
24
|
createWsmtxcaService
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
25
|
+
} from "./chunk-XNBD5NVL.mjs";
|
|
26
|
+
import "./chunk-4QVFXT35.mjs";
|
|
14
27
|
import {
|
|
15
28
|
ArcaAuthenticationError,
|
|
16
29
|
ArcaConfigurationError,
|
|
@@ -22,8 +35,9 @@ import {
|
|
|
22
35
|
ArcaTransportError,
|
|
23
36
|
createResponseBodyDiagnostic,
|
|
24
37
|
createSafeErrorDiagnostic,
|
|
25
|
-
isArcaAuthenticationError
|
|
26
|
-
|
|
38
|
+
isArcaAuthenticationError,
|
|
39
|
+
toArcaSafeErrorMetadata
|
|
40
|
+
} from "./chunk-ZOSNASXB.mjs";
|
|
27
41
|
|
|
28
42
|
// src/config.ts
|
|
29
43
|
var ARCA_ENVIRONMENTS = ["production", "test"];
|
|
@@ -74,17 +88,7 @@ function assertArcaClientConfig(config) {
|
|
|
74
88
|
"Encrypted private keys are not supported. Provide an unencrypted PKCS#8 or RSA private key PEM."
|
|
75
89
|
);
|
|
76
90
|
}
|
|
77
|
-
|
|
78
|
-
invalidFields.push("taxId");
|
|
79
|
-
}
|
|
80
|
-
if (!normalized.certificatePem.startsWith("-----BEGIN CERTIFICATE-----")) {
|
|
81
|
-
invalidFields.push("certificatePem");
|
|
82
|
-
}
|
|
83
|
-
if (!PRIVATE_KEY_PEM_PREFIXES.some(
|
|
84
|
-
(prefix) => normalized.privateKeyPem.startsWith(prefix)
|
|
85
|
-
)) {
|
|
86
|
-
invalidFields.push("privateKeyPem");
|
|
87
|
-
}
|
|
91
|
+
invalidFields.push(...invalidCredentialFields(normalized));
|
|
88
92
|
if (!ARCA_ENVIRONMENTS.includes(normalized.environment)) {
|
|
89
93
|
invalidFields.push("environment");
|
|
90
94
|
}
|
|
@@ -196,10 +200,11 @@ function normalizeArcaClientConfig(config) {
|
|
|
196
200
|
const normalizedEnvironment = normalizeEnvironmentValue(String(config.environment)) ?? config.environment;
|
|
197
201
|
const normalizedLoggerLevel = normalizeLogLevelValue(config.logger?.level);
|
|
198
202
|
return {
|
|
199
|
-
taxId: config.taxId
|
|
200
|
-
certificatePem: config.certificatePem
|
|
201
|
-
privateKeyPem: config.privateKeyPem
|
|
202
|
-
environment: normalizedEnvironment,
|
|
203
|
+
taxId: config.taxId?.trim() ?? "",
|
|
204
|
+
certificatePem: config.certificatePem?.trim() ?? "",
|
|
205
|
+
privateKeyPem: config.privateKeyPem?.trim() ?? "",
|
|
206
|
+
environment: normalizedEnvironment ?? "test",
|
|
207
|
+
...config.store === void 0 ? {} : { store: config.store },
|
|
203
208
|
timeout: config.timeout ?? DEFAULT_ARCA_TIMEOUT_MS,
|
|
204
209
|
retries: config.retries ?? DEFAULT_ARCA_RETRIES,
|
|
205
210
|
retryDelay: config.retryDelay ?? DEFAULT_ARCA_RETRY_DELAY_MS,
|
|
@@ -235,6 +240,40 @@ function normalizeLogLevelValue(value) {
|
|
|
235
240
|
}
|
|
236
241
|
return value;
|
|
237
242
|
}
|
|
243
|
+
function discoverArcaClientConfig(config) {
|
|
244
|
+
const environment = config.environment ?? readEnv(process.env, ARCA_ENV_VARIABLES.environment);
|
|
245
|
+
if (environment === void 0) {
|
|
246
|
+
throw new ArcaConfigurationError(
|
|
247
|
+
`environment is required: pass environment or set ${ARCA_ENV_VARIABLES.environment} to "test" or "production".`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
...config,
|
|
252
|
+
taxId: config.taxId ?? readEnv(process.env, ARCA_ENV_VARIABLES.taxId) ?? "",
|
|
253
|
+
certificatePem: config.certificatePem ?? readEnv(process.env, ARCA_ENV_VARIABLES.certificatePem) ?? "",
|
|
254
|
+
privateKeyPem: config.privateKeyPem ?? readEnv(process.env, ARCA_ENV_VARIABLES.privateKeyPem) ?? "",
|
|
255
|
+
environment
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function invalidCredentialFields(normalized) {
|
|
259
|
+
const invalidFields = [];
|
|
260
|
+
if (!/^\d{11}$/.test(normalized.taxId)) {
|
|
261
|
+
invalidFields.push(normalized.taxId ? "taxId" : "taxId (ARCA_TAX_ID)");
|
|
262
|
+
}
|
|
263
|
+
if (!normalized.certificatePem.startsWith("-----BEGIN CERTIFICATE-----")) {
|
|
264
|
+
invalidFields.push(
|
|
265
|
+
normalized.certificatePem ? "certificatePem" : "certificatePem (ARCA_CERTIFICATE_PEM)"
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
if (!PRIVATE_KEY_PEM_PREFIXES.some(
|
|
269
|
+
(prefix) => normalized.privateKeyPem.startsWith(prefix)
|
|
270
|
+
)) {
|
|
271
|
+
invalidFields.push(
|
|
272
|
+
normalized.privateKeyPem ? "privateKeyPem" : "privateKeyPem (ARCA_PRIVATE_KEY_PEM)"
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
return invalidFields;
|
|
276
|
+
}
|
|
238
277
|
|
|
239
278
|
// src/internal/logger.ts
|
|
240
279
|
var ARCA_LOG_LEVELS = ["debug", "info", "warn", "error"];
|
|
@@ -287,6 +326,1076 @@ function defaultArcaLog(level, message, ...args) {
|
|
|
287
326
|
method(message, ...args);
|
|
288
327
|
}
|
|
289
328
|
|
|
329
|
+
// src/store/types.ts
|
|
330
|
+
import { createHash } from "crypto";
|
|
331
|
+
function attemptKey(environment, taxId, key) {
|
|
332
|
+
return `arca:v1:attempt:${environment}:${taxId}:${key}`;
|
|
333
|
+
}
|
|
334
|
+
function canonicalHash(input) {
|
|
335
|
+
return createHash("sha256").update(JSON.stringify(canonical(input))).digest("hex");
|
|
336
|
+
}
|
|
337
|
+
function canonical(value) {
|
|
338
|
+
if (Array.isArray(value)) {
|
|
339
|
+
return value.map(canonical);
|
|
340
|
+
}
|
|
341
|
+
if (value instanceof Date) {
|
|
342
|
+
return value.toJSON();
|
|
343
|
+
}
|
|
344
|
+
if (value !== null && typeof value === "object") {
|
|
345
|
+
return Object.fromEntries(
|
|
346
|
+
Object.entries(value).filter(([, item]) => item !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => [key, canonical(item)])
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
return value;
|
|
350
|
+
}
|
|
351
|
+
async function storeCall(fn) {
|
|
352
|
+
try {
|
|
353
|
+
return await fn();
|
|
354
|
+
} catch (cause) {
|
|
355
|
+
throw new ArcaConfigurationError("ARCA store operation failed.", { cause });
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/services/wsfe-derive.ts
|
|
360
|
+
var INVOICE_TYPES = {
|
|
361
|
+
A: ARCA_VOUCHER_TYPES.FACTURA_A,
|
|
362
|
+
B: ARCA_VOUCHER_TYPES.FACTURA_B,
|
|
363
|
+
C: ARCA_VOUCHER_TYPES.FACTURA_C
|
|
364
|
+
};
|
|
365
|
+
function deriveWsfeInvoice(input, now = /* @__PURE__ */ new Date()) {
|
|
366
|
+
assertIssueObject(input, "input");
|
|
367
|
+
assertIssueKeys(
|
|
368
|
+
input,
|
|
369
|
+
[
|
|
370
|
+
"issuer",
|
|
371
|
+
"items",
|
|
372
|
+
"salesPoint",
|
|
373
|
+
"to",
|
|
374
|
+
"total",
|
|
375
|
+
"date",
|
|
376
|
+
"currency",
|
|
377
|
+
"exchangeRate",
|
|
378
|
+
"service"
|
|
379
|
+
],
|
|
380
|
+
"input"
|
|
381
|
+
);
|
|
382
|
+
if (typeof input.issuer !== "string" || !Object.hasOwn(ARCA_ISSUER_CONDITION_IDS, input.issuer)) {
|
|
383
|
+
invalid(
|
|
384
|
+
"issuer",
|
|
385
|
+
"responsable_inscripto, monotributo, exento, or no_alcanzado"
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
if (!Number.isSafeInteger(input.salesPoint) || input.salesPoint < 1 || input.salesPoint > 99999) {
|
|
389
|
+
invalid("salesPoint", "an integer from 1 through 99999");
|
|
390
|
+
}
|
|
391
|
+
const receiver = deriveReceiver(input.to);
|
|
392
|
+
const voucherClass = ARCA_INVOICE_CLASS_BY_ISSUER[input.issuer][input.to.condition];
|
|
393
|
+
const { data: amountsData, amounts } = calculateWsfeAmounts(input);
|
|
394
|
+
const currency = deriveCurrency(input);
|
|
395
|
+
if (input.to.condition === "consumidor_final" && receiver.documentType === ARCA_DOCUMENT_TYPES.CONSUMIDOR_FINAL) {
|
|
396
|
+
const [whole, fraction = ""] = currency.exchangeRate.split(".");
|
|
397
|
+
const rate = BigInt(whole) * 1000000n + BigInt(fraction.padEnd(6, "0"));
|
|
398
|
+
if (BigInt(amounts.sentTotal) * rate >= ARCA_FINAL_CONSUMER_IDENTIFICATION_THRESHOLD_MINOR_UNITS * 1000000n) {
|
|
399
|
+
throw new ArcaInputError(
|
|
400
|
+
"The final consumer must be identified for this amount.",
|
|
401
|
+
{
|
|
402
|
+
code: "ARCA_INPUT_MISSING_FIELD",
|
|
403
|
+
field: "to",
|
|
404
|
+
expected: "cuit or dni for operations at or above ARS 10,000,000 (RG 5866)"
|
|
405
|
+
}
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const voucherDate = normalizeWsfeDateInput(
|
|
410
|
+
input.date === void 0 ? buenosAiresDate(now) : input.date,
|
|
411
|
+
"date"
|
|
412
|
+
);
|
|
413
|
+
const data = {
|
|
414
|
+
salesPoint: input.salesPoint,
|
|
415
|
+
voucherType: INVOICE_TYPES[voucherClass],
|
|
416
|
+
voucherDate,
|
|
417
|
+
...receiver,
|
|
418
|
+
...currency,
|
|
419
|
+
...amountsData,
|
|
420
|
+
...deriveService(input.service, voucherDate)
|
|
421
|
+
};
|
|
422
|
+
try {
|
|
423
|
+
normalizeWsfeVoucherInput(data);
|
|
424
|
+
} catch (cause) {
|
|
425
|
+
throw new ArcaError(
|
|
426
|
+
"The derived invoice failed exact WSFE validation. This is an SDK invariant failure.",
|
|
427
|
+
"ARCA_ISSUE_INVARIANT",
|
|
428
|
+
{ cause }
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
return { data, voucherClass, amounts };
|
|
432
|
+
}
|
|
433
|
+
function deriveReceiver(to) {
|
|
434
|
+
assertIssueObject(to, "to");
|
|
435
|
+
assertIssueKeys(to, ["condition", "cuit", "dni"], "to");
|
|
436
|
+
if (typeof to.condition !== "string" || !Object.hasOwn(ARCA_RECEIVER_CONDITION_IDS, to.condition)) {
|
|
437
|
+
invalid("to.condition", "one of the five supported receiver conditions");
|
|
438
|
+
}
|
|
439
|
+
if (to.cuit !== void 0 && to.dni !== void 0) {
|
|
440
|
+
invalid("to", "either cuit or dni, never both");
|
|
441
|
+
}
|
|
442
|
+
if (to.condition !== "consumidor_final" && to.cuit === void 0) {
|
|
443
|
+
throw new ArcaInputError(
|
|
444
|
+
"to.cuit is required for this receiver (WSFE 10063 for class A).",
|
|
445
|
+
{
|
|
446
|
+
code: "ARCA_INPUT_MISSING_FIELD",
|
|
447
|
+
field: "to.cuit",
|
|
448
|
+
expected: "an 11-digit CUIT"
|
|
449
|
+
}
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
const documentType = to.cuit === void 0 ? to.dni === void 0 ? ARCA_DOCUMENT_TYPES.CONSUMIDOR_FINAL : ARCA_DOCUMENT_TYPES.DNI : ARCA_DOCUMENT_TYPES.CUIT;
|
|
453
|
+
const documentNumber = to.cuit === void 0 ? to.dni === void 0 ? 0 : issueDocumentNumber(to.dni, "to.dni", 1, 11) : issueDocumentNumber(to.cuit, "to.cuit", 11, 11);
|
|
454
|
+
return {
|
|
455
|
+
documentType,
|
|
456
|
+
documentNumber,
|
|
457
|
+
receiverVatConditionId: ARCA_RECEIVER_CONDITION_IDS[to.condition]
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
function issueDocumentNumber(value, field, min, max) {
|
|
461
|
+
if (typeof value !== "number" && typeof value !== "string") {
|
|
462
|
+
invalid(field, `a positive document number with ${min} to ${max} digits`);
|
|
463
|
+
}
|
|
464
|
+
const text = String(value);
|
|
465
|
+
if (!/^\d+$/.test(text) || text.length < min || text.length > max || !Number.isSafeInteger(Number(text)) || Number(text) <= 0) {
|
|
466
|
+
invalid(field, `a positive document number with ${min} to ${max} digits`);
|
|
467
|
+
}
|
|
468
|
+
return Number(text);
|
|
469
|
+
}
|
|
470
|
+
function deriveCurrency(input) {
|
|
471
|
+
const currency = input.currency === void 0 ? "ARS" : input.currency;
|
|
472
|
+
if (currency !== "ARS" && currency !== "USD") {
|
|
473
|
+
invalid("currency", "ARS or USD");
|
|
474
|
+
}
|
|
475
|
+
if (input.exchangeRate !== void 0 && typeof input.exchangeRate !== "string") {
|
|
476
|
+
invalid("exchangeRate", "a decimal string");
|
|
477
|
+
}
|
|
478
|
+
if (currency === "USD" && input.exchangeRate === void 0) {
|
|
479
|
+
throw new ArcaInputError("exchangeRate is required for USD.", {
|
|
480
|
+
code: "ARCA_INPUT_MISSING_FIELD",
|
|
481
|
+
field: "exchangeRate",
|
|
482
|
+
expected: "a positive decimal string"
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
const exchangeRate = serializeArcaExchangeRate(
|
|
486
|
+
input.exchangeRate ?? "1",
|
|
487
|
+
"exchangeRate"
|
|
488
|
+
);
|
|
489
|
+
if (currency === "ARS" && exchangeRate !== "1") {
|
|
490
|
+
throw new ArcaInputError("exchangeRate must be 1 for ARS.", {
|
|
491
|
+
code: "ARCA_INPUT_INVALID_EXCHANGE_RATE",
|
|
492
|
+
field: "exchangeRate",
|
|
493
|
+
expected: "1 for ARS"
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
return { currencyId: ARCA_CURRENCY_IDS[currency], exchangeRate };
|
|
497
|
+
}
|
|
498
|
+
function deriveService(service, date) {
|
|
499
|
+
if (service === void 0) {
|
|
500
|
+
return { concept: 1 };
|
|
501
|
+
}
|
|
502
|
+
assertIssueObject(service, "service");
|
|
503
|
+
assertIssueKeys(service, ["from", "to", "dueDate"], "service");
|
|
504
|
+
for (const field of ["from", "to", "dueDate"]) {
|
|
505
|
+
if (service[field] === void 0) {
|
|
506
|
+
throw new ArcaInputError(`service.${field} is required.`, {
|
|
507
|
+
code: "ARCA_INPUT_MISSING_FIELD",
|
|
508
|
+
field: `service.${field}`,
|
|
509
|
+
expected: "a calendar date"
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const serviceStartDate = normalizeWsfeDateInput(
|
|
514
|
+
service.from,
|
|
515
|
+
"service.from"
|
|
516
|
+
);
|
|
517
|
+
const serviceEndDate = normalizeWsfeDateInput(
|
|
518
|
+
service.to,
|
|
519
|
+
"service.to"
|
|
520
|
+
);
|
|
521
|
+
const paymentDueDate = normalizeWsfeDateInput(
|
|
522
|
+
service.dueDate,
|
|
523
|
+
"service.dueDate"
|
|
524
|
+
);
|
|
525
|
+
if (serviceEndDate < serviceStartDate) {
|
|
526
|
+
invalid("service.to", "a date on or after service.from");
|
|
527
|
+
}
|
|
528
|
+
if (paymentDueDate < date) {
|
|
529
|
+
invalid("service.dueDate", "a date on or after date");
|
|
530
|
+
}
|
|
531
|
+
return { concept: 2, serviceStartDate, serviceEndDate, paymentDueDate };
|
|
532
|
+
}
|
|
533
|
+
function buenosAiresDate(now) {
|
|
534
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
535
|
+
timeZone: "America/Argentina/Buenos_Aires",
|
|
536
|
+
year: "numeric",
|
|
537
|
+
month: "2-digit",
|
|
538
|
+
day: "2-digit"
|
|
539
|
+
}).formatToParts(now);
|
|
540
|
+
return ["year", "month", "day"].map((part) => parts.find((entry) => entry.type === part)?.value).join("");
|
|
541
|
+
}
|
|
542
|
+
function assertIssueObject(value, field) {
|
|
543
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
544
|
+
invalid(field, "an object");
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
function assertIssueKeys(value, keys, prefix) {
|
|
548
|
+
for (const key of Object.keys(value)) {
|
|
549
|
+
if (!keys.includes(key)) {
|
|
550
|
+
const field = prefix === "input" ? key : `${prefix}.${key}`;
|
|
551
|
+
throw new ArcaInputError(`${field} is not supported by issue().`, {
|
|
552
|
+
code: "ARCA_INPUT_RESERVED_FIELD",
|
|
553
|
+
field,
|
|
554
|
+
expected: "a supported facade field; use the exact API for other fiscal fields"
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function invalid(field, expected) {
|
|
560
|
+
throw new ArcaInputError(`${field} must be ${expected}.`, {
|
|
561
|
+
code: "ARCA_INPUT_INVALID_VALUE",
|
|
562
|
+
field,
|
|
563
|
+
expected
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// src/services/wsfe-cancel.ts
|
|
568
|
+
var NOTES = {
|
|
569
|
+
1: { voucherType: 3, voucherClass: "A" },
|
|
570
|
+
6: { voucherType: 8, voucherClass: "B" },
|
|
571
|
+
11: { voucherType: 13, voucherClass: "C" }
|
|
572
|
+
};
|
|
573
|
+
function invalid2(reason) {
|
|
574
|
+
throw new ArcaInputError(
|
|
575
|
+
`Cannot cancel: ${reason}. Use wsfe.issue() for exact control.`,
|
|
576
|
+
{ code: "ARCA_INPUT_INVALID_VALUE" }
|
|
577
|
+
);
|
|
578
|
+
}
|
|
579
|
+
function required(value, field) {
|
|
580
|
+
if (value === void 0 || value === null) {
|
|
581
|
+
invalid2(`original is missing ${field}`);
|
|
582
|
+
}
|
|
583
|
+
return value;
|
|
584
|
+
}
|
|
585
|
+
function deriveWsfeCancellation(original, date, now = /* @__PURE__ */ new Date()) {
|
|
586
|
+
const note = NOTES[original.voucherType ?? 0];
|
|
587
|
+
if (!note) {
|
|
588
|
+
invalid2("original must be an invoice of type 1, 6 or 11");
|
|
589
|
+
}
|
|
590
|
+
if (!(["A", "O"].includes(original.result ?? "") && original.cae?.trim() && original.caeExpiry?.trim())) {
|
|
591
|
+
invalid2("original is not authorized");
|
|
592
|
+
}
|
|
593
|
+
rejectExtensions(original);
|
|
594
|
+
if (original.currencyId !== "PES" && original.currencyId !== "DOL") {
|
|
595
|
+
invalid2("unsupported currency; only ARS and USD are supported");
|
|
596
|
+
}
|
|
597
|
+
const voucherDate = normalizeWsfeDateInput(
|
|
598
|
+
date ?? buenosAiresDate(now),
|
|
599
|
+
"date"
|
|
600
|
+
);
|
|
601
|
+
const originalDate = normalizeWsfeDateInput(
|
|
602
|
+
required(original.voucherDate, "voucherDate"),
|
|
603
|
+
"original.voucherDate"
|
|
604
|
+
);
|
|
605
|
+
if (originalDate > voucherDate && originalDate.slice(0, 6) !== voucherDate.slice(0, 6)) {
|
|
606
|
+
invalid2(
|
|
607
|
+
"original date is later than the note and outside its month (10210)"
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
const data = {
|
|
611
|
+
salesPoint: required(original.salesPoint, "salesPoint"),
|
|
612
|
+
voucherType: note.voucherType,
|
|
613
|
+
concept: required(original.concept, "concept"),
|
|
614
|
+
documentType: required(original.documentType, "documentType"),
|
|
615
|
+
documentNumber: Number(required(original.documentNumber, "documentNumber")),
|
|
616
|
+
receiverVatConditionId: required(
|
|
617
|
+
original.receiverVatConditionId,
|
|
618
|
+
"receiverVatConditionId"
|
|
619
|
+
),
|
|
620
|
+
currencyId: original.currencyId,
|
|
621
|
+
exchangeRate: required(original.exchangeRate, "exchangeRate"),
|
|
622
|
+
voucherDate,
|
|
623
|
+
totalAmount: required(original.totalAmount, "totalAmount"),
|
|
624
|
+
netAmount: required(original.netAmount, "netAmount"),
|
|
625
|
+
vatAmount: required(original.vatAmount, "vatAmount"),
|
|
626
|
+
exemptAmount: required(original.exemptAmount, "exemptAmount"),
|
|
627
|
+
nonTaxableAmount: required(original.nonTaxableAmount, "nonTaxableAmount"),
|
|
628
|
+
taxAmount: required(original.taxAmount, "taxAmount"),
|
|
629
|
+
...original.vatRates === void 0 ? {} : { vatRates: original.vatRates.map((rate) => ({ ...rate })) },
|
|
630
|
+
associatedVouchers: [
|
|
631
|
+
{
|
|
632
|
+
type: required(original.voucherType, "voucherType"),
|
|
633
|
+
salesPoint: required(original.salesPoint, "salesPoint"),
|
|
634
|
+
number: original.voucherNumber,
|
|
635
|
+
voucherDate: originalDate
|
|
636
|
+
}
|
|
637
|
+
]
|
|
638
|
+
};
|
|
639
|
+
copyServiceDates(original, data);
|
|
640
|
+
normalizeWsfeVoucherInput(data);
|
|
641
|
+
const total = Number(
|
|
642
|
+
normalizeArcaAmountToMinorUnits(data.totalAmount, "totalAmount")
|
|
643
|
+
);
|
|
644
|
+
return {
|
|
645
|
+
data,
|
|
646
|
+
voucherClass: note.voucherClass,
|
|
647
|
+
amounts: { computedTotal: total, sentTotal: total, vatAdjustment: 0 }
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
function rejectExtensions(original) {
|
|
651
|
+
for (const [field, rawField] of [
|
|
652
|
+
["taxes", "Tributos"],
|
|
653
|
+
["optionalFields", "Opcionales"],
|
|
654
|
+
["buyers", "Compradores"],
|
|
655
|
+
["activities", "Actividades"],
|
|
656
|
+
["associatedPeriod", "PeriodoAsoc"]
|
|
657
|
+
]) {
|
|
658
|
+
const value = original[field];
|
|
659
|
+
const raw = original.raw[rawField];
|
|
660
|
+
if (present(value) || present(raw)) {
|
|
661
|
+
invalid2(`unsupported original ${field}`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if ((original.taxAmount ?? 0) !== 0) {
|
|
665
|
+
invalid2("unsupported original taxes");
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function present(value) {
|
|
669
|
+
return value !== void 0 && value !== null && value !== "" && (!Array.isArray(value) || value.length > 0);
|
|
670
|
+
}
|
|
671
|
+
function copyServiceDates(original, data) {
|
|
672
|
+
if (data.concept !== 2 && data.concept !== 3) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
data.serviceStartDate = required(
|
|
676
|
+
original.serviceStartDate,
|
|
677
|
+
"serviceStartDate"
|
|
678
|
+
);
|
|
679
|
+
data.serviceEndDate = required(
|
|
680
|
+
original.serviceEndDate,
|
|
681
|
+
"serviceEndDate"
|
|
682
|
+
);
|
|
683
|
+
const due = normalizeWsfeDateInput(
|
|
684
|
+
required(original.paymentDueDate, "paymentDueDate"),
|
|
685
|
+
"original.paymentDueDate"
|
|
686
|
+
);
|
|
687
|
+
data.paymentDueDate = due < data.voucherDate ? data.voucherDate : due;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// src/services/wsfe-identity.ts
|
|
691
|
+
function matchWsfeVoucherIdentity(sent, number, found) {
|
|
692
|
+
let missing;
|
|
693
|
+
const compare = (field, expected, actual, normalize) => {
|
|
694
|
+
if (actual === void 0 || actual === null || expected === void 0) {
|
|
695
|
+
missing ??= field;
|
|
696
|
+
return void 0;
|
|
697
|
+
}
|
|
698
|
+
try {
|
|
699
|
+
const left = normalize ? normalize(expected) : expected;
|
|
700
|
+
const right = normalize ? normalize(actual) : actual;
|
|
701
|
+
if (left !== right) {
|
|
702
|
+
return {
|
|
703
|
+
matches: false,
|
|
704
|
+
evidence: "conflict",
|
|
705
|
+
reason: `${field} differs from the sent input`
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
} catch {
|
|
709
|
+
missing ??= field;
|
|
710
|
+
}
|
|
711
|
+
return void 0;
|
|
712
|
+
};
|
|
713
|
+
const checks = [
|
|
714
|
+
["voucherType", sent.voucherType, found.voucherType],
|
|
715
|
+
["salesPoint", sent.salesPoint, found.salesPoint],
|
|
716
|
+
["number", number, found.voucherNumber, normalizeVoucherNumber],
|
|
717
|
+
[
|
|
718
|
+
"date",
|
|
719
|
+
sent.voucherDate,
|
|
720
|
+
found.voucherDate,
|
|
721
|
+
(value) => normalizeWsfeDateInput(value, "date")
|
|
722
|
+
],
|
|
723
|
+
["concept", sent.concept, found.concept],
|
|
724
|
+
["documentType", sent.documentType, found.documentType],
|
|
725
|
+
[
|
|
726
|
+
"documentNumber",
|
|
727
|
+
sent.documentNumber,
|
|
728
|
+
found.documentNumber,
|
|
729
|
+
normalizeDocument
|
|
730
|
+
],
|
|
731
|
+
[
|
|
732
|
+
"receiverVatConditionId",
|
|
733
|
+
sent.receiverVatConditionId,
|
|
734
|
+
found.receiverVatConditionId
|
|
735
|
+
],
|
|
736
|
+
["currencyId", sent.currencyId, found.currencyId],
|
|
737
|
+
[
|
|
738
|
+
"exchangeRate",
|
|
739
|
+
sent.exchangeRate ?? (sent.currencyId === "PES" ? 1 : void 0),
|
|
740
|
+
found.exchangeRate,
|
|
741
|
+
(value) => serializeArcaExchangeRate(value, "exchangeRate")
|
|
742
|
+
]
|
|
743
|
+
];
|
|
744
|
+
for (const field of [
|
|
745
|
+
"totalAmount",
|
|
746
|
+
"netAmount",
|
|
747
|
+
"vatAmount",
|
|
748
|
+
"exemptAmount",
|
|
749
|
+
"nonTaxableAmount",
|
|
750
|
+
"taxAmount"
|
|
751
|
+
]) {
|
|
752
|
+
checks.push([
|
|
753
|
+
field,
|
|
754
|
+
sent[field],
|
|
755
|
+
found[field],
|
|
756
|
+
(value) => normalizeArcaAmountToMinorUnits(value, field)
|
|
757
|
+
]);
|
|
758
|
+
}
|
|
759
|
+
if (sent.concept === 2 || sent.concept === 3) {
|
|
760
|
+
for (const field of [
|
|
761
|
+
"serviceStartDate",
|
|
762
|
+
"serviceEndDate",
|
|
763
|
+
"paymentDueDate"
|
|
764
|
+
]) {
|
|
765
|
+
checks.push([
|
|
766
|
+
field,
|
|
767
|
+
sent[field],
|
|
768
|
+
found[field],
|
|
769
|
+
(value) => normalizeWsfeDateInput(value, field)
|
|
770
|
+
]);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
for (const check of checks) {
|
|
774
|
+
const result = compare(...check);
|
|
775
|
+
if (result) {
|
|
776
|
+
return result;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
const rates = compareVatRates(sent.vatRates ?? [], found.vatRates);
|
|
780
|
+
if (!rates.matches) {
|
|
781
|
+
if (rates.evidence === "conflict") {
|
|
782
|
+
return rates;
|
|
783
|
+
}
|
|
784
|
+
missing ??= rates.reason;
|
|
785
|
+
}
|
|
786
|
+
if (sent.associatedVouchers?.length) {
|
|
787
|
+
if (!found.associatedVouchers) {
|
|
788
|
+
missing ??= "associatedVouchers";
|
|
789
|
+
} else if (associationIdentity(sent.associatedVouchers) !== associationIdentity(found.associatedVouchers)) {
|
|
790
|
+
return {
|
|
791
|
+
matches: false,
|
|
792
|
+
evidence: "conflict",
|
|
793
|
+
reason: "associatedVouchers differ from the sent input"
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
missing ??= incompleteAuthorization(sent, found);
|
|
798
|
+
return missing ? {
|
|
799
|
+
matches: false,
|
|
800
|
+
evidence: "incomplete",
|
|
801
|
+
reason: `Cannot verify ${missing}`
|
|
802
|
+
} : { matches: true };
|
|
803
|
+
}
|
|
804
|
+
function incompleteAuthorization(sent, found) {
|
|
805
|
+
let missing;
|
|
806
|
+
if (sent.concept !== 1 && sent.concept !== 2 && sent.concept !== 3) {
|
|
807
|
+
missing ??= "unsupported concept";
|
|
808
|
+
}
|
|
809
|
+
for (const field of [
|
|
810
|
+
"taxes",
|
|
811
|
+
"associatedPeriod",
|
|
812
|
+
"optionalFields",
|
|
813
|
+
"buyers",
|
|
814
|
+
"activities",
|
|
815
|
+
"sameCurrencyForeignCancellation"
|
|
816
|
+
]) {
|
|
817
|
+
const value = sent[field];
|
|
818
|
+
if (value !== void 0 && (!Array.isArray(value) || value.length > 0)) {
|
|
819
|
+
missing ??= `unsupported exact field ${field}`;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if (!(found.result === "A" || found.result === "O")) {
|
|
823
|
+
missing ??= "authorized result";
|
|
824
|
+
}
|
|
825
|
+
if (!found.cae?.trim()) {
|
|
826
|
+
missing ??= "cae";
|
|
827
|
+
}
|
|
828
|
+
if (!found.caeExpiry?.trim()) {
|
|
829
|
+
missing ??= "caeExpiry";
|
|
830
|
+
}
|
|
831
|
+
return missing;
|
|
832
|
+
}
|
|
833
|
+
function normalizeVoucherNumber(value) {
|
|
834
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 99999999) {
|
|
835
|
+
throw new Error("Invalid voucher number");
|
|
836
|
+
}
|
|
837
|
+
return value;
|
|
838
|
+
}
|
|
839
|
+
function normalizeDocument(value) {
|
|
840
|
+
const text = String(value);
|
|
841
|
+
if (!/^\d+$/.test(text)) {
|
|
842
|
+
throw new Error("Invalid document number");
|
|
843
|
+
}
|
|
844
|
+
return BigInt(text);
|
|
845
|
+
}
|
|
846
|
+
function compareVatRates(expected, actual) {
|
|
847
|
+
if (actual === void 0) {
|
|
848
|
+
return expected.length === 0 ? { matches: true } : { matches: false, evidence: "incomplete", reason: "vatRates" };
|
|
849
|
+
}
|
|
850
|
+
if (actual.length !== expected.length || new Set(actual.map((rate) => rate.id)).size !== actual.length) {
|
|
851
|
+
return {
|
|
852
|
+
matches: false,
|
|
853
|
+
evidence: "conflict",
|
|
854
|
+
reason: "vatRates ids differ from the sent input"
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
for (const rate of expected) {
|
|
858
|
+
const found = actual.find((item) => item.id === rate.id);
|
|
859
|
+
if (!found) {
|
|
860
|
+
return {
|
|
861
|
+
matches: false,
|
|
862
|
+
evidence: "conflict",
|
|
863
|
+
reason: `vatRates id ${rate.id} differs from the sent input`
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
for (const field of ["baseAmount", "amount"]) {
|
|
867
|
+
try {
|
|
868
|
+
if (normalizeArcaAmountToMinorUnits(rate[field], field) !== normalizeArcaAmountToMinorUnits(found[field], field)) {
|
|
869
|
+
return {
|
|
870
|
+
matches: false,
|
|
871
|
+
evidence: "conflict",
|
|
872
|
+
reason: `vatRates[${rate.id}].${field} differs from the sent input`
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
} catch {
|
|
876
|
+
return {
|
|
877
|
+
matches: false,
|
|
878
|
+
evidence: "incomplete",
|
|
879
|
+
reason: `vatRates[${rate.id}].${field}`
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
return { matches: true };
|
|
885
|
+
}
|
|
886
|
+
function toVoucherSummary(found) {
|
|
887
|
+
const summary = { number: found.voucherNumber };
|
|
888
|
+
for (const field of [
|
|
889
|
+
"salesPoint",
|
|
890
|
+
"voucherType",
|
|
891
|
+
"concept",
|
|
892
|
+
"documentType",
|
|
893
|
+
"documentNumber",
|
|
894
|
+
"receiverVatConditionId",
|
|
895
|
+
"currencyId",
|
|
896
|
+
"exchangeRate",
|
|
897
|
+
"totalAmount",
|
|
898
|
+
"netAmount",
|
|
899
|
+
"vatAmount",
|
|
900
|
+
"exemptAmount",
|
|
901
|
+
"nonTaxableAmount",
|
|
902
|
+
"taxAmount",
|
|
903
|
+
"serviceStartDate",
|
|
904
|
+
"serviceEndDate",
|
|
905
|
+
"paymentDueDate",
|
|
906
|
+
"result",
|
|
907
|
+
"cae",
|
|
908
|
+
"caeExpiry"
|
|
909
|
+
]) {
|
|
910
|
+
if (found[field] !== void 0) {
|
|
911
|
+
Object.assign(summary, { [field]: found[field] });
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
if (found.voucherDate !== void 0) {
|
|
915
|
+
summary.date = found.voucherDate;
|
|
916
|
+
}
|
|
917
|
+
if (found.vatRates !== void 0) {
|
|
918
|
+
summary.vatRates = found.vatRates.map(({ id, baseAmount, amount }) => ({
|
|
919
|
+
id,
|
|
920
|
+
baseAmount,
|
|
921
|
+
amount
|
|
922
|
+
}));
|
|
923
|
+
}
|
|
924
|
+
return summary;
|
|
925
|
+
}
|
|
926
|
+
function associationIdentity(vouchers) {
|
|
927
|
+
return JSON.stringify(
|
|
928
|
+
vouchers.map(({ type, salesPoint, number }) => `${type}:${salesPoint}:${number}`).sort()
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// src/services/vouchers.ts
|
|
933
|
+
function createVouchersService(wsfe, context) {
|
|
934
|
+
return {
|
|
935
|
+
cancel: async (target, options) => {
|
|
936
|
+
const result = await cancelInvoice(wsfe, target, options ?? {}, context);
|
|
937
|
+
return result;
|
|
938
|
+
},
|
|
939
|
+
issue: async (input, options) => {
|
|
940
|
+
const result = await issueInvoice(
|
|
941
|
+
wsfe,
|
|
942
|
+
input,
|
|
943
|
+
options === void 0 ? {} : options,
|
|
944
|
+
context
|
|
945
|
+
);
|
|
946
|
+
return result;
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
async function issueInvoice(wsfe, input, options, context) {
|
|
951
|
+
validateOptions(options);
|
|
952
|
+
validateKeyStore(options, context);
|
|
953
|
+
const prepared = deriveWsfeInvoice(input);
|
|
954
|
+
return await runOperation(
|
|
955
|
+
wsfe,
|
|
956
|
+
"issue",
|
|
957
|
+
input,
|
|
958
|
+
() => Promise.resolve(prepared),
|
|
959
|
+
options,
|
|
960
|
+
context,
|
|
961
|
+
prepared.amounts
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
async function runOperation(wsfe, operation, input, prepare, options, context, replayAmounts) {
|
|
965
|
+
const store = context?.store;
|
|
966
|
+
if (options.idempotencyKey === void 0 || !store || !context) {
|
|
967
|
+
return runAuthorization(wsfe, await prepare(), options);
|
|
968
|
+
}
|
|
969
|
+
const key = attemptKey(
|
|
970
|
+
context.environment,
|
|
971
|
+
context.taxId,
|
|
972
|
+
options.idempotencyKey
|
|
973
|
+
);
|
|
974
|
+
const representedTaxId = options.representedTaxId === void 0 ? void 0 : String(options.representedTaxId);
|
|
975
|
+
const inputHash = canonicalHash({ input, representedTaxId });
|
|
976
|
+
const existing = await storeCall(() => store.get(key));
|
|
977
|
+
if (existing !== null) {
|
|
978
|
+
return replay(existing);
|
|
979
|
+
}
|
|
980
|
+
const prepared = await prepare();
|
|
981
|
+
const number = await nextNumber(wsfe, prepared.data, options);
|
|
982
|
+
const record = {
|
|
983
|
+
v: 1,
|
|
984
|
+
operation,
|
|
985
|
+
representedTaxId,
|
|
986
|
+
inputHash,
|
|
987
|
+
number,
|
|
988
|
+
salesPoint: prepared.data.salesPoint,
|
|
989
|
+
voucherType: prepared.data.voucherType,
|
|
990
|
+
sent: prepared.data,
|
|
991
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
992
|
+
};
|
|
993
|
+
if (await storeCall(() => store.add(key, JSON.stringify(record)))) {
|
|
994
|
+
return runAuthorization(wsfe, prepared, options, number);
|
|
995
|
+
}
|
|
996
|
+
const winner = await storeCall(() => store.get(key));
|
|
997
|
+
if (winner === null) {
|
|
998
|
+
throw new ArcaConfigurationError(
|
|
999
|
+
"ARCA reservation disappeared after atomic creation lost."
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
return replay(winner);
|
|
1003
|
+
function replay(json) {
|
|
1004
|
+
const stored = readRecord(json);
|
|
1005
|
+
if (stored.operation !== operation || stored.inputHash !== inputHash || stored.representedTaxId !== representedTaxId) {
|
|
1006
|
+
throw new ArcaInputError(
|
|
1007
|
+
"The idempotency key was already used with different input or operation.",
|
|
1008
|
+
{
|
|
1009
|
+
code: "ARCA_INPUT_IDEMPOTENCY_MISMATCH",
|
|
1010
|
+
field: "options.idempotencyKey"
|
|
1011
|
+
}
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
return runAuthorization(
|
|
1015
|
+
wsfe,
|
|
1016
|
+
{
|
|
1017
|
+
...preparedFromRecord(stored),
|
|
1018
|
+
...replayAmounts ? { amounts: replayAmounts } : {}
|
|
1019
|
+
},
|
|
1020
|
+
options,
|
|
1021
|
+
stored.number,
|
|
1022
|
+
true
|
|
1023
|
+
);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
function validateKeyStore(options, context) {
|
|
1027
|
+
if (options.idempotencyKey === void 0) {
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
if (typeof options.idempotencyKey !== "string" || options.idempotencyKey.length < 1 || options.idempotencyKey.length > 255) {
|
|
1031
|
+
throw new ArcaInputError(
|
|
1032
|
+
"idempotencyKey must contain 1 to 255 characters.",
|
|
1033
|
+
{ code: "ARCA_INPUT_INVALID_VALUE", field: "options.idempotencyKey" }
|
|
1034
|
+
);
|
|
1035
|
+
}
|
|
1036
|
+
if (!context?.store) {
|
|
1037
|
+
throw new ArcaConfigurationError(
|
|
1038
|
+
'idempotencyKey requires a store. Add import { createPostgresStore } from "facturas"; and store: createPostgresStore({ query }) to createArcaClient().'
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
function readRecord(json) {
|
|
1043
|
+
try {
|
|
1044
|
+
const record = JSON.parse(json);
|
|
1045
|
+
if (!record || record.v !== 1 || !["issue", "cancel"].includes(record.operation) || typeof record.inputHash !== "string" || !record.sent || !Number.isSafeInteger(record.number) || record.number < 1 || record.number > 99999999 || record.sent.salesPoint !== record.salesPoint || record.sent.voucherType !== record.voucherType) {
|
|
1046
|
+
throw new Error("Invalid reservation structure");
|
|
1047
|
+
}
|
|
1048
|
+
normalizeWsfeVoucherInput(record.sent);
|
|
1049
|
+
return record;
|
|
1050
|
+
} catch (cause) {
|
|
1051
|
+
throw new ArcaConfigurationError(
|
|
1052
|
+
"Invalid ARCA reservation record; preserve it for reconciliation.",
|
|
1053
|
+
{ cause }
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
function preparedFromRecord(record) {
|
|
1058
|
+
const sentTotal = Number(
|
|
1059
|
+
normalizeArcaAmountToMinorUnits(record.sent.totalAmount, "totalAmount")
|
|
1060
|
+
);
|
|
1061
|
+
return {
|
|
1062
|
+
data: record.sent,
|
|
1063
|
+
voucherClass: [1, 3].includes(record.voucherType) ? "A" : [6, 8].includes(record.voucherType) ? "B" : "C",
|
|
1064
|
+
amounts: { computedTotal: sentTotal, sentTotal, vatAdjustment: 0 }
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
async function nextNumber(wsfe, data, options) {
|
|
1068
|
+
const number = await wsfe.getNextVoucherNumber({
|
|
1069
|
+
representedTaxId: options.representedTaxId,
|
|
1070
|
+
forceRefresh: options.forceRefresh,
|
|
1071
|
+
salesPoint: data.salesPoint,
|
|
1072
|
+
voucherType: data.voucherType
|
|
1073
|
+
});
|
|
1074
|
+
if (!Number.isSafeInteger(number) || number < 1 || number > 99999999) {
|
|
1075
|
+
throw new ArcaServiceError(
|
|
1076
|
+
"WSFE returned an invalid next voucher number.",
|
|
1077
|
+
{ service: "wsfe", operation: "FECompUltimoAutorizado" }
|
|
1078
|
+
);
|
|
1079
|
+
}
|
|
1080
|
+
return number;
|
|
1081
|
+
}
|
|
1082
|
+
async function runAuthorization(wsfe, { data, voucherClass, amounts }, options, reservedNumber, replay = false) {
|
|
1083
|
+
const auth = {
|
|
1084
|
+
representedTaxId: options.representedTaxId,
|
|
1085
|
+
forceRefresh: options.forceRefresh
|
|
1086
|
+
};
|
|
1087
|
+
const includeRaw = options.include?.raw === true;
|
|
1088
|
+
const includeExact = options.include?.exactInput === true;
|
|
1089
|
+
const number = reservedNumber ?? await nextNumber(wsfe, data, options);
|
|
1090
|
+
const attempted = {
|
|
1091
|
+
salesPoint: data.salesPoint,
|
|
1092
|
+
voucherType: data.voucherType,
|
|
1093
|
+
number
|
|
1094
|
+
};
|
|
1095
|
+
const exact = includeExact ? { sent: data } : {};
|
|
1096
|
+
const voucher = (cae, caeExpiry) => ({
|
|
1097
|
+
...attempted,
|
|
1098
|
+
voucherClass,
|
|
1099
|
+
date: data.voucherDate,
|
|
1100
|
+
cae,
|
|
1101
|
+
caeExpiry,
|
|
1102
|
+
amounts
|
|
1103
|
+
});
|
|
1104
|
+
const recovery = { wsfe, auth, data, attempted, includeRaw, exact, voucher };
|
|
1105
|
+
if (replay) {
|
|
1106
|
+
const attempt2 = replayEvidence();
|
|
1107
|
+
let lookup;
|
|
1108
|
+
try {
|
|
1109
|
+
lookup = await wsfe.lookupVoucher({ ...auth, ...attempted });
|
|
1110
|
+
} catch (error) {
|
|
1111
|
+
return {
|
|
1112
|
+
kind: "indeterminate",
|
|
1113
|
+
attempted,
|
|
1114
|
+
attempt: attempt2,
|
|
1115
|
+
lookup: { kind: "failed", error: toArcaSafeErrorMetadata(error) }
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
if (lookup.kind === "found") {
|
|
1119
|
+
return recoverInvoice({ ...recovery, attempt: attempt2, lookup });
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
const authorization = await wsfe.issue({
|
|
1123
|
+
...auth,
|
|
1124
|
+
data,
|
|
1125
|
+
voucherNumber: number
|
|
1126
|
+
});
|
|
1127
|
+
if (authorization.kind === "authorized" && authorization.caeExpiry && authorization.voucherNumber === number) {
|
|
1128
|
+
return {
|
|
1129
|
+
kind: "authorized",
|
|
1130
|
+
recoveredByMatch: false,
|
|
1131
|
+
voucher: voucher(authorization.cae, authorization.caeExpiry),
|
|
1132
|
+
authorization: projectEvidence(authorization, includeRaw),
|
|
1133
|
+
...exact
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
if (authorization.kind === "rejected") {
|
|
1137
|
+
if (options.idempotencyKey !== void 0 && [...authorization.errors, ...authorization.observations].some(
|
|
1138
|
+
(issue) => issue.code === "10016"
|
|
1139
|
+
)) {
|
|
1140
|
+
const recovered = await recoverInvoice({
|
|
1141
|
+
...recovery,
|
|
1142
|
+
attempt: projectEvidence(
|
|
1143
|
+
{
|
|
1144
|
+
...authorization,
|
|
1145
|
+
kind: "indeterminate",
|
|
1146
|
+
reason: "contradictory_response"
|
|
1147
|
+
},
|
|
1148
|
+
includeRaw
|
|
1149
|
+
)
|
|
1150
|
+
});
|
|
1151
|
+
if (recovered.kind === "authorized" || recovered.kind === "conflict") {
|
|
1152
|
+
return recovered;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
return {
|
|
1156
|
+
kind: "rejected",
|
|
1157
|
+
attempted,
|
|
1158
|
+
issues: [...authorization.errors, ...authorization.observations].map(
|
|
1159
|
+
projectIssue
|
|
1160
|
+
),
|
|
1161
|
+
authorization: projectEvidence(authorization, includeRaw)
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
const uncertain = authorization.kind === "indeterminate" ? authorization : {
|
|
1165
|
+
...authorization,
|
|
1166
|
+
kind: "indeterminate",
|
|
1167
|
+
reason: authorization.voucherNumber === number ? "incomplete_response" : "contradictory_response"
|
|
1168
|
+
};
|
|
1169
|
+
const attempt = projectEvidence(uncertain, includeRaw);
|
|
1170
|
+
return recoverInvoice({
|
|
1171
|
+
wsfe,
|
|
1172
|
+
auth,
|
|
1173
|
+
data,
|
|
1174
|
+
attempted,
|
|
1175
|
+
attempt,
|
|
1176
|
+
includeRaw,
|
|
1177
|
+
exact,
|
|
1178
|
+
voucher
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
async function recoverInvoice({
|
|
1182
|
+
wsfe,
|
|
1183
|
+
auth,
|
|
1184
|
+
data,
|
|
1185
|
+
attempted,
|
|
1186
|
+
attempt,
|
|
1187
|
+
includeRaw,
|
|
1188
|
+
exact,
|
|
1189
|
+
voucher,
|
|
1190
|
+
lookup: suppliedLookup
|
|
1191
|
+
}) {
|
|
1192
|
+
let lookup;
|
|
1193
|
+
try {
|
|
1194
|
+
lookup = suppliedLookup ?? await wsfe.lookupVoucher({ ...auth, ...attempted });
|
|
1195
|
+
} catch (error) {
|
|
1196
|
+
return {
|
|
1197
|
+
kind: "indeterminate",
|
|
1198
|
+
attempted,
|
|
1199
|
+
attempt,
|
|
1200
|
+
lookup: { kind: "failed", error: toArcaSafeErrorMetadata(error) }
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
const raw = includeRaw ? { raw: lookup.raw } : {};
|
|
1204
|
+
if (lookup.kind === "not_found") {
|
|
1205
|
+
return {
|
|
1206
|
+
kind: "indeterminate",
|
|
1207
|
+
attempted,
|
|
1208
|
+
attempt,
|
|
1209
|
+
lookup: { kind: "not_found", ...raw }
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
const matched = matchWsfeVoucherIdentity(
|
|
1213
|
+
data,
|
|
1214
|
+
attempted.number,
|
|
1215
|
+
lookup.voucher
|
|
1216
|
+
);
|
|
1217
|
+
if (!matched.matches) {
|
|
1218
|
+
if (matched.evidence === "conflict") {
|
|
1219
|
+
return {
|
|
1220
|
+
kind: "conflict",
|
|
1221
|
+
attempted,
|
|
1222
|
+
attempt,
|
|
1223
|
+
found: { ...toVoucherSummary(lookup.voucher), ...raw },
|
|
1224
|
+
reason: `${matched.reason}. Configure a store and pass idempotencyKey for retries.`
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
return {
|
|
1228
|
+
kind: "indeterminate",
|
|
1229
|
+
attempted,
|
|
1230
|
+
attempt,
|
|
1231
|
+
lookup: { kind: "incomplete", reason: matched.reason, ...raw }
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
return {
|
|
1235
|
+
kind: "authorized",
|
|
1236
|
+
recoveredByMatch: true,
|
|
1237
|
+
voucher: voucher(
|
|
1238
|
+
lookup.voucher.cae,
|
|
1239
|
+
lookup.voucher.caeExpiry
|
|
1240
|
+
),
|
|
1241
|
+
attempt,
|
|
1242
|
+
lookup: { ...toVoucherSummary(lookup.voucher), ...raw },
|
|
1243
|
+
...exact
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
function projectIssue(issue) {
|
|
1247
|
+
return {
|
|
1248
|
+
service: issue.service,
|
|
1249
|
+
operation: issue.operation,
|
|
1250
|
+
source: issue.source,
|
|
1251
|
+
category: issue.category,
|
|
1252
|
+
message: issue.message,
|
|
1253
|
+
...issue.code === void 0 ? {} : { code: issue.code },
|
|
1254
|
+
...issue.resultLevel === void 0 ? {} : { resultLevel: issue.resultLevel }
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
function projectEvidence(evidence, includeRaw) {
|
|
1258
|
+
const base = {
|
|
1259
|
+
kind: evidence.kind,
|
|
1260
|
+
service: evidence.service,
|
|
1261
|
+
operation: evidence.operation,
|
|
1262
|
+
results: {
|
|
1263
|
+
...evidence.results.header === void 0 ? {} : { header: evidence.results.header },
|
|
1264
|
+
...evidence.results.detail === void 0 ? {} : { detail: evidence.results.detail },
|
|
1265
|
+
...evidence.results.operation === void 0 ? {} : { operation: evidence.results.operation }
|
|
1266
|
+
},
|
|
1267
|
+
errors: evidence.errors.map(projectIssue),
|
|
1268
|
+
observations: evidence.observations.map(projectIssue),
|
|
1269
|
+
...includeRaw && evidence.raw !== void 0 ? { raw: evidence.raw } : {}
|
|
1270
|
+
};
|
|
1271
|
+
const projected = { ...base };
|
|
1272
|
+
for (const field of [
|
|
1273
|
+
"result",
|
|
1274
|
+
"resultLevel",
|
|
1275
|
+
"cae",
|
|
1276
|
+
"caeExpiry",
|
|
1277
|
+
"voucherNumber",
|
|
1278
|
+
"reason"
|
|
1279
|
+
]) {
|
|
1280
|
+
if (field in evidence && evidence[field] !== void 0) {
|
|
1281
|
+
projected[field] = evidence[field];
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
if (evidence.kind === "indeterminate" && evidence.authentication) {
|
|
1285
|
+
const { code, reason, providerCode } = evidence.authentication;
|
|
1286
|
+
projected.authentication = {
|
|
1287
|
+
code,
|
|
1288
|
+
reason,
|
|
1289
|
+
...providerCode === void 0 ? {} : { providerCode }
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
return projected;
|
|
1293
|
+
}
|
|
1294
|
+
function validateOptions(options) {
|
|
1295
|
+
assertIssueObject(options, "options");
|
|
1296
|
+
assertIssueKeys(
|
|
1297
|
+
options,
|
|
1298
|
+
["representedTaxId", "forceRefresh", "include", "idempotencyKey"],
|
|
1299
|
+
"options"
|
|
1300
|
+
);
|
|
1301
|
+
if (options.representedTaxId !== void 0) {
|
|
1302
|
+
issueDocumentNumber(
|
|
1303
|
+
options.representedTaxId,
|
|
1304
|
+
"options.representedTaxId",
|
|
1305
|
+
11,
|
|
1306
|
+
11
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
if (options.forceRefresh !== void 0 && typeof options.forceRefresh !== "boolean") {
|
|
1310
|
+
throw new ArcaInputError("options.forceRefresh must be a boolean.", {
|
|
1311
|
+
code: "ARCA_INPUT_INVALID_VALUE",
|
|
1312
|
+
field: "options.forceRefresh"
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
if (options.include !== void 0) {
|
|
1316
|
+
assertIssueObject(options.include, "options.include");
|
|
1317
|
+
assertIssueKeys(options.include, ["raw", "exactInput"], "options.include");
|
|
1318
|
+
for (const field of ["raw", "exactInput"]) {
|
|
1319
|
+
if (options.include[field] !== void 0 && typeof options.include[field] !== "boolean") {
|
|
1320
|
+
throw new ArcaInputError(
|
|
1321
|
+
`options.include.${field} must be a boolean.`,
|
|
1322
|
+
{
|
|
1323
|
+
code: "ARCA_INPUT_INVALID_VALUE",
|
|
1324
|
+
field: `options.include.${field}`
|
|
1325
|
+
}
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
function replayEvidence() {
|
|
1332
|
+
return {
|
|
1333
|
+
kind: "indeterminate",
|
|
1334
|
+
service: "wsfe",
|
|
1335
|
+
operation: "FECAESolicitar",
|
|
1336
|
+
reason: "incomplete_response",
|
|
1337
|
+
results: {},
|
|
1338
|
+
errors: [],
|
|
1339
|
+
observations: []
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
async function cancelInvoice(wsfe, target, options, context) {
|
|
1343
|
+
assertIssueObject(options, "options");
|
|
1344
|
+
const { date, ...issueOptions } = options;
|
|
1345
|
+
validateOptions(issueOptions);
|
|
1346
|
+
validateKeyStore(issueOptions, context);
|
|
1347
|
+
assertIssueObject(target, "target");
|
|
1348
|
+
for (const [field, max] of [
|
|
1349
|
+
["salesPoint", 99999],
|
|
1350
|
+
["voucherType", 999],
|
|
1351
|
+
["number", 99999999]
|
|
1352
|
+
]) {
|
|
1353
|
+
if (!Number.isSafeInteger(target[field]) || target[field] < 1 || target[field] > max) {
|
|
1354
|
+
throw new ArcaInputError(`Invalid cancel target ${field}.`, {
|
|
1355
|
+
code: "ARCA_INPUT_INVALID_VALUE",
|
|
1356
|
+
field
|
|
1357
|
+
});
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
if (![1, 6, 11].includes(target.voucherType)) {
|
|
1361
|
+
throw new ArcaInputError(
|
|
1362
|
+
"cancel requires an invoice of type 1, 6 or 11; use wsfe.issue() for exact control.",
|
|
1363
|
+
{ code: "ARCA_INPUT_INVALID_VALUE" }
|
|
1364
|
+
);
|
|
1365
|
+
}
|
|
1366
|
+
if (date !== void 0) {
|
|
1367
|
+
normalizeWsfeDateInput(date, "date");
|
|
1368
|
+
}
|
|
1369
|
+
return await runOperation(
|
|
1370
|
+
wsfe,
|
|
1371
|
+
"cancel",
|
|
1372
|
+
{ target, date },
|
|
1373
|
+
async () => {
|
|
1374
|
+
const original = await wsfe.lookupVoucher({
|
|
1375
|
+
representedTaxId: issueOptions.representedTaxId,
|
|
1376
|
+
forceRefresh: issueOptions.forceRefresh,
|
|
1377
|
+
salesPoint: target.salesPoint,
|
|
1378
|
+
voucherType: target.voucherType,
|
|
1379
|
+
number: target.number
|
|
1380
|
+
});
|
|
1381
|
+
if (original.kind === "not_found") {
|
|
1382
|
+
throw new ArcaInputError("original voucher not found", {
|
|
1383
|
+
code: "ARCA_INPUT_INVALID_VALUE"
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
if (original.voucher.salesPoint !== target.salesPoint || original.voucher.voucherType !== target.voucherType || original.voucher.voucherNumber !== target.number) {
|
|
1387
|
+
throw new ArcaInputError(
|
|
1388
|
+
"Original lookup coordinates do not match the cancel target.",
|
|
1389
|
+
{ code: "ARCA_INPUT_INVALID_VALUE" }
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1392
|
+
return deriveWsfeCancellation(original.voucher, date);
|
|
1393
|
+
},
|
|
1394
|
+
issueOptions,
|
|
1395
|
+
context
|
|
1396
|
+
);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
290
1399
|
// src/internal/http.ts
|
|
291
1400
|
import https from "https";
|
|
292
1401
|
var defaultAgent = new https.Agent({
|
|
@@ -695,7 +1804,7 @@ function createSoapTransport(options) {
|
|
|
695
1804
|
}
|
|
696
1805
|
|
|
697
1806
|
// src/wsaa/index.ts
|
|
698
|
-
import { createHash } from "crypto";
|
|
1807
|
+
import { createHash as createHash2 } from "crypto";
|
|
699
1808
|
import forge from "node-forge";
|
|
700
1809
|
|
|
701
1810
|
// src/wsaa/session-store.ts
|
|
@@ -929,7 +2038,7 @@ function buildWsaaSessionKey(config, service) {
|
|
|
929
2038
|
};
|
|
930
2039
|
}
|
|
931
2040
|
function getCertificateFingerprint(config) {
|
|
932
|
-
return
|
|
2041
|
+
return createHash2("sha256").update(config.certificatePem).digest("hex");
|
|
933
2042
|
}
|
|
934
2043
|
function getCachedCredentials(cache, cacheKey) {
|
|
935
2044
|
const localCached = cache.get(cacheKey);
|
|
@@ -1120,10 +2229,40 @@ function parseLoginTicketResponse(xml) {
|
|
|
1120
2229
|
};
|
|
1121
2230
|
}
|
|
1122
2231
|
|
|
2232
|
+
// src/wsaa/store-adapter.ts
|
|
2233
|
+
function createWsaaStoreAdapter(store) {
|
|
2234
|
+
const key = (value) => `arca:v1:wsaa:${serializeWsaaSessionKey(value)}`;
|
|
2235
|
+
const remove = store.delete?.bind(store);
|
|
2236
|
+
const lock = store.withLock?.bind(store);
|
|
2237
|
+
return {
|
|
2238
|
+
get: (value) => storeCall(async () => {
|
|
2239
|
+
const json = await store.get(key(value));
|
|
2240
|
+
if (json === null) {
|
|
2241
|
+
return null;
|
|
2242
|
+
}
|
|
2243
|
+
const credentials = JSON.parse(json);
|
|
2244
|
+
return credentials && typeof credentials.token === "string" && typeof credentials.sign === "string" && isWsaaCredentialValid(credentials) ? credentials : null;
|
|
2245
|
+
}),
|
|
2246
|
+
set: (value, credentials) => storeCall(() => store.set(key(value), JSON.stringify(credentials))),
|
|
2247
|
+
...remove ? {
|
|
2248
|
+
delete: (value) => storeCall(() => remove(key(value)))
|
|
2249
|
+
} : {},
|
|
2250
|
+
...lock ? {
|
|
2251
|
+
withLock: (value, fn) => lock(key(value), fn)
|
|
2252
|
+
} : {}
|
|
2253
|
+
};
|
|
2254
|
+
}
|
|
2255
|
+
|
|
1123
2256
|
// src/client.ts
|
|
1124
|
-
function createArcaClient(config) {
|
|
1125
|
-
|
|
1126
|
-
|
|
2257
|
+
function createArcaClient(config = {}) {
|
|
2258
|
+
const discovered = discoverArcaClientConfig(config);
|
|
2259
|
+
assertArcaClientConfig(discovered);
|
|
2260
|
+
const normalizedConfig = normalizeArcaClientConfig(discovered);
|
|
2261
|
+
if (normalizedConfig.store && !normalizedConfig.wsaaSessionStore) {
|
|
2262
|
+
normalizedConfig.wsaaSessionStore = createWsaaStoreAdapter(
|
|
2263
|
+
normalizedConfig.store
|
|
2264
|
+
);
|
|
2265
|
+
}
|
|
1127
2266
|
const logger = createArcaLogger(normalizedConfig.logger);
|
|
1128
2267
|
const auth = createWsaaAuthModule({ config: normalizedConfig, logger });
|
|
1129
2268
|
const soap = createSoapTransport({ config: normalizedConfig, logger });
|
|
@@ -1134,16 +2273,184 @@ function createArcaClient(config) {
|
|
|
1134
2273
|
retries: normalizedConfig.retries,
|
|
1135
2274
|
retryDelay: normalizedConfig.retryDelay
|
|
1136
2275
|
});
|
|
2276
|
+
const wsfe = createWsfeService({ config: normalizedConfig, auth, soap });
|
|
2277
|
+
const vouchers = createVouchersService(wsfe, normalizedConfig);
|
|
1137
2278
|
return {
|
|
1138
2279
|
config: publicConfig,
|
|
1139
|
-
|
|
2280
|
+
issue: vouchers.issue,
|
|
2281
|
+
cancel: vouchers.cancel,
|
|
2282
|
+
wsfe,
|
|
1140
2283
|
wsmtxca: createWsmtxcaService({ config: normalizedConfig, auth, soap }),
|
|
1141
2284
|
padron: createPadronService({ config: normalizedConfig, auth, soap })
|
|
1142
2285
|
};
|
|
1143
2286
|
}
|
|
2287
|
+
|
|
2288
|
+
// src/store/file.ts
|
|
2289
|
+
import { createHash as createHash3, randomUUID } from "crypto";
|
|
2290
|
+
import { mkdir, readFile, rename, unlink, writeFile } from "fs/promises";
|
|
2291
|
+
import { join } from "path";
|
|
2292
|
+
function createFileStore(directory) {
|
|
2293
|
+
const path = (key) => join(directory, createHash3("sha256").update(key).digest("hex"));
|
|
2294
|
+
const ensure = () => mkdir(directory, { recursive: true, mode: 448 });
|
|
2295
|
+
return {
|
|
2296
|
+
get: (key) => storeCall(async () => {
|
|
2297
|
+
try {
|
|
2298
|
+
return await readFile(path(key), "utf8");
|
|
2299
|
+
} catch (error) {
|
|
2300
|
+
if (error.code === "ENOENT") {
|
|
2301
|
+
return null;
|
|
2302
|
+
}
|
|
2303
|
+
throw error;
|
|
2304
|
+
}
|
|
2305
|
+
}),
|
|
2306
|
+
set: (key, value) => storeCall(async () => {
|
|
2307
|
+
await ensure();
|
|
2308
|
+
const temporary = `${path(key)}.${randomUUID()}.tmp`;
|
|
2309
|
+
try {
|
|
2310
|
+
await writeFile(temporary, value, { mode: 384, flag: "wx" });
|
|
2311
|
+
await rename(temporary, path(key));
|
|
2312
|
+
} finally {
|
|
2313
|
+
await unlink(temporary).catch(() => void 0);
|
|
2314
|
+
}
|
|
2315
|
+
}),
|
|
2316
|
+
add: (key, value) => storeCall(async () => {
|
|
2317
|
+
await ensure();
|
|
2318
|
+
try {
|
|
2319
|
+
await writeFile(path(key), value, { flag: "wx", mode: 384 });
|
|
2320
|
+
return true;
|
|
2321
|
+
} catch (error) {
|
|
2322
|
+
if (error.code === "EEXIST") {
|
|
2323
|
+
return false;
|
|
2324
|
+
}
|
|
2325
|
+
throw error;
|
|
2326
|
+
}
|
|
2327
|
+
}),
|
|
2328
|
+
delete: (key) => storeCall(async () => {
|
|
2329
|
+
try {
|
|
2330
|
+
await unlink(path(key));
|
|
2331
|
+
} catch (error) {
|
|
2332
|
+
if (error.code !== "ENOENT") {
|
|
2333
|
+
throw error;
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
})
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2340
|
+
// src/store/memory.ts
|
|
2341
|
+
function createMemoryStore() {
|
|
2342
|
+
const values = /* @__PURE__ */ new Map();
|
|
2343
|
+
const locks = /* @__PURE__ */ new Map();
|
|
2344
|
+
return {
|
|
2345
|
+
get: (key) => Promise.resolve(values.get(key) ?? null),
|
|
2346
|
+
set(key, value) {
|
|
2347
|
+
values.set(key, value);
|
|
2348
|
+
return Promise.resolve();
|
|
2349
|
+
},
|
|
2350
|
+
add(key, value) {
|
|
2351
|
+
if (values.has(key)) {
|
|
2352
|
+
return Promise.resolve(false);
|
|
2353
|
+
}
|
|
2354
|
+
values.set(key, value);
|
|
2355
|
+
return Promise.resolve(true);
|
|
2356
|
+
},
|
|
2357
|
+
delete(key) {
|
|
2358
|
+
values.delete(key);
|
|
2359
|
+
return Promise.resolve();
|
|
2360
|
+
},
|
|
2361
|
+
async withLock(key, fn) {
|
|
2362
|
+
const previous = locks.get(key) ?? Promise.resolve();
|
|
2363
|
+
let release = () => void 0;
|
|
2364
|
+
const current = new Promise((resolve) => {
|
|
2365
|
+
release = resolve;
|
|
2366
|
+
});
|
|
2367
|
+
const queued = previous.catch(() => void 0).then(() => current);
|
|
2368
|
+
locks.set(key, queued);
|
|
2369
|
+
await previous.catch(() => void 0);
|
|
2370
|
+
try {
|
|
2371
|
+
return await fn();
|
|
2372
|
+
} finally {
|
|
2373
|
+
release();
|
|
2374
|
+
if (locks.get(key) === queued) {
|
|
2375
|
+
locks.delete(key);
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
};
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
// src/store/postgres.ts
|
|
2383
|
+
function createPostgresStore({
|
|
2384
|
+
query,
|
|
2385
|
+
table = "arca_store"
|
|
2386
|
+
}) {
|
|
2387
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(table)) {
|
|
2388
|
+
throw new ArcaConfigurationError("Invalid ARCA store table identifier.");
|
|
2389
|
+
}
|
|
2390
|
+
const name = `"${table}"`;
|
|
2391
|
+
const run = (text, params) => storeCall(async () => {
|
|
2392
|
+
const result = await query(text, params);
|
|
2393
|
+
return Array.isArray(result) ? result : result.rows;
|
|
2394
|
+
});
|
|
2395
|
+
return {
|
|
2396
|
+
async get(key) {
|
|
2397
|
+
return (await run(`SELECT value FROM ${name} WHERE key = $1`, [key]))[0]?.value ?? null;
|
|
2398
|
+
},
|
|
2399
|
+
async set(key, value) {
|
|
2400
|
+
await run(
|
|
2401
|
+
`INSERT INTO ${name} (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, updated_at = now()`,
|
|
2402
|
+
[key, value]
|
|
2403
|
+
);
|
|
2404
|
+
},
|
|
2405
|
+
async add(key, value) {
|
|
2406
|
+
return (await run(
|
|
2407
|
+
`INSERT INTO ${name} (key, value) VALUES ($1, $2) ON CONFLICT DO NOTHING RETURNING key`,
|
|
2408
|
+
[key, value]
|
|
2409
|
+
)).length > 0;
|
|
2410
|
+
},
|
|
2411
|
+
async delete(key) {
|
|
2412
|
+
await run(`DELETE FROM ${name} WHERE key = $1`, [key]);
|
|
2413
|
+
}
|
|
2414
|
+
};
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
// src/store/redis.ts
|
|
2418
|
+
function createRedisStore(client, options = {}) {
|
|
2419
|
+
const flavor = options.flavor ?? (typeof client.call === "function" ? "ioredis" : "upstash");
|
|
2420
|
+
if (flavor === "ioredis" && !client.call) {
|
|
2421
|
+
throw new ArcaConfigurationError(
|
|
2422
|
+
"ARCA Redis ioredis flavor requires client.call."
|
|
2423
|
+
);
|
|
2424
|
+
}
|
|
2425
|
+
const upstashSet = client.set;
|
|
2426
|
+
return {
|
|
2427
|
+
get: (key) => storeCall(async () => {
|
|
2428
|
+
const value = await client.get(key);
|
|
2429
|
+
if (value === null) {
|
|
2430
|
+
return null;
|
|
2431
|
+
}
|
|
2432
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
2433
|
+
}),
|
|
2434
|
+
set: (key, value) => storeCall(async () => {
|
|
2435
|
+
await client.set(key, value);
|
|
2436
|
+
}),
|
|
2437
|
+
add: (key, value) => storeCall(
|
|
2438
|
+
async () => (flavor === "ioredis" ? await client.call?.("SET", key, value, "NX") : await upstashSet.call(client, key, value, { nx: true })) === "OK"
|
|
2439
|
+
),
|
|
2440
|
+
...client.del ? {
|
|
2441
|
+
delete: (key) => storeCall(async () => {
|
|
2442
|
+
await client.del?.(key);
|
|
2443
|
+
})
|
|
2444
|
+
} : {}
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
1144
2447
|
export {
|
|
1145
2448
|
ARCA_ENVIRONMENTS,
|
|
1146
2449
|
ARCA_ENV_VARIABLES,
|
|
2450
|
+
ARCA_FINAL_CONSUMER_IDENTIFICATION_THRESHOLD_MINOR_UNITS,
|
|
2451
|
+
ARCA_INVOICE_CLASS_BY_ISSUER,
|
|
2452
|
+
ARCA_ISSUER_CONDITION_IDS,
|
|
2453
|
+
ARCA_RECEIVER_CONDITION_IDS,
|
|
1147
2454
|
ArcaAuthenticationError,
|
|
1148
2455
|
ArcaConfigurationError,
|
|
1149
2456
|
ArcaError,
|
|
@@ -1157,11 +2464,17 @@ export {
|
|
|
1157
2464
|
buildFacturaC,
|
|
1158
2465
|
createArcaClient,
|
|
1159
2466
|
createArcaClientConfigFromEnv,
|
|
2467
|
+
createFileStore,
|
|
2468
|
+
createMemoryStore,
|
|
1160
2469
|
createMemoryWsaaSessionStore,
|
|
1161
2470
|
createPadronService,
|
|
2471
|
+
createPostgresStore,
|
|
2472
|
+
createRedisStore,
|
|
1162
2473
|
createWsfeService,
|
|
1163
2474
|
createWsmtxcaService,
|
|
1164
2475
|
isArcaAuthenticationError,
|
|
1165
|
-
|
|
2476
|
+
matchWsfeVoucherIdentity,
|
|
2477
|
+
resolveArcaEnvironment,
|
|
2478
|
+
toArcaSafeErrorMetadata
|
|
1166
2479
|
};
|
|
1167
2480
|
//# sourceMappingURL=index.mjs.map
|