UNPKG

facturajs

Version:

Comunicación con los web services de AFIP utilizando nodejs.

276 lines • 10.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AfipSoap = void 0; const https_1 = require("https"); const moment_1 = __importDefault(require("moment")); const soap = __importStar(require("soap")); const util_1 = require("../util"); const ntp_time_sync_1 = require("ntp-time-sync"); class ConfiguredHttpClient extends soap.HttpClient { defaultRequestOptions; constructor(defaultRequestOptions, options) { super(options); this.defaultRequestOptions = defaultRequestOptions; } request(rurl, data, callback, exheaders, exoptions, _caller) { return super.request(rurl, data, callback, exheaders, { ...this.defaultRequestOptions, ...exoptions, }); } requestStream(rurl, data, exheaders, exoptions, _caller) { return super.requestStream(rurl, data, exheaders, { ...this.defaultRequestOptions, ...exoptions, }); } } class AfipSoap { config; tokensAliasServices = { wsfev1: 'wsfe', }; urls = { homo: { login: 'https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl', service: 'https://wswhomo.afip.gov.ar/{name}/service.asmx?wsdl', }, prod: { login: 'https://wsaa.afip.gov.ar/ws/services/LoginCms?wsdl', service: 'https://servicios1.afip.gov.ar/{name}/service.asmx?WSDL', }, }; constructor(config) { this.config = config; } getTlsRequestOptions() { const tlsRequestOptions = { ...(this.config.tlsRequestOptions || {}), }; if (this.config.useLegacyTls !== false && typeof tlsRequestOptions.ciphers === 'undefined') { tlsRequestOptions.ciphers = 'DEFAULT@SECLEVEL=1'; } if (Object.keys(tlsRequestOptions).length === 0) { return {}; } return { httpsAgent: new https_1.Agent(tlsRequestOptions), }; } async getTokens(service) { const aliasedService = this.tokensAliasServices[service] || service; return this.retrieveTokens(aliasedService); } async retrieveTokens(service) { const cacheTokens = await this.getTokensFromCache(service); if (cacheTokens) { (0, util_1.debug)(4, 'Read config from cache'); return cacheTokens; } const fromNetwork = await this.getTokensFromNetwork(service); (0, util_1.debug)(5, 'Tokens from network:', fromNetwork); if (fromNetwork) { await this.saveCredentialsCache(service, fromNetwork); } return fromNetwork; } async saveCredentialsCache(service, credential) { const cache = await AfipSoap.getCredentialsCacheAll(this.config.cacheTokensPath); cache[service] = credential; (0, util_1.debug)(4, 'Write config to cache'); await (0, util_1.writeFile)(this.config.cacheTokensPath, JSON.stringify(cache)); } static async getCredentialsCacheAll(path) { try { const raw = await (0, util_1.readStringFromFile)(path); return JSON.parse(raw); } catch (e) { if (this.isErrnoException(e) && e.code === 'ENOENT') { (0, util_1.debug)(3, 'Cache file does not exists.'); } else { (0, util_1.debug)(2, 'Fail to read cache file: ', e); } return {}; } } static isErrnoException(e) { return typeof e === 'object' && e !== null && 'code' in e; } getLoginXml(service, networkTime) { const expire = (0, moment_1.default)(networkTime).add(this.config.tokensExpireInHours, 'hours'); const formatDate = (date) => (0, moment_1.default)(date).format().replace('-03:00', ''); const xml = ` <?xml version="1.0" encoding="UTF-8" ?> <loginTicketRequest version="1.0"> <header> <uniqueId>${(0, moment_1.default)().format('X')}</uniqueId> <generationTime>${formatDate(networkTime)}</generationTime> <expirationTime>${formatDate(expire)}</expirationTime> </header> <service>${service}</service> </loginTicketRequest> `; return xml.trim(); } async signService(service) { const date = await AfipSoap.getNetworkHour(); const [cert, privateKey] = await this.getKeys(); return (0, util_1.signMessage)(this.getLoginXml(service, date), cert, privateKey); } static async getNetworkHour() { const timeSync = ntp_time_sync_1.NtpTimeSync.getInstance({ servers: ['time.afip.gov.ar'], }); const res = await timeSync.getTime(); return res.now; } async getKeys() { return [await this.getCert(), await this.getPrivateKey()]; } async getCert() { if (this.config.certContents) { return this.config.certContents; } if (this.config.certPath) { return await (0, util_1.readStringFromFile)(this.config.certPath); } throw new Error('Not cert'); } async getPrivateKey() { if (this.config.privateKeyContents) { return this.config.privateKeyContents; } if (this.config.privateKeyPath) { return await (0, util_1.readStringFromFile)(this.config.privateKeyPath); } throw new Error('Not private key'); } getSoapClient(serviceName) { const urls = this.urls[this.getAfipEnvironment()]; const type = serviceName === 'login' ? 'login' : 'service'; const url = urls[type].replace('{name}', encodeURIComponent(serviceName)); const tlsRequestOptions = this.getTlsRequestOptions(); const options = { namespaceArrayElements: false, }; if (Object.keys(tlsRequestOptions).length > 0) { options.wsdl_options = tlsRequestOptions; options.httpClient = new ConfiguredHttpClient(tlsRequestOptions, options); } return soap.createClientAsync(url, options); } getAfipEnvironment() { return this.config.homo ? 'homo' : 'prod'; } async getTokensFromNetwork(service) { const [signedData, client] = await Promise.all([ this.signService(service), this.getSoapClient('login'), ]); (0, util_1.debug)(4, 'Asking tokens from network'); const [result] = await client.loginCmsAsync({ in0: signedData, }); const loginCmsReturn = result.loginCmsReturn; const res = await (0, util_1.parseXml)(loginCmsReturn); return { created: (0, moment_1.default)().format(), service, tokens: res.loginTicketResponse.credentials, }; } isExpired(expireStr) { const now = (0, moment_1.default)(new Date()); const expire = (0, moment_1.default)(expireStr); const duration = moment_1.default.duration(now.diff(expire)); return duration.asHours() > this.config.tokensExpireInHours; } async getTokensFromCache(service) { const cache = await AfipSoap.getCredentialsCacheAll(this.config.cacheTokensPath); const cacheService = typeof cache[service] === 'undefined' ? null : cache[service]; if (cacheService && !this.isExpired(cacheService.created)) { return cacheService; } return null; } async execMethod(service, method, params) { (0, util_1.debug)(4, 'execMethod name', method); (0, util_1.debug)(4, 'execMethod params', params); const cred = await this.getTokens(service); (0, util_1.debug)(4, 'TOKENS', cred.tokens); const paramsWithAuth = { Auth: { ...params.Auth, Token: cred.tokens.token, Sign: cred.tokens.sign, }, ...params.params, }; (0, util_1.debug)(4, 'execMethod params with AUTH', params); const client = await this.getSoapClient(service); const call = client[method + 'Async']; if (!call) { throw new Error(`SOAP method not found: ${method}Async`); } const [result, rawResponse] = await call(paramsWithAuth); (0, util_1.debug)(5, 'execMethod rawResponse', rawResponse); const methodResponse = result[method + 'Result']; AfipSoap.throwOnError(methodResponse); return methodResponse; } static throwOnError(response) { const errorResponse = response; if (!errorResponse.Errors) { return; } if (!errorResponse.Errors.Err) { return; } const resErr = errorResponse.Errors.Err[0]; const err = new Error(resErr.Msg); err.name = 'AfipResponseError'; err.code = resErr.Code; throw err; } } exports.AfipSoap = AfipSoap; //# sourceMappingURL=AfipSoap.js.map