facturas 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +46 -0
- package/dist/constants.d.ts +34 -0
- package/dist/constants.js +36 -0
- package/dist/constants.js.map +1 -0
- package/dist/errors.d.ts +51 -0
- package/dist/errors.js +62 -0
- package/dist/errors.js.map +1 -0
- package/dist/index-BLq3d6xg.d.ts +11 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +1757 -0
- package/dist/index.js.map +1 -0
- package/dist/padron.d.ts +31 -0
- package/dist/padron.js +121 -0
- package/dist/padron.js.map +1 -0
- package/dist/types-SloiIQkT.d.ts +53 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -0
- package/dist/wsfe.d.ts +228 -0
- package/dist/wsfe.js +605 -0
- package/dist/wsfe.js.map +1 -0
- package/dist/wsmtxca.d.ts +53 -0
- package/dist/wsmtxca.js +247 -0
- package/dist/wsmtxca.js.map +1 -0
- package/package.json +94 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1757 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var ArcaError = class extends Error {
|
|
3
|
+
code;
|
|
4
|
+
name = "ArcaError";
|
|
5
|
+
constructor(message, code = "ARCA_ERROR", options) {
|
|
6
|
+
super(message, options);
|
|
7
|
+
this.code = code;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var ArcaConfigurationError = class extends ArcaError {
|
|
11
|
+
name = "ArcaConfigurationError";
|
|
12
|
+
constructor(message, options) {
|
|
13
|
+
super(message, "ARCA_CONFIGURATION_ERROR", options);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var ArcaInputError = class extends ArcaError {
|
|
17
|
+
name = "ArcaInputError";
|
|
18
|
+
detail;
|
|
19
|
+
constructor(message, options) {
|
|
20
|
+
super(message, "ARCA_INPUT_ERROR", options);
|
|
21
|
+
this.detail = options?.detail;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var ArcaTransportError = class extends ArcaError {
|
|
25
|
+
name = "ArcaTransportError";
|
|
26
|
+
statusCode;
|
|
27
|
+
responseBody;
|
|
28
|
+
constructor(message, options) {
|
|
29
|
+
super(message, "ARCA_TRANSPORT_ERROR", options);
|
|
30
|
+
this.statusCode = options?.statusCode;
|
|
31
|
+
this.responseBody = options?.responseBody;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var ArcaSoapFaultError = class extends ArcaError {
|
|
35
|
+
name = "ArcaSoapFaultError";
|
|
36
|
+
faultCode;
|
|
37
|
+
detail;
|
|
38
|
+
constructor(message, options) {
|
|
39
|
+
super(message, "ARCA_SOAP_FAULT", options);
|
|
40
|
+
this.faultCode = options?.faultCode;
|
|
41
|
+
this.detail = options?.detail;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
var ArcaServiceError = class extends ArcaError {
|
|
45
|
+
name = "ArcaServiceError";
|
|
46
|
+
serviceCode;
|
|
47
|
+
detail;
|
|
48
|
+
constructor(message, options) {
|
|
49
|
+
super(message, "ARCA_SERVICE_ERROR", options);
|
|
50
|
+
this.serviceCode = options?.serviceCode;
|
|
51
|
+
this.detail = options?.detail;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// src/config.ts
|
|
56
|
+
var ARCA_ENVIRONMENTS = ["production", "test"];
|
|
57
|
+
var ARCA_ENV_VARIABLES = {
|
|
58
|
+
taxId: "ARCA_TAX_ID",
|
|
59
|
+
certificatePem: "ARCA_CERTIFICATE_PEM",
|
|
60
|
+
privateKeyPem: "ARCA_PRIVATE_KEY_PEM",
|
|
61
|
+
environment: "ARCA_ENVIRONMENT"
|
|
62
|
+
};
|
|
63
|
+
var PRIVATE_KEY_PEM_PREFIXES = [
|
|
64
|
+
"-----BEGIN PRIVATE KEY-----",
|
|
65
|
+
"-----BEGIN RSA PRIVATE KEY-----",
|
|
66
|
+
"-----BEGIN ENCRYPTED PRIVATE KEY-----"
|
|
67
|
+
];
|
|
68
|
+
var VALID_ARCA_LOG_LEVELS = ["debug", "info", "warn", "error"];
|
|
69
|
+
var DEFAULT_ARCA_TIMEOUT_MS = 3e4;
|
|
70
|
+
var DEFAULT_ARCA_RETRIES = 0;
|
|
71
|
+
var DEFAULT_ARCA_RETRY_DELAY_MS = 500;
|
|
72
|
+
function resolveArcaEnvironment(production) {
|
|
73
|
+
return production ? "production" : "test";
|
|
74
|
+
}
|
|
75
|
+
function createArcaClientConfigFromEnv(options = {}) {
|
|
76
|
+
const env = options.env ?? process.env;
|
|
77
|
+
const variableNames = {
|
|
78
|
+
...ARCA_ENV_VARIABLES,
|
|
79
|
+
...options.variableNames
|
|
80
|
+
};
|
|
81
|
+
const environmentInput = readEnv(env, variableNames.environment);
|
|
82
|
+
const environmentValue = normalizeEnvironmentValue(environmentInput);
|
|
83
|
+
const config = {
|
|
84
|
+
taxId: readEnv(env, variableNames.taxId) ?? "",
|
|
85
|
+
certificatePem: readEnv(env, variableNames.certificatePem) ?? "",
|
|
86
|
+
privateKeyPem: readEnv(env, variableNames.privateKeyPem) ?? "",
|
|
87
|
+
environment: environmentValue ?? environmentInput ?? options.defaultEnvironment ?? "test"
|
|
88
|
+
};
|
|
89
|
+
assertArcaClientConfig(config);
|
|
90
|
+
return normalizeArcaClientConfig(config);
|
|
91
|
+
}
|
|
92
|
+
function assertArcaClientConfig(config) {
|
|
93
|
+
const invalidFields = [];
|
|
94
|
+
const normalized = normalizeArcaClientConfig(config);
|
|
95
|
+
const timeout = normalized.timeout ?? DEFAULT_ARCA_TIMEOUT_MS;
|
|
96
|
+
const retries = normalized.retries ?? DEFAULT_ARCA_RETRIES;
|
|
97
|
+
const retryDelay = normalized.retryDelay ?? DEFAULT_ARCA_RETRY_DELAY_MS;
|
|
98
|
+
if (!/^\d{11}$/.test(normalized.taxId)) {
|
|
99
|
+
invalidFields.push("taxId");
|
|
100
|
+
}
|
|
101
|
+
if (!normalized.certificatePem.startsWith("-----BEGIN CERTIFICATE-----")) {
|
|
102
|
+
invalidFields.push("certificatePem");
|
|
103
|
+
}
|
|
104
|
+
if (!PRIVATE_KEY_PEM_PREFIXES.some(
|
|
105
|
+
(prefix) => normalized.privateKeyPem.startsWith(prefix)
|
|
106
|
+
)) {
|
|
107
|
+
invalidFields.push("privateKeyPem");
|
|
108
|
+
}
|
|
109
|
+
if (!ARCA_ENVIRONMENTS.includes(normalized.environment)) {
|
|
110
|
+
invalidFields.push("environment");
|
|
111
|
+
}
|
|
112
|
+
if (!Number.isFinite(timeout) || timeout <= 0) {
|
|
113
|
+
invalidFields.push("timeout");
|
|
114
|
+
}
|
|
115
|
+
if (!Number.isInteger(retries) || retries < 0) {
|
|
116
|
+
invalidFields.push("retries");
|
|
117
|
+
}
|
|
118
|
+
if (!Number.isFinite(retryDelay) || retryDelay < 0) {
|
|
119
|
+
invalidFields.push("retryDelay");
|
|
120
|
+
}
|
|
121
|
+
const loggerLevel = normalized.logger?.level;
|
|
122
|
+
if (loggerLevel !== void 0 && !VALID_ARCA_LOG_LEVELS.includes(loggerLevel)) {
|
|
123
|
+
invalidFields.push("logger.level");
|
|
124
|
+
}
|
|
125
|
+
if (normalized.logger?.log !== void 0 && typeof normalized.logger.log !== "function") {
|
|
126
|
+
invalidFields.push("logger.log");
|
|
127
|
+
}
|
|
128
|
+
if (invalidFields.length > 0) {
|
|
129
|
+
throw new ArcaConfigurationError(
|
|
130
|
+
`Missing or invalid ARCA client config fields: ${invalidFields.join(", ")}`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
var ARCA_WSAA_CONFIG = {
|
|
135
|
+
namespace: "http://wsaa.view.sua.dvadac.desein.afip.gov",
|
|
136
|
+
endpoint: {
|
|
137
|
+
production: "https://wsaa.afip.gov.ar/ws/services/LoginCms",
|
|
138
|
+
test: "https://wsaahomo.afip.gov.ar/ws/services/LoginCms"
|
|
139
|
+
},
|
|
140
|
+
soapVersion: "1.1",
|
|
141
|
+
soapActionBase: "",
|
|
142
|
+
usesEmptySoapAction: true
|
|
143
|
+
};
|
|
144
|
+
var ARCA_SERVICE_CONFIG = {
|
|
145
|
+
wsaa: ARCA_WSAA_CONFIG,
|
|
146
|
+
wsfe: {
|
|
147
|
+
namespace: "http://ar.gov.afip.dif.FEV1/",
|
|
148
|
+
endpoint: {
|
|
149
|
+
production: "https://servicios1.afip.gov.ar/wsfev1/service.asmx",
|
|
150
|
+
test: "https://wswhomo.afip.gov.ar/wsfev1/service.asmx"
|
|
151
|
+
},
|
|
152
|
+
soapVersion: "1.2",
|
|
153
|
+
soapActionBase: "http://ar.gov.afip.dif.FEV1/",
|
|
154
|
+
useLegacyTlsSecurityLevel0: true
|
|
155
|
+
},
|
|
156
|
+
wsmtxca: {
|
|
157
|
+
namespace: "http://impl.service.wsmtxca.afip.gov.ar/service/",
|
|
158
|
+
endpoint: {
|
|
159
|
+
production: "https://serviciosjava.afip.gov.ar/wsmtxca/services/MTXCAService",
|
|
160
|
+
test: "https://fwshomo.afip.gov.ar/wsmtxca/services/MTXCAService"
|
|
161
|
+
},
|
|
162
|
+
soapVersion: "1.1",
|
|
163
|
+
soapActionBase: "http://impl.service.wsmtxca.afip.gov.ar/service/"
|
|
164
|
+
},
|
|
165
|
+
"padron-a5": {
|
|
166
|
+
namespace: "http://a5.soap.ws.server.puc.sr/",
|
|
167
|
+
endpoint: {
|
|
168
|
+
production: "https://aws.afip.gov.ar/sr-padron/webservices/personaServiceA5",
|
|
169
|
+
test: "https://awshomo.afip.gov.ar/sr-padron/webservices/personaServiceA5"
|
|
170
|
+
},
|
|
171
|
+
soapVersion: "1.1",
|
|
172
|
+
soapActionBase: "",
|
|
173
|
+
usesEmptySoapAction: true
|
|
174
|
+
},
|
|
175
|
+
"padron-a13": {
|
|
176
|
+
namespace: "http://a13.soap.ws.server.puc.sr/",
|
|
177
|
+
endpoint: {
|
|
178
|
+
production: "https://aws.afip.gov.ar/sr-padron/webservices/personaServiceA13",
|
|
179
|
+
test: "https://awshomo.afip.gov.ar/sr-padron/webservices/personaServiceA13"
|
|
180
|
+
},
|
|
181
|
+
soapVersion: "1.1",
|
|
182
|
+
soapActionBase: "",
|
|
183
|
+
usesEmptySoapAction: true
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
function getArcaServiceConfig(service) {
|
|
187
|
+
const serviceConfig = ARCA_SERVICE_CONFIG[service];
|
|
188
|
+
if (!serviceConfig) {
|
|
189
|
+
throw new ArcaConfigurationError(
|
|
190
|
+
`Unsupported ARCA service configuration: ${service}`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
return serviceConfig;
|
|
194
|
+
}
|
|
195
|
+
function normalizeArcaClientConfig(config) {
|
|
196
|
+
const normalizedEnvironment = normalizeEnvironmentValue(String(config.environment)) ?? config.environment;
|
|
197
|
+
const normalizedLoggerLevel = normalizeLogLevelValue(config.logger?.level);
|
|
198
|
+
return {
|
|
199
|
+
taxId: config.taxId.trim(),
|
|
200
|
+
certificatePem: config.certificatePem.trim(),
|
|
201
|
+
privateKeyPem: config.privateKeyPem.trim(),
|
|
202
|
+
environment: normalizedEnvironment,
|
|
203
|
+
timeout: config.timeout ?? DEFAULT_ARCA_TIMEOUT_MS,
|
|
204
|
+
retries: config.retries ?? DEFAULT_ARCA_RETRIES,
|
|
205
|
+
retryDelay: config.retryDelay ?? DEFAULT_ARCA_RETRY_DELAY_MS,
|
|
206
|
+
...config.logger === void 0 ? {} : {
|
|
207
|
+
logger: {
|
|
208
|
+
...config.logger,
|
|
209
|
+
...normalizedLoggerLevel === void 0 ? {} : { level: normalizedLoggerLevel }
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function normalizeEnvironmentValue(value) {
|
|
215
|
+
if (!value) {
|
|
216
|
+
return void 0;
|
|
217
|
+
}
|
|
218
|
+
const normalized = value.trim().toLowerCase();
|
|
219
|
+
if (ARCA_ENVIRONMENTS.includes(normalized)) {
|
|
220
|
+
return normalized;
|
|
221
|
+
}
|
|
222
|
+
return void 0;
|
|
223
|
+
}
|
|
224
|
+
function readEnv(env, variableName) {
|
|
225
|
+
return env[variableName]?.trim() || void 0;
|
|
226
|
+
}
|
|
227
|
+
function normalizeLogLevelValue(value) {
|
|
228
|
+
if (!value) {
|
|
229
|
+
return void 0;
|
|
230
|
+
}
|
|
231
|
+
const normalized = value.trim().toLowerCase();
|
|
232
|
+
if (VALID_ARCA_LOG_LEVELS.includes(normalized)) {
|
|
233
|
+
return normalized;
|
|
234
|
+
}
|
|
235
|
+
return value;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/internal/logger.ts
|
|
239
|
+
var ARCA_LOG_LEVELS = ["debug", "info", "warn", "error"];
|
|
240
|
+
function createArcaLogger(config) {
|
|
241
|
+
const disabled = config?.disabled ?? false;
|
|
242
|
+
const level = resolveArcaLogLevel(config?.level);
|
|
243
|
+
const sink = config?.log ?? defaultArcaLog;
|
|
244
|
+
const log = (messageLevel, message, ...args) => {
|
|
245
|
+
if (disabled || !shouldLog(level, messageLevel)) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
sink(messageLevel, message, ...args);
|
|
249
|
+
};
|
|
250
|
+
return {
|
|
251
|
+
disabled,
|
|
252
|
+
level,
|
|
253
|
+
log,
|
|
254
|
+
debug(message, ...args) {
|
|
255
|
+
log("debug", message, ...args);
|
|
256
|
+
},
|
|
257
|
+
info(message, ...args) {
|
|
258
|
+
log("info", message, ...args);
|
|
259
|
+
},
|
|
260
|
+
warn(message, ...args) {
|
|
261
|
+
log("warn", message, ...args);
|
|
262
|
+
},
|
|
263
|
+
error(message, ...args) {
|
|
264
|
+
log("error", message, ...args);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function resolveArcaLogLevel(level) {
|
|
269
|
+
if (isArcaLogLevel(level)) {
|
|
270
|
+
return level;
|
|
271
|
+
}
|
|
272
|
+
const envLevel = process.env.ARCA_LOG_LEVEL?.trim().toLowerCase();
|
|
273
|
+
if (isArcaLogLevel(envLevel)) {
|
|
274
|
+
return envLevel;
|
|
275
|
+
}
|
|
276
|
+
return "warn";
|
|
277
|
+
}
|
|
278
|
+
function shouldLog(threshold, messageLevel) {
|
|
279
|
+
return ARCA_LOG_LEVELS.indexOf(messageLevel) >= ARCA_LOG_LEVELS.indexOf(threshold);
|
|
280
|
+
}
|
|
281
|
+
function isArcaLogLevel(value) {
|
|
282
|
+
return ARCA_LOG_LEVELS.includes(value);
|
|
283
|
+
}
|
|
284
|
+
function defaultArcaLog(level, message, ...args) {
|
|
285
|
+
const method = level === "debug" ? console.debug : level === "info" ? console.info : level === "warn" ? console.warn : console.error;
|
|
286
|
+
method(message, ...args);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// src/services/padron.ts
|
|
290
|
+
function createPadronService(options) {
|
|
291
|
+
return {
|
|
292
|
+
async getTaxpayerDetails(taxId) {
|
|
293
|
+
const raw = await executePadronOperation(
|
|
294
|
+
options,
|
|
295
|
+
"padron-a5",
|
|
296
|
+
"getPersona_v2",
|
|
297
|
+
{
|
|
298
|
+
idPersona: Number.parseInt(String(taxId), 10)
|
|
299
|
+
}
|
|
300
|
+
);
|
|
301
|
+
if (!raw) {
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
const record = raw;
|
|
305
|
+
const datosGenerales = record.datosGenerales;
|
|
306
|
+
return {
|
|
307
|
+
taxId: String(record.idPersona ?? ""),
|
|
308
|
+
...record.tipoPersona === void 0 ? {} : { personType: String(record.tipoPersona) },
|
|
309
|
+
...datosGenerales ? { name: extractPadronName(datosGenerales) } : {},
|
|
310
|
+
raw: record
|
|
311
|
+
};
|
|
312
|
+
},
|
|
313
|
+
async getTaxIdByDocument(documentNumber) {
|
|
314
|
+
const raw = await executePadronOperation(
|
|
315
|
+
options,
|
|
316
|
+
"padron-a13",
|
|
317
|
+
"getIdPersonaListByDocumento",
|
|
318
|
+
{
|
|
319
|
+
documento: String(documentNumber)
|
|
320
|
+
}
|
|
321
|
+
);
|
|
322
|
+
if (!raw) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
const record = raw;
|
|
326
|
+
const idPersona = record.idPersona;
|
|
327
|
+
const taxIds = Array.isArray(idPersona) ? idPersona.map(String) : idPersona === void 0 ? [] : [String(idPersona)];
|
|
328
|
+
return {
|
|
329
|
+
taxIds,
|
|
330
|
+
raw: record
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function extractPadronName(datosGenerales) {
|
|
336
|
+
if (typeof datosGenerales.razonSocial === "string") {
|
|
337
|
+
return datosGenerales.razonSocial;
|
|
338
|
+
}
|
|
339
|
+
const nombre = datosGenerales.nombre;
|
|
340
|
+
const apellido = datosGenerales.apellido;
|
|
341
|
+
if (typeof apellido === "string" && typeof nombre === "string") {
|
|
342
|
+
return `${apellido} ${nombre}`.trim();
|
|
343
|
+
}
|
|
344
|
+
if (typeof apellido === "string") {
|
|
345
|
+
return apellido;
|
|
346
|
+
}
|
|
347
|
+
if (typeof nombre === "string") {
|
|
348
|
+
return nombre;
|
|
349
|
+
}
|
|
350
|
+
return void 0;
|
|
351
|
+
}
|
|
352
|
+
async function executePadronOperation(options, service, operation, body) {
|
|
353
|
+
const auth = await options.auth.login(
|
|
354
|
+
service === "padron-a5" ? "ws_sr_constancia_inscripcion" : "ws_sr_padron_a13"
|
|
355
|
+
);
|
|
356
|
+
try {
|
|
357
|
+
const response = await options.soap.execute({
|
|
358
|
+
service,
|
|
359
|
+
operation,
|
|
360
|
+
bodyElementNamespaceMode: "prefix",
|
|
361
|
+
body: {
|
|
362
|
+
token: auth.token,
|
|
363
|
+
sign: auth.sign,
|
|
364
|
+
cuitRepresentada: Number.parseInt(options.config.taxId, 10),
|
|
365
|
+
...body
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
const operationResponse = response.result;
|
|
369
|
+
if (operation === "getPersona_v2") {
|
|
370
|
+
return operationResponse.personaReturn ?? null;
|
|
371
|
+
}
|
|
372
|
+
if (operation === "getIdPersonaListByDocumento") {
|
|
373
|
+
return operationResponse.idPersonaListReturn ?? null;
|
|
374
|
+
}
|
|
375
|
+
return operationResponse.return ?? null;
|
|
376
|
+
} catch (error) {
|
|
377
|
+
if (error instanceof ArcaSoapFaultError && // Public Padron A5/A13 WSDLs expose only a generic validation fault, so
|
|
378
|
+
// there is no documented not-found-specific fault code to match here.
|
|
379
|
+
// Keep the current message fallback, but treat it as fragile.
|
|
380
|
+
error.message.toLowerCase().includes("no existe")) {
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
383
|
+
throw error;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/services/wsfe.ts
|
|
388
|
+
function createWsfeService(options) {
|
|
389
|
+
async function executeWsfeAuthenticatedOperation(operation, input, body = {}) {
|
|
390
|
+
const auth = await options.auth.login("wsfe", {
|
|
391
|
+
representedTaxId: input.representedTaxId,
|
|
392
|
+
forceRefresh: input.forceAuthRefresh
|
|
393
|
+
});
|
|
394
|
+
const response = await options.soap.execute({
|
|
395
|
+
service: "wsfe",
|
|
396
|
+
operation,
|
|
397
|
+
body: {
|
|
398
|
+
Auth: createWsfeAuth(
|
|
399
|
+
input.representedTaxId ?? options.config.taxId,
|
|
400
|
+
auth.token,
|
|
401
|
+
auth.sign
|
|
402
|
+
),
|
|
403
|
+
...body
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
return unwrapWsfeOperationResult(operation, response.result);
|
|
407
|
+
}
|
|
408
|
+
async function executeWsfeOperation(operation, body = {}) {
|
|
409
|
+
const response = await options.soap.execute({
|
|
410
|
+
service: "wsfe",
|
|
411
|
+
operation,
|
|
412
|
+
body
|
|
413
|
+
});
|
|
414
|
+
return unwrapWsfeOperationResult(operation, response.result);
|
|
415
|
+
}
|
|
416
|
+
async function getNextVoucherNumber({
|
|
417
|
+
representedTaxId,
|
|
418
|
+
salesPoint,
|
|
419
|
+
voucherType,
|
|
420
|
+
forceAuthRefresh
|
|
421
|
+
}) {
|
|
422
|
+
const result = await executeWsfeAuthenticatedOperation(
|
|
423
|
+
"FECompUltimoAutorizado",
|
|
424
|
+
{
|
|
425
|
+
representedTaxId,
|
|
426
|
+
forceAuthRefresh
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
PtoVta: salesPoint,
|
|
430
|
+
CbteTipo: voucherType
|
|
431
|
+
}
|
|
432
|
+
);
|
|
433
|
+
return Number(result.CbteNro ?? 0) + 1;
|
|
434
|
+
}
|
|
435
|
+
async function getWsfeCatalog(operation, resultKey, input) {
|
|
436
|
+
const result = await executeWsfeAuthenticatedOperation(operation, {
|
|
437
|
+
representedTaxId: input.representedTaxId,
|
|
438
|
+
forceAuthRefresh: input.forceAuthRefresh
|
|
439
|
+
});
|
|
440
|
+
return getWsfeResultEntries(result, resultKey).map(mapWsfeCatalogEntry);
|
|
441
|
+
}
|
|
442
|
+
return {
|
|
443
|
+
async createNextVoucher({ representedTaxId, data }) {
|
|
444
|
+
const normalizedInput = normalizeWsfeVoucherInput(data);
|
|
445
|
+
const voucherNumber = await getNextVoucherNumber({
|
|
446
|
+
representedTaxId,
|
|
447
|
+
salesPoint: normalizedInput.salesPoint,
|
|
448
|
+
voucherType: normalizedInput.voucherType
|
|
449
|
+
});
|
|
450
|
+
const requestData = mapWsfeVoucherInput(normalizedInput, voucherNumber);
|
|
451
|
+
const auth = await options.auth.login("wsfe", { representedTaxId });
|
|
452
|
+
const response = await options.soap.execute({
|
|
453
|
+
service: "wsfe",
|
|
454
|
+
operation: "FECAESolicitar",
|
|
455
|
+
body: {
|
|
456
|
+
Auth: createWsfeAuth(
|
|
457
|
+
representedTaxId ?? options.config.taxId,
|
|
458
|
+
auth.token,
|
|
459
|
+
auth.sign
|
|
460
|
+
),
|
|
461
|
+
FeCAEReq: {
|
|
462
|
+
FeCabReq: {
|
|
463
|
+
CantReg: 1,
|
|
464
|
+
PtoVta: normalizedInput.salesPoint,
|
|
465
|
+
CbteTipo: normalizedInput.voucherType
|
|
466
|
+
},
|
|
467
|
+
FeDetReq: {
|
|
468
|
+
FECAEDetRequest: requestData
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
const result = unwrapWsfeOperationResult(
|
|
474
|
+
"FECAESolicitar",
|
|
475
|
+
response.result
|
|
476
|
+
);
|
|
477
|
+
const detailResponse = normalizeWsfeDetailResponse(result);
|
|
478
|
+
const cae = detailResponse.CAE;
|
|
479
|
+
const caeExpiry = detailResponse.CAEFchVto;
|
|
480
|
+
if (typeof cae !== "string" || typeof caeExpiry !== "string") {
|
|
481
|
+
throw new ArcaServiceError(
|
|
482
|
+
"WSFE did not return CAE authorization data",
|
|
483
|
+
{ detail: result }
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
return {
|
|
487
|
+
cae,
|
|
488
|
+
caeExpiry: String(caeExpiry),
|
|
489
|
+
voucherNumber,
|
|
490
|
+
raw: result
|
|
491
|
+
};
|
|
492
|
+
},
|
|
493
|
+
getNextVoucherNumber,
|
|
494
|
+
getLastVoucher(input) {
|
|
495
|
+
return getNextVoucherNumber(input);
|
|
496
|
+
},
|
|
497
|
+
async getSalesPoints({ representedTaxId, forceAuthRefresh }) {
|
|
498
|
+
const result = await executeWsfeAuthenticatedOperation(
|
|
499
|
+
"FEParamGetPtosVenta",
|
|
500
|
+
{
|
|
501
|
+
representedTaxId,
|
|
502
|
+
forceAuthRefresh
|
|
503
|
+
}
|
|
504
|
+
);
|
|
505
|
+
const rawPoints = result.ResultGet?.PtoVenta;
|
|
506
|
+
if (!rawPoints) {
|
|
507
|
+
return [];
|
|
508
|
+
}
|
|
509
|
+
const entries = Array.isArray(rawPoints) ? rawPoints : [rawPoints];
|
|
510
|
+
return entries.map(mapWsfeSalesPoint);
|
|
511
|
+
},
|
|
512
|
+
getVoucherTypes(input) {
|
|
513
|
+
return getWsfeCatalog("FEParamGetTiposCbte", "CbteTipo", input);
|
|
514
|
+
},
|
|
515
|
+
getDocumentTypes(input) {
|
|
516
|
+
return getWsfeCatalog("FEParamGetTiposDoc", "DocTipo", input);
|
|
517
|
+
},
|
|
518
|
+
getConceptTypes(input) {
|
|
519
|
+
return getWsfeCatalog("FEParamGetTiposConcepto", "ConceptoTipo", input);
|
|
520
|
+
},
|
|
521
|
+
async getCurrencyTypes({ representedTaxId, forceAuthRefresh }) {
|
|
522
|
+
const result = await executeWsfeAuthenticatedOperation(
|
|
523
|
+
"FEParamGetTiposMonedas",
|
|
524
|
+
{
|
|
525
|
+
representedTaxId,
|
|
526
|
+
forceAuthRefresh
|
|
527
|
+
}
|
|
528
|
+
);
|
|
529
|
+
return getWsfeResultEntries(result, "Moneda").map(mapWsfeCurrencyType);
|
|
530
|
+
},
|
|
531
|
+
getVatRates(input) {
|
|
532
|
+
return getWsfeCatalog("FEParamGetTiposIva", "IvaTipo", input);
|
|
533
|
+
},
|
|
534
|
+
getTaxTypes(input) {
|
|
535
|
+
return getWsfeCatalog("FEParamGetTiposTributos", "TributoTipo", input);
|
|
536
|
+
},
|
|
537
|
+
getOptionalTypes(input) {
|
|
538
|
+
return getWsfeCatalog("FEParamGetTiposOpcional", "OpcionalTipo", input);
|
|
539
|
+
},
|
|
540
|
+
async getActivities({ representedTaxId, forceAuthRefresh }) {
|
|
541
|
+
const result = await executeWsfeAuthenticatedOperation(
|
|
542
|
+
"FEParamGetActividades",
|
|
543
|
+
{
|
|
544
|
+
representedTaxId,
|
|
545
|
+
forceAuthRefresh
|
|
546
|
+
}
|
|
547
|
+
);
|
|
548
|
+
return getWsfeResultEntries(result, "ActividadesTipo").map(
|
|
549
|
+
mapWsfeActivityType
|
|
550
|
+
);
|
|
551
|
+
},
|
|
552
|
+
async getReceiverVatConditions({
|
|
553
|
+
representedTaxId,
|
|
554
|
+
voucherClass,
|
|
555
|
+
forceAuthRefresh
|
|
556
|
+
}) {
|
|
557
|
+
const result = await executeWsfeAuthenticatedOperation(
|
|
558
|
+
"FEParamGetCondicionIvaReceptor",
|
|
559
|
+
{
|
|
560
|
+
representedTaxId,
|
|
561
|
+
forceAuthRefresh
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
...voucherClass === void 0 ? {} : { ClaseCmp: voucherClass }
|
|
565
|
+
}
|
|
566
|
+
);
|
|
567
|
+
return getWsfeResultEntries(result, "CondicionIvaReceptor").map(
|
|
568
|
+
mapWsfeReceiverVatCondition
|
|
569
|
+
);
|
|
570
|
+
},
|
|
571
|
+
async getServerStatus() {
|
|
572
|
+
const result = await executeWsfeOperation("FEDummy");
|
|
573
|
+
return mapWsfeServerStatus(result);
|
|
574
|
+
},
|
|
575
|
+
async getQuotation({ currencyId, representedTaxId, forceAuthRefresh }) {
|
|
576
|
+
const result = await executeWsfeAuthenticatedOperation(
|
|
577
|
+
"FEParamGetCotizacion",
|
|
578
|
+
{
|
|
579
|
+
representedTaxId,
|
|
580
|
+
forceAuthRefresh
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
MonId: currencyId
|
|
584
|
+
}
|
|
585
|
+
);
|
|
586
|
+
const raw = result.ResultGet ?? {};
|
|
587
|
+
return mapWsfeQuotation(raw);
|
|
588
|
+
},
|
|
589
|
+
async getVoucherInfo({
|
|
590
|
+
representedTaxId,
|
|
591
|
+
number,
|
|
592
|
+
salesPoint,
|
|
593
|
+
voucherType
|
|
594
|
+
}) {
|
|
595
|
+
const result = await executeWsfeAuthenticatedOperation(
|
|
596
|
+
"FECompConsultar",
|
|
597
|
+
{
|
|
598
|
+
representedTaxId
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
FeCompConsReq: {
|
|
602
|
+
CbteNro: number,
|
|
603
|
+
PtoVta: salesPoint,
|
|
604
|
+
CbteTipo: voucherType
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
);
|
|
608
|
+
const raw = result.ResultGet ?? null;
|
|
609
|
+
if (!raw) {
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
return mapWsfeVoucherInfo(raw);
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
function mapWsfeVoucherInput(input, voucherNumber) {
|
|
617
|
+
const data = {
|
|
618
|
+
Concepto: input.concept,
|
|
619
|
+
DocTipo: input.documentType,
|
|
620
|
+
DocNro: input.documentNumber,
|
|
621
|
+
CbteDesde: voucherNumber,
|
|
622
|
+
CbteHasta: voucherNumber,
|
|
623
|
+
CbteFch: input.voucherDate,
|
|
624
|
+
ImpTotal: input.totalAmount,
|
|
625
|
+
ImpTotConc: input.nonTaxableAmount,
|
|
626
|
+
ImpNeto: input.netAmount,
|
|
627
|
+
ImpOpEx: input.exemptAmount,
|
|
628
|
+
ImpTrib: input.taxAmount,
|
|
629
|
+
ImpIVA: input.vatAmount,
|
|
630
|
+
MonId: input.currencyId,
|
|
631
|
+
MonCotiz: input.exchangeRate,
|
|
632
|
+
PtoVta: input.salesPoint,
|
|
633
|
+
CbteTipo: input.voucherType
|
|
634
|
+
};
|
|
635
|
+
if (input.receiverVatConditionId !== void 0) {
|
|
636
|
+
data.CondicionIVAReceptorId = input.receiverVatConditionId;
|
|
637
|
+
}
|
|
638
|
+
if (input.sameCurrencyForeignCancellation !== void 0) {
|
|
639
|
+
data.CanMisMonExt = input.sameCurrencyForeignCancellation;
|
|
640
|
+
}
|
|
641
|
+
if (input.serviceStartDate !== void 0) {
|
|
642
|
+
data.FchServDesde = input.serviceStartDate;
|
|
643
|
+
}
|
|
644
|
+
if (input.serviceEndDate !== void 0) {
|
|
645
|
+
data.FchServHasta = input.serviceEndDate;
|
|
646
|
+
}
|
|
647
|
+
if (input.paymentDueDate !== void 0) {
|
|
648
|
+
data.FchVtoPago = input.paymentDueDate;
|
|
649
|
+
}
|
|
650
|
+
if (input.associatedVouchers) {
|
|
651
|
+
data.CbtesAsoc = {
|
|
652
|
+
CbteAsoc: input.associatedVouchers.map((v) => ({
|
|
653
|
+
Tipo: v.type,
|
|
654
|
+
PtoVta: v.salesPoint,
|
|
655
|
+
Nro: v.number,
|
|
656
|
+
...v.taxId === void 0 ? {} : { Cuit: v.taxId },
|
|
657
|
+
...v.voucherDate === void 0 ? {} : { CbteFch: v.voucherDate }
|
|
658
|
+
}))
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
if (input.associatedPeriod) {
|
|
662
|
+
data.PeriodoAsoc = {
|
|
663
|
+
FchDesde: input.associatedPeriod.startDate,
|
|
664
|
+
FchHasta: input.associatedPeriod.endDate
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
if (input.taxes) {
|
|
668
|
+
data.Tributos = {
|
|
669
|
+
Tributo: input.taxes.map((t) => ({
|
|
670
|
+
Id: t.id,
|
|
671
|
+
...t.description === void 0 ? {} : { Desc: t.description },
|
|
672
|
+
BaseImp: t.baseAmount,
|
|
673
|
+
Alic: t.rate,
|
|
674
|
+
Importe: t.amount
|
|
675
|
+
}))
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
if (input.vatRates) {
|
|
679
|
+
data.Iva = {
|
|
680
|
+
AlicIva: input.vatRates.map((v) => ({
|
|
681
|
+
Id: v.id,
|
|
682
|
+
BaseImp: v.baseAmount,
|
|
683
|
+
Importe: v.amount
|
|
684
|
+
}))
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
if (input.optionalFields) {
|
|
688
|
+
data.Opcionales = {
|
|
689
|
+
Opcional: input.optionalFields.map((o) => ({
|
|
690
|
+
Id: o.id,
|
|
691
|
+
Valor: o.value
|
|
692
|
+
}))
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
if (input.buyers) {
|
|
696
|
+
data.Compradores = {
|
|
697
|
+
Comprador: input.buyers.map((b) => ({
|
|
698
|
+
DocTipo: b.documentType,
|
|
699
|
+
DocNro: b.documentNumber,
|
|
700
|
+
Porcentaje: b.percentage
|
|
701
|
+
}))
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
if (input.activities) {
|
|
705
|
+
data.Actividades = {
|
|
706
|
+
Actividad: input.activities.map((a) => ({
|
|
707
|
+
Id: a.id
|
|
708
|
+
}))
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
return data;
|
|
712
|
+
}
|
|
713
|
+
function normalizeWsfeVoucherInput(input) {
|
|
714
|
+
const {
|
|
715
|
+
voucherDate,
|
|
716
|
+
serviceStartDate,
|
|
717
|
+
serviceEndDate,
|
|
718
|
+
paymentDueDate,
|
|
719
|
+
associatedVouchers,
|
|
720
|
+
associatedPeriod,
|
|
721
|
+
...rest
|
|
722
|
+
} = input;
|
|
723
|
+
return {
|
|
724
|
+
...rest,
|
|
725
|
+
voucherDate: normalizeWsfeDateInput(voucherDate, "voucherDate"),
|
|
726
|
+
...serviceStartDate === void 0 ? {} : {
|
|
727
|
+
serviceStartDate: normalizeWsfeDateInput(
|
|
728
|
+
serviceStartDate,
|
|
729
|
+
"serviceStartDate"
|
|
730
|
+
)
|
|
731
|
+
},
|
|
732
|
+
...serviceEndDate === void 0 ? {} : {
|
|
733
|
+
serviceEndDate: normalizeWsfeDateInput(
|
|
734
|
+
serviceEndDate,
|
|
735
|
+
"serviceEndDate"
|
|
736
|
+
)
|
|
737
|
+
},
|
|
738
|
+
...paymentDueDate === void 0 ? {} : {
|
|
739
|
+
paymentDueDate: normalizeWsfeDateInput(
|
|
740
|
+
paymentDueDate,
|
|
741
|
+
"paymentDueDate"
|
|
742
|
+
)
|
|
743
|
+
},
|
|
744
|
+
...associatedVouchers === void 0 ? {} : {
|
|
745
|
+
associatedVouchers: associatedVouchers.map((voucher, index) => {
|
|
746
|
+
const { voucherDate: associatedVoucherDate, ...associatedRest } = voucher;
|
|
747
|
+
return {
|
|
748
|
+
...associatedRest,
|
|
749
|
+
...associatedVoucherDate === void 0 ? {} : {
|
|
750
|
+
voucherDate: normalizeWsfeDateInput(
|
|
751
|
+
associatedVoucherDate,
|
|
752
|
+
`associatedVouchers[${index}].voucherDate`
|
|
753
|
+
)
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
})
|
|
757
|
+
},
|
|
758
|
+
...associatedPeriod === void 0 ? {} : {
|
|
759
|
+
associatedPeriod: {
|
|
760
|
+
startDate: normalizeWsfeDateInput(
|
|
761
|
+
associatedPeriod.startDate,
|
|
762
|
+
"associatedPeriod.startDate"
|
|
763
|
+
),
|
|
764
|
+
endDate: normalizeWsfeDateInput(
|
|
765
|
+
associatedPeriod.endDate,
|
|
766
|
+
"associatedPeriod.endDate"
|
|
767
|
+
)
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
function normalizeWsfeDateInput(value, fieldName) {
|
|
773
|
+
if (typeof value !== "string") {
|
|
774
|
+
throw new ArcaInputError(
|
|
775
|
+
`Invalid WSFE ${fieldName}: expected a YYYY-MM-DD or YYYYMMDD string`,
|
|
776
|
+
{
|
|
777
|
+
detail: { field: fieldName, value }
|
|
778
|
+
}
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
const normalizedValue = value.trim();
|
|
782
|
+
const afipMatch = normalizedValue.match(/^(\d{4})(\d{2})(\d{2})$/);
|
|
783
|
+
if (afipMatch) {
|
|
784
|
+
const [, year, month, day] = afipMatch;
|
|
785
|
+
assertValidCalendarDate(year, month, day, fieldName, normalizedValue);
|
|
786
|
+
return normalizedValue;
|
|
787
|
+
}
|
|
788
|
+
const isoMatch = normalizedValue.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
789
|
+
if (isoMatch) {
|
|
790
|
+
const [, year, month, day] = isoMatch;
|
|
791
|
+
assertValidCalendarDate(year, month, day, fieldName, normalizedValue);
|
|
792
|
+
return `${year}${month}${day}`;
|
|
793
|
+
}
|
|
794
|
+
throw new ArcaInputError(
|
|
795
|
+
`Invalid WSFE ${fieldName}: expected a YYYY-MM-DD or YYYYMMDD string`,
|
|
796
|
+
{
|
|
797
|
+
detail: { field: fieldName, value: normalizedValue }
|
|
798
|
+
}
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
function assertValidCalendarDate(yearInput, monthInput, dayInput, fieldName, value) {
|
|
802
|
+
const year = Number(yearInput);
|
|
803
|
+
const month = Number(monthInput);
|
|
804
|
+
const day = Number(dayInput);
|
|
805
|
+
const candidate = new Date(Date.UTC(year, month - 1, day));
|
|
806
|
+
if (candidate.getUTCFullYear() !== year || candidate.getUTCMonth() !== month - 1 || candidate.getUTCDate() !== day) {
|
|
807
|
+
throw new ArcaInputError(
|
|
808
|
+
`Invalid WSFE ${fieldName}: received a non-existent calendar date`,
|
|
809
|
+
{
|
|
810
|
+
detail: { field: fieldName, value }
|
|
811
|
+
}
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
function mapWsfeSalesPoint(raw) {
|
|
816
|
+
const record = raw;
|
|
817
|
+
return {
|
|
818
|
+
number: Number(record.Nro ?? 0),
|
|
819
|
+
...record.EmisionTipo === void 0 ? {} : { emissionType: String(record.EmisionTipo) },
|
|
820
|
+
...record.Bloqueado === void 0 ? {} : { blocked: String(record.Bloqueado) },
|
|
821
|
+
...record.FchBaja === void 0 ? {} : { deletedSince: String(record.FchBaja) }
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
function mapWsfeCatalogEntry(raw) {
|
|
825
|
+
const record = raw;
|
|
826
|
+
return {
|
|
827
|
+
id: Number(record.Id ?? 0),
|
|
828
|
+
description: String(record.Desc ?? "")
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
function mapWsfeActivityType(raw) {
|
|
832
|
+
const record = raw;
|
|
833
|
+
return {
|
|
834
|
+
id: Number(record.Id ?? 0),
|
|
835
|
+
description: String(record.Desc ?? ""),
|
|
836
|
+
order: Number(record.Orden ?? 0)
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
function mapWsfeReceiverVatCondition(raw) {
|
|
840
|
+
const record = raw;
|
|
841
|
+
return {
|
|
842
|
+
id: Number(record.Id ?? 0),
|
|
843
|
+
description: String(record.Desc ?? ""),
|
|
844
|
+
voucherClass: String(record.Cmp_Clase ?? "")
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
function mapWsfeCurrencyType(raw) {
|
|
848
|
+
const record = raw;
|
|
849
|
+
return {
|
|
850
|
+
id: String(record.Id ?? ""),
|
|
851
|
+
description: String(record.Desc ?? ""),
|
|
852
|
+
validFrom: String(record.FchDesde ?? ""),
|
|
853
|
+
validTo: String(record.FchHasta ?? "")
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
function mapWsfeServerStatus(raw) {
|
|
857
|
+
return {
|
|
858
|
+
appServer: String(raw.AppServer ?? ""),
|
|
859
|
+
dbServer: String(raw.DbServer ?? ""),
|
|
860
|
+
authServer: String(raw.AuthServer ?? "")
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
function mapWsfeQuotation(raw) {
|
|
864
|
+
return {
|
|
865
|
+
currencyId: String(raw.MonId ?? ""),
|
|
866
|
+
rate: Number(raw.MonCotiz ?? 0),
|
|
867
|
+
date: String(raw.FchCotiz ?? "")
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
function mapWsfeVoucherInfo(raw) {
|
|
871
|
+
return {
|
|
872
|
+
voucherNumber: Number(raw.CbteDesde ?? raw.CbteHasta ?? 0),
|
|
873
|
+
...raw.CbteFch === void 0 ? {} : { voucherDate: String(raw.CbteFch) },
|
|
874
|
+
...raw.PtoVta === void 0 ? {} : { salesPoint: Number(raw.PtoVta) },
|
|
875
|
+
...raw.CbteTipo === void 0 ? {} : { voucherType: Number(raw.CbteTipo) },
|
|
876
|
+
...raw.ImpTotal === void 0 ? {} : { totalAmount: Number(raw.ImpTotal) },
|
|
877
|
+
...raw.Resultado === void 0 ? {} : { result: String(raw.Resultado) },
|
|
878
|
+
...raw.CAE === void 0 ? {} : { cae: String(raw.CAE) },
|
|
879
|
+
...raw.CAEFchVto === void 0 ? {} : { caeExpiry: String(raw.CAEFchVto) },
|
|
880
|
+
raw
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
function createWsfeAuth(representedTaxId, token, sign) {
|
|
884
|
+
return {
|
|
885
|
+
Token: token,
|
|
886
|
+
Sign: sign,
|
|
887
|
+
Cuit: Number.parseInt(String(representedTaxId), 10)
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
function unwrapWsfeOperationResult(operation, response) {
|
|
891
|
+
const operationResponse = response[`${operation}Response`];
|
|
892
|
+
const result = operationResponse?.[`${operation}Result`] ?? response[`${operation}Result`] ?? response;
|
|
893
|
+
if (operation === "FECAESolicitar") {
|
|
894
|
+
const detailResponse = normalizeWsfeDetailResponse(result);
|
|
895
|
+
const resultCode = detailResponse.Resultado;
|
|
896
|
+
if (resultCode && resultCode !== "A") {
|
|
897
|
+
const observationsContainer = detailResponse.Observaciones;
|
|
898
|
+
const observations = normalizeWsfeErrors(observationsContainer?.Obs);
|
|
899
|
+
if (observations.length > 0) {
|
|
900
|
+
const firstObservation = observations[0];
|
|
901
|
+
if (!firstObservation) {
|
|
902
|
+
throw new ArcaServiceError(
|
|
903
|
+
"WSFE returned an empty observation list",
|
|
904
|
+
{
|
|
905
|
+
detail: result
|
|
906
|
+
}
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
throw new ArcaServiceError(firstObservation.message, {
|
|
910
|
+
serviceCode: firstObservation.code,
|
|
911
|
+
detail: result
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
const errorsContainer = result.Errors;
|
|
917
|
+
const errors = normalizeWsfeErrors(errorsContainer?.Err);
|
|
918
|
+
if (errors.length > 0) {
|
|
919
|
+
const firstError = errors[0];
|
|
920
|
+
if (!firstError) {
|
|
921
|
+
throw new ArcaServiceError("WSFE returned an empty error list", {
|
|
922
|
+
detail: result
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
throw new ArcaServiceError(firstError.message, {
|
|
926
|
+
serviceCode: firstError.code,
|
|
927
|
+
detail: result
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
return result;
|
|
931
|
+
}
|
|
932
|
+
function normalizeWsfeDetailResponse(result) {
|
|
933
|
+
const detailResponse = result.FeDetResp;
|
|
934
|
+
const rawDetail = detailResponse?.FECAEDetResponse;
|
|
935
|
+
if (Array.isArray(rawDetail)) {
|
|
936
|
+
return rawDetail[0] ?? {};
|
|
937
|
+
}
|
|
938
|
+
return rawDetail ?? {};
|
|
939
|
+
}
|
|
940
|
+
function normalizeWsfeErrors(rawErrors) {
|
|
941
|
+
const entries = Array.isArray(rawErrors) ? rawErrors : rawErrors ? [rawErrors] : [];
|
|
942
|
+
return entries.map((entry) => entry).map((entry) => {
|
|
943
|
+
const code = entry.Code ?? entry.code ?? "N/A";
|
|
944
|
+
const message = entry.Msg ?? entry.msg ?? "Unknown WSFE error";
|
|
945
|
+
return {
|
|
946
|
+
code: String(code),
|
|
947
|
+
message: `(${String(code)}) ${String(message)}`
|
|
948
|
+
};
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
function getWsfeResultEntries(result, key) {
|
|
952
|
+
const rawEntries = result.ResultGet?.[key];
|
|
953
|
+
if (!rawEntries) {
|
|
954
|
+
return [];
|
|
955
|
+
}
|
|
956
|
+
return (Array.isArray(rawEntries) ? rawEntries : [rawEntries]).map(
|
|
957
|
+
(entry) => entry
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// src/services/wsmtxca.ts
|
|
962
|
+
function createWsmtxcaService(options) {
|
|
963
|
+
return {
|
|
964
|
+
async authorizeVoucher({ representedTaxId, data }) {
|
|
965
|
+
const auth = await options.auth.login("wsmtxca", { representedTaxId });
|
|
966
|
+
const response = await options.soap.execute({
|
|
967
|
+
service: "wsmtxca",
|
|
968
|
+
operation: "autorizarComprobante",
|
|
969
|
+
bodyElementName: "autorizarComprobanteRequest",
|
|
970
|
+
bodyElementNamespaceMode: "prefix",
|
|
971
|
+
body: {
|
|
972
|
+
authRequest: createWsmtxcaAuth(
|
|
973
|
+
representedTaxId ?? options.config.taxId,
|
|
974
|
+
auth.token,
|
|
975
|
+
auth.sign
|
|
976
|
+
),
|
|
977
|
+
...data
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
const raw = unwrapWsmtxcaOperationResponse(
|
|
981
|
+
response.result,
|
|
982
|
+
"autorizarComprobante"
|
|
983
|
+
);
|
|
984
|
+
const authorizationPayload = extractWsmtxcaAuthorizationPayload(raw);
|
|
985
|
+
const messages = extractWsmtxcaMessages(raw);
|
|
986
|
+
const resultado = raw.resultado ?? authorizationPayload.resultado;
|
|
987
|
+
const caeValue = authorizationPayload.CAE ?? authorizationPayload.codigoAutorizacion ?? raw.codigoAutorizacion;
|
|
988
|
+
if (resultado === "R" || caeValue == null) {
|
|
989
|
+
throw new ArcaServiceError(
|
|
990
|
+
messages.join(" | ") || "WSMTXCA rejected the voucher authorization",
|
|
991
|
+
{ detail: raw }
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
return {
|
|
995
|
+
cae: String(caeValue),
|
|
996
|
+
caeExpiry: normalizeWsmtxcaResponseDate(
|
|
997
|
+
authorizationPayload.fechaVencimientoCAE ?? authorizationPayload.fechaVencimiento ?? raw.fechaVencimiento
|
|
998
|
+
),
|
|
999
|
+
voucherNumber: parseWsmtxcaVoucherNumber(
|
|
1000
|
+
authorizationPayload.numeroComprobante ?? raw.numeroComprobante,
|
|
1001
|
+
"WSMTXCA did not return the authorized voucher number",
|
|
1002
|
+
raw
|
|
1003
|
+
),
|
|
1004
|
+
messages,
|
|
1005
|
+
raw
|
|
1006
|
+
};
|
|
1007
|
+
},
|
|
1008
|
+
async getLastAuthorizedVoucher({
|
|
1009
|
+
representedTaxId,
|
|
1010
|
+
voucherType,
|
|
1011
|
+
salesPoint
|
|
1012
|
+
}) {
|
|
1013
|
+
const auth = await options.auth.login("wsmtxca", { representedTaxId });
|
|
1014
|
+
const response = await options.soap.execute({
|
|
1015
|
+
service: "wsmtxca",
|
|
1016
|
+
operation: "consultarUltimoComprobanteAutorizado",
|
|
1017
|
+
bodyElementName: "consultarUltimoComprobanteAutorizadoRequest",
|
|
1018
|
+
bodyElementNamespaceMode: "prefix",
|
|
1019
|
+
body: {
|
|
1020
|
+
authRequest: createWsmtxcaAuth(
|
|
1021
|
+
representedTaxId ?? options.config.taxId,
|
|
1022
|
+
auth.token,
|
|
1023
|
+
auth.sign
|
|
1024
|
+
),
|
|
1025
|
+
consultaUltimoComprobanteAutorizadoRequest: {
|
|
1026
|
+
codigoTipoComprobante: voucherType,
|
|
1027
|
+
numeroPuntoVenta: salesPoint
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
const raw = unwrapWsmtxcaOperationResponse(
|
|
1032
|
+
response.result,
|
|
1033
|
+
"consultarUltimoComprobanteAutorizado"
|
|
1034
|
+
);
|
|
1035
|
+
return {
|
|
1036
|
+
voucherNumber: parseWsmtxcaVoucherNumber(
|
|
1037
|
+
raw.numeroComprobante ?? raw.cbteNro ?? raw.nroComprobante,
|
|
1038
|
+
extractWsmtxcaMessages(raw).join(" | ") || "WSMTXCA did not return the last authorized voucher number",
|
|
1039
|
+
raw
|
|
1040
|
+
),
|
|
1041
|
+
raw
|
|
1042
|
+
};
|
|
1043
|
+
},
|
|
1044
|
+
async getVoucher({
|
|
1045
|
+
representedTaxId,
|
|
1046
|
+
voucherType,
|
|
1047
|
+
salesPoint,
|
|
1048
|
+
voucherNumber
|
|
1049
|
+
}) {
|
|
1050
|
+
const auth = await options.auth.login("wsmtxca", { representedTaxId });
|
|
1051
|
+
const response = await options.soap.execute({
|
|
1052
|
+
service: "wsmtxca",
|
|
1053
|
+
operation: "consultarComprobante",
|
|
1054
|
+
bodyElementName: "consultarComprobanteRequest",
|
|
1055
|
+
bodyElementNamespaceMode: "prefix",
|
|
1056
|
+
body: {
|
|
1057
|
+
authRequest: createWsmtxcaAuth(
|
|
1058
|
+
representedTaxId ?? options.config.taxId,
|
|
1059
|
+
auth.token,
|
|
1060
|
+
auth.sign
|
|
1061
|
+
),
|
|
1062
|
+
consultaComprobanteRequest: {
|
|
1063
|
+
codigoTipoComprobante: voucherType,
|
|
1064
|
+
numeroPuntoVenta: salesPoint,
|
|
1065
|
+
numeroComprobante: voucherNumber
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1069
|
+
const raw = unwrapWsmtxcaOperationResponse(
|
|
1070
|
+
response.result,
|
|
1071
|
+
"consultarComprobante"
|
|
1072
|
+
);
|
|
1073
|
+
const voucher = extractWsmtxcaVoucherPayload(raw);
|
|
1074
|
+
const messages = extractWsmtxcaMessages(raw);
|
|
1075
|
+
const invoiceDate = normalizeWsmtxcaResponseDate(
|
|
1076
|
+
voucher.fechaEmision ?? voucher.fecha ?? voucher.CbteFch
|
|
1077
|
+
);
|
|
1078
|
+
if (!invoiceDate) {
|
|
1079
|
+
throw new ArcaServiceError(
|
|
1080
|
+
messages[0] ?? "WSMTXCA did not return the voucher issue date",
|
|
1081
|
+
{ detail: raw }
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
return {
|
|
1085
|
+
invoiceDate,
|
|
1086
|
+
voucher,
|
|
1087
|
+
messages,
|
|
1088
|
+
raw
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
function createWsmtxcaAuth(representedTaxId, token, sign) {
|
|
1094
|
+
return {
|
|
1095
|
+
token,
|
|
1096
|
+
sign,
|
|
1097
|
+
cuitRepresentada: Number.parseInt(String(representedTaxId), 10)
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
function toRecord(value) {
|
|
1101
|
+
return value && typeof value === "object" ? value : void 0;
|
|
1102
|
+
}
|
|
1103
|
+
function unwrapWsmtxcaOperationResponse(response, operation) {
|
|
1104
|
+
const responseRecord = toRecord(response) ?? {};
|
|
1105
|
+
if (operation === "autorizarComprobante") {
|
|
1106
|
+
return toRecord(responseRecord.autorizarComprobanteResponse) ?? toRecord(responseRecord.autorizarComprobanteResult) ?? toRecord(responseRecord.comprobanteCAEResponse) ?? toRecord(responseRecord.comprobanteCAEReponse) ?? responseRecord;
|
|
1107
|
+
}
|
|
1108
|
+
if (operation === "consultarComprobante") {
|
|
1109
|
+
return toRecord(responseRecord.consultarComprobanteResponse) ?? toRecord(responseRecord.consultaComprobanteResponse) ?? toRecord(responseRecord.consultarComprobanteResult) ?? responseRecord;
|
|
1110
|
+
}
|
|
1111
|
+
return toRecord(responseRecord.consultarUltimoComprobanteAutorizadoResponse) ?? toRecord(responseRecord.consultaUltimoComprobanteAutorizadoResponse) ?? toRecord(responseRecord.consultarUltimoComprobanteAutorizadoResult) ?? responseRecord;
|
|
1112
|
+
}
|
|
1113
|
+
function extractWsmtxcaAuthorizationPayload(raw) {
|
|
1114
|
+
return toRecord(raw.comprobanteResponse) ?? toRecord(raw.comprobanteCAEResponse) ?? toRecord(raw.comprobanteCAEReponse) ?? raw;
|
|
1115
|
+
}
|
|
1116
|
+
function extractWsmtxcaVoucherPayload(raw) {
|
|
1117
|
+
return toRecord(raw.comprobanteResponse) ?? toRecord(raw.comprobante) ?? toRecord(raw.cmp) ?? raw;
|
|
1118
|
+
}
|
|
1119
|
+
function extractWsmtxcaMessages(raw) {
|
|
1120
|
+
const rawErrors = raw.arrayErrores;
|
|
1121
|
+
const rawObservations = raw.arrayObservaciones;
|
|
1122
|
+
const toEntries = (value) => {
|
|
1123
|
+
if (!value) {
|
|
1124
|
+
return [];
|
|
1125
|
+
}
|
|
1126
|
+
if (Array.isArray(value)) {
|
|
1127
|
+
return value;
|
|
1128
|
+
}
|
|
1129
|
+
if (typeof value === "object") {
|
|
1130
|
+
return [value];
|
|
1131
|
+
}
|
|
1132
|
+
return [];
|
|
1133
|
+
};
|
|
1134
|
+
const errors = toEntries(rawErrors?.codigoDescripcion).map((entry) => {
|
|
1135
|
+
const code = entry.codigo == null ? "N/A" : String(entry.codigo);
|
|
1136
|
+
const description = entry.descripcion == null ? "Unknown WSMTXCA error" : String(entry.descripcion);
|
|
1137
|
+
return `Error ${code}: ${description}`;
|
|
1138
|
+
});
|
|
1139
|
+
const observations = toEntries(rawObservations?.codigoDescripcion).map(
|
|
1140
|
+
(entry) => {
|
|
1141
|
+
const code = entry.codigo == null ? "N/A" : String(entry.codigo);
|
|
1142
|
+
const description = entry.descripcion == null ? "" : String(entry.descripcion);
|
|
1143
|
+
return `Obs ${code}: ${description}`.trim();
|
|
1144
|
+
}
|
|
1145
|
+
);
|
|
1146
|
+
return [...errors, ...observations];
|
|
1147
|
+
}
|
|
1148
|
+
function parseWsmtxcaVoucherNumber(value, message, detail) {
|
|
1149
|
+
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
1150
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
1151
|
+
throw new ArcaServiceError(message, { detail });
|
|
1152
|
+
}
|
|
1153
|
+
return parsed;
|
|
1154
|
+
}
|
|
1155
|
+
function normalizeWsmtxcaResponseDate(value) {
|
|
1156
|
+
if (typeof value === "number" && Number.isInteger(value)) {
|
|
1157
|
+
return formatCompactDateToIso(value);
|
|
1158
|
+
}
|
|
1159
|
+
if (typeof value !== "string") {
|
|
1160
|
+
return void 0;
|
|
1161
|
+
}
|
|
1162
|
+
const trimmed = value.trim();
|
|
1163
|
+
if (!trimmed) {
|
|
1164
|
+
return void 0;
|
|
1165
|
+
}
|
|
1166
|
+
if (/^\d{8}$/.test(trimmed)) {
|
|
1167
|
+
return formatCompactDateToIso(Number.parseInt(trimmed, 10));
|
|
1168
|
+
}
|
|
1169
|
+
if (/^\d{4}-\d{2}-\d{2}/.test(trimmed)) {
|
|
1170
|
+
return trimmed.slice(0, 10);
|
|
1171
|
+
}
|
|
1172
|
+
return void 0;
|
|
1173
|
+
}
|
|
1174
|
+
function formatCompactDateToIso(dateValue) {
|
|
1175
|
+
if (!dateValue) {
|
|
1176
|
+
return void 0;
|
|
1177
|
+
}
|
|
1178
|
+
const raw = String(dateValue);
|
|
1179
|
+
if (raw.length !== 8) {
|
|
1180
|
+
return void 0;
|
|
1181
|
+
}
|
|
1182
|
+
return `${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}`;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
// src/internal/http.ts
|
|
1186
|
+
import https from "https";
|
|
1187
|
+
var defaultAgent = new https.Agent({
|
|
1188
|
+
keepAlive: true
|
|
1189
|
+
});
|
|
1190
|
+
var legacyTlsAgent = new https.Agent({
|
|
1191
|
+
keepAlive: true,
|
|
1192
|
+
ciphers: "DEFAULT@SECLEVEL=0"
|
|
1193
|
+
});
|
|
1194
|
+
async function postXml({
|
|
1195
|
+
url,
|
|
1196
|
+
body,
|
|
1197
|
+
contentType,
|
|
1198
|
+
soapAction,
|
|
1199
|
+
useLegacyTlsSecurityLevel0 = false,
|
|
1200
|
+
timeout = 3e4,
|
|
1201
|
+
retries = 0,
|
|
1202
|
+
retryDelay = 500,
|
|
1203
|
+
logger,
|
|
1204
|
+
service,
|
|
1205
|
+
operation
|
|
1206
|
+
}) {
|
|
1207
|
+
const totalAttempts = retries + 1;
|
|
1208
|
+
for (let attempt = 1; attempt <= totalAttempts; attempt += 1) {
|
|
1209
|
+
try {
|
|
1210
|
+
return await postXmlOnce({
|
|
1211
|
+
url,
|
|
1212
|
+
body,
|
|
1213
|
+
contentType,
|
|
1214
|
+
soapAction,
|
|
1215
|
+
useLegacyTlsSecurityLevel0,
|
|
1216
|
+
timeout
|
|
1217
|
+
});
|
|
1218
|
+
} catch (error) {
|
|
1219
|
+
if (!(error instanceof ArcaTransportError)) {
|
|
1220
|
+
throw error;
|
|
1221
|
+
}
|
|
1222
|
+
if (attempt >= totalAttempts) {
|
|
1223
|
+
logger?.error("ARCA transport request failed", {
|
|
1224
|
+
service,
|
|
1225
|
+
operation,
|
|
1226
|
+
url,
|
|
1227
|
+
attempt,
|
|
1228
|
+
attempts: totalAttempts,
|
|
1229
|
+
error
|
|
1230
|
+
});
|
|
1231
|
+
throw error;
|
|
1232
|
+
}
|
|
1233
|
+
const nextAttempt = attempt + 1;
|
|
1234
|
+
logger?.warn(
|
|
1235
|
+
`Retrying ARCA request after transport failure (attempt ${nextAttempt}/${totalAttempts})`,
|
|
1236
|
+
{
|
|
1237
|
+
service,
|
|
1238
|
+
operation,
|
|
1239
|
+
url,
|
|
1240
|
+
attempt: nextAttempt,
|
|
1241
|
+
attempts: totalAttempts,
|
|
1242
|
+
error
|
|
1243
|
+
}
|
|
1244
|
+
);
|
|
1245
|
+
await delay(retryDelay);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
throw new ArcaTransportError("ARCA HTTP request exhausted retries");
|
|
1249
|
+
}
|
|
1250
|
+
async function postXmlOnce({
|
|
1251
|
+
url,
|
|
1252
|
+
body,
|
|
1253
|
+
contentType,
|
|
1254
|
+
soapAction,
|
|
1255
|
+
useLegacyTlsSecurityLevel0,
|
|
1256
|
+
timeout
|
|
1257
|
+
}) {
|
|
1258
|
+
const endpoint = new URL(url);
|
|
1259
|
+
const requestBody = Buffer.from(body, "utf8");
|
|
1260
|
+
return await new Promise((resolve, reject) => {
|
|
1261
|
+
let settled = false;
|
|
1262
|
+
const settleResolve = (responseBody) => {
|
|
1263
|
+
if (settled) {
|
|
1264
|
+
return;
|
|
1265
|
+
}
|
|
1266
|
+
settled = true;
|
|
1267
|
+
resolve(responseBody);
|
|
1268
|
+
};
|
|
1269
|
+
const settleReject = (error) => {
|
|
1270
|
+
if (settled) {
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
settled = true;
|
|
1274
|
+
reject(error);
|
|
1275
|
+
};
|
|
1276
|
+
const request = https.request(
|
|
1277
|
+
{
|
|
1278
|
+
protocol: endpoint.protocol,
|
|
1279
|
+
hostname: endpoint.hostname,
|
|
1280
|
+
port: endpoint.port || void 0,
|
|
1281
|
+
path: `${endpoint.pathname}${endpoint.search}`,
|
|
1282
|
+
method: "POST",
|
|
1283
|
+
agent: useLegacyTlsSecurityLevel0 ? legacyTlsAgent : defaultAgent,
|
|
1284
|
+
headers: {
|
|
1285
|
+
Accept: "text/xml, application/soap+xml",
|
|
1286
|
+
"Content-Length": requestBody.byteLength,
|
|
1287
|
+
"Content-Type": contentType,
|
|
1288
|
+
...soapAction === void 0 ? {} : { SOAPAction: `"${soapAction}"` }
|
|
1289
|
+
}
|
|
1290
|
+
},
|
|
1291
|
+
(response) => {
|
|
1292
|
+
const chunks = [];
|
|
1293
|
+
const getResponseBody = () => Buffer.concat(chunks).toString("utf8");
|
|
1294
|
+
response.on("data", (chunk) => {
|
|
1295
|
+
chunks.push(
|
|
1296
|
+
typeof chunk === "string" ? Buffer.from(chunk, "utf8") : chunk
|
|
1297
|
+
);
|
|
1298
|
+
});
|
|
1299
|
+
response.on("error", (error) => {
|
|
1300
|
+
settleReject(
|
|
1301
|
+
new ArcaTransportError(
|
|
1302
|
+
`ARCA HTTP response stream failed: ${error.message}`,
|
|
1303
|
+
{
|
|
1304
|
+
cause: error,
|
|
1305
|
+
statusCode: response.statusCode,
|
|
1306
|
+
responseBody: getResponseBody()
|
|
1307
|
+
}
|
|
1308
|
+
)
|
|
1309
|
+
);
|
|
1310
|
+
});
|
|
1311
|
+
response.on("aborted", () => {
|
|
1312
|
+
settleReject(
|
|
1313
|
+
new ArcaTransportError("ARCA HTTP response was aborted", {
|
|
1314
|
+
statusCode: response.statusCode,
|
|
1315
|
+
responseBody: getResponseBody()
|
|
1316
|
+
})
|
|
1317
|
+
);
|
|
1318
|
+
});
|
|
1319
|
+
response.on("end", () => {
|
|
1320
|
+
const responseBody = getResponseBody();
|
|
1321
|
+
const statusCode = response.statusCode ?? 500;
|
|
1322
|
+
const responseContentType = Array.isArray(
|
|
1323
|
+
response.headers["content-type"]
|
|
1324
|
+
) ? response.headers["content-type"].join("; ") : response.headers["content-type"];
|
|
1325
|
+
if (statusCode >= 200 && statusCode < 300) {
|
|
1326
|
+
settleResolve(responseBody);
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
if (isXmlLikeResponse(responseBody, responseContentType)) {
|
|
1330
|
+
settleResolve(responseBody);
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
settleReject(
|
|
1334
|
+
new ArcaTransportError(
|
|
1335
|
+
`ARCA HTTP request failed with status ${statusCode}`,
|
|
1336
|
+
{
|
|
1337
|
+
statusCode,
|
|
1338
|
+
responseBody
|
|
1339
|
+
}
|
|
1340
|
+
)
|
|
1341
|
+
);
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
);
|
|
1345
|
+
request.setTimeout(timeout, () => {
|
|
1346
|
+
request.destroy(
|
|
1347
|
+
new Error(`ARCA HTTP request timed out after ${timeout}ms`)
|
|
1348
|
+
);
|
|
1349
|
+
});
|
|
1350
|
+
request.on("error", (error) => {
|
|
1351
|
+
settleReject(
|
|
1352
|
+
new ArcaTransportError(`ARCA HTTP request failed: ${error.message}`, {
|
|
1353
|
+
cause: error
|
|
1354
|
+
})
|
|
1355
|
+
);
|
|
1356
|
+
});
|
|
1357
|
+
request.write(requestBody);
|
|
1358
|
+
request.end();
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
function isXmlLikeResponse(body, contentType) {
|
|
1362
|
+
const normalizedContentType = contentType?.toLowerCase() ?? "";
|
|
1363
|
+
if (normalizedContentType.includes("xml") || normalizedContentType.includes("soap")) {
|
|
1364
|
+
return true;
|
|
1365
|
+
}
|
|
1366
|
+
return body.trimStart().startsWith("<");
|
|
1367
|
+
}
|
|
1368
|
+
function delay(ms) {
|
|
1369
|
+
return new Promise((resolve) => {
|
|
1370
|
+
setTimeout(resolve, ms);
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
// src/internal/xml.ts
|
|
1375
|
+
import { XMLBuilder, XMLParser } from "fast-xml-parser";
|
|
1376
|
+
var xmlBuilder = new XMLBuilder({
|
|
1377
|
+
attributeNamePrefix: "@_",
|
|
1378
|
+
format: false,
|
|
1379
|
+
ignoreAttributes: false,
|
|
1380
|
+
suppressBooleanAttributes: false,
|
|
1381
|
+
suppressEmptyNode: true
|
|
1382
|
+
});
|
|
1383
|
+
var xmlParser = new XMLParser({
|
|
1384
|
+
attributeNamePrefix: "@_",
|
|
1385
|
+
ignoreAttributes: false,
|
|
1386
|
+
parseAttributeValue: false,
|
|
1387
|
+
parseTagValue: false,
|
|
1388
|
+
removeNSPrefix: true,
|
|
1389
|
+
trimValues: true
|
|
1390
|
+
});
|
|
1391
|
+
function buildSoapEnvelope(soapVersion, operation, namespace, body, options) {
|
|
1392
|
+
const prefix = soapVersion === "1.2" ? "soap12" : "soap";
|
|
1393
|
+
const envelopeNamespace = soapVersion === "1.2" ? "http://www.w3.org/2003/05/soap-envelope" : "http://schemas.xmlsoap.org/soap/envelope/";
|
|
1394
|
+
const namespaceMode = options?.namespaceMode ?? "default";
|
|
1395
|
+
const operationElementName = namespaceMode === "prefix" ? `tns:${operation}` : operation;
|
|
1396
|
+
const operationNamespaceAttributes = namespaceMode === "prefix" ? { "@_xmlns:tns": namespace } : { "@_xmlns": namespace };
|
|
1397
|
+
const payload = {
|
|
1398
|
+
[`${prefix}:Envelope`]: {
|
|
1399
|
+
"@_xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
|
|
1400
|
+
"@_xmlns:xsd": "http://www.w3.org/2001/XMLSchema",
|
|
1401
|
+
[`@_xmlns:${prefix}`]: envelopeNamespace,
|
|
1402
|
+
[`${prefix}:Body`]: {
|
|
1403
|
+
[operationElementName]: {
|
|
1404
|
+
...operationNamespaceAttributes,
|
|
1405
|
+
...pruneUndefinedDeep(body)
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
return `<?xml version="1.0" encoding="utf-8"?>${xmlBuilder.build(payload)}`;
|
|
1411
|
+
}
|
|
1412
|
+
function parseSoapBody(xml) {
|
|
1413
|
+
const parsed = xmlParser.parse(xml);
|
|
1414
|
+
const envelope = parsed.Envelope;
|
|
1415
|
+
const body = envelope?.Body;
|
|
1416
|
+
if (!body) {
|
|
1417
|
+
throw new ArcaSoapFaultError("Invalid SOAP response: missing body", {
|
|
1418
|
+
detail: parsed
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
const fault = body.Fault;
|
|
1422
|
+
if (fault) {
|
|
1423
|
+
throw createSoapFaultError(fault);
|
|
1424
|
+
}
|
|
1425
|
+
return body;
|
|
1426
|
+
}
|
|
1427
|
+
function getSingleBodyEntry(body) {
|
|
1428
|
+
const entries = Object.entries(body).filter(([key]) => key !== "@_xmlns");
|
|
1429
|
+
if (entries.length !== 1) {
|
|
1430
|
+
throw new ArcaSoapFaultError(
|
|
1431
|
+
`Invalid SOAP response: expected a single body entry, got ${entries.length}`,
|
|
1432
|
+
{
|
|
1433
|
+
detail: body
|
|
1434
|
+
}
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
return entries[0];
|
|
1438
|
+
}
|
|
1439
|
+
function parseXmlDocument(xml) {
|
|
1440
|
+
return xmlParser.parse(xml);
|
|
1441
|
+
}
|
|
1442
|
+
function pruneUndefinedDeep(value) {
|
|
1443
|
+
if (Array.isArray(value)) {
|
|
1444
|
+
return value.map((item) => pruneUndefinedDeep(item)).filter((item) => item !== void 0);
|
|
1445
|
+
}
|
|
1446
|
+
if (value && typeof value === "object") {
|
|
1447
|
+
const entries = Object.entries(value).filter(([, nestedValue]) => nestedValue !== void 0).map(([key, nestedValue]) => [key, pruneUndefinedDeep(nestedValue)]);
|
|
1448
|
+
return Object.fromEntries(entries);
|
|
1449
|
+
}
|
|
1450
|
+
return value;
|
|
1451
|
+
}
|
|
1452
|
+
function createSoapFaultError(fault) {
|
|
1453
|
+
const faultCode = typeof fault.faultcode === "string" ? fault.faultcode : getNestedString(fault, ["Code", "Value"]);
|
|
1454
|
+
const message = typeof fault.faultstring === "string" ? fault.faultstring : getNestedString(fault, ["Reason", "Text"]) ?? "ARCA SOAP fault response";
|
|
1455
|
+
return new ArcaSoapFaultError(message, {
|
|
1456
|
+
faultCode: faultCode ?? void 0,
|
|
1457
|
+
detail: fault
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
function getNestedString(value, path) {
|
|
1461
|
+
let current = value;
|
|
1462
|
+
for (const key of path) {
|
|
1463
|
+
if (!current || typeof current !== "object") {
|
|
1464
|
+
return null;
|
|
1465
|
+
}
|
|
1466
|
+
current = current[key];
|
|
1467
|
+
}
|
|
1468
|
+
return typeof current === "string" ? current : null;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// src/soap/index.ts
|
|
1472
|
+
function createSoapTransport(options) {
|
|
1473
|
+
return {
|
|
1474
|
+
async execute(request) {
|
|
1475
|
+
const serviceConfig = getArcaServiceConfig(request.service);
|
|
1476
|
+
const url = serviceConfig.endpoint[options.config.environment];
|
|
1477
|
+
const soapActionOperation = request.operation;
|
|
1478
|
+
const bodyElementName = request.bodyElementName ?? request.operation;
|
|
1479
|
+
const soapAction = serviceConfig.usesEmptySoapAction ? "" : `${serviceConfig.soapActionBase}${soapActionOperation}`;
|
|
1480
|
+
const contentType = serviceConfig.soapVersion === "1.2" ? `application/soap+xml; charset=utf-8; action="${soapAction}"` : 'text/xml; charset="utf-8"';
|
|
1481
|
+
const xml = buildSoapEnvelope(
|
|
1482
|
+
serviceConfig.soapVersion,
|
|
1483
|
+
bodyElementName,
|
|
1484
|
+
serviceConfig.namespace,
|
|
1485
|
+
request.body,
|
|
1486
|
+
{
|
|
1487
|
+
namespaceMode: request.bodyElementNamespaceMode
|
|
1488
|
+
}
|
|
1489
|
+
);
|
|
1490
|
+
const startedAt = Date.now();
|
|
1491
|
+
options.logger?.debug("Sending ARCA SOAP request", {
|
|
1492
|
+
service: request.service,
|
|
1493
|
+
operation: request.operation,
|
|
1494
|
+
url
|
|
1495
|
+
});
|
|
1496
|
+
try {
|
|
1497
|
+
const responseXml = await postXml({
|
|
1498
|
+
url,
|
|
1499
|
+
body: xml,
|
|
1500
|
+
contentType,
|
|
1501
|
+
soapAction: serviceConfig.soapVersion === "1.1" ? soapAction : void 0,
|
|
1502
|
+
useLegacyTlsSecurityLevel0: options.config.environment === "production" && serviceConfig.useLegacyTlsSecurityLevel0 === true,
|
|
1503
|
+
timeout: options.config.timeout,
|
|
1504
|
+
retries: options.config.retries,
|
|
1505
|
+
retryDelay: options.config.retryDelay,
|
|
1506
|
+
logger: options.logger,
|
|
1507
|
+
service: request.service,
|
|
1508
|
+
operation: request.operation
|
|
1509
|
+
});
|
|
1510
|
+
options.logger?.debug("Received ARCA SOAP response", {
|
|
1511
|
+
service: request.service,
|
|
1512
|
+
operation: request.operation,
|
|
1513
|
+
durationMs: Date.now() - startedAt
|
|
1514
|
+
});
|
|
1515
|
+
const soapBody = parseSoapBody(responseXml);
|
|
1516
|
+
const [, result] = getSingleBodyEntry(soapBody);
|
|
1517
|
+
return {
|
|
1518
|
+
service: request.service,
|
|
1519
|
+
operation: request.operation,
|
|
1520
|
+
raw: responseXml,
|
|
1521
|
+
result
|
|
1522
|
+
};
|
|
1523
|
+
} catch (error) {
|
|
1524
|
+
if (error instanceof ArcaSoapFaultError) {
|
|
1525
|
+
options.logger?.error("ARCA SOAP fault response", {
|
|
1526
|
+
service: request.service,
|
|
1527
|
+
operation: request.operation,
|
|
1528
|
+
url,
|
|
1529
|
+
faultCode: error.faultCode,
|
|
1530
|
+
error
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
throw error;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
// src/wsaa/index.ts
|
|
1540
|
+
import { createHash } from "crypto";
|
|
1541
|
+
import forge from "node-forge";
|
|
1542
|
+
function createWsaaAuthModule(options) {
|
|
1543
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1544
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
1545
|
+
return {
|
|
1546
|
+
async login(service, authOptions = {}) {
|
|
1547
|
+
const cacheKey = buildWsaaCacheKey(options.config, service);
|
|
1548
|
+
const running = inFlight.get(cacheKey);
|
|
1549
|
+
if (running) {
|
|
1550
|
+
return running;
|
|
1551
|
+
}
|
|
1552
|
+
const loginPromise = (async () => {
|
|
1553
|
+
if (!authOptions.forceRefresh) {
|
|
1554
|
+
const cached = getCachedCredentials(cache, cacheKey);
|
|
1555
|
+
if (cached) {
|
|
1556
|
+
options.logger?.debug("Attempting WSAA login", {
|
|
1557
|
+
service,
|
|
1558
|
+
source: "cached"
|
|
1559
|
+
});
|
|
1560
|
+
return cached;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
options.logger?.debug("Attempting WSAA login", {
|
|
1564
|
+
service,
|
|
1565
|
+
source: "fresh"
|
|
1566
|
+
});
|
|
1567
|
+
const credentials = await requestCredentials(options.config, service, {
|
|
1568
|
+
logger: options.logger
|
|
1569
|
+
});
|
|
1570
|
+
options.logger?.info("WSAA login succeeded", {
|
|
1571
|
+
service,
|
|
1572
|
+
expiresAt: credentials.expiresAt
|
|
1573
|
+
});
|
|
1574
|
+
cache.set(cacheKey, credentials);
|
|
1575
|
+
return credentials;
|
|
1576
|
+
})();
|
|
1577
|
+
inFlight.set(cacheKey, loginPromise);
|
|
1578
|
+
try {
|
|
1579
|
+
return await loginPromise;
|
|
1580
|
+
} catch (error) {
|
|
1581
|
+
if (!authOptions.forceRefresh && error instanceof ArcaSoapFaultError && error.faultCode === "ns1:coe.alreadyAuthenticated") {
|
|
1582
|
+
const cached = getCachedCredentials(cache, cacheKey);
|
|
1583
|
+
if (cached) {
|
|
1584
|
+
options.logger?.warn(
|
|
1585
|
+
"Recovered WSAA coe.alreadyAuthenticated fault",
|
|
1586
|
+
{
|
|
1587
|
+
service,
|
|
1588
|
+
faultCode: error.faultCode
|
|
1589
|
+
}
|
|
1590
|
+
);
|
|
1591
|
+
return cached;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
if (error instanceof ArcaSoapFaultError) {
|
|
1595
|
+
options.logger?.error("WSAA SOAP fault response", {
|
|
1596
|
+
service,
|
|
1597
|
+
operation: "loginCms",
|
|
1598
|
+
url: ARCA_WSAA_CONFIG.endpoint[options.config.environment],
|
|
1599
|
+
faultCode: error.faultCode,
|
|
1600
|
+
error
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
throw error;
|
|
1604
|
+
} finally {
|
|
1605
|
+
inFlight.delete(cacheKey);
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
async function requestCredentials(config, service, options) {
|
|
1611
|
+
const loginTicketRequestXml = buildLoginTicketRequest(service);
|
|
1612
|
+
const signedCms = signLoginTicketRequest(loginTicketRequestXml, {
|
|
1613
|
+
certificatePem: config.certificatePem,
|
|
1614
|
+
privateKeyPem: config.privateKeyPem
|
|
1615
|
+
});
|
|
1616
|
+
const requestXml = buildSoapEnvelope(
|
|
1617
|
+
ARCA_WSAA_CONFIG.soapVersion,
|
|
1618
|
+
"loginCms",
|
|
1619
|
+
ARCA_WSAA_CONFIG.namespace,
|
|
1620
|
+
{ in0: signedCms }
|
|
1621
|
+
);
|
|
1622
|
+
const responseXml = await postXml({
|
|
1623
|
+
url: ARCA_WSAA_CONFIG.endpoint[config.environment],
|
|
1624
|
+
body: requestXml,
|
|
1625
|
+
contentType: 'text/xml; charset="utf-8"',
|
|
1626
|
+
soapAction: ARCA_WSAA_CONFIG.soapActionBase,
|
|
1627
|
+
timeout: config.timeout,
|
|
1628
|
+
retries: config.retries,
|
|
1629
|
+
retryDelay: config.retryDelay,
|
|
1630
|
+
logger: options?.logger,
|
|
1631
|
+
service: "wsaa",
|
|
1632
|
+
operation: "loginCms"
|
|
1633
|
+
});
|
|
1634
|
+
const soapBody = parseSoapBody(responseXml);
|
|
1635
|
+
const [, response] = getSingleBodyEntry(soapBody);
|
|
1636
|
+
const loginCmsReturn = response.loginCmsReturn;
|
|
1637
|
+
if (typeof loginCmsReturn !== "string" || loginCmsReturn.trim().length < 1) {
|
|
1638
|
+
throw new ArcaTransportError(
|
|
1639
|
+
"WSAA response did not include loginCmsReturn XML"
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1642
|
+
return parseLoginTicketResponse(loginCmsReturn);
|
|
1643
|
+
}
|
|
1644
|
+
function buildWsaaCacheKey(config, service) {
|
|
1645
|
+
return [config.environment, service, getCertificateFingerprint(config)].join(
|
|
1646
|
+
":"
|
|
1647
|
+
);
|
|
1648
|
+
}
|
|
1649
|
+
function getCertificateFingerprint(config) {
|
|
1650
|
+
return createHash("sha256").update(config.certificatePem).digest("hex");
|
|
1651
|
+
}
|
|
1652
|
+
function isCredentialValid(credentials) {
|
|
1653
|
+
return new Date(credentials.expiresAt).getTime() - Date.now() > 6e4;
|
|
1654
|
+
}
|
|
1655
|
+
function getCachedCredentials(cache, cacheKey) {
|
|
1656
|
+
const localCached = cache.get(cacheKey);
|
|
1657
|
+
if (localCached && isCredentialValid(localCached)) {
|
|
1658
|
+
return localCached;
|
|
1659
|
+
}
|
|
1660
|
+
return null;
|
|
1661
|
+
}
|
|
1662
|
+
function buildLoginTicketRequest(service) {
|
|
1663
|
+
const uniqueId = Math.floor(Date.now() / 1e3);
|
|
1664
|
+
const generationTime = new Date(Date.now() - 5 * 6e4).toISOString().replace(".000Z", "Z");
|
|
1665
|
+
const expirationTime = new Date(Date.now() + 5 * 6e4).toISOString().replace(".000Z", "Z");
|
|
1666
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1667
|
+
<loginTicketRequest version="1.0">
|
|
1668
|
+
<header>
|
|
1669
|
+
<uniqueId>${uniqueId}</uniqueId>
|
|
1670
|
+
<generationTime>${generationTime}</generationTime>
|
|
1671
|
+
<expirationTime>${expirationTime}</expirationTime>
|
|
1672
|
+
</header>
|
|
1673
|
+
<service>${service}</service>
|
|
1674
|
+
</loginTicketRequest>`;
|
|
1675
|
+
}
|
|
1676
|
+
function signLoginTicketRequest(loginTicketRequestXml, options) {
|
|
1677
|
+
const certificate = forge.pki.certificateFromPem(options.certificatePem);
|
|
1678
|
+
const privateKey = forge.pki.privateKeyFromPem(options.privateKeyPem);
|
|
1679
|
+
const signedData = forge.pkcs7.createSignedData();
|
|
1680
|
+
signedData.content = forge.util.createBuffer(loginTicketRequestXml, "utf8");
|
|
1681
|
+
signedData.addCertificate(certificate);
|
|
1682
|
+
const authenticatedAttributes = [
|
|
1683
|
+
{
|
|
1684
|
+
type: String(forge.pki.oids.contentType),
|
|
1685
|
+
value: String(forge.pki.oids.data)
|
|
1686
|
+
},
|
|
1687
|
+
{
|
|
1688
|
+
type: String(forge.pki.oids.messageDigest)
|
|
1689
|
+
},
|
|
1690
|
+
{
|
|
1691
|
+
type: String(forge.pki.oids.signingTime),
|
|
1692
|
+
value: /* @__PURE__ */ new Date()
|
|
1693
|
+
}
|
|
1694
|
+
];
|
|
1695
|
+
const signerOptions = {
|
|
1696
|
+
key: privateKey,
|
|
1697
|
+
certificate,
|
|
1698
|
+
digestAlgorithm: String(forge.pki.oids.sha1),
|
|
1699
|
+
authenticatedAttributes
|
|
1700
|
+
};
|
|
1701
|
+
signedData.addSigner(signerOptions);
|
|
1702
|
+
signedData.sign();
|
|
1703
|
+
const der = forge.asn1.toDer(signedData.toAsn1()).getBytes();
|
|
1704
|
+
return Buffer.from(der, "binary").toString("base64");
|
|
1705
|
+
}
|
|
1706
|
+
function parseLoginTicketResponse(xml) {
|
|
1707
|
+
const parsed = parseXmlDocument(xml);
|
|
1708
|
+
const response = parsed.loginTicketResponse ?? parsed;
|
|
1709
|
+
const header = response.header;
|
|
1710
|
+
const credentials = response.credentials;
|
|
1711
|
+
const token = credentials?.token;
|
|
1712
|
+
const sign = credentials?.sign;
|
|
1713
|
+
const expiresAt = header?.expirationTime;
|
|
1714
|
+
if (typeof token !== "string" || typeof sign !== "string" || typeof expiresAt !== "string") {
|
|
1715
|
+
throw new ArcaTransportError(
|
|
1716
|
+
"Invalid WSAA login ticket response structure"
|
|
1717
|
+
);
|
|
1718
|
+
}
|
|
1719
|
+
return {
|
|
1720
|
+
token,
|
|
1721
|
+
sign,
|
|
1722
|
+
expiresAt
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
// src/client.ts
|
|
1727
|
+
function createArcaClient(config) {
|
|
1728
|
+
assertArcaClientConfig(config);
|
|
1729
|
+
const normalizedConfig = normalizeArcaClientConfig(config);
|
|
1730
|
+
const logger = createArcaLogger(normalizedConfig.logger);
|
|
1731
|
+
const auth = createWsaaAuthModule({ config: normalizedConfig, logger });
|
|
1732
|
+
const soap = createSoapTransport({ config: normalizedConfig, logger });
|
|
1733
|
+
return {
|
|
1734
|
+
config: normalizedConfig,
|
|
1735
|
+
wsfe: createWsfeService({ config: normalizedConfig, auth, soap }),
|
|
1736
|
+
wsmtxca: createWsmtxcaService({ config: normalizedConfig, auth, soap }),
|
|
1737
|
+
padron: createPadronService({ config: normalizedConfig, auth, soap })
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
export {
|
|
1741
|
+
ARCA_ENVIRONMENTS,
|
|
1742
|
+
ARCA_ENV_VARIABLES,
|
|
1743
|
+
ArcaConfigurationError,
|
|
1744
|
+
ArcaError,
|
|
1745
|
+
ArcaInputError,
|
|
1746
|
+
ArcaServiceError,
|
|
1747
|
+
ArcaSoapFaultError,
|
|
1748
|
+
ArcaTransportError,
|
|
1749
|
+
assertArcaClientConfig,
|
|
1750
|
+
createArcaClient,
|
|
1751
|
+
createArcaClientConfigFromEnv,
|
|
1752
|
+
createPadronService,
|
|
1753
|
+
createWsfeService,
|
|
1754
|
+
createWsmtxcaService,
|
|
1755
|
+
resolveArcaEnvironment
|
|
1756
|
+
};
|
|
1757
|
+
//# sourceMappingURL=index.js.map
|