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.
Files changed (37) hide show
  1. package/README.md +799 -27
  2. package/dist/{chunk-IOKZX6CA.mjs → chunk-4QVFXT35.mjs} +2 -2
  3. package/dist/chunk-76WU5BVI.mjs +109 -0
  4. package/dist/chunk-76WU5BVI.mjs.map +1 -0
  5. package/dist/{chunk-EDY3PNKJ.mjs → chunk-7LEKQ4P7.mjs} +2 -2
  6. package/dist/{chunk-OHHXHYLV.mjs → chunk-RCM4NAAG.mjs} +244 -48
  7. package/dist/chunk-RCM4NAAG.mjs.map +1 -0
  8. package/dist/{chunk-A3C3Y5PI.mjs → chunk-XNBD5NVL.mjs} +32 -31
  9. package/dist/chunk-XNBD5NVL.mjs.map +1 -0
  10. package/dist/{chunk-MBWOFO67.mjs → chunk-ZOSNASXB.mjs} +14 -2
  11. package/dist/chunk-ZOSNASXB.mjs.map +1 -0
  12. package/dist/constants.d.ts +49 -1
  13. package/dist/constants.mjs +9 -1
  14. package/dist/errors.d.ts +2 -2
  15. package/dist/errors.mjs +5 -3
  16. package/dist/{fiscal-evidence-BQuBbJ3h.d.ts → fiscal-evidence-DBzakqBE.d.ts} +11 -3
  17. package/dist/{index-CVpygFb0.d.ts → index-CVGQyyYM.d.ts} +1 -1
  18. package/dist/index.d.ts +238 -9
  19. package/dist/index.mjs +1343 -30
  20. package/dist/index.mjs.map +1 -1
  21. package/dist/padron.d.ts +2 -2
  22. package/dist/padron.mjs +2 -2
  23. package/dist/{types-BXq0da71.d.ts → types-BiznC1-X.d.ts} +12 -1
  24. package/dist/types.d.ts +1 -1
  25. package/dist/wsfe-DF0jAE2k.d.ts +325 -0
  26. package/dist/wsfe.d.ts +4 -309
  27. package/dist/wsfe.mjs +4 -4
  28. package/dist/wsmtxca.d.ts +14 -5
  29. package/dist/wsmtxca.mjs +3 -3
  30. package/package.json +1 -1
  31. package/dist/chunk-A3C3Y5PI.mjs.map +0 -1
  32. package/dist/chunk-MBWOFO67.mjs.map +0 -1
  33. package/dist/chunk-OHHXHYLV.mjs.map +0 -1
  34. package/dist/chunk-VVY2LZIZ.mjs +0 -61
  35. package/dist/chunk-VVY2LZIZ.mjs.map +0 -1
  36. /package/dist/{chunk-IOKZX6CA.mjs.map → chunk-4QVFXT35.mjs.map} +0 -0
  37. /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-EDY3PNKJ.mjs";
3
+ } from "./chunk-7LEKQ4P7.mjs";
4
4
  import {
5
5
  buildFacturaB,
6
6
  buildFacturaC,
7
- createWsfeService
8
- } from "./chunk-OHHXHYLV.mjs";
9
- import "./chunk-VVY2LZIZ.mjs";
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-A3C3Y5PI.mjs";
13
- import "./chunk-IOKZX6CA.mjs";
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
- } from "./chunk-MBWOFO67.mjs";
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
- if (!/^\d{11}$/.test(normalized.taxId)) {
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.trim(),
200
- certificatePem: config.certificatePem.trim(),
201
- privateKeyPem: config.privateKeyPem.trim(),
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 createHash("sha256").update(config.certificatePem).digest("hex");
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
- assertArcaClientConfig(config);
1126
- const normalizedConfig = normalizeArcaClientConfig(config);
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
- wsfe: createWsfeService({ config: normalizedConfig, auth, soap }),
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
- resolveArcaEnvironment
2476
+ matchWsfeVoucherIdentity,
2477
+ resolveArcaEnvironment,
2478
+ toArcaSafeErrorMetadata
1166
2479
  };
1167
2480
  //# sourceMappingURL=index.mjs.map