├── private └── .gitignore ├── facturajs.jpg ├── .npmignore ├── .gitignore ├── .prettierrc.js ├── src ├── index.ts ├── IConfigService.ts ├── examples │ ├── get-last-bill-number.ts │ ├── last-bill.ts │ ├── create-bill-monotributo.ts │ └── create-bill-facturaB.ts ├── AfipServices.ts ├── SoapMethods.ts ├── util.ts └── lib │ └── AfipSoap.ts ├── tsconfig.json ├── LICENSE ├── package.json ├── README.md └── yarn.lock /private/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | 3 | -------------------------------------------------------------------------------- /facturajs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emilioastarita/facturajs/HEAD/facturajs.jpg -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | node_modules/* 3 | private/* 4 | .lastTokens 5 | yarn-error.log 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | .idea/* 3 | node_modules/* 4 | private/* 5 | .lastTokens 6 | yarn-error.log 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'es5', 3 | tabWidth: 4, 4 | semi: true, 5 | singleQuote: true, 6 | }; 7 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { AfipSoap } from './lib/AfipSoap'; 2 | export { AfipServices } from './AfipServices'; 3 | export { IConfigService } from './IConfigService'; 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2022"], 4 | "module": "commonjs", 5 | "target": "es2022", 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "declaration": true, 10 | "strict": true, 11 | "removeComments": true, 12 | "noLib": false, 13 | "outDir": "./dist/", 14 | "sourceMap": true, 15 | "rootDir": "./src/" 16 | }, 17 | "filesGlob": [ 18 | "*.ts" 19 | ], 20 | "include": [ 21 | "src/**/*", 22 | "node_modules/ntpclient/dist/cjs/**/*" 23 | ], 24 | "exclude": [ 25 | "node_modules", 26 | "**/*.spec.ts" 27 | ] 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/IConfigService.ts: -------------------------------------------------------------------------------- 1 | export interface IConfigServiceBasics { 2 | homo: boolean; 3 | cacheTokensPath: string; 4 | tokensExpireInHours: number; 5 | privateKeyContents?: string; 6 | privateKeyPath?: string; 7 | certPath?: string; 8 | certContents?: string; 9 | } 10 | 11 | export type RequireOnlyOne = Pick< 12 | T, 13 | Exclude 14 | > & 15 | { 16 | [K in Keys]-?: Required> & 17 | Partial, undefined>>; 18 | }[Keys]; 19 | 20 | export type IConfigService = RequireOnlyOne< 21 | IConfigServiceBasics, 22 | 'privateKeyContents' | 'privateKeyPath' 23 | > & 24 | RequireOnlyOne; 25 | -------------------------------------------------------------------------------- /src/examples/get-last-bill-number.ts: -------------------------------------------------------------------------------- 1 | import 'source-map-support/register'; 2 | import { AfipServices } from '../AfipServices'; 3 | import { IConfigService } from '../IConfigService'; 4 | 5 | const config: IConfigService = { 6 | certPath: './private/dev/cert.pem', 7 | privateKeyPath: './private/dev/private_key.key', 8 | cacheTokensPath: './.lastTokens', 9 | homo: true, 10 | tokensExpireInHours: 12, 11 | }; 12 | 13 | const afip = new AfipServices(config); 14 | const cuit = 27310090854; 15 | 16 | async function facturaBExample() { 17 | const res = await afip.getLastBillNumber({ 18 | Auth: { Cuit: cuit }, 19 | params: { 20 | CbteTipo: 6, 21 | PtoVta: 7, 22 | }, 23 | }); 24 | console.log('Last bill number: ', res.CbteNro); 25 | } 26 | 27 | facturaBExample().catch((err) => { 28 | console.error('Something was wrong!'); 29 | console.error(err); 30 | }); 31 | -------------------------------------------------------------------------------- /src/examples/last-bill.ts: -------------------------------------------------------------------------------- 1 | import { IConfigService } from '../IConfigService'; 2 | 3 | import * as fs from 'fs'; 4 | import 'source-map-support/register'; 5 | import { AfipServices } from '../AfipServices'; 6 | 7 | const config: IConfigService = { 8 | // use path or content keys: 9 | // certPath: './private/dev/cert.pem', 10 | // privateKeyPath: './private/dev/private_key.key', 11 | certContents: fs.readFileSync('./private/dev/cert.pem').toString('utf8'), 12 | privateKeyContents: fs 13 | .readFileSync('./private/dev/private_key.key') 14 | .toString('utf8'), 15 | cacheTokensPath: './.lastTokens', 16 | homo: true, 17 | tokensExpireInHours: 12, 18 | }; 19 | 20 | const afip = new AfipServices(config); 21 | 22 | const cuit = 27310090854; 23 | 24 | afip.getLastBillNumber({ 25 | Auth: { Cuit: cuit }, 26 | params: { 27 | CbteTipo: 11, 28 | PtoVta: 2, 29 | }, 30 | }).then((res) => { 31 | console.log('Last bill number: ', res.CbteNro); 32 | return res.CbteNro; 33 | }); 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Emilio Astarita 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/AfipServices.ts: -------------------------------------------------------------------------------- 1 | import { IConfigService } from './IConfigService'; 2 | import { 3 | IParamsFECAESolicitar, 4 | IParamsFECompUltimoAutorizado, 5 | WsServicesNames, 6 | } from './SoapMethods'; 7 | import { AfipSoap } from './lib/AfipSoap'; 8 | 9 | export class AfipServices { 10 | private afipSoap: AfipSoap; 11 | 12 | constructor(private config: IConfigService) { 13 | this.afipSoap = new AfipSoap(config); 14 | } 15 | 16 | public createBill(params: IParamsFECAESolicitar) { 17 | const service = `wsfev1`; 18 | const method = `FECAESolicitar`; 19 | return this.afipSoap.execMethod(service, method, params); 20 | } 21 | 22 | public getLastBillNumber(params: IParamsFECompUltimoAutorizado) { 23 | const service = `wsfev1`; 24 | const method = `FECompUltimoAutorizado`; 25 | return this.afipSoap.execMethod(service, method, params); 26 | } 27 | 28 | public execRemote(service: string, method: string, params: any) { 29 | return this.afipSoap.execMethod( 30 | service as WsServicesNames, 31 | method, 32 | params 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "facturajs", 3 | "description": "Comunicación con los web services de AFIP utilizando nodejs.", 4 | "author": "Emilio Astarita", 5 | "licence": "MIT", 6 | "version": "0.3.2", 7 | "engines": { 8 | "node": ">=6.0.0" 9 | }, 10 | "main": "dist/index.js", 11 | "types": "dist/index.d.ts", 12 | "scripts": { 13 | "prettier": "prettier --write \"src/**/*.{md,json,ts}\"", 14 | "clean": "rm -rf dist/*", 15 | "prepare": "yarn prettier; yarn clean ; yarn build", 16 | "build": "yarn run tsc", 17 | "type:dts": "tsc --emitDeclarationOnly --project tsconfig.build.json" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/emilioastarita/facturajs.git" 22 | }, 23 | "bugs": { 24 | "url": "git+https://github.com/emilioastarita/facturajs/issues" 25 | }, 26 | "files": [ 27 | "dist/" 28 | ], 29 | "keywords": [ 30 | "afip", 31 | "factura", 32 | "factura electrónica", 33 | "wsfe", 34 | "wsfev1", 35 | "web service", 36 | "soap", 37 | "nodejs" 38 | ], 39 | "dependencies": { 40 | "debug": "^4.4.0", 41 | "moment": "^2.30.1", 42 | "node-forge": "^1.3.1", 43 | "ntp-time-sync": "^0.5.0", 44 | "soap": "^1.1.11", 45 | "source-map-support": "^0.5.21", 46 | "uniqid": "^5.4.0", 47 | "xml2js": "^0.6.2" 48 | }, 49 | "devDependencies": { 50 | "@types/debug": "^4.1.12", 51 | "@types/node-forge": "^1.3.11", 52 | "@types/uniqid": "^5.3.4", 53 | "@types/xml2js": "^0.4.14", 54 | "prettier": "^3.5.3", 55 | "typescript": "^5.8.3" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/SoapMethods.ts: -------------------------------------------------------------------------------- 1 | export type WsServicesNames = 'wsfe' | 'wsfev1' | 'login'; 2 | 3 | export interface IParamsAuth { 4 | Auth?: { 5 | token?: string; 6 | sign?: string; 7 | Cuit: number; 8 | }; 9 | } 10 | 11 | export interface IParamsFECompUltimoAutorizado extends IParamsAuth { 12 | params: { 13 | PtoVta: number; 14 | CbteTipo: number; 15 | }; 16 | } 17 | 18 | export interface IParamsFECAESolicitar extends IParamsAuth { 19 | params: { 20 | FeCAEReq: { 21 | FeCabReq: { 22 | CantReg: number; 23 | PtoVta: number; 24 | CbteTipo: number; 25 | }; 26 | FeDetReq: { 27 | FECAEDetRequest: { 28 | DocTipo: number; 29 | DocNro: number; 30 | Concepto: number; 31 | CbteDesde: number; 32 | CbteHasta: number; 33 | CbteFch: string; 34 | ImpTotal: number; 35 | ImpTotConc: number; 36 | ImpNeto: number; 37 | ImpOpEx: number; 38 | ImpIVA: number; 39 | ImpTrib: number; 40 | MonId: 'PES'; 41 | MonCotiz: number; 42 | Iva?: { 43 | AlicIva: { 44 | Id: number; 45 | BaseImp: number; 46 | Importe: number; 47 | }; 48 | }[]; 49 | }; 50 | }; 51 | }; 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /src/examples/create-bill-monotributo.ts: -------------------------------------------------------------------------------- 1 | import 'source-map-support/register'; 2 | import { AfipServices } from '../AfipServices'; 3 | import { IConfigService } from '../IConfigService'; 4 | import moment from 'moment'; 5 | 6 | const config: IConfigService = { 7 | certPath: './private/dev/cert.pem', 8 | privateKeyPath: './private/dev/private_key.key', 9 | // or use directly content keys if you need 10 | // certContents: fs.readFileSync('./private/dev/cert.pem').toString('utf8'), 11 | // privateKeyContents: fs.readFileSync('./private/dev/private_key.key').toString('utf8'), 12 | cacheTokensPath: './.lastTokens', 13 | homo: true, 14 | tokensExpireInHours: 12, 15 | }; 16 | 17 | const afip = new AfipServices(config); 18 | const cuit = 27310090854; 19 | 20 | async function monotributoExample() { 21 | const res = await afip.getLastBillNumber({ 22 | Auth: { Cuit: cuit }, 23 | params: { 24 | CbteTipo: 11, 25 | PtoVta: 2, 26 | }, 27 | }); 28 | console.log('Last bill number: ', res.CbteNro); 29 | const num = res.CbteNro; 30 | const next = num + 1; 31 | console.log('Next bill number to create: ', next); 32 | const resBill = await afip.createBill({ 33 | Auth: { Cuit: cuit }, 34 | params: { 35 | FeCAEReq: { 36 | FeCabReq: { 37 | CantReg: 1, 38 | PtoVta: 2, 39 | // monotributo 40 | CbteTipo: 11, 41 | }, 42 | FeDetReq: { 43 | FECAEDetRequest: { 44 | DocTipo: 99, 45 | DocNro: 0, 46 | Concepto: 1, 47 | CbteDesde: next, 48 | CbteHasta: next, 49 | CbteFch: moment().format('YYYYMMDD'), 50 | ImpTotal: 75.0, 51 | ImpTotConc: 0, 52 | ImpNeto: 75.0, 53 | ImpOpEx: 0, 54 | ImpIVA: 0, 55 | ImpTrib: 0, 56 | MonId: 'PES', 57 | MonCotiz: 1, 58 | }, 59 | }, 60 | }, 61 | }, 62 | }); 63 | console.log('Created bill', JSON.stringify(resBill, null, 4)); 64 | } 65 | 66 | monotributoExample().catch((err) => { 67 | console.error('Something was wrong!'); 68 | console.error(err); 69 | }); 70 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as forge from 'node-forge'; 3 | import xml2js from 'xml2js'; 4 | import debugInit from 'debug'; 5 | const LOG_RUNTIME_LEVEL: LOG = parseProcessLevel(); 6 | const LOG_NAMESPACE = 'facturajs'; 7 | const debugLib = debugInit(LOG_NAMESPACE); 8 | 9 | export const readFile = fs.promises.readFile; 10 | export const writeFile = fs.promises.writeFile; 11 | 12 | export const enum LOG { 13 | NONE = 1, 14 | ERROR, 15 | WARN, 16 | INFO, 17 | DEBUG, 18 | } 19 | 20 | function parseProcessLevel(): LOG { 21 | if (typeof process.env.LOG_LEVEL !== 'undefined') { 22 | return parseInt(process.env.LOG_LEVEL as string, 10); 23 | } 24 | return LOG.INFO; 25 | } 26 | 27 | export function debug(level: number, ...rest: any[]) { 28 | if (LOG_RUNTIME_LEVEL < level) { 29 | return; 30 | } 31 | //@ts-expect-error not typed 32 | return debugLib(...rest); 33 | } 34 | 35 | export function parseXml(xml: string) { 36 | const options = { 37 | explicitArray: false, 38 | }; 39 | return new Promise((resolve, reject) => { 40 | xml2js.parseString(xml, options, (err, parsed) => { 41 | if (err) { 42 | reject(err); 43 | } else { 44 | resolve(parsed); 45 | } 46 | }); 47 | }); 48 | } 49 | 50 | export async function readStringFromFile( 51 | path: string, 52 | encoding = 'utf8' 53 | ): Promise { 54 | return (await readFile(path)).toString(encoding); 55 | } 56 | 57 | export function signMessage( 58 | message: string, 59 | cert: string, 60 | privateKey: string 61 | ): string { 62 | const p7 = (forge as any).pkcs7.createSignedData(); 63 | p7.content = forge.util.createBuffer(message, 'utf8'); 64 | p7.addCertificate(cert); 65 | p7.addSigner({ 66 | authenticatedAttributes: [ 67 | { 68 | type: forge.pki.oids.contentType, 69 | value: forge.pki.oids.data, 70 | }, 71 | { 72 | type: forge.pki.oids.messageDigest, 73 | }, 74 | { 75 | type: forge.pki.oids.signingTime, 76 | value: new Date(), 77 | }, 78 | ], 79 | certificate: cert, 80 | digestAlgorithm: forge.pki.oids.sha256, 81 | key: privateKey, 82 | }); 83 | p7.sign(); 84 | const bytes = forge.asn1.toDer(p7.toAsn1()).getBytes(); 85 | return Buffer.from(bytes, 'binary').toString('base64'); 86 | } 87 | -------------------------------------------------------------------------------- /src/examples/create-bill-facturaB.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import 'source-map-support/register'; 3 | import { AfipServices } from '../AfipServices'; 4 | import { IConfigService } from '../IConfigService'; 5 | 6 | const config: IConfigService = { 7 | certPath: './private/dev/cert.pem', 8 | privateKeyPath: './private/dev/private_key.key', 9 | // or use directly content keys if you need 10 | // certContents: fs.readFileSync('./private/dev/cert.pem').toString('utf8'), 11 | // privateKeyContents: fs.readFileSync('./private/dev/private_key.key').toString('utf8'), 12 | cacheTokensPath: './.lastTokens', 13 | homo: true, 14 | tokensExpireInHours: 12, 15 | }; 16 | 17 | const afip = new AfipServices(config); 18 | const cuit = 27310090854; 19 | 20 | async function facturaBExample() { 21 | const res = await afip.getLastBillNumber({ 22 | Auth: { Cuit: cuit }, 23 | params: { 24 | CbteTipo: 6, 25 | PtoVta: 7, 26 | }, 27 | }); 28 | console.log('Last bill number: ', res.CbteNro); 29 | const num = res.CbteNro; 30 | const next = num + 1; 31 | console.log('Next bill number to create: ', next); 32 | const resBill = await afip.createBill({ 33 | Auth: { Cuit: cuit }, 34 | params: { 35 | FeCAEReq: { 36 | FeCabReq: { 37 | CantReg: 1, 38 | PtoVta: 7, 39 | // Factura B 40 | CbteTipo: 6, 41 | }, 42 | FeDetReq: { 43 | FECAEDetRequest: { 44 | DocTipo: 99, 45 | DocNro: 0, 46 | Concepto: 1, 47 | CbteDesde: next, 48 | CbteHasta: next, 49 | CbteFch: moment().format('YYYYMMDD'), 50 | ImpTotal: 121.0, 51 | ImpTotConc: 0, 52 | ImpNeto: 100, 53 | ImpOpEx: 0, 54 | ImpIVA: 21, 55 | ImpTrib: 0, 56 | MonId: 'PES', 57 | MonCotiz: 1, 58 | Iva: [ 59 | { 60 | AlicIva: { 61 | Id: 5, // Id del tipo de IVA (5 es 21%) 62 | BaseImp: 100, // Base imponible 63 | Importe: 21, // Importe 64 | }, 65 | }, 66 | ], 67 | }, 68 | }, 69 | }, 70 | }, 71 | }); 72 | console.log('Created bill', JSON.stringify(resBill, null, 4)); 73 | } 74 | 75 | facturaBExample().catch((err) => { 76 | console.error('Something was wrong!'); 77 | console.error(err); 78 | }); 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![facturajs](facturajs.jpg?raw=true&1 "FacturaJS") 2 | 3 | # facturajs 4 | 5 | Afip Web Services desde nodejs. 6 | 7 | ````bash 8 | ~$ yarn add facturajs 9 | # OR 10 | ~$ npm i facturajs 11 | ```` 12 | 13 | ## WebServices de la AFIP con node 14 | 15 | El objetivo de esta lib es facilitar la comunicación con la AFIP utilizando node.js. En mi caso particular, necesitaba *generar facturas electrónicas como monotributista* de manera automática. 16 | 17 | No tiene muchas pretenciones, pero el código puede ser ilustrativo para armar otras integraciones más complejas. 18 | 19 | ### Obtener certificados en la AFIP 20 | 21 | Los servicios de AFIP están disponibles mediante SOAP y su página principal es [AFIP WS](http://www.afip.gob.ar/ws). 22 | 23 | Los servicios poseen dos entornos conocidos como homologación (para testing), y producción. 24 | 25 | Para utilizar ambos entornos hay que obtener certificados digitales de la AFIP. 26 | 27 | De nuestro lado lo primero es generar certificados relacionados a nuestro CUIT: 28 | 29 | ```bash 30 | # Crear clave privada 31 | ~ $ openssl genrsa -out private/private_key.key 2048 32 | # Crear certificado 33 | ~ $ openssl req -new -key private/private_key.key -subj "/C=AR/O=TU_EMPRESA/CN=TU_SISTEMA/serialNumber=CUIT TU_CUIT" -out private/afip.csr 34 | 35 | ``` 36 | (*) Dónde dice `CUIT TU_CUIT` es importante el espacio y conservar la palabra "CUIT". 37 | 38 | ### Adherirse a homologación (testing) 39 | 40 | En este [pdf (WSASS como adherirse)](https://www.afip.gob.ar/ws/WSASS/WSASS_como_adherirse.pdf) pueden ver el proceso de adhesión al servicio de homologación. Básicamente das de alta el servicio y le cargas el CSR generado en el paso anterior, la página web te va a entregar otro cert que lo tenes que copiar y lo guardas en un archivo de texto: `private/cert.pem`. El último paso, en esa misma página, sería autorizar tu cert al servicio que quieras usar. En mi caso quería el de facturas digitales llamado: `wsfe`. 41 | 42 | ### Ejemplo de uso 43 | 44 | 45 | #### Crear una factura electrónica 46 | 47 | En este ejemplo pueden [ver como crear una factura electrónica con el último número de comprobante válido](src/examples/create-bill.ts). 48 | 49 | Para poder ver más en detalle que está sucediendo se puede configurar el `LOG_LEVEL` y namespace de `DEBUG`: 50 | 51 | ```bash 52 | ~$ LOG_LEVEL=3 DEBUG=facturajs node dist/examples/create-bill-monotributo.js 53 | # Salida: 54 | # Last bill number: 43 55 | # Next bill number to create: 44 56 | # Created bill { 57 | # "FeCabResp": { 58 | # "Cuit": "XXXXXXXXXX", 59 | # "PtoVta": 2, 60 | # "CbteTipo": 11, 61 | # "FchProceso": "20180423081028", 62 | # "CantReg": 1, 63 | # "Resultado": "A", 64 | # "Reproceso": "N" 65 | # }, 66 | # "FeDetResp": { 67 | # "FECAEDetResponse": [ 68 | # { 69 | # "Concepto": 1, 70 | # "DocTipo": 99, 71 | # "DocNro": "0", 72 | # "CbteDesde": "44", 73 | # "CbteHasta": "44", 74 | # "CbteFch": "20180423", 75 | # "Resultado": "A", 76 | # "CAE": "68174646182386", 77 | # "CAEFchVto": "20180503" 78 | # } 79 | # ] 80 | # } 81 | # } 82 | ``` 83 | 84 | #### Ejecutar cualquier método del WebService 85 | 86 | De manera general cualquier método del WebService se puede llamar con `execRemote(servicio, método, parámetros)`, por ejemplo: 87 | 88 | ````typescript 89 | const afip = new AfipServices(config); 90 | afip.execRemote('wsfev1', 'FECAESolicitar', { 91 | Auth: {Cuit: 27000000000}, 92 | params: { 93 | CbteTipo: 11, 94 | PtoVta: 2 95 | } 96 | }).then(res => console.log(res)) 97 | ```` 98 | 99 | 100 | 101 | #### Config 102 | 103 | El constructor `AfipServices` acepta un objeto que cumpla con la interfaz de `IConfigService`. La descripción de sus propiedades: 104 | 105 | * `homo` Un booleano que determina el uso del entorno de homologación. 106 | * `cacheTokensPath` Path a un file dónde se cachearan los tokens obtenidos para no solicitarlos cada vez. 107 | * `tokensExpireInHours` La cantidad de horas en la que expirará el archivo de tokens cacheados. 108 | * `privateKeyContents` El contenido de la private key (no hace falta path en este caso) 109 | * `privateKeyPath` Path a la private key (al igual que antes podemos omitir el contenido) 110 | * `certContents` El contenido del certificado (no hace falta path en este caso) 111 | * `certPath` Path al certificado (al igual que antes podemos omitir el contenido) 112 | 113 | 114 | #### Nota acerca de métodos de cifrado openssl 115 | 116 | En algunos sistemas operativos con una versión de la configuración de openssl mas nueva 117 | se reportó un error porque openssl rechaza los métodos de cifrado de AFIP. 118 | Algunos usuarios reportaron que en un sistema Debian la solución fue editar `/etc/ssl/openssl.cnf`. 119 | Y comentar la siguiente línea: 120 | ``` 121 | # CipherString = DEFAULT@SECLEVEL=2 122 | ``` 123 | 124 | 125 | #### Proyectos relacionados 126 | 127 | - [SOAP de la AFIP a REST](https://github.com/sarriaroman/AFIP-API) enciende un server expressjs y sirve de wrapper alrededor del SOAP para poder consumir los servicios con una interfaz REST. A partir de ese repo se armó esta lib. 128 | 129 | 130 | #### Colaboradores 131 | 132 | * mbenedettini 133 | -------------------------------------------------------------------------------- /src/lib/AfipSoap.ts: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | import * as soap from 'soap'; 3 | import { IConfigService } from '../IConfigService'; 4 | import { WsServicesNames } from '../SoapMethods'; 5 | import { 6 | debug, 7 | LOG, 8 | parseXml, 9 | readStringFromFile, 10 | signMessage, 11 | writeFile, 12 | } from '../util'; 13 | import { NtpTimeSync } from 'ntp-time-sync'; 14 | 15 | type SoapServiceAlias = { 16 | [K in WsServicesNames]?: WsServicesNames; 17 | }; 18 | 19 | interface ICredential { 20 | tokens: { 21 | token: string; 22 | sign: string; 23 | }; 24 | created: string; 25 | service: WsServicesNames; 26 | } 27 | 28 | type ICredentialsCache = { 29 | [K in WsServicesNames]?: ICredential; 30 | }; 31 | 32 | export class AfipSoap { 33 | private tokensAliasServices: SoapServiceAlias = { 34 | wsfev1: 'wsfe', 35 | }; 36 | private urls = { 37 | homo: { 38 | login: 'https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl', 39 | service: 'https://wswhomo.afip.gov.ar/{name}/service.asmx?wsdl', 40 | }, 41 | prod: { 42 | login: 'https://wsaa.afip.gov.ar/ws/services/LoginCms?wsdl', 43 | service: 'https://servicios1.afip.gov.ar/{name}/service.asmx?WSDL', 44 | }, 45 | }; 46 | 47 | constructor(private config: IConfigService) {} 48 | 49 | private async getTokens(service: WsServicesNames): Promise { 50 | const aliasedService = this.tokensAliasServices[service] || service; 51 | return this.retrieveTokens(aliasedService); 52 | } 53 | 54 | private async retrieveTokens( 55 | service: WsServicesNames 56 | ): Promise { 57 | const cacheTokens = await this.getTokensFromCache(service); 58 | if (cacheTokens) { 59 | debug(LOG.INFO, 'Read config from cache'); 60 | return cacheTokens; 61 | } 62 | const fromNetwork = await this.getTokensFromNetwork(service); 63 | debug(LOG.DEBUG, 'Tokens from network:', fromNetwork); 64 | if (fromNetwork) { 65 | await this.saveCredentialsCache(service, fromNetwork); 66 | } 67 | return fromNetwork; 68 | } 69 | 70 | private async saveCredentialsCache( 71 | service: WsServicesNames, 72 | credential: ICredential 73 | ) { 74 | const cache: ICredentialsCache = await AfipSoap.getCredentialsCacheAll( 75 | this.config.cacheTokensPath 76 | ); 77 | cache[service] = credential; 78 | debug(LOG.INFO, 'Write config to cache'); 79 | await writeFile(this.config.cacheTokensPath, JSON.stringify(cache)); 80 | } 81 | 82 | private static async getCredentialsCacheAll( 83 | path: string 84 | ): Promise { 85 | try { 86 | const raw = await readStringFromFile(path); 87 | return JSON.parse(raw) as ICredentialsCache; 88 | } catch (e: unknown) { 89 | if (this.isErrnoException(e) && e.code === 'ENOENT') { 90 | debug(LOG.WARN, 'Cache file does not exists.'); 91 | } else { 92 | debug(LOG.ERROR, 'Fail to read cache file: ', e); 93 | } 94 | return {}; 95 | } 96 | } 97 | 98 | private static isErrnoException(e: unknown): e is NodeJS.ErrnoException { 99 | return 'code' in (e as any); 100 | } 101 | 102 | private getLoginXml(service: string, networkTime: Date): string { 103 | const expire = moment(networkTime).add( 104 | this.config.tokensExpireInHours, 105 | 'hours' 106 | ); 107 | const formatDate = (date: Date | moment.Moment) => 108 | moment(date).format().replace('-03:00', ''); 109 | const xml = ` 110 | 111 | 112 |
113 | ${moment().format('X')} 114 | ${formatDate(networkTime)} 115 | ${formatDate(expire)} 116 |
117 | ${service} 118 |
119 | `; 120 | return xml.trim(); 121 | } 122 | 123 | private async signService(service: string): Promise { 124 | const date = await AfipSoap.getNetworkHour(); 125 | const [cert, privateKey] = await this.getKeys(); 126 | return signMessage(this.getLoginXml(service, date), cert, privateKey); 127 | } 128 | 129 | private static async getNetworkHour(): Promise { 130 | const timeSync = NtpTimeSync.getInstance({ 131 | servers: ['time.afip.gov.ar'], 132 | }); 133 | const res = await timeSync.getTime(); 134 | return res.now; 135 | } 136 | 137 | private async getKeys() { 138 | return [await this.getCert(), await this.getPrivateKey()]; 139 | } 140 | 141 | private async getCert(): Promise { 142 | if (this.config.certContents) { 143 | return this.config.certContents; 144 | } 145 | if (this.config.certPath) { 146 | return await readStringFromFile(this.config.certPath); 147 | } 148 | throw new Error('Not cert'); 149 | } 150 | 151 | private async getPrivateKey(): Promise { 152 | if (this.config.privateKeyContents) { 153 | return this.config.privateKeyContents; 154 | } 155 | if (this.config.privateKeyPath) { 156 | return await readStringFromFile(this.config.privateKeyPath); 157 | } 158 | throw new Error('Not private key'); 159 | } 160 | 161 | private getSoapClient(serviceName: WsServicesNames) { 162 | const urls = this.urls[this.getAfipEnvironment()]; 163 | const type = serviceName === 'login' ? 'login' : 'service'; 164 | const url = urls[type].replace( 165 | '{name}', 166 | encodeURIComponent(serviceName) 167 | ); 168 | 169 | return soap.createClientAsync(url, { 170 | namespaceArrayElements: false, 171 | }); 172 | } 173 | 174 | private getAfipEnvironment(): 'homo' | 'prod' { 175 | return this.config.homo ? 'homo' : 'prod'; 176 | } 177 | 178 | private async getTokensFromNetwork( 179 | service: WsServicesNames 180 | ): Promise { 181 | const [signedData, client] = await Promise.all([ 182 | this.signService(service), 183 | this.getSoapClient('login'), 184 | ]); 185 | debug(LOG.INFO, 'Asking tokens from network'); 186 | const result: [any] = await client.loginCmsAsync({ in0: signedData }); 187 | const loginCmsReturn: string = result[0].loginCmsReturn; 188 | const res = await parseXml<{ 189 | loginTicketResponse: { 190 | credentials: { 191 | token: string; 192 | sign: string; 193 | }; 194 | }; 195 | }>(loginCmsReturn); 196 | return { 197 | created: moment().format(), 198 | service, 199 | tokens: res.loginTicketResponse.credentials, 200 | }; 201 | } 202 | 203 | private isExpired(expireStr: string) { 204 | const now = moment(new Date()); 205 | const expire = moment(expireStr); 206 | const duration = moment.duration(now.diff(expire)); 207 | return duration.asHours() > this.config.tokensExpireInHours; 208 | } 209 | 210 | private async getTokensFromCache( 211 | service: WsServicesNames 212 | ): Promise { 213 | const cache = await AfipSoap.getCredentialsCacheAll( 214 | this.config.cacheTokensPath 215 | ); 216 | const cacheService = 217 | typeof cache[service] === 'undefined' ? null : cache[service]; 218 | 219 | if (cacheService && !this.isExpired(cacheService.created)) { 220 | return cacheService; 221 | } 222 | return null; 223 | } 224 | 225 | public async execMethod( 226 | service: WsServicesNames, 227 | method: string, 228 | params: any 229 | ) { 230 | debug(LOG.INFO, 'execMethod name', method); 231 | debug(LOG.INFO, 'execMethod params', params); 232 | const cred = await this.getTokens(service); 233 | debug(LOG.INFO, 'TOKENS', cred.tokens); 234 | 235 | const paramsWithAuth = { 236 | Auth: { 237 | ...params.Auth, 238 | Token: cred.tokens.token, 239 | Sign: cred.tokens.sign, 240 | }, 241 | ...params.params, 242 | }; 243 | debug(LOG.INFO, 'execMethod params with AUTH', params); 244 | const client = await this.getSoapClient(service); 245 | const call = client[method + 'Async']; 246 | const [result, rawResponse] = await call(paramsWithAuth); 247 | debug(LOG.DEBUG, 'execMethod rawResponse', rawResponse); 248 | const methodResponse = result[method + 'Result']; 249 | AfipSoap.throwOnError(methodResponse); 250 | return methodResponse; 251 | } 252 | 253 | private static throwOnError(response: any) { 254 | if (!response.Errors) { 255 | return; 256 | } 257 | if (!response.Errors.Err) { 258 | return; 259 | } 260 | const resErr = response.Errors.Err[0]; 261 | const err: any = new Error(resErr.Msg); 262 | err.name = 'AfipResponseError'; 263 | err.code = resErr.Code; 264 | throw err; 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@noble/hashes@^1.1.5": 6 | version "1.8.0" 7 | resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" 8 | integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== 9 | 10 | "@paralleldrive/cuid2@^2.2.2": 11 | version "2.2.2" 12 | resolved "https://registry.yarnpkg.com/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz#7f91364d53b89e2c9cb9e02e8dd0f129e834455f" 13 | integrity sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA== 14 | dependencies: 15 | "@noble/hashes" "^1.1.5" 16 | 17 | "@types/debug@^4.1.12": 18 | version "4.1.12" 19 | resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" 20 | integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== 21 | dependencies: 22 | "@types/ms" "*" 23 | 24 | "@types/ms@*": 25 | version "0.7.31" 26 | resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" 27 | integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== 28 | 29 | "@types/node-forge@^1.3.11": 30 | version "1.3.11" 31 | resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" 32 | integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ== 33 | dependencies: 34 | "@types/node" "*" 35 | 36 | "@types/node@*": 37 | version "12.0.8" 38 | resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.8.tgz#551466be11b2adc3f3d47156758f610bd9f6b1d8" 39 | 40 | "@types/uniqid@^5.3.4": 41 | version "5.3.4" 42 | resolved "https://registry.yarnpkg.com/@types/uniqid/-/uniqid-5.3.4.tgz#e5deca439a1db381e4692f7679ecf3d7f1264eb2" 43 | integrity sha512-AgC+o3/8/QEHuU3w5w2jZ8auQtjSJ/s8G8RfEk9CYLogK1RGXqxhHH0wOEAu8uHXjvj8oh/dRtfgok4IHKxh/Q== 44 | 45 | "@types/xml2js@^0.4.14": 46 | version "0.4.14" 47 | resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.14.tgz#5d462a2a7330345e2309c6b549a183a376de8f9a" 48 | integrity sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ== 49 | dependencies: 50 | "@types/node" "*" 51 | 52 | "@xmldom/is-dom-node@^1.0.1": 53 | version "1.0.1" 54 | resolved "https://registry.yarnpkg.com/@xmldom/is-dom-node/-/is-dom-node-1.0.1.tgz#83b9f3e1260fb008061c6fa787b93a00f9be0629" 55 | integrity sha512-CJDxIgE5I0FH+ttq/Fxy6nRpxP70+e2O048EPe85J2use3XKdatVM7dDVvFNjQudd9B49NPoZ+8PG49zj4Er8Q== 56 | 57 | "@xmldom/xmldom@^0.8.10": 58 | version "0.8.10" 59 | resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz#a1337ca426aa61cef9fe15b5b28e340a72f6fa99" 60 | integrity sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw== 61 | 62 | asap@^2.0.0: 63 | version "2.0.6" 64 | resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" 65 | integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== 66 | 67 | asynckit@^0.4.0: 68 | version "0.4.0" 69 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 70 | 71 | axios-ntlm@^1.4.4: 72 | version "1.4.4" 73 | resolved "https://registry.yarnpkg.com/axios-ntlm/-/axios-ntlm-1.4.4.tgz#3f58a06f3acca632e89b4e6ef9c626eb5609fab9" 74 | integrity sha512-kpCRdzMfL8gi0Z0o96P3QPAK4XuC8iciGgxGXe+PeQ4oyjI2LZN8WSOKbu0Y9Jo3T/A7pB81n6jYVPIpglEuRA== 75 | dependencies: 76 | axios "^1.8.4" 77 | des.js "^1.1.0" 78 | dev-null "^0.1.1" 79 | js-md4 "^0.3.2" 80 | 81 | axios@^1.8.4: 82 | version "1.8.4" 83 | resolved "https://registry.yarnpkg.com/axios/-/axios-1.8.4.tgz#78990bb4bc63d2cae072952d374835950a82f447" 84 | integrity sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw== 85 | dependencies: 86 | follow-redirects "^1.15.6" 87 | form-data "^4.0.0" 88 | proxy-from-env "^1.1.0" 89 | 90 | buffer-from@^1.0.0: 91 | version "1.1.1" 92 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 93 | 94 | combined-stream@^1.0.8: 95 | version "1.0.8" 96 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" 97 | integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== 98 | dependencies: 99 | delayed-stream "~1.0.0" 100 | 101 | debug@^4.4.0: 102 | version "4.4.0" 103 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" 104 | integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== 105 | dependencies: 106 | ms "^2.1.3" 107 | 108 | delayed-stream@~1.0.0: 109 | version "1.0.0" 110 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 111 | 112 | des.js@^1.1.0: 113 | version "1.1.0" 114 | resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.1.0.tgz#1d37f5766f3bbff4ee9638e871a8768c173b81da" 115 | integrity sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg== 116 | dependencies: 117 | inherits "^2.0.1" 118 | minimalistic-assert "^1.0.0" 119 | 120 | dev-null@^0.1.1: 121 | version "0.1.1" 122 | resolved "https://registry.yarnpkg.com/dev-null/-/dev-null-0.1.1.tgz#5a205ce3c2b2ef77b6238d6ba179eb74c6a0e818" 123 | integrity sha512-nMNZG0zfMgmdv8S5O0TM5cpwNbGKRGPCxVsr0SmA3NZZy9CYBbuNLL0PD3Acx9e5LIUgwONXtM9kM6RlawPxEQ== 124 | 125 | dezalgo@^1.0.4: 126 | version "1.0.4" 127 | resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" 128 | integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== 129 | dependencies: 130 | asap "^2.0.0" 131 | wrappy "1" 132 | 133 | follow-redirects@^1.15.6: 134 | version "1.15.9" 135 | resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" 136 | integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== 137 | 138 | form-data@^4.0.0: 139 | version "4.0.0" 140 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" 141 | integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== 142 | dependencies: 143 | asynckit "^0.4.0" 144 | combined-stream "^1.0.8" 145 | mime-types "^2.1.12" 146 | 147 | formidable@^3.5.2: 148 | version "3.5.4" 149 | resolved "https://registry.yarnpkg.com/formidable/-/formidable-3.5.4.tgz#ac9a593b951e829b3298f21aa9a2243932f32ed9" 150 | integrity sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug== 151 | dependencies: 152 | "@paralleldrive/cuid2" "^2.2.2" 153 | dezalgo "^1.0.4" 154 | once "^1.4.0" 155 | 156 | get-stream@^6.0.1: 157 | version "6.0.1" 158 | resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" 159 | integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== 160 | 161 | inherits@^2.0.1: 162 | version "2.0.4" 163 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 164 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 165 | 166 | js-md4@^0.3.2: 167 | version "0.3.2" 168 | resolved "https://registry.yarnpkg.com/js-md4/-/js-md4-0.3.2.tgz#cd3b3dc045b0c404556c81ddb5756c23e59d7cf5" 169 | integrity sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA== 170 | 171 | lodash@^4.17.21: 172 | version "4.17.21" 173 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" 174 | integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== 175 | 176 | mime-db@1.40.0: 177 | version "1.40.0" 178 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" 179 | 180 | mime-types@^2.1.12: 181 | version "2.1.24" 182 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" 183 | dependencies: 184 | mime-db "1.40.0" 185 | 186 | minimalistic-assert@^1.0.0: 187 | version "1.0.1" 188 | resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" 189 | integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== 190 | 191 | moment@^2.30.1: 192 | version "2.30.1" 193 | resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" 194 | integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== 195 | 196 | ms@^2.1.3: 197 | version "2.1.3" 198 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 199 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 200 | 201 | node-forge@^1.3.1: 202 | version "1.3.1" 203 | resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" 204 | integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== 205 | 206 | ntp-packet-parser@^0.5.0: 207 | version "0.5.0" 208 | resolved "https://registry.yarnpkg.com/ntp-packet-parser/-/ntp-packet-parser-0.5.0.tgz#2eb098ec3a3a9f8d5388b066d2fa0d87ddc3a2e1" 209 | integrity sha512-W18iLyaV17jH4inpvSu2gPep2f1Gs3oABra5NXXzG7MXhLcdHqJrJBO23bTMLkRf4HP9SWHL7FEwwtUf6R1q+g== 210 | 211 | ntp-time-sync@^0.5.0: 212 | version "0.5.0" 213 | resolved "https://registry.yarnpkg.com/ntp-time-sync/-/ntp-time-sync-0.5.0.tgz#28244e5119ec63de323ac951273f147ac079d011" 214 | integrity sha512-ztb/P8wf6vwyJS2FPJ8i4nnDfbvMqVxs5+WoeVMbGkSGe0TiTsBhsV6qsFDiKXoNFwsBt+MUauwk2pKlgVLLmw== 215 | dependencies: 216 | ntp-packet-parser "^0.5.0" 217 | 218 | once@^1.4.0: 219 | version "1.4.0" 220 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 221 | integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== 222 | dependencies: 223 | wrappy "1" 224 | 225 | prettier@^3.5.3: 226 | version "3.5.3" 227 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5" 228 | integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw== 229 | 230 | proxy-from-env@^1.1.0: 231 | version "1.1.0" 232 | resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" 233 | integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== 234 | 235 | sax@>=0.6.0: 236 | version "1.2.4" 237 | resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" 238 | 239 | sax@^1.4.1: 240 | version "1.4.1" 241 | resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" 242 | integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== 243 | 244 | soap@^1.1.11: 245 | version "1.1.11" 246 | resolved "https://registry.yarnpkg.com/soap/-/soap-1.1.11.tgz#864b73272ec5255c066ddb39c039e8c32fae7319" 247 | integrity sha512-wxpKktgEZZvxHIKisFPB4VgURNhSuSJU3mX/1kP11GxlENNzYe0gWk3w/+vLhpx68mMSMjeMR/8sahaPXVBj+Q== 248 | dependencies: 249 | axios "^1.8.4" 250 | axios-ntlm "^1.4.4" 251 | debug "^4.4.0" 252 | formidable "^3.5.2" 253 | get-stream "^6.0.1" 254 | lodash "^4.17.21" 255 | sax "^1.4.1" 256 | strip-bom "^3.0.0" 257 | whatwg-mimetype "4.0.0" 258 | xml-crypto "^6.1.1" 259 | 260 | source-map-support@^0.5.21: 261 | version "0.5.21" 262 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" 263 | integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== 264 | dependencies: 265 | buffer-from "^1.0.0" 266 | source-map "^0.6.0" 267 | 268 | source-map@^0.6.0: 269 | version "0.6.1" 270 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 271 | 272 | strip-bom@^3.0.0: 273 | version "3.0.0" 274 | resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" 275 | integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= 276 | 277 | typescript@^5.8.3: 278 | version "5.8.3" 279 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" 280 | integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== 281 | 282 | uniqid@^5.4.0: 283 | version "5.4.0" 284 | resolved "https://registry.yarnpkg.com/uniqid/-/uniqid-5.4.0.tgz#4e17bfcab66dfe33563411ae0c801f46ef964e66" 285 | integrity sha512-38JRbJ4Fj94VmnC7G/J/5n5SC7Ab46OM5iNtSstB/ko3l1b5g7ALt4qzHFgGciFkyiRNtDXtLNb+VsxtMSE77A== 286 | 287 | whatwg-mimetype@4.0.0: 288 | version "4.0.0" 289 | resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz#bc1bf94a985dc50388d54a9258ac405c3ca2fc0a" 290 | integrity sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg== 291 | 292 | wrappy@1: 293 | version "1.0.2" 294 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 295 | integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== 296 | 297 | xml-crypto@^6.1.1: 298 | version "6.1.2" 299 | resolved "https://registry.yarnpkg.com/xml-crypto/-/xml-crypto-6.1.2.tgz#ed93e87d9538f92ad1ad2db442e9ec586723d07d" 300 | integrity sha512-leBOVQdVi8FvPJrMYoum7Ici9qyxfE4kVi+AkpUoYCSXaQF4IlBm1cneTK9oAxR61LpYxTx7lNcsnBIeRpGW2w== 301 | dependencies: 302 | "@xmldom/is-dom-node" "^1.0.1" 303 | "@xmldom/xmldom" "^0.8.10" 304 | xpath "^0.0.33" 305 | 306 | xml2js@^0.6.2: 307 | version "0.6.2" 308 | resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" 309 | integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== 310 | dependencies: 311 | sax ">=0.6.0" 312 | xmlbuilder "~11.0.0" 313 | 314 | xmlbuilder@~11.0.0: 315 | version "11.0.1" 316 | resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" 317 | integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== 318 | 319 | xpath@^0.0.33: 320 | version "0.0.33" 321 | resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.33.tgz#5136b6094227c5df92002e7c3a13516a5074eb07" 322 | integrity sha512-NNXnzrkDrAzalLhIUc01jO2mOzXGXh1JwPgkihcLLzw98c0WgYDmmjSh1Kl3wzaxSVWMuA+fe0WTWOBDWCBmNA== 323 | --------------------------------------------------------------------------------