facturas 0.9.0 → 0.11.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 (38) hide show
  1. package/README.md +95 -786
  2. package/bin/facturas.mjs +4 -0
  3. package/dist/{chunk-RCM4NAAG.mjs → chunk-5KCTRQJX.mjs} +83 -284
  4. package/dist/chunk-5KCTRQJX.mjs.map +1 -0
  5. package/dist/{chunk-XNBD5NVL.mjs → chunk-ALH5IPSY.mjs} +2 -57
  6. package/dist/chunk-ALH5IPSY.mjs.map +1 -0
  7. package/dist/{chunk-76WU5BVI.mjs → chunk-GX5ERXT6.mjs} +1 -1
  8. package/dist/chunk-GX5ERXT6.mjs.map +1 -0
  9. package/dist/chunk-LOWV7IF6.mjs +3758 -0
  10. package/dist/chunk-LOWV7IF6.mjs.map +1 -0
  11. package/dist/chunk-LR4M2AMG.mjs +169 -0
  12. package/dist/chunk-LR4M2AMG.mjs.map +1 -0
  13. package/dist/{chunk-4QVFXT35.mjs → chunk-UDX6HLAU.mjs} +1 -17
  14. package/dist/{chunk-4QVFXT35.mjs.map → chunk-UDX6HLAU.mjs.map} +1 -1
  15. package/dist/cli.d.ts +49 -0
  16. package/dist/cli.mjs +2316 -0
  17. package/dist/cli.mjs.map +1 -0
  18. package/dist/client-BZxeScuK.d.ts +521 -0
  19. package/dist/constants.d.ts +1 -1
  20. package/dist/constants.mjs +1 -1
  21. package/dist/{wsfe-DF0jAE2k.d.ts → decimal-B0HtrMFY.d.ts} +9 -66
  22. package/dist/errors.d.ts +2 -2
  23. package/dist/{fiscal-evidence-DBzakqBE.d.ts → fiscal-evidence-B1Qe6XMg.d.ts} +2 -2
  24. package/dist/{index-CVGQyyYM.d.ts → index-D8LGFvxV.d.ts} +1 -1
  25. package/dist/index.d.ts +11 -235
  26. package/dist/index.mjs +21 -2315
  27. package/dist/index.mjs.map +1 -1
  28. package/dist/padron.d.ts +2 -2
  29. package/dist/{types-BiznC1-X.d.ts → types-r2nf55gh.d.ts} +1 -1
  30. package/dist/types.d.ts +1 -1
  31. package/dist/wsfe.d.ts +45 -4
  32. package/dist/wsfe.mjs +6 -4
  33. package/dist/wsmtxca.d.ts +4 -20
  34. package/dist/wsmtxca.mjs +2 -2
  35. package/package.json +5 -1
  36. package/dist/chunk-76WU5BVI.mjs.map +0 -1
  37. package/dist/chunk-RCM4NAAG.mjs.map +0 -1
  38. package/dist/chunk-XNBD5NVL.mjs.map +0 -1
@@ -0,0 +1,3758 @@
1
+ import {
2
+ createPadronService
3
+ } from "./chunk-7LEKQ4P7.mjs";
4
+ import {
5
+ arcaMinorUnitsToNumber,
6
+ assertArcaMinorUnits,
7
+ calculateWsfeAmounts,
8
+ createWsfeService,
9
+ isWithinArcaTolerance,
10
+ normalizeArcaAmountToMinorUnits,
11
+ normalizeWsfeDateInput,
12
+ normalizeWsfeVoucherInput,
13
+ serializeArcaExchangeRate
14
+ } from "./chunk-5KCTRQJX.mjs";
15
+ import {
16
+ ARCA_CURRENCY_IDS,
17
+ ARCA_DOCUMENT_TYPES,
18
+ ARCA_FINAL_CONSUMER_IDENTIFICATION_THRESHOLD_MINOR_UNITS,
19
+ ARCA_INVOICE_CLASS_BY_ISSUER,
20
+ ARCA_ISSUER_CONDITION_IDS,
21
+ ARCA_RECEIVER_CONDITION_IDS,
22
+ ARCA_VOUCHER_TYPES
23
+ } from "./chunk-GX5ERXT6.mjs";
24
+ import {
25
+ createWsmtxcaService
26
+ } from "./chunk-ALH5IPSY.mjs";
27
+ import {
28
+ ArcaConfigurationError,
29
+ ArcaError,
30
+ ArcaInputError,
31
+ ArcaInvalidSoapResponseError,
32
+ ArcaServiceError,
33
+ ArcaSoapFaultError,
34
+ ArcaTransportError,
35
+ createResponseBodyDiagnostic,
36
+ createSafeErrorDiagnostic,
37
+ toArcaSafeErrorMetadata
38
+ } from "./chunk-ZOSNASXB.mjs";
39
+
40
+ // src/config.ts
41
+ var ARCA_ENVIRONMENTS = ["production", "test"];
42
+ var ARCA_ENV_VARIABLES = {
43
+ taxId: "ARCA_TAX_ID",
44
+ certificatePem: "ARCA_CERTIFICATE_PEM",
45
+ privateKeyPem: "ARCA_PRIVATE_KEY_PEM",
46
+ environment: "ARCA_ENVIRONMENT"
47
+ };
48
+ var PRIVATE_KEY_PEM_PREFIXES = [
49
+ "-----BEGIN PRIVATE KEY-----",
50
+ "-----BEGIN RSA PRIVATE KEY-----"
51
+ ];
52
+ var ENCRYPTED_PRIVATE_KEY_PEM_PREFIX = "-----BEGIN ENCRYPTED PRIVATE KEY-----";
53
+ var LEGACY_ENCRYPTED_RSA_PRIVATE_KEY_PATTERN = /^-----BEGIN RSA PRIVATE KEY-----[\s\S]*^Proc-Type:\s*4,\s*ENCRYPTED\s*$/m;
54
+ var VALID_ARCA_LOG_LEVELS = ["debug", "info", "warn", "error"];
55
+ var DEFAULT_ARCA_TIMEOUT_MS = 3e4;
56
+ var DEFAULT_ARCA_RETRIES = 0;
57
+ var DEFAULT_ARCA_RETRY_DELAY_MS = 500;
58
+ function resolveArcaEnvironment(production) {
59
+ return production ? "production" : "test";
60
+ }
61
+ function createArcaClientConfigFromEnv(options = {}) {
62
+ const env = options.env ?? process.env;
63
+ const variableNames = {
64
+ ...ARCA_ENV_VARIABLES,
65
+ ...options.variableNames
66
+ };
67
+ const environmentInput = readEnv(env, variableNames.environment);
68
+ const environmentValue = normalizeEnvironmentValue(environmentInput);
69
+ const config = {
70
+ taxId: readEnv(env, variableNames.taxId) ?? "",
71
+ certificatePem: readEnv(env, variableNames.certificatePem) ?? "",
72
+ privateKeyPem: readEnv(env, variableNames.privateKeyPem) ?? "",
73
+ environment: environmentValue ?? environmentInput ?? options.defaultEnvironment ?? "test"
74
+ };
75
+ assertArcaClientConfig(config);
76
+ return normalizeArcaClientConfig(config);
77
+ }
78
+ function assertArcaClientConfig(config) {
79
+ const invalidFields = [];
80
+ const normalized = normalizeArcaClientConfig(config);
81
+ const timeout = normalized.timeout ?? DEFAULT_ARCA_TIMEOUT_MS;
82
+ const retries = normalized.retries ?? DEFAULT_ARCA_RETRIES;
83
+ const retryDelay = normalized.retryDelay ?? DEFAULT_ARCA_RETRY_DELAY_MS;
84
+ if (normalized.privateKeyPem.startsWith(ENCRYPTED_PRIVATE_KEY_PEM_PREFIX) || LEGACY_ENCRYPTED_RSA_PRIVATE_KEY_PATTERN.test(normalized.privateKeyPem)) {
85
+ throw new ArcaConfigurationError(
86
+ "Encrypted private keys are not supported. Provide an unencrypted PKCS#8 or RSA private key PEM."
87
+ );
88
+ }
89
+ invalidFields.push(...invalidCredentialFields(normalized));
90
+ if (!ARCA_ENVIRONMENTS.includes(normalized.environment)) {
91
+ invalidFields.push("environment");
92
+ }
93
+ if (!Number.isFinite(timeout) || timeout <= 0) {
94
+ invalidFields.push("timeout");
95
+ }
96
+ if (!Number.isInteger(retries) || retries < 0) {
97
+ invalidFields.push("retries");
98
+ }
99
+ if (!Number.isFinite(retryDelay) || retryDelay < 0) {
100
+ invalidFields.push("retryDelay");
101
+ }
102
+ const loggerLevel = normalized.logger?.level;
103
+ if (loggerLevel !== void 0 && !VALID_ARCA_LOG_LEVELS.includes(loggerLevel)) {
104
+ invalidFields.push("logger.level");
105
+ }
106
+ if (normalized.logger?.log !== void 0 && typeof normalized.logger.log !== "function") {
107
+ invalidFields.push("logger.log");
108
+ }
109
+ invalidFields.push(...getInvalidWsaaSessionStoreFields(normalized));
110
+ if (invalidFields.length > 0) {
111
+ throw new ArcaConfigurationError(
112
+ `Missing or invalid ARCA client config fields: ${invalidFields.join(", ")}`
113
+ );
114
+ }
115
+ }
116
+ function getInvalidWsaaSessionStoreFields(config) {
117
+ const store = config.wsaaSessionStore;
118
+ if (store === void 0) {
119
+ return [];
120
+ }
121
+ const invalidFields = [];
122
+ if (typeof store.get !== "function") {
123
+ invalidFields.push("wsaaSessionStore.get");
124
+ }
125
+ if (typeof store.set !== "function") {
126
+ invalidFields.push("wsaaSessionStore.set");
127
+ }
128
+ if (store.delete !== void 0 && typeof store.delete !== "function") {
129
+ invalidFields.push("wsaaSessionStore.delete");
130
+ }
131
+ if (store.withLock !== void 0 && typeof store.withLock !== "function") {
132
+ invalidFields.push("wsaaSessionStore.withLock");
133
+ }
134
+ return invalidFields;
135
+ }
136
+ var ARCA_WSAA_CONFIG = {
137
+ namespace: "http://wsaa.view.sua.dvadac.desein.afip.gov",
138
+ endpoint: {
139
+ production: "https://wsaa.afip.gov.ar/ws/services/LoginCms",
140
+ test: "https://wsaahomo.afip.gov.ar/ws/services/LoginCms"
141
+ },
142
+ soapVersion: "1.1",
143
+ soapActionBase: "",
144
+ usesEmptySoapAction: true
145
+ };
146
+ var ARCA_SERVICE_CONFIG = {
147
+ wsaa: ARCA_WSAA_CONFIG,
148
+ wsfe: {
149
+ namespace: "http://ar.gov.afip.dif.FEV1/",
150
+ endpoint: {
151
+ production: "https://servicios1.afip.gov.ar/wsfev1/service.asmx",
152
+ test: "https://wswhomo.afip.gov.ar/wsfev1/service.asmx"
153
+ },
154
+ soapVersion: "1.2",
155
+ soapActionBase: "http://ar.gov.afip.dif.FEV1/",
156
+ useLegacyTlsSecurityLevel0: true
157
+ },
158
+ wsmtxca: {
159
+ namespace: "http://impl.service.wsmtxca.afip.gov.ar/service/",
160
+ endpoint: {
161
+ production: "https://serviciosjava.afip.gov.ar/wsmtxca/services/MTXCAService",
162
+ test: "https://fwshomo.afip.gov.ar/wsmtxca/services/MTXCAService"
163
+ },
164
+ soapVersion: "1.1",
165
+ soapActionBase: "http://impl.service.wsmtxca.afip.gov.ar/service/"
166
+ },
167
+ "padron-a5": {
168
+ namespace: "http://a5.soap.ws.server.puc.sr/",
169
+ endpoint: {
170
+ production: "https://aws.afip.gov.ar/sr-padron/webservices/personaServiceA5",
171
+ test: "https://awshomo.afip.gov.ar/sr-padron/webservices/personaServiceA5"
172
+ },
173
+ soapVersion: "1.1",
174
+ soapActionBase: "",
175
+ usesEmptySoapAction: true
176
+ },
177
+ "padron-a13": {
178
+ namespace: "http://a13.soap.ws.server.puc.sr/",
179
+ endpoint: {
180
+ production: "https://aws.afip.gov.ar/sr-padron/webservices/personaServiceA13",
181
+ test: "https://awshomo.afip.gov.ar/sr-padron/webservices/personaServiceA13"
182
+ },
183
+ soapVersion: "1.1",
184
+ soapActionBase: "",
185
+ usesEmptySoapAction: true
186
+ }
187
+ };
188
+ function getArcaServiceConfig(service) {
189
+ const serviceConfig = ARCA_SERVICE_CONFIG[service];
190
+ if (!serviceConfig) {
191
+ throw new ArcaConfigurationError(
192
+ `Unsupported ARCA service configuration: ${service}`
193
+ );
194
+ }
195
+ return serviceConfig;
196
+ }
197
+ function normalizeArcaClientConfig(config) {
198
+ const normalizedEnvironment = normalizeEnvironmentValue(String(config.environment)) ?? config.environment;
199
+ const normalizedLoggerLevel = normalizeLogLevelValue(config.logger?.level);
200
+ return {
201
+ taxId: config.taxId?.trim() ?? "",
202
+ certificatePem: config.certificatePem?.trim() ?? "",
203
+ privateKeyPem: config.privateKeyPem?.trim() ?? "",
204
+ environment: normalizedEnvironment ?? "test",
205
+ ...config.store === void 0 ? {} : { store: config.store },
206
+ timeout: config.timeout ?? DEFAULT_ARCA_TIMEOUT_MS,
207
+ retries: config.retries ?? DEFAULT_ARCA_RETRIES,
208
+ retryDelay: config.retryDelay ?? DEFAULT_ARCA_RETRY_DELAY_MS,
209
+ ...config.logger === void 0 ? {} : {
210
+ logger: {
211
+ ...config.logger,
212
+ ...normalizedLoggerLevel === void 0 ? {} : { level: normalizedLoggerLevel }
213
+ }
214
+ },
215
+ ...config.wsaaSessionStore === void 0 ? {} : { wsaaSessionStore: config.wsaaSessionStore }
216
+ };
217
+ }
218
+ function normalizeEnvironmentValue(value) {
219
+ if (!value) {
220
+ return void 0;
221
+ }
222
+ const normalized = value.trim().toLowerCase();
223
+ if (ARCA_ENVIRONMENTS.includes(normalized)) {
224
+ return normalized;
225
+ }
226
+ return void 0;
227
+ }
228
+ function readEnv(env, variableName) {
229
+ return env[variableName]?.trim() || void 0;
230
+ }
231
+ function normalizeLogLevelValue(value) {
232
+ if (!value) {
233
+ return void 0;
234
+ }
235
+ const normalized = value.trim().toLowerCase();
236
+ if (VALID_ARCA_LOG_LEVELS.includes(normalized)) {
237
+ return normalized;
238
+ }
239
+ return value;
240
+ }
241
+ function discoverArcaClientConfig(config) {
242
+ const environment = config.environment ?? readEnv(process.env, ARCA_ENV_VARIABLES.environment);
243
+ if (environment === void 0) {
244
+ throw new ArcaConfigurationError(
245
+ `environment is required: pass environment or set ${ARCA_ENV_VARIABLES.environment} to "test" or "production".`
246
+ );
247
+ }
248
+ return {
249
+ ...config,
250
+ taxId: config.taxId ?? readEnv(process.env, ARCA_ENV_VARIABLES.taxId) ?? "",
251
+ certificatePem: config.certificatePem ?? readEnv(process.env, ARCA_ENV_VARIABLES.certificatePem) ?? "",
252
+ privateKeyPem: config.privateKeyPem ?? readEnv(process.env, ARCA_ENV_VARIABLES.privateKeyPem) ?? "",
253
+ environment
254
+ };
255
+ }
256
+ function invalidCredentialFields(normalized) {
257
+ const invalidFields = [];
258
+ if (!/^\d{11}$/.test(normalized.taxId)) {
259
+ invalidFields.push(normalized.taxId ? "taxId" : "taxId (ARCA_TAX_ID)");
260
+ }
261
+ if (!normalized.certificatePem.startsWith("-----BEGIN CERTIFICATE-----")) {
262
+ invalidFields.push(
263
+ normalized.certificatePem ? "certificatePem" : "certificatePem (ARCA_CERTIFICATE_PEM)"
264
+ );
265
+ }
266
+ if (!PRIVATE_KEY_PEM_PREFIXES.some(
267
+ (prefix) => normalized.privateKeyPem.startsWith(prefix)
268
+ )) {
269
+ invalidFields.push(
270
+ normalized.privateKeyPem ? "privateKeyPem" : "privateKeyPem (ARCA_PRIVATE_KEY_PEM)"
271
+ );
272
+ }
273
+ return invalidFields;
274
+ }
275
+
276
+ // src/store/types.ts
277
+ import { createHash } from "crypto";
278
+ function attemptKey(environment, taxId, key) {
279
+ return `arca:v1:attempt:${environment}:${taxId}:${key}`;
280
+ }
281
+ function canonicalHash(input) {
282
+ return createHash("sha256").update(JSON.stringify(canonical(input))).digest("hex");
283
+ }
284
+ function canonical(value) {
285
+ if (Array.isArray(value)) {
286
+ return value.map(canonical);
287
+ }
288
+ if (value instanceof Date) {
289
+ return value.toJSON();
290
+ }
291
+ if (value !== null && typeof value === "object") {
292
+ return Object.fromEntries(
293
+ Object.entries(value).filter(([, item]) => item !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([key, item]) => [key, canonical(item)])
294
+ );
295
+ }
296
+ return value;
297
+ }
298
+ async function storeCall(fn) {
299
+ try {
300
+ return await fn();
301
+ } catch (cause) {
302
+ throw new ArcaConfigurationError("ARCA store operation failed.", { cause });
303
+ }
304
+ }
305
+
306
+ // src/services/wsfe-identity.ts
307
+ function matchWsfeVoucherIdentity(sent, number, found) {
308
+ let missing;
309
+ const compare = (field, expected, actual, normalize) => {
310
+ if (actual === void 0 || actual === null || expected === void 0) {
311
+ missing ??= field;
312
+ return void 0;
313
+ }
314
+ try {
315
+ const left = normalize ? normalize(expected) : expected;
316
+ const right = normalize ? normalize(actual) : actual;
317
+ if (left !== right) {
318
+ return {
319
+ matches: false,
320
+ evidence: "conflict",
321
+ reason: `${field} differs from the sent input`
322
+ };
323
+ }
324
+ } catch {
325
+ missing ??= field;
326
+ }
327
+ return void 0;
328
+ };
329
+ const checks = [
330
+ ["voucherType", sent.voucherType, found.voucherType],
331
+ ["salesPoint", sent.salesPoint, found.salesPoint],
332
+ ["number", number, found.voucherNumber, normalizeVoucherNumber],
333
+ [
334
+ "date",
335
+ sent.voucherDate,
336
+ found.voucherDate,
337
+ (value) => normalizeWsfeDateInput(value, "date")
338
+ ],
339
+ ["concept", sent.concept, found.concept],
340
+ ["documentType", sent.documentType, found.documentType],
341
+ [
342
+ "documentNumber",
343
+ sent.documentNumber,
344
+ found.documentNumber,
345
+ normalizeDocument
346
+ ],
347
+ [
348
+ "receiverVatConditionId",
349
+ sent.receiverVatConditionId,
350
+ found.receiverVatConditionId
351
+ ],
352
+ ["currencyId", sent.currencyId, found.currencyId],
353
+ [
354
+ "exchangeRate",
355
+ sent.exchangeRate ?? (sent.currencyId === "PES" ? 1 : void 0),
356
+ found.exchangeRate,
357
+ (value) => serializeArcaExchangeRate(value, "exchangeRate")
358
+ ]
359
+ ];
360
+ for (const field of [
361
+ "totalAmount",
362
+ "netAmount",
363
+ "vatAmount",
364
+ "exemptAmount",
365
+ "nonTaxableAmount",
366
+ "taxAmount"
367
+ ]) {
368
+ checks.push([
369
+ field,
370
+ sent[field],
371
+ found[field],
372
+ (value) => normalizeArcaAmountToMinorUnits(value, field)
373
+ ]);
374
+ }
375
+ if (sent.concept === 2 || sent.concept === 3) {
376
+ for (const field of [
377
+ "serviceStartDate",
378
+ "serviceEndDate",
379
+ "paymentDueDate"
380
+ ]) {
381
+ checks.push([
382
+ field,
383
+ sent[field],
384
+ found[field],
385
+ (value) => normalizeWsfeDateInput(value, field)
386
+ ]);
387
+ }
388
+ }
389
+ if (sent.concept === 1 && sent.paymentDueDate) {
390
+ checks.push([
391
+ "paymentDueDate",
392
+ sent.paymentDueDate,
393
+ found.paymentDueDate,
394
+ (value) => normalizeWsfeDateInput(value, "paymentDueDate")
395
+ ]);
396
+ }
397
+ for (const check of checks) {
398
+ const result = compare(...check);
399
+ if (result) {
400
+ return result;
401
+ }
402
+ }
403
+ const detailMatch = compareDetails(sent, found);
404
+ if (!detailMatch.matches) {
405
+ if (detailMatch.evidence === "conflict") {
406
+ return detailMatch;
407
+ }
408
+ missing ??= detailMatch.reason;
409
+ }
410
+ missing ??= incompleteAuthorization(sent, found);
411
+ return missing ? {
412
+ matches: false,
413
+ evidence: "incomplete",
414
+ reason: `Cannot verify ${missing}`
415
+ } : { matches: true };
416
+ }
417
+ function incompleteAuthorization(sent, found) {
418
+ let missing;
419
+ if (sent.concept !== 1 && sent.concept !== 2 && sent.concept !== 3) {
420
+ missing ??= "unsupported concept";
421
+ }
422
+ if (!(found.result === "A" || found.result === "O")) {
423
+ missing ??= "authorized result";
424
+ }
425
+ if (!found.cae?.trim()) {
426
+ missing ??= "cae";
427
+ }
428
+ if (!found.caeExpiry?.trim()) {
429
+ missing ??= "caeExpiry";
430
+ }
431
+ return missing;
432
+ }
433
+ function normalizeVoucherNumber(value) {
434
+ if (!Number.isSafeInteger(value) || value < 1 || value > 99999999) {
435
+ throw new Error("Invalid voucher number");
436
+ }
437
+ return value;
438
+ }
439
+ function normalizeDocument(value) {
440
+ const text = String(value);
441
+ if (!/^\d+$/.test(text)) {
442
+ throw new Error("Invalid document number");
443
+ }
444
+ return BigInt(text);
445
+ }
446
+ function compareVatRates(expected, actual) {
447
+ if (actual === void 0) {
448
+ return expected.length === 0 ? { matches: true } : { matches: false, evidence: "incomplete", reason: "vatRates" };
449
+ }
450
+ if (actual.length !== expected.length || new Set(actual.map((rate) => rate.id)).size !== actual.length) {
451
+ return {
452
+ matches: false,
453
+ evidence: "conflict",
454
+ reason: "vatRates ids differ from the sent input"
455
+ };
456
+ }
457
+ for (const rate of expected) {
458
+ const found = actual.find((item) => item.id === rate.id);
459
+ if (!found) {
460
+ return {
461
+ matches: false,
462
+ evidence: "conflict",
463
+ reason: `vatRates id ${rate.id} differs from the sent input`
464
+ };
465
+ }
466
+ for (const field of ["baseAmount", "amount"]) {
467
+ try {
468
+ if (normalizeArcaAmountToMinorUnits(rate[field], field) !== normalizeArcaAmountToMinorUnits(found[field], field)) {
469
+ return {
470
+ matches: false,
471
+ evidence: "conflict",
472
+ reason: `vatRates[${rate.id}].${field} differs from the sent input`
473
+ };
474
+ }
475
+ } catch {
476
+ return {
477
+ matches: false,
478
+ evidence: "incomplete",
479
+ reason: `vatRates[${rate.id}].${field}`
480
+ };
481
+ }
482
+ }
483
+ }
484
+ return { matches: true };
485
+ }
486
+ function toVoucherSummary(found) {
487
+ const summary = { number: found.voucherNumber };
488
+ for (const field of [
489
+ "salesPoint",
490
+ "voucherType",
491
+ "concept",
492
+ "documentType",
493
+ "documentNumber",
494
+ "receiverVatConditionId",
495
+ "currencyId",
496
+ "exchangeRate",
497
+ "totalAmount",
498
+ "netAmount",
499
+ "vatAmount",
500
+ "exemptAmount",
501
+ "nonTaxableAmount",
502
+ "taxAmount",
503
+ "serviceStartDate",
504
+ "serviceEndDate",
505
+ "paymentDueDate",
506
+ "result",
507
+ "cae",
508
+ "caeExpiry"
509
+ ]) {
510
+ if (found[field] !== void 0) {
511
+ Object.assign(summary, { [field]: found[field] });
512
+ }
513
+ }
514
+ if (found.voucherDate !== void 0) {
515
+ summary.date = found.voucherDate;
516
+ }
517
+ if (found.vatRates !== void 0) {
518
+ summary.vatRates = found.vatRates.map(({ id, baseAmount, amount }) => ({
519
+ id,
520
+ baseAmount,
521
+ amount
522
+ }));
523
+ }
524
+ return summary;
525
+ }
526
+ function compareAssociations(sent, found) {
527
+ const expected = sent.associatedVouchers ?? [];
528
+ const actual = found.associatedVouchers;
529
+ if (!actual) {
530
+ return expected.length ? { matches: false, evidence: "incomplete", reason: "associatedVouchers" } : { matches: true };
531
+ }
532
+ if (expected.length !== actual.length) {
533
+ return {
534
+ matches: false,
535
+ evidence: "conflict",
536
+ reason: "associatedVouchers count differs"
537
+ };
538
+ }
539
+ for (const association of expected) {
540
+ const match = actual.find(
541
+ (v) => v.type === association.type && v.salesPoint === association.salesPoint && v.number === association.number
542
+ );
543
+ if (!match) {
544
+ return {
545
+ matches: false,
546
+ evidence: "conflict",
547
+ reason: "associatedVouchers differ from the sent input"
548
+ };
549
+ }
550
+ const metadata = compareAssociationMetadata(association, match);
551
+ if (!metadata.matches) {
552
+ return metadata;
553
+ }
554
+ }
555
+ return { matches: true };
556
+ }
557
+ function compareDetails(sent, found) {
558
+ let incomplete;
559
+ for (const result of [
560
+ compareVatRates(sent.vatRates ?? [], found.vatRates),
561
+ compareAssociations(sent, found),
562
+ compareExtensions(sent, found)
563
+ ]) {
564
+ if (result.matches) {
565
+ continue;
566
+ }
567
+ if (result.evidence === "conflict") {
568
+ return result;
569
+ }
570
+ incomplete ??= result;
571
+ }
572
+ return incomplete ?? { matches: true };
573
+ }
574
+ function extensionIdentity(field, value) {
575
+ if (Array.isArray(value)) {
576
+ return canonicalHash(
577
+ value.map((item) => {
578
+ if (field === "taxes") {
579
+ const tax = item;
580
+ return {
581
+ id: tax.id,
582
+ base: String(
583
+ normalizeArcaAmountToMinorUnits(tax.baseAmount, "base")
584
+ ),
585
+ amount: String(
586
+ normalizeArcaAmountToMinorUnits(tax.amount, "amount")
587
+ ),
588
+ rate: Number(tax.rate)
589
+ };
590
+ }
591
+ return item;
592
+ }).sort((a, b) => canonicalHash(a).localeCompare(canonicalHash(b)))
593
+ );
594
+ }
595
+ if (field === "associatedPeriod" && value) {
596
+ const period = value;
597
+ return canonicalHash({
598
+ start: normalizeWsfeDateInput(period.startDate, "start"),
599
+ end: normalizeWsfeDateInput(period.endDate, "end")
600
+ });
601
+ }
602
+ return canonicalHash(value ?? null);
603
+ }
604
+ function compareExtensions(sent, found) {
605
+ let missing;
606
+ for (const field of [
607
+ "taxes",
608
+ "optionalFields",
609
+ "buyers",
610
+ "activities",
611
+ "associatedPeriod",
612
+ "sameCurrencyForeignCancellation"
613
+ ]) {
614
+ const expected = sent[field];
615
+ const actual = found[field];
616
+ const empty = (value) => value === void 0 || Array.isArray(value) && value.length === 0;
617
+ if (empty(expected) && empty(actual)) {
618
+ continue;
619
+ }
620
+ if (actual === void 0) {
621
+ missing ??= field;
622
+ continue;
623
+ }
624
+ try {
625
+ if (extensionIdentity(field, expected) !== extensionIdentity(field, actual)) {
626
+ return {
627
+ matches: false,
628
+ evidence: "conflict",
629
+ reason: `${field} differs from the sent input`
630
+ };
631
+ }
632
+ } catch {
633
+ missing ??= field;
634
+ }
635
+ }
636
+ return missing ? { matches: false, evidence: "incomplete", reason: missing } : { matches: true };
637
+ }
638
+ function compareAssociationMetadata(association, match) {
639
+ for (const field of ["taxId", "voucherDate"]) {
640
+ if (association[field] === void 0) {
641
+ continue;
642
+ }
643
+ if (match[field] === void 0) {
644
+ return {
645
+ matches: false,
646
+ evidence: "incomplete",
647
+ reason: `associatedVouchers.${field}`
648
+ };
649
+ }
650
+ const normalize = (value) => field === "taxId" ? String(BigInt(value)) : normalizeWsfeDateInput(
651
+ value,
652
+ "association.date"
653
+ );
654
+ try {
655
+ if (normalize(association[field]) !== normalize(match[field])) {
656
+ return {
657
+ matches: false,
658
+ evidence: "conflict",
659
+ reason: `associatedVouchers.${field} differs`
660
+ };
661
+ }
662
+ } catch {
663
+ return {
664
+ matches: false,
665
+ evidence: "incomplete",
666
+ reason: `associatedVouchers.${field}`
667
+ };
668
+ }
669
+ }
670
+ return { matches: true };
671
+ }
672
+
673
+ // src/wsaa/session-store.ts
674
+ var CREDENTIAL_EXPIRY_SAFETY_MARGIN_MS = 6e4;
675
+ function createMemoryWsaaSessionStore() {
676
+ const sessions = /* @__PURE__ */ new Map();
677
+ const locks = /* @__PURE__ */ new Map();
678
+ return {
679
+ get(key) {
680
+ const credentials = sessions.get(serializeWsaaSessionKey(key));
681
+ if (!(credentials && isWsaaCredentialValid(credentials))) {
682
+ return Promise.resolve(null);
683
+ }
684
+ return Promise.resolve({ ...credentials });
685
+ },
686
+ set(key, credentials) {
687
+ sessions.set(serializeWsaaSessionKey(key), { ...credentials });
688
+ return Promise.resolve();
689
+ },
690
+ delete(key) {
691
+ sessions.delete(serializeWsaaSessionKey(key));
692
+ return Promise.resolve();
693
+ },
694
+ async withLock(key, fn) {
695
+ const lockKey = serializeWsaaSessionKey(key);
696
+ const previous = locks.get(lockKey) ?? Promise.resolve();
697
+ let release = () => void 0;
698
+ const current = new Promise((resolve) => {
699
+ release = resolve;
700
+ });
701
+ const queued = previous.catch(() => void 0).then(() => current);
702
+ locks.set(lockKey, queued);
703
+ await previous.catch(() => void 0);
704
+ try {
705
+ return await fn();
706
+ } finally {
707
+ release();
708
+ if (locks.get(lockKey) === queued) {
709
+ locks.delete(lockKey);
710
+ }
711
+ }
712
+ }
713
+ };
714
+ }
715
+ function serializeWsaaSessionKey(key) {
716
+ return [key.environment, key.service, key.certificateFingerprint].join(":");
717
+ }
718
+ function isWsaaCredentialValid(credentials) {
719
+ return new Date(credentials.expiresAt).getTime() - Date.now() > CREDENTIAL_EXPIRY_SAFETY_MARGIN_MS;
720
+ }
721
+
722
+ // src/wsaa/index.ts
723
+ import { createHash as createHash2 } from "crypto";
724
+ import forge from "node-forge";
725
+
726
+ // src/internal/http.ts
727
+ import https from "https";
728
+ var defaultAgent = new https.Agent({
729
+ keepAlive: true
730
+ });
731
+ var legacyTlsAgent = new https.Agent({
732
+ keepAlive: true,
733
+ ciphers: "DEFAULT@SECLEVEL=0"
734
+ });
735
+ async function postXmlWithMetadata({
736
+ url,
737
+ body,
738
+ contentType,
739
+ soapAction,
740
+ useLegacyTlsSecurityLevel0 = false,
741
+ timeout = 3e4,
742
+ retries = 0,
743
+ retryDelay = 500,
744
+ logger,
745
+ service,
746
+ operation
747
+ }) {
748
+ const totalAttempts = retries + 1;
749
+ for (let attempt = 1; attempt <= totalAttempts; attempt += 1) {
750
+ try {
751
+ return await postXmlOnce({
752
+ url,
753
+ body,
754
+ contentType,
755
+ soapAction,
756
+ useLegacyTlsSecurityLevel0,
757
+ timeout
758
+ });
759
+ } catch (error) {
760
+ if (!(error instanceof ArcaTransportError)) {
761
+ throw error;
762
+ }
763
+ if (attempt >= totalAttempts) {
764
+ logger?.error("ARCA transport request failed", {
765
+ service,
766
+ operation,
767
+ url,
768
+ attempt,
769
+ attempts: totalAttempts,
770
+ ...createSafeErrorDiagnostic(error)
771
+ });
772
+ throw error;
773
+ }
774
+ const nextAttempt = attempt + 1;
775
+ logger?.warn(
776
+ `Retrying ARCA request after transport failure (attempt ${nextAttempt}/${totalAttempts})`,
777
+ {
778
+ service,
779
+ operation,
780
+ url,
781
+ attempt: nextAttempt,
782
+ attempts: totalAttempts,
783
+ ...createSafeErrorDiagnostic(error)
784
+ }
785
+ );
786
+ await delay(retryDelay);
787
+ }
788
+ }
789
+ throw new ArcaTransportError("ARCA HTTP request exhausted retries");
790
+ }
791
+ async function postXmlOnce({
792
+ url,
793
+ body,
794
+ contentType,
795
+ soapAction,
796
+ useLegacyTlsSecurityLevel0,
797
+ timeout
798
+ }) {
799
+ const endpoint = new URL(url);
800
+ const requestBody = Buffer.from(body, "utf8");
801
+ return await new Promise((resolve, reject) => {
802
+ let settled = false;
803
+ const settleResolve = (response) => {
804
+ if (settled) {
805
+ return;
806
+ }
807
+ settled = true;
808
+ resolve(response);
809
+ };
810
+ const settleReject = (error) => {
811
+ if (settled) {
812
+ return;
813
+ }
814
+ settled = true;
815
+ reject(error);
816
+ };
817
+ const request = https.request(
818
+ {
819
+ protocol: endpoint.protocol,
820
+ hostname: endpoint.hostname,
821
+ port: endpoint.port || void 0,
822
+ path: `${endpoint.pathname}${endpoint.search}`,
823
+ method: "POST",
824
+ agent: useLegacyTlsSecurityLevel0 ? legacyTlsAgent : defaultAgent,
825
+ headers: {
826
+ Accept: "text/xml, application/soap+xml",
827
+ "Content-Length": requestBody.byteLength,
828
+ "Content-Type": contentType,
829
+ ...soapAction === void 0 ? {} : { SOAPAction: `"${soapAction}"` }
830
+ }
831
+ },
832
+ (response) => {
833
+ const chunks = [];
834
+ const getResponseBody = () => Buffer.concat(chunks).toString("utf8");
835
+ response.on("data", (chunk) => {
836
+ chunks.push(
837
+ typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk
838
+ );
839
+ });
840
+ response.on("error", (error) => {
841
+ settleReject(
842
+ new ArcaTransportError("ARCA HTTP response stream failed", {
843
+ cause: error,
844
+ statusCode: response.statusCode,
845
+ ...createResponseBodyDiagnostic(getResponseBody())
846
+ })
847
+ );
848
+ });
849
+ response.on("aborted", () => {
850
+ settleReject(
851
+ new ArcaTransportError("ARCA HTTP response was aborted", {
852
+ statusCode: response.statusCode,
853
+ ...createResponseBodyDiagnostic(getResponseBody())
854
+ })
855
+ );
856
+ });
857
+ response.on("end", () => {
858
+ const responseBody = getResponseBody();
859
+ const statusCode = response.statusCode ?? 500;
860
+ const responseContentType = Array.isArray(
861
+ response.headers["content-type"]
862
+ ) ? response.headers["content-type"].join("; ") : response.headers["content-type"];
863
+ if (statusCode >= 200 && statusCode < 300) {
864
+ settleResolve({
865
+ body: responseBody,
866
+ statusCode,
867
+ contentType: responseContentType
868
+ });
869
+ return;
870
+ }
871
+ if (isXmlLikeResponse(responseBody, responseContentType)) {
872
+ settleResolve({
873
+ body: responseBody,
874
+ statusCode,
875
+ contentType: responseContentType
876
+ });
877
+ return;
878
+ }
879
+ settleReject(
880
+ new ArcaTransportError(
881
+ `ARCA HTTP request failed with status ${statusCode}`,
882
+ {
883
+ statusCode,
884
+ contentType: responseContentType,
885
+ ...createResponseBodyDiagnostic(responseBody)
886
+ }
887
+ )
888
+ );
889
+ });
890
+ }
891
+ );
892
+ request.setTimeout(timeout, () => {
893
+ const timeoutCause = new Error(
894
+ `ARCA HTTP request timed out after ${timeout}ms`
895
+ );
896
+ settleReject(
897
+ new ArcaTransportError(
898
+ `ARCA HTTP request timed out after ${timeout}ms`,
899
+ { cause: timeoutCause }
900
+ )
901
+ );
902
+ request.destroy(timeoutCause);
903
+ });
904
+ request.on("error", (error) => {
905
+ settleReject(
906
+ new ArcaTransportError("ARCA HTTP request failed", {
907
+ cause: error
908
+ })
909
+ );
910
+ });
911
+ request.write(requestBody);
912
+ request.end();
913
+ });
914
+ }
915
+ function isXmlLikeResponse(body, contentType) {
916
+ const normalizedContentType = contentType?.toLowerCase() ?? "";
917
+ if (normalizedContentType.includes("xml") || normalizedContentType.includes("soap")) {
918
+ return true;
919
+ }
920
+ return body.trimStart().startsWith("<");
921
+ }
922
+ function delay(ms) {
923
+ return new Promise((resolve) => {
924
+ setTimeout(resolve, ms);
925
+ });
926
+ }
927
+
928
+ // src/internal/xml.ts
929
+ import { XMLBuilder, XMLParser } from "fast-xml-parser";
930
+ var xmlBuilder = new XMLBuilder({
931
+ attributeNamePrefix: "@_",
932
+ format: false,
933
+ ignoreAttributes: false,
934
+ suppressBooleanAttributes: false,
935
+ suppressEmptyNode: true
936
+ });
937
+ var xmlParser = new XMLParser({
938
+ attributeNamePrefix: "@_",
939
+ ignoreAttributes: false,
940
+ parseAttributeValue: false,
941
+ parseTagValue: false,
942
+ removeNSPrefix: true,
943
+ trimValues: true
944
+ });
945
+ function buildSoapEnvelope(soapVersion, operation, namespace, body, options) {
946
+ const prefix = soapVersion === "1.2" ? "soap12" : "soap";
947
+ const envelopeNamespace = soapVersion === "1.2" ? "http://www.w3.org/2003/05/soap-envelope" : "http://schemas.xmlsoap.org/soap/envelope/";
948
+ const namespaceMode = options?.namespaceMode ?? "default";
949
+ const operationElementName = namespaceMode === "prefix" ? `tns:${operation}` : operation;
950
+ const operationNamespaceAttributes = namespaceMode === "prefix" ? { "@_xmlns:tns": namespace } : { "@_xmlns": namespace };
951
+ const payload = {
952
+ [`${prefix}:Envelope`]: {
953
+ "@_xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
954
+ "@_xmlns:xsd": "http://www.w3.org/2001/XMLSchema",
955
+ [`@_xmlns:${prefix}`]: envelopeNamespace,
956
+ [`${prefix}:Body`]: {
957
+ [operationElementName]: {
958
+ ...operationNamespaceAttributes,
959
+ ...pruneUndefinedDeep(body)
960
+ }
961
+ }
962
+ }
963
+ };
964
+ return `<?xml version="1.0" encoding="utf-8"?>${xmlBuilder.build(payload)}`;
965
+ }
966
+ function parseSoapBody(xml, context = {}) {
967
+ let parsed;
968
+ try {
969
+ parsed = xmlParser.parse(xml);
970
+ } catch (error) {
971
+ throw createInvalidSoapResponseError(
972
+ "Invalid SOAP response: XML parse failed",
973
+ context,
974
+ error instanceof Error ? error : void 0
975
+ );
976
+ }
977
+ const envelope = parsed.Envelope;
978
+ const body = envelope?.Body;
979
+ if (!body) {
980
+ throw createInvalidSoapResponseError(
981
+ "Invalid SOAP response: missing body",
982
+ context
983
+ );
984
+ }
985
+ const fault = body.Fault;
986
+ if (fault) {
987
+ throw createSoapFaultError(fault);
988
+ }
989
+ return body;
990
+ }
991
+ function getSingleBodyEntry(body, context = {}) {
992
+ const entries = Object.entries(body).filter(([key]) => key !== "@_xmlns");
993
+ if (entries.length !== 1) {
994
+ throw createInvalidSoapResponseError(
995
+ `Invalid SOAP response: expected a single body entry, got ${entries.length}`,
996
+ context
997
+ );
998
+ }
999
+ return entries[0];
1000
+ }
1001
+ function createInvalidSoapResponseError(message, context, cause) {
1002
+ const responseBody = context.responseBody ?? "";
1003
+ return new ArcaInvalidSoapResponseError(message, {
1004
+ cause,
1005
+ service: context.service,
1006
+ operation: context.operation,
1007
+ endpointUrl: context.endpointUrl,
1008
+ statusCode: context.statusCode,
1009
+ contentType: context.contentType,
1010
+ ...createResponseBodyDiagnostic(
1011
+ responseBody,
1012
+ context.responseBodyPreviewLength
1013
+ )
1014
+ });
1015
+ }
1016
+ function parseXmlDocument(xml) {
1017
+ return xmlParser.parse(xml);
1018
+ }
1019
+ function pruneUndefinedDeep(value) {
1020
+ if (Array.isArray(value)) {
1021
+ return value.map((item) => pruneUndefinedDeep(item)).filter((item) => item !== void 0);
1022
+ }
1023
+ if (value && typeof value === "object") {
1024
+ const entries = Object.entries(value).filter(([, nestedValue]) => nestedValue !== void 0).map(([key, nestedValue]) => [key, pruneUndefinedDeep(nestedValue)]);
1025
+ return Object.fromEntries(entries);
1026
+ }
1027
+ return value;
1028
+ }
1029
+ function createSoapFaultError(fault) {
1030
+ const faultCode = typeof fault.faultcode === "string" ? fault.faultcode : getNestedString(fault, ["Code", "Value"]);
1031
+ const message = typeof fault.faultstring === "string" ? fault.faultstring : getNestedString(fault, ["Reason", "Text"]) ?? "ARCA SOAP fault response";
1032
+ return new ArcaSoapFaultError(message, {
1033
+ faultCode: faultCode ?? void 0
1034
+ });
1035
+ }
1036
+ function getNestedString(value, path) {
1037
+ let current = value;
1038
+ for (const key of path) {
1039
+ if (!current || typeof current !== "object") {
1040
+ return null;
1041
+ }
1042
+ current = current[key];
1043
+ }
1044
+ return typeof current === "string" ? current : null;
1045
+ }
1046
+
1047
+ // src/wsaa/index.ts
1048
+ function createWsaaAuthModule(options) {
1049
+ const cache = /* @__PURE__ */ new Map();
1050
+ const ordinaryInFlight = /* @__PURE__ */ new Map();
1051
+ const forcedInFlight = /* @__PURE__ */ new Map();
1052
+ function trackLogin(target, cacheKey, login) {
1053
+ const promise = login();
1054
+ target.set(cacheKey, promise);
1055
+ const cleanup = () => {
1056
+ if (target.get(cacheKey) === promise) {
1057
+ target.delete(cacheKey);
1058
+ }
1059
+ };
1060
+ promise.then(cleanup, cleanup);
1061
+ return promise;
1062
+ }
1063
+ async function requestOrReuseWsaaCredentials(service, sessionKey, cacheKey, forceRefresh) {
1064
+ const reuse = await getReusableCredentials({
1065
+ config: options.config,
1066
+ cache,
1067
+ cacheKey,
1068
+ sessionKey,
1069
+ logger: options.logger,
1070
+ service,
1071
+ allowStore: !forceRefresh,
1072
+ allowCache: !forceRefresh
1073
+ });
1074
+ if (reuse) {
1075
+ return reuse;
1076
+ }
1077
+ const refresh = () => refreshWsaaCredentials({
1078
+ config: options.config,
1079
+ cache,
1080
+ cacheKey,
1081
+ sessionKey,
1082
+ logger: options.logger,
1083
+ service,
1084
+ forceRefresh
1085
+ });
1086
+ if (options.config.wsaaSessionStore?.withLock) {
1087
+ return await withWsaaSessionStoreLock(
1088
+ options.config,
1089
+ sessionKey,
1090
+ service,
1091
+ refresh
1092
+ );
1093
+ }
1094
+ return await refresh();
1095
+ }
1096
+ async function performLogin(service, sessionKey, cacheKey, forceRefresh) {
1097
+ try {
1098
+ return await requestOrReuseWsaaCredentials(
1099
+ service,
1100
+ sessionKey,
1101
+ cacheKey,
1102
+ forceRefresh
1103
+ );
1104
+ } catch (error) {
1105
+ if (error instanceof ArcaSoapFaultError && error.faultCode === "ns1:coe.alreadyAuthenticated") {
1106
+ const recovered = await getReusableCredentials({
1107
+ config: options.config,
1108
+ cache,
1109
+ cacheKey,
1110
+ sessionKey,
1111
+ logger: options.logger,
1112
+ service,
1113
+ allowStore: true,
1114
+ allowCache: true
1115
+ });
1116
+ if (recovered) {
1117
+ options.logger?.warn(
1118
+ "Recovered WSAA coe.alreadyAuthenticated fault",
1119
+ {
1120
+ service,
1121
+ faultCode: error.faultCode
1122
+ }
1123
+ );
1124
+ return recovered;
1125
+ }
1126
+ if (!options.config.wsaaSessionStore) {
1127
+ throw new ArcaConfigurationError(
1128
+ "WSAA login failed because another process likely owns a valid TA. Configure a durable wsaaSessionStore for multi-process or serverless deployments.",
1129
+ { cause: error }
1130
+ );
1131
+ }
1132
+ }
1133
+ if (error instanceof ArcaSoapFaultError) {
1134
+ options.logger?.error("WSAA SOAP fault response", {
1135
+ service,
1136
+ operation: "loginCms",
1137
+ url: ARCA_WSAA_CONFIG.endpoint[options.config.environment],
1138
+ ...createSafeErrorDiagnostic(error)
1139
+ });
1140
+ }
1141
+ throw error;
1142
+ }
1143
+ }
1144
+ return {
1145
+ login(service, authOptions = {}) {
1146
+ const sessionKey = buildWsaaSessionKey(options.config, service);
1147
+ const cacheKey = serializeWsaaSessionKey(sessionKey);
1148
+ if (authOptions.forceRefresh) {
1149
+ const runningForced2 = forcedInFlight.get(cacheKey);
1150
+ if (runningForced2) {
1151
+ return runningForced2;
1152
+ }
1153
+ const runningOrdinary2 = ordinaryInFlight.get(cacheKey);
1154
+ return trackLogin(forcedInFlight, cacheKey, async () => {
1155
+ await runningOrdinary2?.catch(() => void 0);
1156
+ return await performLogin(service, sessionKey, cacheKey, true);
1157
+ });
1158
+ }
1159
+ const runningOrdinary = ordinaryInFlight.get(cacheKey);
1160
+ if (runningOrdinary) {
1161
+ return runningOrdinary;
1162
+ }
1163
+ const runningForced = forcedInFlight.get(cacheKey);
1164
+ if (runningForced) {
1165
+ return runningForced;
1166
+ }
1167
+ return trackLogin(
1168
+ ordinaryInFlight,
1169
+ cacheKey,
1170
+ () => performLogin(service, sessionKey, cacheKey, false)
1171
+ );
1172
+ }
1173
+ };
1174
+ }
1175
+ async function requestCredentials(config, service, options) {
1176
+ const loginTicketRequestXml = buildLoginTicketRequest(service);
1177
+ const signedCms = signLoginTicketRequest(loginTicketRequestXml, {
1178
+ certificatePem: config.certificatePem,
1179
+ privateKeyPem: config.privateKeyPem
1180
+ });
1181
+ const requestXml = buildSoapEnvelope(
1182
+ ARCA_WSAA_CONFIG.soapVersion,
1183
+ "loginCms",
1184
+ ARCA_WSAA_CONFIG.namespace,
1185
+ { in0: signedCms }
1186
+ );
1187
+ const url = ARCA_WSAA_CONFIG.endpoint[config.environment];
1188
+ const response = await postXmlWithMetadata({
1189
+ url: ARCA_WSAA_CONFIG.endpoint[config.environment],
1190
+ body: requestXml,
1191
+ contentType: 'text/xml; charset="utf-8"',
1192
+ soapAction: ARCA_WSAA_CONFIG.soapActionBase,
1193
+ timeout: config.timeout,
1194
+ retries: config.retries,
1195
+ retryDelay: config.retryDelay,
1196
+ logger: options?.logger,
1197
+ service: "wsaa",
1198
+ operation: "loginCms"
1199
+ });
1200
+ const parseContext = {
1201
+ service: "wsaa",
1202
+ operation: "loginCms",
1203
+ endpointUrl: url,
1204
+ statusCode: response.statusCode,
1205
+ contentType: response.contentType,
1206
+ responseBody: response.body
1207
+ };
1208
+ const soapBody = parseSoapBody(response.body, parseContext);
1209
+ const [, responseBody] = getSingleBodyEntry(
1210
+ soapBody,
1211
+ parseContext
1212
+ );
1213
+ const loginCmsReturn = responseBody.loginCmsReturn;
1214
+ if (typeof loginCmsReturn !== "string" || loginCmsReturn.trim().length < 1) {
1215
+ throw new ArcaTransportError(
1216
+ "WSAA response did not include loginCmsReturn XML"
1217
+ );
1218
+ }
1219
+ return parseLoginTicketResponse(loginCmsReturn);
1220
+ }
1221
+ function buildWsaaSessionKey(config, service) {
1222
+ return {
1223
+ environment: config.environment,
1224
+ service,
1225
+ certificateFingerprint: getCertificateFingerprint(config)
1226
+ };
1227
+ }
1228
+ function getCertificateFingerprint(config) {
1229
+ return createHash2("sha256").update(config.certificatePem).digest("hex");
1230
+ }
1231
+ function getCachedCredentials(cache, cacheKey) {
1232
+ const localCached = cache.get(cacheKey);
1233
+ if (localCached && isWsaaCredentialValid(localCached)) {
1234
+ return localCached;
1235
+ }
1236
+ return null;
1237
+ }
1238
+ async function getReusableCredentials(options) {
1239
+ if (options.allowCache) {
1240
+ const cached = getCachedCredentials(options.cache, options.cacheKey);
1241
+ if (cached) {
1242
+ options.logger?.debug("Attempting WSAA login", {
1243
+ service: options.service,
1244
+ source: "cached"
1245
+ });
1246
+ return cached;
1247
+ }
1248
+ }
1249
+ if (!(options.allowStore && options.config.wsaaSessionStore)) {
1250
+ return null;
1251
+ }
1252
+ const stored = await getStoredCredentials(
1253
+ options.config,
1254
+ options.sessionKey,
1255
+ options.service
1256
+ );
1257
+ if (!stored) {
1258
+ return null;
1259
+ }
1260
+ options.cache.set(options.cacheKey, stored);
1261
+ options.logger?.debug("Attempting WSAA login", {
1262
+ service: options.service,
1263
+ source: "store"
1264
+ });
1265
+ return stored;
1266
+ }
1267
+ async function refreshWsaaCredentials(options) {
1268
+ if (!options.forceRefresh) {
1269
+ const reuse = await getReusableCredentials({
1270
+ config: options.config,
1271
+ cache: options.cache,
1272
+ cacheKey: options.cacheKey,
1273
+ sessionKey: options.sessionKey,
1274
+ logger: options.logger,
1275
+ service: options.service,
1276
+ allowStore: true,
1277
+ allowCache: true
1278
+ });
1279
+ if (reuse) {
1280
+ return reuse;
1281
+ }
1282
+ }
1283
+ options.logger?.debug("Attempting WSAA login", {
1284
+ service: options.service,
1285
+ source: "fresh"
1286
+ });
1287
+ const credentials = await requestCredentials(
1288
+ options.config,
1289
+ options.service,
1290
+ {
1291
+ logger: options.logger
1292
+ }
1293
+ );
1294
+ options.logger?.info("WSAA login succeeded", {
1295
+ service: options.service,
1296
+ expiresAt: credentials.expiresAt
1297
+ });
1298
+ options.cache.set(options.cacheKey, credentials);
1299
+ await setStoredCredentials(options.config, options.sessionKey, credentials);
1300
+ return credentials;
1301
+ }
1302
+ async function getStoredCredentials(config, key, service) {
1303
+ if (!config.wsaaSessionStore) {
1304
+ return null;
1305
+ }
1306
+ try {
1307
+ const credentials = await config.wsaaSessionStore.get(key);
1308
+ if (!(credentials && isWsaaCredentialValid(credentials))) {
1309
+ return null;
1310
+ }
1311
+ return credentials;
1312
+ } catch (error) {
1313
+ throw new ArcaConfigurationError(
1314
+ `WSAA session store get failed for service ${service}`,
1315
+ { cause: error instanceof Error ? error : void 0 }
1316
+ );
1317
+ }
1318
+ }
1319
+ async function setStoredCredentials(config, key, credentials) {
1320
+ if (!config.wsaaSessionStore) {
1321
+ return;
1322
+ }
1323
+ try {
1324
+ await config.wsaaSessionStore.set(key, credentials);
1325
+ } catch (error) {
1326
+ throw new ArcaConfigurationError(
1327
+ `WSAA session store set failed for service ${key.service}`,
1328
+ { cause: error instanceof Error ? error : void 0 }
1329
+ );
1330
+ }
1331
+ }
1332
+ async function withWsaaSessionStoreLock(config, key, service, fn) {
1333
+ const store = config.wsaaSessionStore;
1334
+ if (!store?.withLock) {
1335
+ return await fn();
1336
+ }
1337
+ let entered = false;
1338
+ try {
1339
+ return await store.withLock(key, async () => {
1340
+ entered = true;
1341
+ return await fn();
1342
+ });
1343
+ } catch (error) {
1344
+ if (entered) {
1345
+ throw error;
1346
+ }
1347
+ if (error instanceof ArcaConfigurationError) {
1348
+ throw error;
1349
+ }
1350
+ throw new ArcaConfigurationError(
1351
+ `WSAA session store lock failed for service ${service}`,
1352
+ { cause: error instanceof Error ? error : void 0 }
1353
+ );
1354
+ }
1355
+ }
1356
+ function buildLoginTicketRequest(service) {
1357
+ const uniqueId = Math.floor(Date.now() / 1e3);
1358
+ const generationTime = new Date(Date.now() - 5 * 6e4).toISOString().replace(".000Z", "Z");
1359
+ const expirationTime = new Date(Date.now() + 5 * 6e4).toISOString().replace(".000Z", "Z");
1360
+ return `<?xml version="1.0" encoding="UTF-8"?>
1361
+ <loginTicketRequest version="1.0">
1362
+ <header>
1363
+ <uniqueId>${uniqueId}</uniqueId>
1364
+ <generationTime>${generationTime}</generationTime>
1365
+ <expirationTime>${expirationTime}</expirationTime>
1366
+ </header>
1367
+ <service>${service}</service>
1368
+ </loginTicketRequest>`;
1369
+ }
1370
+ function signLoginTicketRequest(loginTicketRequestXml, options) {
1371
+ const certificate = forge.pki.certificateFromPem(options.certificatePem);
1372
+ const privateKey = forge.pki.privateKeyFromPem(options.privateKeyPem);
1373
+ const signedData = forge.pkcs7.createSignedData();
1374
+ signedData.content = forge.util.createBuffer(loginTicketRequestXml, "utf8");
1375
+ signedData.addCertificate(certificate);
1376
+ const authenticatedAttributes = [
1377
+ {
1378
+ type: String(forge.pki.oids.contentType),
1379
+ value: String(forge.pki.oids.data)
1380
+ },
1381
+ {
1382
+ type: String(forge.pki.oids.messageDigest)
1383
+ },
1384
+ {
1385
+ type: String(forge.pki.oids.signingTime),
1386
+ value: /* @__PURE__ */ new Date()
1387
+ }
1388
+ ];
1389
+ const signerOptions = {
1390
+ key: privateKey,
1391
+ certificate,
1392
+ digestAlgorithm: String(forge.pki.oids.sha1),
1393
+ authenticatedAttributes
1394
+ };
1395
+ signedData.addSigner(signerOptions);
1396
+ signedData.sign();
1397
+ const der = forge.asn1.toDer(signedData.toAsn1()).getBytes();
1398
+ return Buffer.from(der, "binary").toString("base64");
1399
+ }
1400
+ function parseLoginTicketResponse(xml) {
1401
+ const parsed = parseXmlDocument(xml);
1402
+ const response = parsed.loginTicketResponse ?? parsed;
1403
+ const header = response.header;
1404
+ const credentials = response.credentials;
1405
+ const token = credentials?.token;
1406
+ const sign = credentials?.sign;
1407
+ const expiresAt = header?.expirationTime;
1408
+ if (typeof token !== "string" || typeof sign !== "string" || typeof expiresAt !== "string") {
1409
+ throw new ArcaTransportError(
1410
+ "Invalid WSAA login ticket response structure"
1411
+ );
1412
+ }
1413
+ return {
1414
+ token,
1415
+ sign,
1416
+ expiresAt
1417
+ };
1418
+ }
1419
+
1420
+ // src/internal/logger.ts
1421
+ var ARCA_LOG_LEVELS = ["debug", "info", "warn", "error"];
1422
+ function createArcaLogger(config) {
1423
+ const disabled = config?.disabled ?? false;
1424
+ const level = resolveArcaLogLevel(config?.level);
1425
+ const sink = config?.log ?? defaultArcaLog;
1426
+ const log = (messageLevel, message, ...args) => {
1427
+ if (disabled || !shouldLog(level, messageLevel)) {
1428
+ return;
1429
+ }
1430
+ sink(messageLevel, message, ...args);
1431
+ };
1432
+ return {
1433
+ disabled,
1434
+ level,
1435
+ log,
1436
+ debug(message, ...args) {
1437
+ log("debug", message, ...args);
1438
+ },
1439
+ info(message, ...args) {
1440
+ log("info", message, ...args);
1441
+ },
1442
+ warn(message, ...args) {
1443
+ log("warn", message, ...args);
1444
+ },
1445
+ error(message, ...args) {
1446
+ log("error", message, ...args);
1447
+ }
1448
+ };
1449
+ }
1450
+ function resolveArcaLogLevel(level) {
1451
+ if (isArcaLogLevel(level)) {
1452
+ return level;
1453
+ }
1454
+ const envLevel = process.env.ARCA_LOG_LEVEL?.trim().toLowerCase();
1455
+ if (isArcaLogLevel(envLevel)) {
1456
+ return envLevel;
1457
+ }
1458
+ return "warn";
1459
+ }
1460
+ function shouldLog(threshold, messageLevel) {
1461
+ return ARCA_LOG_LEVELS.indexOf(messageLevel) >= ARCA_LOG_LEVELS.indexOf(threshold);
1462
+ }
1463
+ function isArcaLogLevel(value) {
1464
+ return ARCA_LOG_LEVELS.includes(value);
1465
+ }
1466
+ function defaultArcaLog(level, message, ...args) {
1467
+ const method = level === "debug" ? console.debug : level === "info" ? console.info : level === "warn" ? console.warn : console.error;
1468
+ method(message, ...args);
1469
+ }
1470
+
1471
+ // src/services/issuance-fields.ts
1472
+ var FAMILIES = {
1473
+ ordinary: { A: [1, 2, 3], B: [6, 7, 8], C: [11, 12, 13] },
1474
+ retention_legend: { A: [51, 52, 53] },
1475
+ fce: { A: [201, 202, 203], B: [206, 207, 208], C: [211, 212, 213] }
1476
+ };
1477
+ function voucherFamily(type) {
1478
+ for (const [family, classes] of Object.entries(FAMILIES)) {
1479
+ for (const [voucherClass, types] of Object.entries(classes)) {
1480
+ if (types.includes(type)) {
1481
+ return {
1482
+ family,
1483
+ voucherClass,
1484
+ types
1485
+ };
1486
+ }
1487
+ }
1488
+ }
1489
+ throw new ArcaInputError("Unsupported invoice or note type.", {
1490
+ code: "ARCA_INPUT_INVALID_VALUE",
1491
+ field: "voucherType"
1492
+ });
1493
+ }
1494
+ function invoiceType(family, voucherClass) {
1495
+ const classes = FAMILIES[family];
1496
+ const types = classes && classes[voucherClass];
1497
+ if (!types) {
1498
+ throw new ArcaInputError("Invoice family does not support this class.", {
1499
+ code: "ARCA_INPUT_INVALID_VALUE",
1500
+ field: "family"
1501
+ });
1502
+ }
1503
+ return types[0];
1504
+ }
1505
+ function minor(value, field) {
1506
+ return arcaMinorUnitsToNumber(assertArcaMinorUnits(value, field), field);
1507
+ }
1508
+ function applyIssuanceFields(data, fields) {
1509
+ if (fields.taxes !== void 0) {
1510
+ if (!Array.isArray(fields.taxes)) {
1511
+ throw new ArcaInputError("taxes must be an array", {
1512
+ code: "ARCA_INPUT_INVALID_VALUE",
1513
+ field: "taxes"
1514
+ });
1515
+ }
1516
+ data.taxes = fields.taxes.map((tax) => ({
1517
+ id: tax.id,
1518
+ description: tax.description,
1519
+ baseAmount: minor(tax.base, "taxes.base"),
1520
+ rate: tax.rate,
1521
+ amount: minor(tax.amount, "taxes.amount")
1522
+ }));
1523
+ data.taxAmount = minor(tributeTotal(fields.taxes), "taxes.total");
1524
+ }
1525
+ if (fields.amounts) {
1526
+ Object.assign(data, reviewedHeaderAmounts(fields.amounts));
1527
+ }
1528
+ if (fields.concept) {
1529
+ data.concept = { products: 1, services: 2, products_and_services: 3 }[fields.concept];
1530
+ }
1531
+ if (fields.dueDate) {
1532
+ data.paymentDueDate = fields.dueDate;
1533
+ }
1534
+ if (fields.paidInForeignCurrency !== void 0) {
1535
+ if (typeof fields.paidInForeignCurrency !== "boolean") {
1536
+ throw new ArcaInputError("paidInForeignCurrency must be boolean", {
1537
+ code: "ARCA_INPUT_INVALID_VALUE"
1538
+ });
1539
+ }
1540
+ data.sameCurrencyForeignCancellation = fields.paidInForeignCurrency ? "S" : "N";
1541
+ }
1542
+ for (const key of ["optionalFields", "buyers", "activities"]) {
1543
+ if (fields[key] !== void 0) {
1544
+ Object.assign(data, { [key]: structuredClone(fields[key]) });
1545
+ }
1546
+ }
1547
+ applyFceFields(data, fields.fce);
1548
+ }
1549
+ function tributeTotal(taxes) {
1550
+ return taxes.reduce((sum, tax) => sum + tax.amount, 0);
1551
+ }
1552
+ function reviewedHeaderAmounts(amounts) {
1553
+ return {
1554
+ netAmount: minor(amounts.net, "amounts.net"),
1555
+ vatAmount: minor(amounts.vat, "amounts.vat"),
1556
+ exemptAmount: minor(amounts.exempt ?? 0, "amounts.exempt"),
1557
+ nonTaxableAmount: minor(amounts.untaxed ?? 0, "amounts.untaxed"),
1558
+ vatRates: amounts.vatRates?.map((rate) => ({
1559
+ id: rate.id,
1560
+ baseAmount: minor(rate.base, "amounts.vatRates.base"),
1561
+ amount: minor(rate.amount, "amounts.vatRates.amount")
1562
+ }))
1563
+ };
1564
+ }
1565
+ var ISSUANCE_KEYS = [
1566
+ "fce",
1567
+ "taxes",
1568
+ "amounts",
1569
+ "concept",
1570
+ "dueDate",
1571
+ "paidInForeignCurrency",
1572
+ "optionalFields",
1573
+ "buyers",
1574
+ "activities"
1575
+ ];
1576
+ function validateIssuanceFields(fields) {
1577
+ validateRows(
1578
+ fields.taxes,
1579
+ "taxes",
1580
+ ["id", "description", "base", "rate", "amount"],
1581
+ (row) => {
1582
+ positiveId(row.id, "taxes.id");
1583
+ if (row.description !== void 0 && typeof row.description !== "string") {
1584
+ bad("taxes.description");
1585
+ }
1586
+ minor(row.base, "taxes.base");
1587
+ minor(row.amount, "taxes.amount");
1588
+ if (typeof row.rate !== "number" || !Number.isFinite(row.rate) || row.rate < 0) {
1589
+ bad("taxes.rate");
1590
+ }
1591
+ }
1592
+ );
1593
+ if (fields.amounts !== void 0) {
1594
+ objectKeys(fields.amounts, "amounts", [
1595
+ "net",
1596
+ "vat",
1597
+ "exempt",
1598
+ "untaxed",
1599
+ "vatRates"
1600
+ ]);
1601
+ minor(fields.amounts.net, "amounts.net");
1602
+ minor(fields.amounts.vat, "amounts.vat");
1603
+ validateRows(
1604
+ fields.amounts.vatRates,
1605
+ "amounts.vatRates",
1606
+ ["id", "base", "amount"],
1607
+ (row) => {
1608
+ if (![3, 4, 5, 6, 8, 9].includes(row.id)) {
1609
+ bad("amounts.vatRates.id");
1610
+ }
1611
+ minor(row.base, "amounts.vatRates.base");
1612
+ minor(row.amount, "amounts.vatRates.amount");
1613
+ }
1614
+ );
1615
+ const ids = fields.amounts.vatRates?.map((r) => r.id) ?? [];
1616
+ if (new Set(ids).size !== ids.length) {
1617
+ bad("amounts.vatRates");
1618
+ }
1619
+ }
1620
+ validateRows(
1621
+ fields.optionalFields,
1622
+ "optionalFields",
1623
+ ["id", "value"],
1624
+ (row) => {
1625
+ if (typeof row.id !== "string" || !/^\d+$/.test(row.id) || typeof row.value !== "string") {
1626
+ bad("optionalFields");
1627
+ }
1628
+ }
1629
+ );
1630
+ validateRows(
1631
+ fields.buyers,
1632
+ "buyers",
1633
+ ["documentType", "documentNumber", "percentage"],
1634
+ (row) => {
1635
+ positiveId(row.documentType, "buyers.documentType");
1636
+ positiveId(row.documentNumber, "buyers.documentNumber");
1637
+ if (typeof row.percentage !== "number" || !Number.isFinite(row.percentage) || row.percentage <= 0 || row.percentage > 100) {
1638
+ bad("buyers.percentage");
1639
+ }
1640
+ }
1641
+ );
1642
+ validateRows(
1643
+ fields.activities,
1644
+ "activities",
1645
+ ["id"],
1646
+ (row) => positiveId(row.id, "activities.id")
1647
+ );
1648
+ if (fields.concept !== void 0 && !["products", "services", "products_and_services"].includes(fields.concept)) {
1649
+ bad("concept");
1650
+ }
1651
+ }
1652
+ function positiveId(value, field) {
1653
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
1654
+ bad(field);
1655
+ }
1656
+ }
1657
+ function bad(field) {
1658
+ throw new ArcaInputError(`Invalid ${field}`, {
1659
+ code: "ARCA_INPUT_INVALID_VALUE",
1660
+ field
1661
+ });
1662
+ }
1663
+ function objectKeys(value, field, keys) {
1664
+ if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((key) => !keys.includes(key))) {
1665
+ bad(field);
1666
+ }
1667
+ }
1668
+ function validateRows(value, field, keys, check) {
1669
+ if (value === void 0) {
1670
+ return;
1671
+ }
1672
+ if (!Array.isArray(value)) {
1673
+ bad(field);
1674
+ }
1675
+ for (const row of value) {
1676
+ objectKeys(row, field, keys);
1677
+ check(row);
1678
+ }
1679
+ }
1680
+ function validateFiscalHeader(data) {
1681
+ const family = voucherFamily(data.voucherType);
1682
+ validateFceHeader(data);
1683
+ if (family.voucherClass === "C" && (data.vatAmount !== 0 || data.vatRates?.length || data.exemptAmount !== 0 || data.nonTaxableAmount !== 0)) {
1684
+ bad("amounts");
1685
+ }
1686
+ if (![1, 2, 3].includes(data.concept)) {
1687
+ bad("concept");
1688
+ }
1689
+ const date = normalizeWsfeDateInput(data.voucherDate, "date");
1690
+ if (data.concept === 1 && (data.serviceStartDate || data.serviceEndDate)) {
1691
+ bad("service");
1692
+ }
1693
+ if (data.concept === 2 || data.concept === 3) {
1694
+ if (!(data.serviceStartDate && data.serviceEndDate && data.paymentDueDate)) {
1695
+ bad("service");
1696
+ }
1697
+ const start = normalizeWsfeDateInput(data.serviceStartDate, "service.from");
1698
+ const end = normalizeWsfeDateInput(data.serviceEndDate, "service.to");
1699
+ if (start > end) {
1700
+ bad("service.to");
1701
+ }
1702
+ }
1703
+ if (data.paymentDueDate && normalizeWsfeDateInput(data.paymentDueDate, "dueDate") < date) {
1704
+ bad("dueDate");
1705
+ }
1706
+ if (data.currencyId === "PES" && data.sameCurrencyForeignCancellation !== void 0) {
1707
+ bad("paidInForeignCurrency");
1708
+ }
1709
+ }
1710
+ function applyFceFields(data, fce) {
1711
+ if (fce === void 0) {
1712
+ return;
1713
+ }
1714
+ objectKeys(fce, "fce", [
1715
+ "cbu",
1716
+ "alias",
1717
+ "transfer",
1718
+ "annulment",
1719
+ "reference"
1720
+ ]);
1721
+ if (voucherFamily(data.voucherType).family !== "fce") {
1722
+ bad("fce");
1723
+ }
1724
+ validateFceOptions(fce);
1725
+ const extra = [
1726
+ ...fce.cbu === void 0 ? [] : [{ id: "2101", value: fce.cbu }],
1727
+ ...fce.alias === void 0 ? [] : [{ id: "2102", value: fce.alias }],
1728
+ ...fce.transfer === void 0 ? [] : [{ id: "27", value: fce.transfer }],
1729
+ ...fce.annulment === void 0 ? [] : [{ id: "22", value: fce.annulment ? "S" : "N" }],
1730
+ ...fce.reference === void 0 ? [] : [{ id: "23", value: fce.reference }]
1731
+ ];
1732
+ const options = [...data.optionalFields ?? [], ...extra];
1733
+ if (new Set(options.map((o) => o.id)).size !== options.length) {
1734
+ bad("fce");
1735
+ }
1736
+ data.optionalFields = options;
1737
+ }
1738
+ function validateFceHeader(data) {
1739
+ const family = voucherFamily(data.voucherType);
1740
+ if (family.family === "fce") {
1741
+ const options = new Map(
1742
+ (data.optionalFields ?? []).map((o) => [o.id, o.value])
1743
+ );
1744
+ if (family.types[0] === data.voucherType) {
1745
+ if (!/^\d{22}$/.test(options.get("2101") ?? "") || options.has("22")) {
1746
+ bad("fce.cbu");
1747
+ }
1748
+ if (!data.paymentDueDate) {
1749
+ bad("dueDate");
1750
+ }
1751
+ } else if (!["S", "N"].includes(options.get("22") ?? "") || options.has("2101") || options.has("2102") || options.has("27")) {
1752
+ bad("fce.annulment");
1753
+ }
1754
+ }
1755
+ }
1756
+ function validateFceOptions(fce) {
1757
+ if (fce.cbu !== void 0 && (typeof fce.cbu !== "string" || !/^\d{22}$/.test(fce.cbu))) {
1758
+ bad("fce.cbu");
1759
+ }
1760
+ if (fce.alias !== void 0 && (typeof fce.alias !== "string" || !/^[A-Za-z0-9.-]{6,20}$/.test(fce.alias))) {
1761
+ bad("fce.alias");
1762
+ }
1763
+ if (fce.transfer !== void 0 && !["ADC", "SCA"].includes(fce.transfer)) {
1764
+ bad("fce.transfer");
1765
+ }
1766
+ if (fce.annulment !== void 0 && typeof fce.annulment !== "boolean") {
1767
+ bad("fce.annulment");
1768
+ }
1769
+ if (fce.reference !== void 0 && (typeof fce.reference !== "string" || !fce.reference.trim())) {
1770
+ bad("fce.reference");
1771
+ }
1772
+ }
1773
+
1774
+ // src/services/issuance-wsmtxca.ts
1775
+ var iso = (value) => {
1776
+ if (value === void 0) {
1777
+ return void 0;
1778
+ }
1779
+ const date = normalizeWsfeDateInput(
1780
+ value,
1781
+ "date"
1782
+ );
1783
+ return `${date.slice(0, 4)}-${date.slice(4, 6)}-${date.slice(6, 8)}`;
1784
+ };
1785
+ function wsmtxcaRequest(data, number) {
1786
+ if (!data.details?.length) {
1787
+ invalid("details", "WSMTXCA requires detailed items");
1788
+ }
1789
+ const items = data.details.map((item, index) => {
1790
+ if (!item || typeof item !== "object" || Object.keys(item).some(
1791
+ (k) => ![
1792
+ "description",
1793
+ "quantity",
1794
+ "unit",
1795
+ "unitPrice",
1796
+ "discount",
1797
+ "vatCondition",
1798
+ "vatAmount",
1799
+ "amount",
1800
+ "code",
1801
+ "matrixCode",
1802
+ "matrixUnits"
1803
+ ].includes(k)
1804
+ )) {
1805
+ invalid(`details[${index}]`, "Invalid detailed item fields");
1806
+ }
1807
+ if (typeof item.description !== "string" || !(item.description.trim() && Number.isFinite(item.quantity)) || item.quantity <= 0 || !Number.isInteger(item.unit) || item.unit < 0 || typeof item.unitPrice !== "string" || !/^\d+(\.\d{1,6})?$/.test(item.unitPrice) || !Number.isInteger(item.vatCondition)) {
1808
+ invalid(`details[${index}]`, "Invalid detailed item");
1809
+ }
1810
+ return {
1811
+ unidadesMtx: item.matrixUnits,
1812
+ codigoMtx: item.matrixCode,
1813
+ codigo: item.code,
1814
+ descripcion: item.description,
1815
+ cantidad: item.quantity,
1816
+ codigoUnidadMedida: item.unit,
1817
+ precioUnitario: item.unitPrice,
1818
+ importeBonificacion: minor(item.discount ?? 0, "details.discount"),
1819
+ codigoCondicionIVA: item.vatCondition,
1820
+ ...item.vatAmount === void 0 ? {} : { importeIVA: minor(item.vatAmount, "details.vatAmount") },
1821
+ importeItem: minor(item.amount, "details.amount")
1822
+ };
1823
+ });
1824
+ const itemTotal = data.details.reduce(
1825
+ (sum, item) => sum + BigInt(item.amount),
1826
+ 0n
1827
+ );
1828
+ const expected = normalizeArcaAmountToMinorUnits(data.totalAmount, "total") - normalizeArcaAmountToMinorUnits(data.taxAmount, "taxes");
1829
+ if (!isWithinArcaTolerance(itemTotal, expected, 1)) {
1830
+ invalid(
1831
+ "details",
1832
+ "Item totals must equal the voucher total excluding tributes"
1833
+ );
1834
+ }
1835
+ return {
1836
+ comprobanteCAERequest: {
1837
+ codigoTipoComprobante: data.voucherType,
1838
+ numeroPuntoVenta: data.salesPoint,
1839
+ ...number === void 0 ? {} : { numeroComprobante: number },
1840
+ fechaEmision: iso(data.voucherDate),
1841
+ codigoTipoDocumento: data.documentType,
1842
+ numeroDocumento: data.documentNumber,
1843
+ condicionIVAReceptor: data.receiverVatConditionId,
1844
+ importeGravado: data.netAmount,
1845
+ importeNoGravado: data.nonTaxableAmount,
1846
+ importeExento: data.exemptAmount,
1847
+ importeSubtotal: Number(
1848
+ normalizeArcaAmountToMinorUnits(data.netAmount, "net") + normalizeArcaAmountToMinorUnits(data.nonTaxableAmount, "untaxed") + normalizeArcaAmountToMinorUnits(data.exemptAmount, "exempt")
1849
+ ) / 100,
1850
+ importeOtrosTributos: data.taxAmount,
1851
+ importeTotal: data.totalAmount,
1852
+ codigoMoneda: data.currencyId,
1853
+ cotizacionMoneda: data.exchangeRate,
1854
+ codigoConcepto: data.concept,
1855
+ fechaServicioDesde: iso(data.serviceStartDate),
1856
+ fechaServicioHasta: iso(data.serviceEndDate),
1857
+ fechaVencimientoPago: iso(data.paymentDueDate),
1858
+ ...data.sameCurrencyForeignCancellation === void 0 ? {} : {
1859
+ cancelaEnMismaMonedaExtranjera: data.sameCurrencyForeignCancellation
1860
+ },
1861
+ arrayComprobantesAsociados: data.associatedVouchers?.length ? {
1862
+ comprobanteAsociado: data.associatedVouchers.map((v) => ({
1863
+ codigoTipoComprobante: v.type,
1864
+ numeroPuntoVenta: v.salesPoint,
1865
+ numeroComprobante: v.number,
1866
+ cuit: v.taxId,
1867
+ fechaEmision: iso(v.voucherDate)
1868
+ }))
1869
+ } : void 0,
1870
+ periodoComprobantesAsociados: data.associatedPeriod ? {
1871
+ fechaDesde: iso(data.associatedPeriod.startDate),
1872
+ fechaHasta: iso(data.associatedPeriod.endDate)
1873
+ } : void 0,
1874
+ arrayCompradores: data.buyers?.length ? {
1875
+ comprador: data.buyers.map((b) => ({
1876
+ codigoTipoDocumento: b.documentType,
1877
+ numeroDocumento: b.documentNumber,
1878
+ porcentaje: b.percentage
1879
+ }))
1880
+ } : void 0,
1881
+ arrayOtrosTributos: data.taxes?.length ? {
1882
+ otroTributo: data.taxes.map((t) => ({
1883
+ codigo: t.id,
1884
+ descripcion: t.description,
1885
+ baseImponible: t.baseAmount,
1886
+ importe: t.amount
1887
+ }))
1888
+ } : void 0,
1889
+ arrayItems: { item: items },
1890
+ arraySubtotalesIVA: data.vatRates?.length ? {
1891
+ subtotalIVA: data.vatRates.map((v) => ({
1892
+ codigo: v.id,
1893
+ importe: v.amount
1894
+ }))
1895
+ } : void 0,
1896
+ arrayDatosAdicionales: wsmtxcaAdditionalData(data.optionalFields),
1897
+ arrayActividades: data.activities?.length ? { actividad: data.activities.map((a) => ({ codigo: a.id })) } : void 0
1898
+ }
1899
+ };
1900
+ }
1901
+ function invalid(field, message) {
1902
+ throw new ArcaInputError(message, {
1903
+ code: "ARCA_INPUT_INVALID_VALUE",
1904
+ field
1905
+ });
1906
+ }
1907
+ function rows(value, key) {
1908
+ const data = value && typeof value === "object" ? value[key] : void 0;
1909
+ if (data === void 0) {
1910
+ return void 0;
1911
+ }
1912
+ const list = Array.isArray(data) ? data : [data];
1913
+ if (list.some((item) => !item || typeof item !== "object")) {
1914
+ return void 0;
1915
+ }
1916
+ return list;
1917
+ }
1918
+ function wsmtxcaHeader(found) {
1919
+ const raw = found.raw;
1920
+ const items = rows(raw.arrayItems, "item");
1921
+ const details = items?.map((i) => ({
1922
+ description: String(i.descripcion ?? ""),
1923
+ quantity: Number(i.cantidad),
1924
+ unit: Number(i.codigoUnidadMedida),
1925
+ unitPrice: String(i.precioUnitario),
1926
+ discount: Number(
1927
+ normalizeArcaAmountToMinorUnits(
1928
+ Number(i.importeBonificacion ?? 0),
1929
+ "discount"
1930
+ )
1931
+ ),
1932
+ vatCondition: Number(i.codigoCondicionIVA),
1933
+ vatAmount: i.importeIVA === void 0 ? void 0 : Number(normalizeArcaAmountToMinorUnits(Number(i.importeIVA), "vat")),
1934
+ amount: Number(
1935
+ normalizeArcaAmountToMinorUnits(Number(i.importeItem), "amount")
1936
+ ),
1937
+ code: i.codigo === void 0 ? void 0 : String(i.codigo),
1938
+ matrixCode: i.codigoMtx === void 0 ? void 0 : String(i.codigoMtx),
1939
+ matrixUnits: i.unidadesMtx === void 0 ? void 0 : Number(i.unidadesMtx)
1940
+ }));
1941
+ return {
1942
+ ...found,
1943
+ voucherNumber: found.voucherNumber ?? 0,
1944
+ voucherDate: found.invoiceDate,
1945
+ netAmount: found.taxableAmount,
1946
+ // consultarComprobante is an authorized-voucher lookup. No result flag is returned.
1947
+ result: found.cae ? "A" : void 0,
1948
+ serviceStartDate: raw.fechaServicioDesde === void 0 ? void 0 : String(raw.fechaServicioDesde),
1949
+ serviceEndDate: raw.fechaServicioHasta === void 0 ? void 0 : String(raw.fechaServicioHasta),
1950
+ paymentDueDate: raw.fechaVencimientoPago === void 0 ? void 0 : String(raw.fechaVencimientoPago),
1951
+ details,
1952
+ vatRates: rows(raw.arraySubtotalesIVA, "subtotalIVA")?.map((v) => ({
1953
+ id: Number(v.codigo),
1954
+ amount: Number(v.importe),
1955
+ baseAmount: Number(
1956
+ (details ?? []).filter((i) => i.vatCondition === Number(v.codigo)).reduce((sum, i) => sum + BigInt(i.amount), 0n) - normalizeArcaAmountToMinorUnits(Number(v.importe), "vat")
1957
+ ) / 100
1958
+ })),
1959
+ taxes: rows(raw.arrayOtrosTributos, "otroTributo")?.map((t) => ({
1960
+ id: Number(t.codigo),
1961
+ description: t.descripcion === void 0 ? void 0 : String(t.descripcion),
1962
+ baseAmount: Number(t.baseImponible),
1963
+ amount: Number(t.importe),
1964
+ rate: 0
1965
+ })),
1966
+ sameCurrencyForeignCancellation: raw.cancelaEnMismaMonedaExtranjera,
1967
+ optionalFields: rows(raw.arrayDatosAdicionales, "datoAdicional")?.map(
1968
+ (v) => ({ id: String(v.t), value: String(v.c1) })
1969
+ ),
1970
+ activities: rows(raw.arrayActividades, "actividad")?.map((v) => ({
1971
+ id: Number(v.codigo)
1972
+ })),
1973
+ buyers: rows(raw.arrayCompradores, "comprador")?.map((v) => ({
1974
+ documentType: Number(v.codigoTipoDocumento),
1975
+ documentNumber: Number(v.numeroDocumento),
1976
+ percentage: Number(v.porcentaje)
1977
+ })),
1978
+ associatedVouchers: rows(
1979
+ raw.arrayComprobantesAsociados,
1980
+ "comprobanteAsociado"
1981
+ )?.map((v) => ({
1982
+ type: Number(v.codigoTipoComprobante),
1983
+ salesPoint: Number(v.numeroPuntoVenta),
1984
+ number: Number(v.numeroComprobante),
1985
+ taxId: v.cuit === void 0 ? void 0 : String(v.cuit),
1986
+ voucherDate: v.fechaEmision
1987
+ }))
1988
+ };
1989
+ }
1990
+ function matchWsmtxcaDetails(data, number, raw) {
1991
+ const request = wsmtxcaRequest(
1992
+ data,
1993
+ number
1994
+ ).comprobanteCAERequest;
1995
+ let missing = false;
1996
+ for (const key of Object.keys(request)) {
1997
+ const expected = request[key];
1998
+ const actual = raw[key];
1999
+ if (expected === void 0) {
2000
+ if (!emptyWire(actual)) {
2001
+ return "conflict";
2002
+ }
2003
+ continue;
2004
+ }
2005
+ const result = compareWire(
2006
+ normalizeWire(expected, key),
2007
+ normalizeWire(actual, key)
2008
+ );
2009
+ if (result === "conflict") {
2010
+ return "conflict";
2011
+ }
2012
+ missing ||= result === "incomplete";
2013
+ }
2014
+ return missing ? "incomplete" : "match";
2015
+ }
2016
+ function emptyWire(value) {
2017
+ return value === void 0 || value === null || typeof value === "object" && Object.values(value).every(emptyWire);
2018
+ }
2019
+ function compareWire(expected, actual) {
2020
+ if (actual === void 0 || actual === null) {
2021
+ return "incomplete";
2022
+ }
2023
+ if (expected !== null && typeof expected === "object") {
2024
+ if (typeof actual !== "object" || Array.isArray(expected) !== Array.isArray(actual)) {
2025
+ return "conflict";
2026
+ }
2027
+ const left = expected;
2028
+ const right = actual;
2029
+ if (Object.keys(right).some((key) => !(key in left || emptyWire(right[key])))) {
2030
+ return "conflict";
2031
+ }
2032
+ let missing = false;
2033
+ for (const key of Object.keys(left)) {
2034
+ const result = compareWire(left[key], right[key]);
2035
+ if (result === "conflict") {
2036
+ return result;
2037
+ }
2038
+ missing ||= result === "incomplete";
2039
+ }
2040
+ return missing ? "incomplete" : "match";
2041
+ }
2042
+ return expected === actual ? "match" : "conflict";
2043
+ }
2044
+ var LIST_KEYS = /* @__PURE__ */ new Set([
2045
+ "item",
2046
+ "comprobanteAsociado",
2047
+ "otroTributo",
2048
+ "subtotalIVA",
2049
+ "datoAdicional",
2050
+ "actividad",
2051
+ "comprador"
2052
+ ]);
2053
+ var TEXT_KEYS = /* @__PURE__ */ new Set([
2054
+ "codigo",
2055
+ "descripcion",
2056
+ "codigoMtx",
2057
+ "c1",
2058
+ "c2",
2059
+ "c3",
2060
+ "c4",
2061
+ "c5",
2062
+ "c6",
2063
+ "codigoMoneda",
2064
+ "cancelaEnMismaMonedaExtranjera"
2065
+ ]);
2066
+ function normalizeWire(value, key = "") {
2067
+ if (value === void 0 || value === null) {
2068
+ return value;
2069
+ }
2070
+ if (LIST_KEYS.has(key)) {
2071
+ return (Array.isArray(value) ? value : [value]).map(
2072
+ (v) => normalizeWire(v)
2073
+ );
2074
+ }
2075
+ if (Array.isArray(value)) {
2076
+ return value.map((v) => normalizeWire(v));
2077
+ }
2078
+ if (value && typeof value === "object") {
2079
+ return Object.fromEntries(
2080
+ Object.entries(value).filter(([, v]) => v !== void 0).map(([k, v]) => [k, normalizeWire(v, k)])
2081
+ );
2082
+ }
2083
+ if (TEXT_KEYS.has(key) && value !== void 0 && value !== null) {
2084
+ return String(value);
2085
+ }
2086
+ if (!TEXT_KEYS.has(key) && typeof value === "string" && /^\d+(\.\d+)?$/.test(value)) {
2087
+ return Number(value);
2088
+ }
2089
+ return value;
2090
+ }
2091
+ function createWsmtxcaIssuanceService(wsmtxca) {
2092
+ return {
2093
+ getNextVoucherNumber: async (input) => (await wsmtxca.getLastAuthorizedVoucher(input)).voucherNumber + 1,
2094
+ issue: (input) => wsmtxca.issue({
2095
+ representedTaxId: input.representedTaxId,
2096
+ forceRefresh: input.forceRefresh,
2097
+ data: wsmtxcaRequest(input.data, input.voucherNumber)
2098
+ }),
2099
+ lookupVoucher: async (input) => {
2100
+ const result = await wsmtxca.lookupVoucher({
2101
+ ...input,
2102
+ voucherNumber: input.number
2103
+ });
2104
+ return result.kind === "found" ? { ...result, voucher: wsmtxcaHeader(result.voucher) } : result;
2105
+ }
2106
+ };
2107
+ }
2108
+ function wsmtxcaAdditionalData(fields) {
2109
+ if (!fields?.length) {
2110
+ return void 0;
2111
+ }
2112
+ const options = new Map(fields.map((f) => [f.id, f.value]));
2113
+ return {
2114
+ datoAdicional: [
2115
+ ...options.has("2101") ? [{ t: 21, c1: options.get("2101"), c2: options.get("2102") }] : [],
2116
+ ...fields.filter((f) => f.id !== "2101" && f.id !== "2102").map((f) => ({ t: Number(f.id), c1: f.value }))
2117
+ ]
2118
+ };
2119
+ }
2120
+
2121
+ // src/services/wsfe-derive.ts
2122
+ var INVOICE_TYPES = {
2123
+ A: ARCA_VOUCHER_TYPES.FACTURA_A,
2124
+ B: ARCA_VOUCHER_TYPES.FACTURA_B,
2125
+ C: ARCA_VOUCHER_TYPES.FACTURA_C
2126
+ };
2127
+ function deriveWsfeInvoice(input, now = /* @__PURE__ */ new Date()) {
2128
+ assertIssueObject(input, "input");
2129
+ assertIssueKeys(
2130
+ input,
2131
+ [
2132
+ ...ISSUANCE_KEYS,
2133
+ "family",
2134
+ "details",
2135
+ "issuer",
2136
+ "items",
2137
+ "salesPoint",
2138
+ "to",
2139
+ "total",
2140
+ "date",
2141
+ "currency",
2142
+ "exchangeRate",
2143
+ "service"
2144
+ ],
2145
+ "input"
2146
+ );
2147
+ validateIssuanceFields(input);
2148
+ if (input.amounts !== void 0 && input.items !== void 0) {
2149
+ invalid2("amounts", "used instead of items, never with items");
2150
+ }
2151
+ assertIssuerCondition(input.issuer);
2152
+ assertSalesPoint(input.salesPoint);
2153
+ const receiver = deriveReceiver(input.to);
2154
+ const voucherClass = resolveInvoiceClass(input.issuer, input.to.condition);
2155
+ const { data: amountsData, amounts } = input.amounts ? reviewedInvoiceAmounts(input.amounts, input.taxes) : calculateWsfeAmounts({
2156
+ voucherClass,
2157
+ items: input.items,
2158
+ total: input.total === void 0 ? void 0 : input.total - tributeTotal(input.taxes ?? [])
2159
+ });
2160
+ const currency = deriveCurrency(input);
2161
+ const voucherDate = normalizeWsfeDateInput(
2162
+ input.date === void 0 ? buenosAiresDate(now) : input.date,
2163
+ "date"
2164
+ );
2165
+ const data = {
2166
+ salesPoint: input.salesPoint,
2167
+ voucherType: input.family === void 0 ? INVOICE_TYPES[voucherClass] : invoiceType(input.family, voucherClass),
2168
+ voucherDate,
2169
+ ...receiver,
2170
+ ...currency,
2171
+ ...amountsData,
2172
+ ...deriveService(input.service, voucherDate)
2173
+ };
2174
+ applyIssuanceFields(data, input);
2175
+ const headerTotal = Number(
2176
+ normalizeArcaAmountToMinorUnits(data.netAmount, "net") + normalizeArcaAmountToMinorUnits(data.vatAmount, "vat") + normalizeArcaAmountToMinorUnits(data.exemptAmount, "exempt") + normalizeArcaAmountToMinorUnits(data.nonTaxableAmount, "untaxed") + normalizeArcaAmountToMinorUnits(data.taxAmount, "tax")
2177
+ );
2178
+ const reviewedTotal = input.amounts ? input.total : void 0;
2179
+ data.totalAmount = reviewedTotal === void 0 ? headerTotal / 100 : minor(reviewedTotal, "total");
2180
+ amounts.computedTotal += headerTotal - amounts.sentTotal;
2181
+ amounts.sentTotal = reviewedTotal ?? headerTotal;
2182
+ if (receiver.receiverVatConditionId === 5 && receiver.documentType === ARCA_DOCUMENT_TYPES.CONSUMIDOR_FINAL) {
2183
+ const [whole, fraction = ""] = currency.exchangeRate.split(".");
2184
+ const rate = BigInt(whole) * 1000000n + BigInt(fraction.padEnd(6, "0"));
2185
+ if (BigInt(amounts.sentTotal) * rate >= ARCA_FINAL_CONSUMER_IDENTIFICATION_THRESHOLD_MINOR_UNITS * 1000000n) {
2186
+ throw new ArcaInputError(
2187
+ "The final consumer must be identified for this amount.",
2188
+ {
2189
+ code: "ARCA_INPUT_MISSING_FIELD",
2190
+ field: "to",
2191
+ expected: "cuit or dni for operations at or above ARS 10,000,000 (RG 5866)"
2192
+ }
2193
+ );
2194
+ }
2195
+ }
2196
+ validateFiscalHeader(data);
2197
+ try {
2198
+ normalizeWsfeVoucherInput(data);
2199
+ } catch (cause) {
2200
+ if (cause instanceof ArcaInputError) {
2201
+ throw cause;
2202
+ }
2203
+ throw new ArcaError(
2204
+ "The derived invoice failed exact WSFE validation. This is an SDK invariant failure.",
2205
+ "ARCA_ISSUE_INVARIANT",
2206
+ { cause }
2207
+ );
2208
+ }
2209
+ return { data, voucherClass, amounts };
2210
+ }
2211
+ function reviewedInvoiceAmounts(amounts, taxes) {
2212
+ const taxTotal = taxes === void 0 ? 0 : tributeTotal(taxes);
2213
+ const total = amounts.net + amounts.vat + (amounts.exempt ?? 0) + (amounts.untaxed ?? 0) + taxTotal;
2214
+ return {
2215
+ data: {
2216
+ totalAmount: minor(total, "total"),
2217
+ taxAmount: minor(taxTotal, "taxes.total"),
2218
+ ...reviewedHeaderAmounts(amounts)
2219
+ },
2220
+ amounts: { computedTotal: total, sentTotal: total, vatAdjustment: 0 }
2221
+ };
2222
+ }
2223
+ function assertIssuerCondition(issuer) {
2224
+ if (typeof issuer !== "string" || !Object.hasOwn(ARCA_ISSUER_CONDITION_IDS, issuer)) {
2225
+ invalid2(
2226
+ "issuer",
2227
+ "responsable_inscripto, monotributo, exento, or no_alcanzado"
2228
+ );
2229
+ }
2230
+ }
2231
+ function assertSalesPoint(salesPoint) {
2232
+ if (!Number.isSafeInteger(salesPoint) || salesPoint < 1 || salesPoint > 99999) {
2233
+ invalid2("salesPoint", "an integer from 1 through 99999");
2234
+ }
2235
+ }
2236
+ function resolveInvoiceClass(issuer, condition) {
2237
+ if (typeof condition === "number") {
2238
+ return issuer === "responsable_inscripto" ? [1, 6, 13, 16].includes(condition) ? "A" : "B" : "C";
2239
+ }
2240
+ return ARCA_INVOICE_CLASS_BY_ISSUER[issuer][condition];
2241
+ }
2242
+ function deriveReceiver(to) {
2243
+ assertIssueObject(to, "to");
2244
+ assertIssueKeys(to, ["condition", "cuit", "dni", "document"], "to");
2245
+ if (typeof to.condition === "number") {
2246
+ return deriveNumericReceiver(
2247
+ to
2248
+ );
2249
+ }
2250
+ if (typeof to.condition !== "string" || !Object.hasOwn(ARCA_RECEIVER_CONDITION_IDS, to.condition)) {
2251
+ invalid2("to.condition", "one of the five supported receiver conditions");
2252
+ }
2253
+ if (to.cuit !== void 0 && to.dni !== void 0) {
2254
+ invalid2("to", "either cuit or dni, never both");
2255
+ }
2256
+ if (to.condition !== "consumidor_final" && to.cuit === void 0) {
2257
+ throw new ArcaInputError(
2258
+ "to.cuit is required for this receiver (WSFE 10063 for class A).",
2259
+ {
2260
+ code: "ARCA_INPUT_MISSING_FIELD",
2261
+ field: "to.cuit",
2262
+ expected: "an 11-digit CUIT"
2263
+ }
2264
+ );
2265
+ }
2266
+ const documentType = to.cuit === void 0 ? to.dni === void 0 ? ARCA_DOCUMENT_TYPES.CONSUMIDOR_FINAL : ARCA_DOCUMENT_TYPES.DNI : ARCA_DOCUMENT_TYPES.CUIT;
2267
+ 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);
2268
+ return {
2269
+ documentType,
2270
+ documentNumber,
2271
+ receiverVatConditionId: ARCA_RECEIVER_CONDITION_IDS[to.condition]
2272
+ };
2273
+ }
2274
+ function issueDocumentNumber(value, field, min, max) {
2275
+ if (typeof value !== "number" && typeof value !== "string") {
2276
+ invalid2(field, `a positive document number with ${min} to ${max} digits`);
2277
+ }
2278
+ const text = String(value);
2279
+ if (!/^\d+$/.test(text) || text.length < min || text.length > max || !Number.isSafeInteger(Number(text)) || Number(text) <= 0) {
2280
+ invalid2(field, `a positive document number with ${min} to ${max} digits`);
2281
+ }
2282
+ return Number(text);
2283
+ }
2284
+ function deriveCurrency(input) {
2285
+ const currency = input.currency === void 0 ? "ARS" : input.currency;
2286
+ if (typeof currency === "object" && currency !== null) {
2287
+ assertIssueKeys(currency, ["id"], "currency");
2288
+ if (!/^[A-Z0-9]{3}$/.test(currency.id)) {
2289
+ invalid2("currency.id", "a three-character ARCA currency code");
2290
+ }
2291
+ if (input.exchangeRate === void 0) {
2292
+ invalid2("exchangeRate", "an explicit rate for this currency");
2293
+ }
2294
+ return {
2295
+ currencyId: currency.id,
2296
+ exchangeRate: serializeArcaExchangeRate(
2297
+ input.exchangeRate,
2298
+ "exchangeRate"
2299
+ )
2300
+ };
2301
+ }
2302
+ if (currency !== "ARS" && currency !== "USD") {
2303
+ invalid2("currency", "ARS or USD");
2304
+ }
2305
+ if (input.exchangeRate !== void 0 && typeof input.exchangeRate !== "string") {
2306
+ invalid2("exchangeRate", "a decimal string");
2307
+ }
2308
+ if (currency === "USD" && input.exchangeRate === void 0) {
2309
+ throw new ArcaInputError("exchangeRate is required for USD.", {
2310
+ code: "ARCA_INPUT_MISSING_FIELD",
2311
+ field: "exchangeRate",
2312
+ expected: "a positive decimal string"
2313
+ });
2314
+ }
2315
+ const exchangeRate = serializeArcaExchangeRate(
2316
+ input.exchangeRate ?? "1",
2317
+ "exchangeRate"
2318
+ );
2319
+ if (currency === "ARS" && exchangeRate !== "1") {
2320
+ throw new ArcaInputError("exchangeRate must be 1 for ARS.", {
2321
+ code: "ARCA_INPUT_INVALID_EXCHANGE_RATE",
2322
+ field: "exchangeRate",
2323
+ expected: "1 for ARS"
2324
+ });
2325
+ }
2326
+ return { currencyId: ARCA_CURRENCY_IDS[currency], exchangeRate };
2327
+ }
2328
+ function deriveService(service, date) {
2329
+ if (service === void 0) {
2330
+ return { concept: 1 };
2331
+ }
2332
+ assertIssueObject(service, "service");
2333
+ assertIssueKeys(service, ["from", "to", "dueDate"], "service");
2334
+ for (const field of ["from", "to", "dueDate"]) {
2335
+ if (service[field] === void 0) {
2336
+ throw new ArcaInputError(`service.${field} is required.`, {
2337
+ code: "ARCA_INPUT_MISSING_FIELD",
2338
+ field: `service.${field}`,
2339
+ expected: "a calendar date"
2340
+ });
2341
+ }
2342
+ }
2343
+ const serviceStartDate = normalizeWsfeDateInput(
2344
+ service.from,
2345
+ "service.from"
2346
+ );
2347
+ const serviceEndDate = normalizeWsfeDateInput(
2348
+ service.to,
2349
+ "service.to"
2350
+ );
2351
+ const paymentDueDate = normalizeWsfeDateInput(
2352
+ service.dueDate,
2353
+ "service.dueDate"
2354
+ );
2355
+ if (serviceEndDate < serviceStartDate) {
2356
+ invalid2("service.to", "a date on or after service.from");
2357
+ }
2358
+ if (paymentDueDate < date) {
2359
+ invalid2("service.dueDate", "a date on or after date");
2360
+ }
2361
+ return { concept: 2, serviceStartDate, serviceEndDate, paymentDueDate };
2362
+ }
2363
+ function buenosAiresDate(now) {
2364
+ const parts = new Intl.DateTimeFormat("en-CA", {
2365
+ timeZone: "America/Argentina/Buenos_Aires",
2366
+ year: "numeric",
2367
+ month: "2-digit",
2368
+ day: "2-digit"
2369
+ }).formatToParts(now);
2370
+ return ["year", "month", "day"].map((part) => parts.find((entry) => entry.type === part)?.value).join("");
2371
+ }
2372
+ function assertIssueObject(value, field) {
2373
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
2374
+ invalid2(field, "an object");
2375
+ }
2376
+ }
2377
+ function assertIssueKeys(value, keys, prefix, method = "issue()") {
2378
+ for (const key of Object.keys(value)) {
2379
+ if (!keys.includes(key)) {
2380
+ const field = prefix === "input" ? key : `${prefix}.${key}`;
2381
+ throw new ArcaInputError(`${field} is not supported by ${method}.`, {
2382
+ code: "ARCA_INPUT_RESERVED_FIELD",
2383
+ field,
2384
+ expected: "a supported high-level API field; use the exact API for other fiscal fields"
2385
+ });
2386
+ }
2387
+ }
2388
+ }
2389
+ function invalid2(field, expected) {
2390
+ throw new ArcaInputError(`${field} must be ${expected}.`, {
2391
+ code: "ARCA_INPUT_INVALID_VALUE",
2392
+ field,
2393
+ expected
2394
+ });
2395
+ }
2396
+ function deriveNumericReceiver(to) {
2397
+ if (![1, 4, 5, 6, 7, 8, 9, 10, 13, 15, 16].includes(to.condition)) {
2398
+ invalid2("to.condition", "an ARCA receiver condition");
2399
+ }
2400
+ const document = "document" in to ? to.document : void 0;
2401
+ if (document) {
2402
+ assertIssueKeys(document, ["type", "number"], "to.document");
2403
+ if (to.cuit !== void 0 || to.dni !== void 0) {
2404
+ invalid2("to", "one document identity");
2405
+ }
2406
+ if (!Number.isInteger(document.type) || document.type < 0 || document.type > 99 || !/^\d{1,11}$/.test(String(document.number)) || !Number.isSafeInteger(Number(document.number))) {
2407
+ invalid2("to.document", "a valid document type and number");
2408
+ }
2409
+ if (document.type === 80 && String(document.number).length !== 11) {
2410
+ invalid2("to.document.number", "an 11-digit CUIT");
2411
+ }
2412
+ return {
2413
+ documentType: document.type,
2414
+ documentNumber: Number(document.number),
2415
+ receiverVatConditionId: to.condition
2416
+ };
2417
+ }
2418
+ if (to.cuit === void 0 && to.dni === void 0) {
2419
+ invalid2("to", "an explicit document");
2420
+ }
2421
+ if (to.cuit !== void 0 && to.dni !== void 0) {
2422
+ invalid2("to", "one document identity");
2423
+ }
2424
+ return {
2425
+ documentType: to.cuit === void 0 ? 96 : 80,
2426
+ documentNumber: issueDocumentNumber(
2427
+ to.cuit ?? to.dni,
2428
+ "to.document",
2429
+ to.cuit === void 0 ? 1 : 11,
2430
+ 11
2431
+ ),
2432
+ receiverVatConditionId: to.condition
2433
+ };
2434
+ }
2435
+
2436
+ // src/services/wsfe-credit-note.ts
2437
+ function invalid3(reason) {
2438
+ throw new ArcaInputError(
2439
+ `issueCreditNote cannot proceed: ${reason}. Use the exact service API for manual control.`,
2440
+ { code: "ARCA_INPUT_INVALID_VALUE" }
2441
+ );
2442
+ }
2443
+ function required(value, field) {
2444
+ if (value === void 0 || value === null) {
2445
+ invalid3(`original is missing ${field}`);
2446
+ }
2447
+ return value;
2448
+ }
2449
+ function deriveWsfeFullCreditNote(original, input, now = /* @__PURE__ */ new Date(), kind = "creditNote") {
2450
+ const { note, header } = prepareCreditNote(original, input, now, kind);
2451
+ const data = {
2452
+ ...header,
2453
+ ...original.taxes ? { taxes: structuredClone(original.taxes) } : {},
2454
+ totalAmount: required(original.totalAmount, "totalAmount"),
2455
+ netAmount: required(original.netAmount, "netAmount"),
2456
+ vatAmount: required(original.vatAmount, "vatAmount"),
2457
+ exemptAmount: required(original.exemptAmount, "exemptAmount"),
2458
+ nonTaxableAmount: required(original.nonTaxableAmount, "nonTaxableAmount"),
2459
+ taxAmount: required(original.taxAmount, "taxAmount"),
2460
+ ...original.vatRates === void 0 ? {} : { vatRates: original.vatRates.map((rate) => ({ ...rate })) }
2461
+ };
2462
+ normalizeWsfeVoucherInput(data);
2463
+ const total = Number(
2464
+ normalizeArcaAmountToMinorUnits(data.totalAmount, "totalAmount")
2465
+ );
2466
+ return {
2467
+ data,
2468
+ voucherClass: note.voucherClass,
2469
+ amounts: { computedTotal: total, sentTotal: total, vatAdjustment: 0 }
2470
+ };
2471
+ }
2472
+ function deriveWsfePartialCreditNote(original, input, now = /* @__PURE__ */ new Date(), kind = "creditNote") {
2473
+ if (input.items === void 0 && input.amounts === void 0) {
2474
+ invalid3("items is required to credit chosen lines");
2475
+ }
2476
+ const requestedTotal = input.total === void 0 ? void 0 : assertArcaMinorUnits(input.total, "total");
2477
+ const { note, header } = prepareCreditNote(original, input, now, kind);
2478
+ const { data: amountsData, amounts } = input.amounts ? reviewedInvoiceAmounts(input.amounts, input.taxes) : calculateWsfeAmounts({
2479
+ voucherClass: note.voucherClass,
2480
+ items: input.items,
2481
+ total: input.total === void 0 ? void 0 : input.total - tributeTotal(input.taxes ?? [])
2482
+ });
2483
+ const originalTotal = normalizeArcaAmountToMinorUnits(
2484
+ required(original.totalAmount, "totalAmount"),
2485
+ "totalAmount"
2486
+ );
2487
+ if (kind === "creditNote" && (requestedTotal ?? BigInt(amounts.sentTotal)) > originalTotal) {
2488
+ invalid3(
2489
+ "the note total is greater than the original; the SDK does not track earlier notes against an original"
2490
+ );
2491
+ }
2492
+ const data = { ...header, ...amountsData };
2493
+ applyIssuanceFields(data, {
2494
+ ...input,
2495
+ fce: void 0,
2496
+ optionalFields: void 0
2497
+ });
2498
+ const total = Number(
2499
+ [
2500
+ data.netAmount,
2501
+ data.vatAmount,
2502
+ data.exemptAmount,
2503
+ data.nonTaxableAmount,
2504
+ data.taxAmount
2505
+ ].reduce(
2506
+ (sum, amount) => sum + normalizeArcaAmountToMinorUnits(amount, "amount"),
2507
+ 0n
2508
+ )
2509
+ );
2510
+ const sentTotal = input.amounts && requestedTotal !== void 0 ? Number(requestedTotal) : total;
2511
+ if (kind === "creditNote" && BigInt(sentTotal) > originalTotal) {
2512
+ invalid3("the note total is greater than the original");
2513
+ }
2514
+ data.totalAmount = minor(sentTotal, "total");
2515
+ amounts.computedTotal += total - amounts.sentTotal;
2516
+ amounts.sentTotal = sentTotal;
2517
+ normalizeWsfeVoucherInput(data);
2518
+ return { data, voucherClass: note.voucherClass, amounts };
2519
+ }
2520
+ function prepareCreditNote(original, input, now, kind) {
2521
+ assertOriginalExtensions(original);
2522
+ const family = voucherFamily(original.voucherType ?? 0);
2523
+ if (family.types[2] === original.voucherType) {
2524
+ invalid3("original must be an invoice or debit note");
2525
+ }
2526
+ const note = {
2527
+ voucherClass: family.voucherClass,
2528
+ voucherType: family.types[kind === "creditNote" ? 2 : 1]
2529
+ };
2530
+ if (!(["A", "O"].includes(original.result ?? "") && original.cae?.trim() && original.caeExpiry?.trim())) {
2531
+ invalid3("original is not authorized");
2532
+ }
2533
+ const voucherDate = normalizeWsfeDateInput(
2534
+ input.date ?? buenosAiresDate(now),
2535
+ "date"
2536
+ );
2537
+ const originalDate = normalizeWsfeDateInput(
2538
+ required(original.voucherDate, "voucherDate"),
2539
+ "original.voucherDate"
2540
+ );
2541
+ if (originalDate > voucherDate && originalDate.slice(0, 6) !== voucherDate.slice(0, 6)) {
2542
+ invalid3(
2543
+ "original date is later than the note and outside its month (10210)"
2544
+ );
2545
+ }
2546
+ const header = {
2547
+ salesPoint: input.salesPoint ?? required(original.salesPoint, "salesPoint"),
2548
+ voucherType: note.voucherType,
2549
+ concept: required(original.concept, "concept"),
2550
+ documentType: required(original.documentType, "documentType"),
2551
+ documentNumber: Number(required(original.documentNumber, "documentNumber")),
2552
+ receiverVatConditionId: required(
2553
+ original.receiverVatConditionId,
2554
+ "receiverVatConditionId"
2555
+ ),
2556
+ currencyId: required(original.currencyId, "currencyId"),
2557
+ ...original.sameCurrencyForeignCancellation === void 0 ? {} : {
2558
+ sameCurrencyForeignCancellation: original.sameCurrencyForeignCancellation
2559
+ },
2560
+ ...original.buyers ? { buyers: structuredClone(original.buyers) } : {},
2561
+ ...original.activities ? { activities: structuredClone(original.activities) } : {},
2562
+ ...input.optionalFields ? { optionalFields: structuredClone(input.optionalFields) } : {},
2563
+ exchangeRate: required(original.exchangeRate, "exchangeRate"),
2564
+ voucherDate,
2565
+ associatedVouchers: [
2566
+ {
2567
+ type: required(original.voucherType, "voucherType"),
2568
+ salesPoint: required(original.salesPoint, "salesPoint"),
2569
+ number: original.voucherNumber,
2570
+ voucherDate: originalDate
2571
+ }
2572
+ ]
2573
+ };
2574
+ applyFceFields(header, input.fce);
2575
+ copyServiceDates(original, header);
2576
+ return { note, header };
2577
+ }
2578
+ function copyServiceDates(original, header) {
2579
+ if (header.concept !== 2 && header.concept !== 3) {
2580
+ return;
2581
+ }
2582
+ header.serviceStartDate = required(
2583
+ original.serviceStartDate,
2584
+ "serviceStartDate"
2585
+ );
2586
+ header.serviceEndDate = required(
2587
+ original.serviceEndDate,
2588
+ "serviceEndDate"
2589
+ );
2590
+ const due = normalizeWsfeDateInput(
2591
+ required(original.paymentDueDate, "paymentDueDate"),
2592
+ "original.paymentDueDate"
2593
+ );
2594
+ header.paymentDueDate = due < header.voucherDate ? header.voucherDate : due;
2595
+ }
2596
+ var CREDIT_NOTE_KEYS = [
2597
+ "for",
2598
+ "salesPoint",
2599
+ "date",
2600
+ "items",
2601
+ "total",
2602
+ "all",
2603
+ "taxes",
2604
+ "amounts",
2605
+ "optionalFields",
2606
+ "details",
2607
+ "fce"
2608
+ ];
2609
+ var TARGET_BOUNDS = [
2610
+ ["salesPoint", 99999],
2611
+ ["voucherType", 999],
2612
+ ["number", 99999999]
2613
+ ];
2614
+ function assertCreditNoteInput(input) {
2615
+ assertIssueObject(input, "input");
2616
+ validateIssuanceFields(input);
2617
+ if ("associatedPeriod" in input) {
2618
+ throw new ArcaInputError(
2619
+ "issueCreditNote does not support associatedPeriod; a note against a period is exact-layer work. Use the exact service API for manual control.",
2620
+ {
2621
+ code: "ARCA_INPUT_RESERVED_FIELD",
2622
+ field: "associatedPeriod",
2623
+ expected: "an associated invoice through for"
2624
+ }
2625
+ );
2626
+ }
2627
+ assertIssueKeys(input, CREDIT_NOTE_KEYS, "input", "issueCreditNote()");
2628
+ const target = assertCreditNoteTarget(input.for);
2629
+ if (input.salesPoint !== void 0) {
2630
+ assertCreditNoteBound(input.salesPoint, 99999, "salesPoint");
2631
+ }
2632
+ const date = input.date === void 0 ? void 0 : normalizeWsfeDateInput(input.date, "date");
2633
+ const common = {
2634
+ ...input.fce === void 0 ? {} : { fce: structuredClone(input.fce) },
2635
+ ...input.taxes === void 0 ? {} : { taxes: structuredClone(input.taxes) },
2636
+ ...input.details === void 0 ? {} : { details: structuredClone(input.details) },
2637
+ ...input.optionalFields === void 0 ? {} : { optionalFields: structuredClone(input.optionalFields) },
2638
+ for: target,
2639
+ ...input.salesPoint === void 0 ? {} : { salesPoint: input.salesPoint },
2640
+ ...date === void 0 ? {} : { date }
2641
+ };
2642
+ if (input.items !== void 0 && input.amounts !== void 0) {
2643
+ invalid3("use items or amounts, never both");
2644
+ }
2645
+ if ((input.items === void 0 && input.amounts === void 0) === (input.all === void 0)) {
2646
+ throw new ArcaInputError(
2647
+ "issueCreditNote needs exactly one mode: items or amounts for a partial note, or all: true for the whole original.",
2648
+ {
2649
+ code: "ARCA_INPUT_INVALID_VALUE",
2650
+ field: input.items === void 0 ? "input.items" : "input.all",
2651
+ expected: "exactly one of items, amounts or all: true"
2652
+ }
2653
+ );
2654
+ }
2655
+ if (input.all !== void 0) {
2656
+ assertFullMode(input);
2657
+ return { ...common, all: true };
2658
+ }
2659
+ if (input.amounts !== void 0) {
2660
+ return {
2661
+ ...common,
2662
+ amounts: structuredClone(input.amounts),
2663
+ ...input.total === void 0 ? {} : { total: input.total }
2664
+ };
2665
+ }
2666
+ return {
2667
+ ...common,
2668
+ amounts: void 0,
2669
+ items: copyCreditNoteItems(input.items),
2670
+ ...input.total === void 0 ? {} : { total: input.total }
2671
+ };
2672
+ }
2673
+ function assertCreditNoteTarget(value) {
2674
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
2675
+ throw new ArcaInputError(
2676
+ "issueCreditNote requires for: the coordinates of the authorized invoice the note corrects.",
2677
+ {
2678
+ code: "ARCA_INPUT_MISSING_FIELD",
2679
+ field: "input.for",
2680
+ expected: "{ salesPoint, voucherType, number }"
2681
+ }
2682
+ );
2683
+ }
2684
+ assertIssueKeys(
2685
+ value,
2686
+ ["salesPoint", "voucherType", "number"],
2687
+ "input.for",
2688
+ "issueCreditNote()"
2689
+ );
2690
+ for (const [field, max] of TARGET_BOUNDS) {
2691
+ assertCreditNoteBound(value[field], max, `for.${field}`);
2692
+ }
2693
+ if (![1, 2, 6, 7, 11, 12, 51, 52, 201, 202, 206, 207, 211, 212].includes(
2694
+ value.voucherType
2695
+ )) {
2696
+ throw new ArcaInputError(
2697
+ "issueCreditNote requires an authorized invoice or debit note in a supported family in for.voucherType. Use the exact service API for manual control.",
2698
+ {
2699
+ code: "ARCA_INPUT_INVALID_VALUE",
2700
+ field: "input.for.voucherType",
2701
+ expected: "1, 6 or 11"
2702
+ }
2703
+ );
2704
+ }
2705
+ return {
2706
+ salesPoint: value.salesPoint,
2707
+ voucherType: value.voucherType,
2708
+ number: value.number
2709
+ };
2710
+ }
2711
+ function assertCreditNoteBound(value, max, path) {
2712
+ if (!Number.isSafeInteger(value) || value < 1 || value > max) {
2713
+ throw new ArcaInputError(
2714
+ `issueCreditNote requires input.${path} to be an integer from 1 through ${max}.`,
2715
+ { code: "ARCA_INPUT_INVALID_VALUE", field: `input.${path}` }
2716
+ );
2717
+ }
2718
+ }
2719
+ function copyCreditNoteItems(items) {
2720
+ if (!Array.isArray(items)) {
2721
+ throw new ArcaInputError(
2722
+ "issueCreditNote requires items to be a non-empty array of items.",
2723
+ {
2724
+ code: "ARCA_INPUT_INVALID_VALUE",
2725
+ field: "input.items",
2726
+ expected: "a non-empty array of items"
2727
+ }
2728
+ );
2729
+ }
2730
+ return items.map(
2731
+ (item) => item === null || typeof item !== "object" ? item : { ...item }
2732
+ );
2733
+ }
2734
+ function assertOriginalExtensions(original) {
2735
+ for (const [field, rawField] of [
2736
+ ["taxes", "Tributos"],
2737
+ ["optionalFields", "Opcionales"],
2738
+ ["buyers", "Compradores"],
2739
+ ["activities", "Actividades"],
2740
+ ["associatedPeriod", "PeriodoAsoc"]
2741
+ ]) {
2742
+ if (original.raw[rawField] && original[field] === void 0) {
2743
+ invalid3(`original ${field} could not be decoded`);
2744
+ }
2745
+ }
2746
+ try {
2747
+ validateIssuanceFields({
2748
+ taxes: original.taxes?.map((t) => ({
2749
+ id: t.id,
2750
+ description: t.description,
2751
+ base: Number(
2752
+ normalizeArcaAmountToMinorUnits(t.baseAmount, "taxes.base")
2753
+ ),
2754
+ rate: t.rate,
2755
+ amount: Number(
2756
+ normalizeArcaAmountToMinorUnits(t.amount, "taxes.amount")
2757
+ )
2758
+ })),
2759
+ optionalFields: original.optionalFields,
2760
+ buyers: original.buyers,
2761
+ activities: original.activities
2762
+ });
2763
+ if (original.associatedPeriod) {
2764
+ normalizeWsfeDateInput(
2765
+ original.associatedPeriod.startDate,
2766
+ "associatedPeriod.startDate"
2767
+ );
2768
+ normalizeWsfeDateInput(
2769
+ original.associatedPeriod.endDate,
2770
+ "associatedPeriod.endDate"
2771
+ );
2772
+ }
2773
+ } catch {
2774
+ invalid3("original extension fields are incomplete or malformed");
2775
+ }
2776
+ }
2777
+ function assertFullMode(input) {
2778
+ if (input.all !== true) {
2779
+ throw new ArcaInputError(
2780
+ "issueCreditNote accepts only all: true; pass items to credit chosen lines.",
2781
+ {
2782
+ code: "ARCA_INPUT_INVALID_VALUE",
2783
+ field: "input.all",
2784
+ expected: "the literal true"
2785
+ }
2786
+ );
2787
+ }
2788
+ if (input.total !== void 0 || input.taxes !== void 0 || input.amounts !== void 0) {
2789
+ throw new ArcaInputError(
2790
+ "issueCreditNote takes total only with items; all: true credits the original's own total.",
2791
+ {
2792
+ code: "ARCA_INPUT_INVALID_VALUE",
2793
+ field: "input.total",
2794
+ expected: "no total when all is true"
2795
+ }
2796
+ );
2797
+ }
2798
+ }
2799
+
2800
+ // src/services/vouchers.ts
2801
+ function createVouchersService(wsfe, context, wsmtxca) {
2802
+ const select = (options = {}) => {
2803
+ validateOptions(options);
2804
+ if (options.service !== "wsmtxca") {
2805
+ return wsfe;
2806
+ }
2807
+ if (!wsmtxca) {
2808
+ throw new ArcaConfigurationError("WSMTXCA service is not configured");
2809
+ }
2810
+ return createWsmtxcaIssuanceService(wsmtxca);
2811
+ };
2812
+ return {
2813
+ recover: async (key, options) => recoverOperation(
2814
+ select,
2815
+ key,
2816
+ options === void 0 ? {} : options,
2817
+ context
2818
+ ),
2819
+ issueDebitNote: async (input, options) => issueCreditNote(
2820
+ select(options),
2821
+ input,
2822
+ options ?? {},
2823
+ context,
2824
+ "debitNote"
2825
+ ),
2826
+ previewCreditNote: async (input, options) => previewNote(
2827
+ select(options),
2828
+ input,
2829
+ options ?? {},
2830
+ context,
2831
+ "creditNote"
2832
+ ),
2833
+ previewDebitNote: async (input, options) => previewNote(
2834
+ select(options),
2835
+ input,
2836
+ options ?? {},
2837
+ context,
2838
+ "debitNote"
2839
+ ),
2840
+ issueCreditNote: async (input, options) => {
2841
+ const result = await issueCreditNote(
2842
+ select(options),
2843
+ input,
2844
+ options === void 0 ? {} : options,
2845
+ context
2846
+ );
2847
+ return result;
2848
+ },
2849
+ issue: async (input, options) => {
2850
+ const result = await issueInvoice(
2851
+ select(options),
2852
+ input,
2853
+ options === void 0 ? {} : options,
2854
+ context
2855
+ );
2856
+ return result;
2857
+ },
2858
+ preview: (input, options) => previewInvoice(
2859
+ input,
2860
+ options === void 0 ? {} : options
2861
+ )
2862
+ };
2863
+ }
2864
+ function previewInvoice(input, options) {
2865
+ assertIssueObject(options, "options");
2866
+ assertIssueKeys(options, ["representedTaxId", "service"], "options");
2867
+ validateOptions(options);
2868
+ if (options.representedTaxId !== void 0) {
2869
+ issueDocumentNumber(
2870
+ options.representedTaxId,
2871
+ "options.representedTaxId",
2872
+ 11,
2873
+ 11
2874
+ );
2875
+ }
2876
+ return toPreview(prepareInvoice(input, options), options);
2877
+ }
2878
+ function prepareInvoice(input, options) {
2879
+ const prepared = deriveWsfeInvoice(input);
2880
+ if (input.details !== void 0) {
2881
+ prepared.data.details = structuredClone(input.details);
2882
+ }
2883
+ validatePrepared(prepared, options);
2884
+ return prepared;
2885
+ }
2886
+ function validatePrepared(prepared, options) {
2887
+ validateFiscalHeader(prepared.data);
2888
+ if (options.service === "wsmtxca") {
2889
+ wsmtxcaRequest(prepared.data);
2890
+ } else if (prepared.data.details !== void 0) {
2891
+ throw new ArcaInputError("Detailed items require service: wsmtxca", {
2892
+ code: "ARCA_INPUT_INVALID_VALUE",
2893
+ field: "details"
2894
+ });
2895
+ }
2896
+ }
2897
+ function toPreview(prepared, options) {
2898
+ const { data, voucherClass, amounts } = prepared;
2899
+ return {
2900
+ voucherClass,
2901
+ voucherType: data.voucherType,
2902
+ amounts,
2903
+ request: options.service === "wsmtxca" ? wsmtxcaRequest(data) : data,
2904
+ ...options.service === "wsmtxca" ? { service: "wsmtxca" } : {}
2905
+ };
2906
+ }
2907
+ async function issueInvoice(wsfe, input, inputOptions, context) {
2908
+ const options = structuredClone(inputOptions);
2909
+ validateOptions(options);
2910
+ validateKeyStore(options, context);
2911
+ const prepared = prepareInvoice(input, options);
2912
+ return await runOperation(
2913
+ wsfe,
2914
+ "issue",
2915
+ input,
2916
+ () => Promise.resolve(prepared),
2917
+ options,
2918
+ context,
2919
+ prepared.amounts
2920
+ );
2921
+ }
2922
+ async function runOperation(wsfe, operation, input, prepare, options, context, replayAmounts) {
2923
+ const store = context?.store;
2924
+ if (options.idempotencyKey === void 0 || !store || !context) {
2925
+ return runAuthorization(wsfe, await prepare(), options);
2926
+ }
2927
+ const key = attemptKey(
2928
+ context.environment,
2929
+ context.taxId,
2930
+ options.idempotencyKey
2931
+ );
2932
+ const representedTaxId = options.representedTaxId === void 0 ? void 0 : String(options.representedTaxId);
2933
+ const inputHash = canonicalHash({
2934
+ input,
2935
+ representedTaxId,
2936
+ ...options.service === "wsmtxca" ? { service: "wsmtxca" } : {},
2937
+ ...options.number === void 0 ? {} : { number: options.number }
2938
+ });
2939
+ const existing = await storeCall(() => store.get(key));
2940
+ if (existing !== null) {
2941
+ return replay(existing);
2942
+ }
2943
+ const prepared = await prepare();
2944
+ const number = await nextNumber(wsfe, prepared.data, options);
2945
+ const service = options.service ?? "wsfe";
2946
+ const versioned = service === "wsmtxca" || prepared.data.details !== void 0;
2947
+ const record = {
2948
+ v: versioned ? 2 : 1,
2949
+ operation,
2950
+ ...versioned ? { service } : {},
2951
+ representedTaxId,
2952
+ inputHash,
2953
+ number,
2954
+ salesPoint: prepared.data.salesPoint,
2955
+ voucherType: prepared.data.voucherType,
2956
+ sent: prepared.data,
2957
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2958
+ };
2959
+ if (await storeCall(() => store.add(key, JSON.stringify(record)))) {
2960
+ return runAuthorization(wsfe, prepared, options, number);
2961
+ }
2962
+ const winner = await storeCall(() => store.get(key));
2963
+ if (winner === null) {
2964
+ throw new ArcaConfigurationError(
2965
+ "ARCA reservation disappeared after atomic creation lost."
2966
+ );
2967
+ }
2968
+ return replay(winner);
2969
+ function replay(json) {
2970
+ const stored = readRecord(json);
2971
+ if ((stored.service ?? "wsfe") !== (options.service ?? "wsfe") || stored.operation !== operation || stored.inputHash !== inputHash || stored.representedTaxId !== representedTaxId) {
2972
+ throw new ArcaInputError(
2973
+ "The idempotency key was already used with different input or operation.",
2974
+ {
2975
+ code: "ARCA_INPUT_IDEMPOTENCY_MISMATCH",
2976
+ field: "options.idempotencyKey"
2977
+ }
2978
+ );
2979
+ }
2980
+ return runAuthorization(
2981
+ wsfe,
2982
+ {
2983
+ ...preparedFromRecord(stored),
2984
+ ...replayAmounts ? { amounts: replayAmounts } : {}
2985
+ },
2986
+ options,
2987
+ stored.number,
2988
+ true
2989
+ );
2990
+ }
2991
+ }
2992
+ function validateKeyStore(options, context) {
2993
+ if (options.idempotencyKey === void 0) {
2994
+ return;
2995
+ }
2996
+ if (typeof options.idempotencyKey !== "string" || options.idempotencyKey.length < 1 || options.idempotencyKey.length > 255) {
2997
+ throw new ArcaInputError(
2998
+ "idempotencyKey must contain 1 to 255 characters.",
2999
+ { code: "ARCA_INPUT_INVALID_VALUE", field: "options.idempotencyKey" }
3000
+ );
3001
+ }
3002
+ if (!context?.store) {
3003
+ throw new ArcaConfigurationError(
3004
+ 'idempotencyKey requires a store. Add import { createPostgresStore } from "facturas"; and store: createPostgresStore({ query }) to createArcaClient().'
3005
+ );
3006
+ }
3007
+ }
3008
+ function readRecord(json) {
3009
+ try {
3010
+ const record = JSON.parse(json);
3011
+ if (!record || record.v !== 1 && record.v !== 2 || record.v === 2 && record.service === void 0 || !["issue", "creditNote", "debitNote"].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) {
3012
+ throw new Error("Invalid reservation structure");
3013
+ }
3014
+ normalizeWsfeVoucherInput(record.sent);
3015
+ if (record.service !== void 0 && record.service !== "wsfe" && record.service !== "wsmtxca") {
3016
+ throw new Error("Invalid provider");
3017
+ }
3018
+ if (record.service === "wsmtxca") {
3019
+ wsmtxcaRequest(record.sent, record.number);
3020
+ }
3021
+ return record;
3022
+ } catch (cause) {
3023
+ throw new ArcaConfigurationError(
3024
+ "Invalid ARCA reservation record; preserve it for reconciliation.",
3025
+ { cause }
3026
+ );
3027
+ }
3028
+ }
3029
+ function preparedFromRecord(record) {
3030
+ const sentTotal = Number(
3031
+ normalizeArcaAmountToMinorUnits(record.sent.totalAmount, "totalAmount")
3032
+ );
3033
+ return {
3034
+ data: record.sent,
3035
+ voucherClass: voucherFamily(record.voucherType).voucherClass,
3036
+ amounts: { computedTotal: sentTotal, sentTotal, vatAdjustment: 0 }
3037
+ };
3038
+ }
3039
+ async function nextNumber(wsfe, data, options) {
3040
+ if (options.number !== void 0) {
3041
+ return options.number;
3042
+ }
3043
+ const number = await wsfe.getNextVoucherNumber({
3044
+ representedTaxId: options.representedTaxId,
3045
+ forceRefresh: options.forceRefresh,
3046
+ salesPoint: data.salesPoint,
3047
+ voucherType: data.voucherType
3048
+ });
3049
+ if (!Number.isSafeInteger(number) || number < 1 || number > 99999999) {
3050
+ throw new ArcaServiceError(
3051
+ "ARCA returned an invalid next voucher number.",
3052
+ {
3053
+ service: options.service ?? "wsfe",
3054
+ operation: options.service === "wsmtxca" ? "consultarUltimoComprobanteAutorizado" : "FECompUltimoAutorizado"
3055
+ }
3056
+ );
3057
+ }
3058
+ return number;
3059
+ }
3060
+ async function runAuthorization(wsfe, { data, voucherClass, amounts }, options, reservedNumber, replay = false) {
3061
+ const auth = {
3062
+ representedTaxId: options.representedTaxId,
3063
+ forceRefresh: options.forceRefresh
3064
+ };
3065
+ const includeRaw = options.include?.raw === true;
3066
+ const includeExact = options.include?.exactInput === true;
3067
+ const number = reservedNumber ?? await nextNumber(wsfe, data, options);
3068
+ const attempted = {
3069
+ salesPoint: data.salesPoint,
3070
+ voucherType: data.voucherType,
3071
+ number
3072
+ };
3073
+ const exact = exactEvidence(data, number, options, includeExact);
3074
+ const voucher = (cae, caeExpiry) => ({
3075
+ ...attempted,
3076
+ voucherClass,
3077
+ date: data.voucherDate,
3078
+ cae,
3079
+ caeExpiry,
3080
+ amounts
3081
+ });
3082
+ const recovery = {
3083
+ wsfe,
3084
+ auth,
3085
+ data,
3086
+ attempted,
3087
+ includeRaw,
3088
+ exact,
3089
+ voucher,
3090
+ service: options.service
3091
+ };
3092
+ if (replay) {
3093
+ const attempt2 = replayEvidence(options.service);
3094
+ let lookup;
3095
+ try {
3096
+ lookup = await wsfe.lookupVoucher({ ...auth, ...attempted });
3097
+ } catch (error) {
3098
+ return {
3099
+ kind: "indeterminate",
3100
+ attempted,
3101
+ attempt: attempt2,
3102
+ lookup: { kind: "failed", error: toArcaSafeErrorMetadata(error) }
3103
+ };
3104
+ }
3105
+ if (lookup.kind === "found") {
3106
+ return recoverInvoice({ ...recovery, attempt: attempt2, lookup });
3107
+ }
3108
+ }
3109
+ const authorization = await wsfe.issue({
3110
+ ...auth,
3111
+ data,
3112
+ voucherNumber: number
3113
+ });
3114
+ if (authorization.kind === "authorized" && authorization.caeExpiry && authorization.voucherNumber === number) {
3115
+ return {
3116
+ kind: "authorized",
3117
+ recoveredByMatch: false,
3118
+ voucher: voucher(authorization.cae, authorization.caeExpiry),
3119
+ authorization: projectEvidence(authorization, includeRaw),
3120
+ ...exact
3121
+ };
3122
+ }
3123
+ if (authorization.kind === "rejected") {
3124
+ if (options.idempotencyKey !== void 0 && (options.service === "wsmtxca" || [...authorization.errors, ...authorization.observations].some(
3125
+ (issue) => issue.code === "10016"
3126
+ ))) {
3127
+ const recovered = await recoverInvoice({
3128
+ ...recovery,
3129
+ attempt: projectEvidence(
3130
+ {
3131
+ ...authorization,
3132
+ kind: "indeterminate",
3133
+ reason: "contradictory_response"
3134
+ },
3135
+ includeRaw
3136
+ )
3137
+ });
3138
+ if (recovered.kind === "authorized" || recovered.kind === "conflict") {
3139
+ return recovered;
3140
+ }
3141
+ }
3142
+ return {
3143
+ kind: "rejected",
3144
+ attempted,
3145
+ issues: [...authorization.errors, ...authorization.observations].map(
3146
+ projectIssue
3147
+ ),
3148
+ authorization: projectEvidence(authorization, includeRaw)
3149
+ };
3150
+ }
3151
+ const uncertain = authorization.kind === "indeterminate" ? authorization : {
3152
+ ...authorization,
3153
+ kind: "indeterminate",
3154
+ reason: authorization.voucherNumber === number ? "incomplete_response" : "contradictory_response"
3155
+ };
3156
+ const attempt = projectEvidence(uncertain, includeRaw);
3157
+ return recoverInvoice({
3158
+ wsfe,
3159
+ service: options.service,
3160
+ auth,
3161
+ data,
3162
+ attempted,
3163
+ attempt,
3164
+ includeRaw,
3165
+ exact,
3166
+ voucher
3167
+ });
3168
+ }
3169
+ async function recoverInvoice({
3170
+ wsfe,
3171
+ auth,
3172
+ data,
3173
+ attempted,
3174
+ attempt,
3175
+ includeRaw,
3176
+ exact,
3177
+ voucher,
3178
+ lookup: suppliedLookup,
3179
+ service
3180
+ }) {
3181
+ let lookup;
3182
+ try {
3183
+ lookup = suppliedLookup ?? await wsfe.lookupVoucher({ ...auth, ...attempted });
3184
+ } catch (error) {
3185
+ return {
3186
+ kind: "indeterminate",
3187
+ attempted,
3188
+ attempt,
3189
+ lookup: { kind: "failed", error: toArcaSafeErrorMetadata(error) }
3190
+ };
3191
+ }
3192
+ const raw = includeRaw ? { raw: lookup.raw } : {};
3193
+ if (lookup.kind === "not_found") {
3194
+ return {
3195
+ kind: "indeterminate",
3196
+ attempted,
3197
+ attempt,
3198
+ lookup: { kind: "not_found", ...raw }
3199
+ };
3200
+ }
3201
+ const detailsMatch = service === "wsmtxca" ? matchWsmtxcaDetails(data, attempted.number, lookup.voucher.raw) : void 0;
3202
+ const matched = detailsMatch === void 0 ? matchWsfeVoucherIdentity(data, attempted.number, lookup.voucher) : detailsMatch === "match" && lookup.voucher.cae && lookup.voucher.caeExpiry ? { matches: true } : {
3203
+ matches: false,
3204
+ evidence: detailsMatch === "conflict" ? "conflict" : "incomplete",
3205
+ reason: "WSMTXCA consultation does not match the complete reserved request"
3206
+ };
3207
+ if (!matched.matches) {
3208
+ if (matched.evidence === "conflict") {
3209
+ return {
3210
+ kind: "conflict",
3211
+ attempted,
3212
+ attempt,
3213
+ found: { ...toVoucherSummary(lookup.voucher), ...raw },
3214
+ reason: `${matched.reason}. Configure a store and pass idempotencyKey for retries.`
3215
+ };
3216
+ }
3217
+ return {
3218
+ kind: "indeterminate",
3219
+ attempted,
3220
+ attempt,
3221
+ lookup: { kind: "incomplete", reason: matched.reason, ...raw }
3222
+ };
3223
+ }
3224
+ return {
3225
+ kind: "authorized",
3226
+ recoveredByMatch: true,
3227
+ voucher: voucher(
3228
+ lookup.voucher.cae,
3229
+ lookup.voucher.caeExpiry
3230
+ ),
3231
+ attempt,
3232
+ lookup: { ...toVoucherSummary(lookup.voucher), ...raw },
3233
+ ...exact
3234
+ };
3235
+ }
3236
+ function projectIssue(issue) {
3237
+ return {
3238
+ service: issue.service,
3239
+ operation: issue.operation,
3240
+ source: issue.source,
3241
+ category: issue.category,
3242
+ message: issue.message,
3243
+ ...issue.code === void 0 ? {} : { code: issue.code },
3244
+ ...issue.resultLevel === void 0 ? {} : { resultLevel: issue.resultLevel }
3245
+ };
3246
+ }
3247
+ function projectEvidence(evidence, includeRaw) {
3248
+ const base = {
3249
+ kind: evidence.kind,
3250
+ service: evidence.service,
3251
+ operation: evidence.operation,
3252
+ results: {
3253
+ ...evidence.results.header === void 0 ? {} : { header: evidence.results.header },
3254
+ ...evidence.results.detail === void 0 ? {} : { detail: evidence.results.detail },
3255
+ ...evidence.results.operation === void 0 ? {} : { operation: evidence.results.operation }
3256
+ },
3257
+ errors: evidence.errors.map(projectIssue),
3258
+ observations: evidence.observations.map(projectIssue),
3259
+ ...includeRaw && evidence.raw !== void 0 ? { raw: evidence.raw } : {}
3260
+ };
3261
+ const projected = { ...base };
3262
+ for (const field of [
3263
+ "result",
3264
+ "resultLevel",
3265
+ "cae",
3266
+ "caeExpiry",
3267
+ "voucherNumber",
3268
+ "reason"
3269
+ ]) {
3270
+ if (field in evidence && evidence[field] !== void 0) {
3271
+ projected[field] = evidence[field];
3272
+ }
3273
+ }
3274
+ if (evidence.kind === "indeterminate" && evidence.authentication) {
3275
+ const { code, reason, providerCode } = evidence.authentication;
3276
+ projected.authentication = {
3277
+ code,
3278
+ reason,
3279
+ ...providerCode === void 0 ? {} : { providerCode }
3280
+ };
3281
+ }
3282
+ return projected;
3283
+ }
3284
+ function validateOptions(options) {
3285
+ assertIssueObject(options, "options");
3286
+ assertIssueKeys(
3287
+ options,
3288
+ [
3289
+ "representedTaxId",
3290
+ "forceRefresh",
3291
+ "include",
3292
+ "idempotencyKey",
3293
+ "service",
3294
+ "number"
3295
+ ],
3296
+ "options"
3297
+ );
3298
+ if (options.service !== void 0 && options.service !== "wsfe" && options.service !== "wsmtxca") {
3299
+ throw new ArcaInputError("Unknown fiscal service", {
3300
+ code: "ARCA_INPUT_INVALID_VALUE",
3301
+ field: "options.service"
3302
+ });
3303
+ }
3304
+ if (options.number !== void 0 && (!Number.isSafeInteger(options.number) || options.number < 1 || options.number > 99999999)) {
3305
+ throw new ArcaInputError("Invalid reserved number", {
3306
+ code: "ARCA_INPUT_INVALID_VALUE",
3307
+ field: "options.number"
3308
+ });
3309
+ }
3310
+ if (options.representedTaxId !== void 0) {
3311
+ issueDocumentNumber(
3312
+ options.representedTaxId,
3313
+ "options.representedTaxId",
3314
+ 11,
3315
+ 11
3316
+ );
3317
+ }
3318
+ if (options.forceRefresh !== void 0 && typeof options.forceRefresh !== "boolean") {
3319
+ throw new ArcaInputError("options.forceRefresh must be a boolean.", {
3320
+ code: "ARCA_INPUT_INVALID_VALUE",
3321
+ field: "options.forceRefresh"
3322
+ });
3323
+ }
3324
+ if (options.include !== void 0) {
3325
+ assertIssueObject(options.include, "options.include");
3326
+ assertIssueKeys(options.include, ["raw", "exactInput"], "options.include");
3327
+ for (const field of ["raw", "exactInput"]) {
3328
+ if (options.include[field] !== void 0 && typeof options.include[field] !== "boolean") {
3329
+ throw new ArcaInputError(
3330
+ `options.include.${field} must be a boolean.`,
3331
+ {
3332
+ code: "ARCA_INPUT_INVALID_VALUE",
3333
+ field: `options.include.${field}`
3334
+ }
3335
+ );
3336
+ }
3337
+ }
3338
+ }
3339
+ }
3340
+ function replayEvidence(service = "wsfe") {
3341
+ return {
3342
+ kind: "indeterminate",
3343
+ service,
3344
+ operation: service === "wsfe" ? "FECAESolicitar" : "autorizarComprobante",
3345
+ reason: "incomplete_response",
3346
+ results: {},
3347
+ errors: [],
3348
+ observations: []
3349
+ };
3350
+ }
3351
+ async function issueCreditNote(wsfe, input, inputOptions, context, kind = "creditNote") {
3352
+ const options = structuredClone(inputOptions);
3353
+ validateOptions(options);
3354
+ validateKeyStore(options, context);
3355
+ assertIssueObject(input, "input");
3356
+ const note = "associatedPeriod" in input && !("for" in input) ? structuredClone(input) : assertCreditNoteInput(input);
3357
+ if (kind === "debitNote" && "all" in note && note.all) {
3358
+ throw new ArcaInputError("Debit notes require explicit items", {
3359
+ code: "ARCA_INPUT_INVALID_VALUE",
3360
+ field: "items"
3361
+ });
3362
+ }
3363
+ return await runOperation(
3364
+ wsfe,
3365
+ kind,
3366
+ note,
3367
+ () => prepareNote(wsfe, note, options, context, kind),
3368
+ options,
3369
+ context
3370
+ );
3371
+ }
3372
+ async function previewNote(wsfe, input, inputOptions, context, kind) {
3373
+ const options = structuredClone(inputOptions);
3374
+ assertIssueKeys(
3375
+ options,
3376
+ ["representedTaxId", "service", "forceRefresh"],
3377
+ "options"
3378
+ );
3379
+ return toPreview(
3380
+ await prepareNote(wsfe, input, options, context, kind),
3381
+ options
3382
+ );
3383
+ }
3384
+ async function prepareNote(wsfe, input, options, context, kind) {
3385
+ validateOptions(options);
3386
+ assertIssueObject(input, "input");
3387
+ if ("associatedPeriod" in input && !("for" in input)) {
3388
+ return preparePeriodNote(input, options, kind);
3389
+ }
3390
+ const note = assertCreditNoteInput(input);
3391
+ if (kind === "debitNote" && note.all) {
3392
+ throw new ArcaInputError("Debit notes require explicit items", {
3393
+ code: "ARCA_INPUT_INVALID_VALUE"
3394
+ });
3395
+ }
3396
+ const target = note.for;
3397
+ const original = await wsfe.lookupVoucher({
3398
+ representedTaxId: options.representedTaxId,
3399
+ forceRefresh: options.forceRefresh,
3400
+ ...target
3401
+ });
3402
+ if (original.kind !== "found") {
3403
+ throw new ArcaInputError(
3404
+ "issueCreditNote failed: original voucher not found",
3405
+ { code: "ARCA_INPUT_INVALID_VALUE", field: "input.for" }
3406
+ );
3407
+ }
3408
+ if (original.voucher.salesPoint !== target.salesPoint || original.voucher.voucherType !== target.voucherType || original.voucher.voucherNumber !== target.number) {
3409
+ throw new ArcaInputError(
3410
+ "Original lookup coordinates do not match input.for",
3411
+ { code: "ARCA_INPUT_INVALID_VALUE", field: "input.for" }
3412
+ );
3413
+ }
3414
+ const prepared = note.all === true ? deriveWsfeFullCreditNote(original.voucher, note) : deriveWsfePartialCreditNote(original.voucher, note, /* @__PURE__ */ new Date(), kind);
3415
+ if (note.details !== void 0) {
3416
+ prepared.data.details = structuredClone(note.details);
3417
+ } else if (note.all && "details" in original.voucher) {
3418
+ prepared.data.details = structuredClone(
3419
+ original.voucher.details
3420
+ );
3421
+ }
3422
+ if (voucherFamily(prepared.data.voucherType).family === "fce") {
3423
+ const taxId = options.representedTaxId ?? context?.taxId;
3424
+ if (!taxId) {
3425
+ throw new ArcaInputError("FCE association requires the issuer tax ID", {
3426
+ code: "ARCA_INPUT_MISSING_FIELD",
3427
+ field: "representedTaxId"
3428
+ });
3429
+ }
3430
+ for (const associated of prepared.data.associatedVouchers ?? []) {
3431
+ associated.taxId = String(taxId);
3432
+ }
3433
+ }
3434
+ validatePrepared(prepared, options);
3435
+ return prepared;
3436
+ }
3437
+ function preparePeriodNote(input, options, kind) {
3438
+ const { associatedPeriod, ...invoice } = input;
3439
+ assertIssueObject(associatedPeriod, "associatedPeriod");
3440
+ assertIssueKeys(associatedPeriod, ["from", "to"], "associatedPeriod");
3441
+ if (normalizeWsfeDateInput(associatedPeriod.from, "associatedPeriod.from") > normalizeWsfeDateInput(associatedPeriod.to, "associatedPeriod.to")) {
3442
+ throw new ArcaInputError("Associated period starts after its end", {
3443
+ code: "ARCA_INPUT_INVALID_VALUE",
3444
+ field: "associatedPeriod"
3445
+ });
3446
+ }
3447
+ const prepared = prepareInvoice(invoice, options);
3448
+ const family = voucherFamily(prepared.data.voucherType);
3449
+ if (family.family === "fce") {
3450
+ throw new ArcaInputError("FCE notes require an associated invoice", {
3451
+ code: "ARCA_INPUT_INVALID_VALUE",
3452
+ field: "associatedPeriod"
3453
+ });
3454
+ }
3455
+ prepared.data.voucherType = family.types[kind === "creditNote" ? 2 : 1];
3456
+ prepared.data.associatedPeriod = {
3457
+ startDate: associatedPeriod.from,
3458
+ endDate: associatedPeriod.to
3459
+ };
3460
+ normalizeWsfeVoucherInput(prepared.data);
3461
+ validatePrepared(prepared, options);
3462
+ return prepared;
3463
+ }
3464
+ function exactEvidence(data, number, options, include) {
3465
+ return include ? {
3466
+ sent: options.service === "wsmtxca" ? wsmtxcaRequest(data, number) : data
3467
+ } : {};
3468
+ }
3469
+ async function recoverOperation(select, key, inputOptions, context) {
3470
+ const options = structuredClone(inputOptions);
3471
+ assertIssueObject(options, "options");
3472
+ assertIssueKeys(
3473
+ options,
3474
+ ["representedTaxId", "forceRefresh", "include"],
3475
+ "options"
3476
+ );
3477
+ validateOptions(options);
3478
+ validateKeyStore({ ...options, idempotencyKey: key }, context);
3479
+ const json = await storeCall(
3480
+ () => context.store.get(
3481
+ attemptKey(
3482
+ context.environment,
3483
+ context.taxId,
3484
+ key
3485
+ )
3486
+ )
3487
+ );
3488
+ if (json === null) {
3489
+ throw new ArcaInputError("No reservation exists for this idempotency key", {
3490
+ code: "ARCA_INPUT_INVALID_VALUE",
3491
+ field: "idempotencyKey"
3492
+ });
3493
+ }
3494
+ const record = readRecord(json);
3495
+ if (options.representedTaxId !== void 0 && String(options.representedTaxId) !== (record.representedTaxId ?? context?.taxId)) {
3496
+ throw new ArcaInputError(
3497
+ "Reservation belongs to another represented taxpayer",
3498
+ { code: "ARCA_INPUT_IDEMPOTENCY_MISMATCH" }
3499
+ );
3500
+ }
3501
+ const storedOptions = {
3502
+ ...options,
3503
+ service: record.service ?? "wsfe",
3504
+ representedTaxId: record.representedTaxId
3505
+ };
3506
+ const prepared = preparedFromRecord(record);
3507
+ return recoverInvoice({
3508
+ wsfe: select(storedOptions),
3509
+ service: storedOptions.service,
3510
+ auth: storedOptions,
3511
+ data: prepared.data,
3512
+ attempted: {
3513
+ salesPoint: record.salesPoint,
3514
+ voucherType: record.voucherType,
3515
+ number: record.number
3516
+ },
3517
+ attempt: replayEvidence(storedOptions.service),
3518
+ includeRaw: options.include?.raw === true,
3519
+ exact: exactEvidence(
3520
+ prepared.data,
3521
+ record.number,
3522
+ storedOptions,
3523
+ options.include?.exactInput === true
3524
+ ),
3525
+ voucher: (cae, caeExpiry) => ({
3526
+ salesPoint: record.salesPoint,
3527
+ voucherType: record.voucherType,
3528
+ number: record.number,
3529
+ voucherClass: prepared.voucherClass,
3530
+ date: prepared.data.voucherDate,
3531
+ amounts: prepared.amounts,
3532
+ cae,
3533
+ caeExpiry
3534
+ })
3535
+ });
3536
+ }
3537
+
3538
+ // src/soap/index.ts
3539
+ function createSoapTransport(options) {
3540
+ return {
3541
+ async execute(request) {
3542
+ const serviceConfig = getArcaServiceConfig(request.service);
3543
+ const url = serviceConfig.endpoint[options.config.environment];
3544
+ const soapActionOperation = request.operation;
3545
+ const bodyElementName = request.bodyElementName ?? request.operation;
3546
+ const soapAction = serviceConfig.usesEmptySoapAction ? "" : `${serviceConfig.soapActionBase}${soapActionOperation}`;
3547
+ const contentType = serviceConfig.soapVersion === "1.2" ? `application/soap+xml; charset=utf-8; action="${soapAction}"` : 'text/xml; charset="utf-8"';
3548
+ const xml = buildSoapEnvelope(
3549
+ serviceConfig.soapVersion,
3550
+ bodyElementName,
3551
+ serviceConfig.namespace,
3552
+ request.body,
3553
+ {
3554
+ namespaceMode: request.bodyElementNamespaceMode
3555
+ }
3556
+ );
3557
+ const startedAt = Date.now();
3558
+ options.logger?.debug("Sending ARCA SOAP request", {
3559
+ service: request.service,
3560
+ operation: request.operation,
3561
+ url
3562
+ });
3563
+ try {
3564
+ const response = await postXmlWithMetadata({
3565
+ url,
3566
+ body: xml,
3567
+ contentType,
3568
+ soapAction: serviceConfig.soapVersion === "1.1" ? soapAction : void 0,
3569
+ useLegacyTlsSecurityLevel0: options.config.environment === "production" && serviceConfig.useLegacyTlsSecurityLevel0 === true,
3570
+ timeout: options.config.timeout,
3571
+ retries: request.retries ?? options.config.retries,
3572
+ retryDelay: options.config.retryDelay,
3573
+ logger: options.logger,
3574
+ service: request.service,
3575
+ operation: request.operation
3576
+ });
3577
+ options.logger?.debug("Received ARCA SOAP response", {
3578
+ service: request.service,
3579
+ operation: request.operation,
3580
+ durationMs: Date.now() - startedAt
3581
+ });
3582
+ const parseContext = {
3583
+ service: request.service,
3584
+ operation: request.operation,
3585
+ endpointUrl: url,
3586
+ statusCode: response.statusCode,
3587
+ contentType: response.contentType,
3588
+ responseBody: response.body
3589
+ };
3590
+ const soapBody = parseSoapBody(response.body, parseContext);
3591
+ const [, result] = getSingleBodyEntry(
3592
+ soapBody,
3593
+ parseContext
3594
+ );
3595
+ return {
3596
+ service: request.service,
3597
+ operation: request.operation,
3598
+ raw: response.body,
3599
+ result
3600
+ };
3601
+ } catch (error) {
3602
+ if (error instanceof ArcaSoapFaultError) {
3603
+ options.logger?.error("ARCA SOAP fault response", {
3604
+ service: request.service,
3605
+ operation: request.operation,
3606
+ url,
3607
+ ...createSafeErrorDiagnostic(error)
3608
+ });
3609
+ }
3610
+ if (error instanceof ArcaInvalidSoapResponseError) {
3611
+ options.logger?.error("ARCA invalid SOAP response", {
3612
+ service: request.service,
3613
+ operation: request.operation,
3614
+ url,
3615
+ ...createSafeErrorDiagnostic(error)
3616
+ });
3617
+ }
3618
+ throw error;
3619
+ }
3620
+ }
3621
+ };
3622
+ }
3623
+
3624
+ // src/wsaa/store-adapter.ts
3625
+ function createWsaaStoreAdapter(store) {
3626
+ const key = (value) => `arca:v1:wsaa:${serializeWsaaSessionKey(value)}`;
3627
+ const remove = store.delete?.bind(store);
3628
+ const lock = store.withLock?.bind(store);
3629
+ return {
3630
+ get: (value) => storeCall(async () => {
3631
+ const json = await store.get(key(value));
3632
+ if (json === null) {
3633
+ return null;
3634
+ }
3635
+ const credentials = JSON.parse(json);
3636
+ return credentials && typeof credentials.token === "string" && typeof credentials.sign === "string" && isWsaaCredentialValid(credentials) ? credentials : null;
3637
+ }),
3638
+ set: (value, credentials) => storeCall(() => store.set(key(value), JSON.stringify(credentials))),
3639
+ ...remove ? {
3640
+ delete: (value) => storeCall(() => remove(key(value)))
3641
+ } : {},
3642
+ ...lock ? {
3643
+ withLock: (value, fn) => lock(key(value), fn)
3644
+ } : {}
3645
+ };
3646
+ }
3647
+
3648
+ // src/client.ts
3649
+ function createArcaClient(config = {}) {
3650
+ const discovered = discoverArcaClientConfig(config);
3651
+ assertArcaClientConfig(discovered);
3652
+ const normalizedConfig = normalizeArcaClientConfig(discovered);
3653
+ if (normalizedConfig.store && !normalizedConfig.wsaaSessionStore) {
3654
+ normalizedConfig.wsaaSessionStore = createWsaaStoreAdapter(
3655
+ normalizedConfig.store
3656
+ );
3657
+ }
3658
+ const logger = createArcaLogger(normalizedConfig.logger);
3659
+ const auth = createWsaaAuthModule({ config: normalizedConfig, logger });
3660
+ const soap = createSoapTransport({ config: normalizedConfig, logger });
3661
+ const publicConfig = Object.freeze({
3662
+ taxId: normalizedConfig.taxId,
3663
+ environment: normalizedConfig.environment,
3664
+ timeout: normalizedConfig.timeout,
3665
+ retries: normalizedConfig.retries,
3666
+ retryDelay: normalizedConfig.retryDelay
3667
+ });
3668
+ const wsfe = createWsfeService({ config: normalizedConfig, auth, soap });
3669
+ const wsmtxca = createWsmtxcaService({
3670
+ config: normalizedConfig,
3671
+ auth,
3672
+ soap
3673
+ });
3674
+ const vouchers = createVouchersService(wsfe, normalizedConfig, wsmtxca);
3675
+ return {
3676
+ config: publicConfig,
3677
+ issue: vouchers.issue,
3678
+ preview: vouchers.preview,
3679
+ issueCreditNote: vouchers.issueCreditNote,
3680
+ recover: vouchers.recover,
3681
+ issueDebitNote: vouchers.issueDebitNote,
3682
+ previewCreditNote: vouchers.previewCreditNote,
3683
+ previewDebitNote: vouchers.previewDebitNote,
3684
+ wsfe,
3685
+ wsmtxca,
3686
+ padron: createPadronService({ config: normalizedConfig, auth, soap })
3687
+ };
3688
+ }
3689
+
3690
+ // src/store/file.ts
3691
+ import { createHash as createHash3, randomUUID } from "crypto";
3692
+ import { mkdir, readFile, rename, unlink, writeFile } from "fs/promises";
3693
+ import { join } from "path";
3694
+ function createFileStore(directory) {
3695
+ const path = (key) => join(directory, createHash3("sha256").update(key).digest("hex"));
3696
+ const ensure = () => mkdir(directory, { recursive: true, mode: 448 });
3697
+ return {
3698
+ get: (key) => storeCall(async () => {
3699
+ try {
3700
+ return await readFile(path(key), "utf8");
3701
+ } catch (error) {
3702
+ if (error.code === "ENOENT") {
3703
+ return null;
3704
+ }
3705
+ throw error;
3706
+ }
3707
+ }),
3708
+ set: (key, value) => storeCall(async () => {
3709
+ await ensure();
3710
+ const temporary = `${path(key)}.${randomUUID()}.tmp`;
3711
+ try {
3712
+ await writeFile(temporary, value, { mode: 384, flag: "wx" });
3713
+ await rename(temporary, path(key));
3714
+ } finally {
3715
+ await unlink(temporary).catch(() => void 0);
3716
+ }
3717
+ }),
3718
+ add: (key, value) => storeCall(async () => {
3719
+ await ensure();
3720
+ try {
3721
+ await writeFile(path(key), value, { flag: "wx", mode: 384 });
3722
+ return true;
3723
+ } catch (error) {
3724
+ if (error.code === "EEXIST") {
3725
+ return false;
3726
+ }
3727
+ throw error;
3728
+ }
3729
+ }),
3730
+ delete: (key) => storeCall(async () => {
3731
+ try {
3732
+ await unlink(path(key));
3733
+ } catch (error) {
3734
+ if (error.code !== "ENOENT") {
3735
+ throw error;
3736
+ }
3737
+ }
3738
+ })
3739
+ };
3740
+ }
3741
+
3742
+ export {
3743
+ ARCA_ENVIRONMENTS,
3744
+ ARCA_ENV_VARIABLES,
3745
+ resolveArcaEnvironment,
3746
+ createArcaClientConfigFromEnv,
3747
+ assertArcaClientConfig,
3748
+ ARCA_WSAA_CONFIG,
3749
+ discoverArcaClientConfig,
3750
+ storeCall,
3751
+ matchWsfeVoucherIdentity,
3752
+ createMemoryWsaaSessionStore,
3753
+ createWsaaAuthModule,
3754
+ createWsaaStoreAdapter,
3755
+ createArcaClient,
3756
+ createFileStore
3757
+ };
3758
+ //# sourceMappingURL=chunk-LOWV7IF6.mjs.map