├── .editorconfig ├── .gitignore ├── .jshintrc ├── Dockerfile ├── cache └── index.js ├── docker-compose.yml ├── handler.js ├── helpers ├── SignHelper.js ├── urls.js └── wsaa.js ├── package.json ├── postman └── afip_api.json.postman_collection ├── readme.md ├── routes ├── api │ └── endpoints.js └── index.js ├── server.js └── tools └── keygen.sh /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = false 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.dat 5 | *.out 6 | *.pid 7 | *.gz 8 | *.lock 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | cache/tokens 17 | 18 | .DS_Store 19 | 20 | keys 21 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "camelcase": false, 7 | "curly": true, 8 | "eqeqeq": false, 9 | "immed": true, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "regexp": true, 14 | "undef": true, 15 | "unused": true, 16 | "trailing": true, 17 | "smarttabs": true, 18 | "white": true, 19 | "globals": {}, 20 | "predef": [ 21 | "global", 22 | "module" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mhart/alpine-node:7 2 | RUN mkdir -p /usr/src/afip-api 3 | WORKDIR /usr/src/afip-api 4 | COPY package.json /usr/src/afip-api 5 | RUN apk --update add --virtual compile-deps \ 6 | g++ gcc libgcc libstdc++ linux-headers make python libressl-dev git && \ 7 | apk --update add libressl && \ 8 | npm install --quiet && \ 9 | apk del compile-deps 10 | COPY . /usr/src/afip-api 11 | EXPOSE 3000 12 | CMD ["npm", "run", "dev"] 13 | -------------------------------------------------------------------------------- /cache/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'), 2 | path = require('path'); 3 | 4 | class Cache { 5 | constructor(name) { 6 | this.storePath = path.join('.', 'cache', `${name}`); 7 | } 8 | 9 | getItem(key) { 10 | let obj = fs.readFileSync(this.storePath, 'utf8'); 11 | 12 | if (obj && obj != '') { 13 | return JSON.parse(obj); 14 | } 15 | 16 | return false; 17 | } 18 | 19 | setItem(key, value) { 20 | if (value) { 21 | return fs.writeFileSync(this.storePath, JSON.stringify(value)); 22 | } 23 | 24 | return false; 25 | } 26 | 27 | clear() { 28 | return fs.unlink(this.storePath); 29 | } 30 | } 31 | 32 | module.exports = Cache; 33 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | services: 3 | afip-api: 4 | container_name: afip-api 5 | restart: always 6 | build: . 7 | ports: 8 | - "3000:3000" 9 | -------------------------------------------------------------------------------- /handler.js: -------------------------------------------------------------------------------- 1 | // Going Serverless :) 2 | const serverless = require('serverless-http'), 3 | restana = require('restana') 4 | 5 | // creating service 6 | const app = restana(); 7 | 8 | // Start Routes 9 | index(app, true); 10 | 11 | // lambda integration 12 | const handler = serverless(app); 13 | 14 | // Export the handler 15 | module.exports.handler = async (event, context) => { 16 | return await handler(event, context) 17 | }; 18 | -------------------------------------------------------------------------------- /helpers/SignHelper.js: -------------------------------------------------------------------------------- 1 | var util = require('util'), 2 | spawn = require('child_process').spawn; 3 | 4 | // Expose methods. 5 | exports.sign = sign; 6 | 7 | /** 8 | * Sign a file. 9 | * 10 | * @param {object} options Options 11 | * @param {string} options.key Key path 12 | * @param {string} options.cert Cert path 13 | * @param {string} [options.password] Key password 14 | * @returns {Promise} result Result 15 | */ 16 | 17 | function sign(options) { 18 | return new Promise(function (resolve, reject) { 19 | options = options || {}; 20 | 21 | if (!options.content) 22 | reject('Invalid content.'); 23 | 24 | if (!options.key) 25 | reject('Invalid key.'); 26 | 27 | if (!options.cert) 28 | reject('Invalid certificate.'); 29 | 30 | var command = util.format( 31 | 'openssl smime -sign -signer %s -inkey %s -outform DER -nodetach', 32 | options.cert, 33 | options.key 34 | ); 35 | 36 | if (options.password) 37 | command += util.format(' -passin pass:%s', options.password); 38 | 39 | var args = command.split(' '); 40 | var child = spawn(args[0], args.splice(1), { encoding: 'base64' }); 41 | 42 | var der = []; 43 | 44 | child.stdout.on('data', function (chunk) { 45 | der.push(chunk); 46 | }); 47 | 48 | child.on('close', function (code) { 49 | if (code !== 0) { 50 | reject(new Error('Process failed.')); 51 | } else { 52 | resolve(Buffer.concat(der).toString('base64')); 53 | } 54 | }); 55 | 56 | child.stdin.write(options.content); //.replace(/["']/g, '\\"') 57 | child.stdin.end(); 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /helpers/urls.js: -------------------------------------------------------------------------------- 1 | const afip_urls = { 2 | HOMO: { 3 | wsaa: 'https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl', 4 | service: 'https://wswhomo.afip.gov.ar/{service}/service.asmx?wsdl' //wsfev1 5 | }, 6 | PROD: { 7 | wsaa: 'https://wsaa.afip.gov.ar/ws/services/LoginCms?wsdl', 8 | service: 'https://servicios1.afip.gov.ar/{service}/service.asmx?WSDL' //wsfev1 9 | } 10 | }; 11 | 12 | class AfipUrls { 13 | constructor() { 14 | this.urls = afip_urls.HOMO; 15 | 16 | if (!process.env.HOMO) { 17 | this.urls = afip_urls.PROD; 18 | } 19 | } 20 | 21 | getWSAA() { 22 | return this.urls.wsaa; 23 | } 24 | 25 | getService(service) { 26 | return this.urls.service.replace('{service}', service); 27 | } 28 | } 29 | 30 | module.exports = new AfipUrls(); 31 | -------------------------------------------------------------------------------- /helpers/wsaa.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'), 2 | soap = require('soap'), 3 | moment = require('moment'), 4 | xml2js = require('xml2js'), 5 | parseString = xml2js.parseString, 6 | ntpClient = require('ntp-client'), 7 | SignHelper = require('./SignHelper'), 8 | AfipURLs = require('./urls'), 9 | Cache = require('../cache'); 10 | 11 | class Tokens { 12 | constructor() { 13 | this.privateKey = fs.readFileSync(global.keys.private, 'utf8'); 14 | this.publicKey = fs.readFileSync(global.keys.public, 'utf8'); 15 | 16 | this.client = false; 17 | 18 | this.cache = new Cache('tokens'); 19 | } 20 | 21 | createClient() { 22 | return new Promise((resolve, reject) => { 23 | if (this.client) { 24 | resolve(this.client); 25 | } else { 26 | soap.createClient(AfipURLs.getWSAA(), (err, client) => { 27 | if (err && !client) { 28 | reject(); 29 | } else { 30 | this.client = client; 31 | 32 | resolve(this.client); 33 | } 34 | 35 | }); 36 | } 37 | }); 38 | } 39 | 40 | isExpired(service) { 41 | try { 42 | const cachedService = this.cache.getItem(service); 43 | if (cachedService && cachedService.date) { 44 | var hours = Math.abs((new Date()) - cachedService.date) / 36e5; 45 | 46 | return (hours > 12); 47 | } else { 48 | return true; 49 | } 50 | } catch (e) { 51 | return true; 52 | } 53 | } 54 | 55 | getCurrentTime() { 56 | return new Promise((resolve, reject) => { 57 | ntpClient.getNetworkTime("time.afip.gov.ar", 123, function (err, date) { 58 | if (err) { 59 | reject(err); 60 | } else { 61 | console.log("Current time: ", date); 62 | resolve(date); 63 | } 64 | }); 65 | }); 66 | } 67 | 68 | openssl_pkcs7_sign(data, callback) { 69 | SignHelper.sign({ 70 | content: data, 71 | key: global.keys.private, 72 | cert: global.keys.public 73 | }).catch(function (err) { 74 | callback(err); 75 | }).then(function (result) { 76 | callback(null, result); 77 | }); 78 | } 79 | 80 | encryptXML(xml) { 81 | return new Promise((resolve) => { 82 | this.openssl_pkcs7_sign(xml, (err, enc) => { 83 | resolve(enc); 84 | }); 85 | }); 86 | } 87 | 88 | parseXML(data) { 89 | return new Promise((resolve, reject) => { 90 | parseString(data, { 91 | normalizeTags: true, 92 | normalize: true, 93 | explicitArray: false, 94 | attrkey: 'header', 95 | tagNameProcessors: [(key) => { return key.replace('soapenv:', ''); }] 96 | }, (err, res) => { 97 | if (err) reject(err); 98 | else resolve(res); 99 | }); 100 | }); 101 | } 102 | 103 | formatDate(date) { 104 | return moment(date).format().replace('-03:00', ''); 105 | } 106 | 107 | generateCMS(service) { 108 | return new Promise((resolve, reject) => { 109 | this.getCurrentTime().then((date) => { 110 | var tomorrow = new Date(); 111 | 112 | // add a day 113 | tomorrow.setDate(date.getDate() + 1); 114 | 115 | tomorrow.setMinutes(date.getMinutes()); 116 | 117 | var xml = `
{uniqueId}{generationTime}{expirationTime}
{service}
`; 118 | 119 | xml = xml.replace('{uniqueId}', moment().format('X')); 120 | xml = xml.replace('{generationTime}', this.formatDate(date)); 121 | xml = xml.replace('{expirationTime}', this.formatDate(tomorrow)); 122 | xml = xml.replace('{service}', service); 123 | 124 | xml = xml.trim(); 125 | 126 | this.encryptXML(xml).then(resolve).catch(reject); 127 | }); 128 | }); 129 | } 130 | 131 | generateToken(service, refresh = false) { 132 | // Parse some of the Services 133 | if (service == 'wsfev1') { 134 | service = 'wsfe'; 135 | } 136 | 137 | return new Promise((resolve, reject) => { 138 | 139 | if (this.isExpired(service) || refresh === true) { 140 | 141 | this.createClient().then((client) => { 142 | 143 | this.generateCMS(service).then((data) => { 144 | client.loginCms({ 145 | in0: data 146 | }, (err, result, raw, soapHeader) => { 147 | this.parseXML(raw).then((res) => { 148 | //console.info(res.envelope.body); 149 | var xml_response = res.envelope.body.logincmsresponse.logincmsreturn; 150 | 151 | if (xml_response) { 152 | this.parseXML(xml_response).then((res) => { 153 | //console.info(res.loginticketresponse.header); 154 | var credentials = res.loginticketresponse.credentials; 155 | 156 | this.cache.setItem(service, { 157 | date: new Date(), 158 | credentials: credentials 159 | }); 160 | 161 | resolve(credentials); 162 | }).catch(reject); 163 | } else { 164 | reject(res.envelope.body.fault); 165 | } 166 | }).catch(reject); 167 | }); 168 | }); 169 | 170 | }); 171 | 172 | } else { 173 | resolve(this.cache.getItem(service).credentials); 174 | } 175 | 176 | }); 177 | } 178 | } 179 | 180 | module.exports = new Tokens(); 181 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "afipapi", 3 | "version": "0.7.0", 4 | "dependencies": { 5 | "body-parser": "1.19.0", 6 | "cors": "2.8.5", 7 | "moment": "2.24.0", 8 | "ntp-client": "0.5.3", 9 | "restana": "3.3.1", 10 | "serverless-http": "2.3.0", 11 | "soap": "0.29.0", 12 | "xml2js": "0.4.19" 13 | }, 14 | "engines": { 15 | "node": ">=10.0.0", 16 | "npm": ">=6.0.0" 17 | }, 18 | "scripts": { 19 | "start": "node server.js", 20 | "debug": "nodemon --inspect=5858 server.js", 21 | "dev": "HOMO=true node server.js" 22 | }, 23 | "devDependencies": { 24 | "nodemon": "1.19.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /postman/afip_api.json.postman_collection: -------------------------------------------------------------------------------- 1 | { 2 | "id": "95f14553-ca37-2ebf-3835-12f014e2c776", 3 | "name": "CASFe", 4 | "description": "", 5 | "order": [ 6 | "d4e8cf61-01c4-5103-244b-3086b8ea54a5", 7 | "a042db86-2b19-4141-3083-484d0c2ea3e4", 8 | "108308a8-40a6-87ec-4bc6-42705468d8b8", 9 | "a0c496e8-ba18-6900-e8ff-db77adf87aef" 10 | ], 11 | "folders": [], 12 | "timestamp": 0, 13 | "owner": "4534", 14 | "public": false, 15 | "requests": [ 16 | { 17 | "id": "108308a8-40a6-87ec-4bc6-42705468d8b8", 18 | "headers": "", 19 | "url": "http://localhost:5000/api/wsfev1/FEParamGetTiposTributos", 20 | "preRequestScript": null, 21 | "pathVariables": {}, 22 | "method": "POST", 23 | "data": [ 24 | { 25 | "key": "auth[key]", 26 | "value": "Auth", 27 | "type": "text", 28 | "enabled": true, 29 | "warning": "" 30 | }, 31 | { 32 | "key": "params[Auth][Cuit]", 33 | "value": "00000000000", 34 | "type": "text", 35 | "enabled": true, 36 | "warning": "" 37 | }, 38 | { 39 | "key": "auth[token]", 40 | "value": "Token", 41 | "type": "text", 42 | "enabled": true, 43 | "warning": "" 44 | }, 45 | { 46 | "key": "auth[sign]", 47 | "value": "Sign", 48 | "type": "text", 49 | "enabled": true, 50 | "warning": "" 51 | } 52 | ], 53 | "dataMode": "urlencoded", 54 | "tests": null, 55 | "currentHelper": "normal", 56 | "helperAttributes": {}, 57 | "time": 1481653556489, 58 | "name": "Endpoint FEParamGetTiposTributos", 59 | "description": "", 60 | "collectionId": "95f14553-ca37-2ebf-3835-12f014e2c776", 61 | "responses": [] 62 | }, 63 | { 64 | "folder": null, 65 | "id": "a042db86-2b19-4141-3083-484d0c2ea3e4", 66 | "name": "Endpoint FEDummy", 67 | "dataMode": "urlencoded", 68 | "data": [ 69 | { 70 | "key": "auth[key]", 71 | "value": "Auth", 72 | "type": "text", 73 | "enabled": true 74 | }, 75 | { 76 | "key": "Auth[Cuit]", 77 | "value": "00000000000", 78 | "type": "text", 79 | "enabled": true 80 | } 81 | ], 82 | "rawModeData": null, 83 | "descriptionFormat": "html", 84 | "description": "", 85 | "headers": "", 86 | "method": "POST", 87 | "pathVariables": {}, 88 | "url": "http://localhost:3000/api/wsfev1/FEDummy", 89 | "preRequestScript": null, 90 | "tests": null, 91 | "currentHelper": "normal", 92 | "helperAttributes": "{}", 93 | "responses": [], 94 | "collectionId": "95f14553-ca37-2ebf-3835-12f014e2c776" 95 | }, 96 | { 97 | "id": "a0c496e8-ba18-6900-e8ff-db77adf87aef", 98 | "headers": "", 99 | "url": "http://localhost:5000/api/wsfev1/FECAEASolicitar", 100 | "pathVariables": {}, 101 | "preRequestScript": null, 102 | "method": "POST", 103 | "collectionId": "95f14553-ca37-2ebf-3835-12f014e2c776", 104 | "data": [ 105 | { 106 | "key": "auth[key]", 107 | "value": "Auth", 108 | "type": "text", 109 | "enabled": true, 110 | "warning": "" 111 | }, 112 | { 113 | "key": "params[Auth][Cuit]", 114 | "value": "00000000000", 115 | "type": "text", 116 | "enabled": true, 117 | "warning": "" 118 | }, 119 | { 120 | "key": "auth[token]", 121 | "value": "Token", 122 | "type": "text", 123 | "enabled": true, 124 | "warning": "" 125 | }, 126 | { 127 | "key": "auth[sign]", 128 | "value": "Sign", 129 | "type": "text", 130 | "enabled": true, 131 | "warning": "" 132 | } 133 | ], 134 | "dataMode": "urlencoded", 135 | "name": "Endpoint FECAEASolicitar", 136 | "description": "", 137 | "descriptionFormat": "html", 138 | "time": 1481653751667, 139 | "version": 2, 140 | "responses": [], 141 | "tests": null, 142 | "currentHelper": "normal", 143 | "helperAttributes": {} 144 | }, 145 | { 146 | "folder": null, 147 | "id": "d4e8cf61-01c4-5103-244b-3086b8ea54a5", 148 | "name": "http://localhost:5060/api/wsfev1/describe", 149 | "dataMode": "urlencoded", 150 | "data": [ 151 | { 152 | "key": "auth[key]", 153 | "value": "Auth", 154 | "type": "text", 155 | "enabled": true 156 | }, 157 | { 158 | "key": "Auth[Cuit]", 159 | "value": "00000000000", 160 | "type": "text", 161 | "enabled": true 162 | } 163 | ], 164 | "rawModeData": null, 165 | "descriptionFormat": "html", 166 | "description": "", 167 | "headers": "", 168 | "method": "GET", 169 | "pathVariables": {}, 170 | "url": "http://localhost:3000/api/wsfev1/describe", 171 | "preRequestScript": null, 172 | "tests": null, 173 | "currentHelper": "normal", 174 | "helperAttributes": "{}", 175 | "responses": [], 176 | "collectionId": "95f14553-ca37-2ebf-3835-12f014e2c776" 177 | } 178 | ] 179 | } 180 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | # AFIP API simplificada 3 | 4 | La función principal de este API es simplificar el acceso a los WebServices de AFIP y principalmente Factura Electrónica. Para generar los certificados y darse de alta en el Servicio de Homologación (Pruebas) usar esta web: [AFIP WS](http://www.afip.gob.ar/ws) 5 | 6 | ### Pasos para hacer funcionar el API 7 | 1) Desde el root ```npm install``` 8 | 2) Desde el root correr ```./tools/keygen.sh /C=AR/O=Nombre Desarrollador/CN=Nombre Desarrollador/serialNumber=CUIT 00000000000``` 9 | 3) Correr la app 10 | 3a) Para Homologación: ```HOMO=true node server.js``` 11 | 3b) Para Producción: ```node server.js``` 12 | 13 | 14 | ### Endpoints 15 | > Para probar los endpoints que genera el API se proveen ejemplos con el API WSFEv1 mediante postman (Descarga: https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop ) 16 | 17 | 1) Luego de descargar Postman importar el archivo que se encuentra en la carpeta "postman" 18 | 1) Para aquellos Endpoints que requiren CUIT Revisar los parametros Body y cambiar CUIT 19 | 20 | 21 | ### Cómo funcionan los endpoints 22 | > La idea del API es hacer genéricas las llamadas y preservar la autenticación obtenida 23 | 24 | 1) Describir el endpoint: ```/api/aqui_servicio/describe```. Ej. de Servicio: ```wsfev1``` 25 | 2) Para realizar llamado ```/api/aqui_servicio/aqui_metodo``` 26 | 2a) Servicio: ```wsfev1``` 27 | 2b) Método: ```FEDummy```. Puede ser cualquiera de los obtenidos mediante describe. 28 | 29 | Versiones: 30 | 31 | __0.7.0:__ 32 | - Se elimina Express y se lo reemplaza por Restana para mayor performance. 33 | - Se agrega un handler para poder ser usado en ambientes serverless ( Requiere cambiar el servicio de Cache en estos ambientes ) 34 | - La cache se basa en archivos y no librerias externas. 35 | - Se elimina Lodash. 36 | - El cambio a Restana permite utilizar HTTPS y HTTP/2 37 | - Mejoras en algunos métodos basadas en NodeJS 10+. 38 | - Se cambia la versión de Node a 10 como mínimo. 39 | - Se elimina la necesidad de Node-GYP 40 | -------------------------------------------------------------------------------- /routes/api/endpoints.js: -------------------------------------------------------------------------------- 1 | const soap = require('soap'), 2 | WSAA = require('../../helpers/wsaa'), 3 | AfipURLs = require('../../helpers/urls'); 4 | 5 | class Endpoints { 6 | 7 | constructor(app) { 8 | app.get('/api/:service/describe', this.describe.bind(this)); 9 | 10 | app.post('/api/:service/refresh/token', this.recreate_token.bind(this)); 11 | 12 | app.post('/api/:service/:endpoint', this.endpoint.bind(this)); 13 | 14 | this.clients = {}; 15 | } 16 | 17 | createClientForService(service) { 18 | return new Promise((resolve, reject) => { 19 | if (this.clients[service]) { 20 | resolve(this.clients[service]); 21 | } else { 22 | soap.createClient(AfipURLs.getService(service), (err, client) => { 23 | if (err && !client) { 24 | reject(err); 25 | } else { 26 | this.clients[service] = client; 27 | 28 | resolve(client); 29 | } 30 | }); 31 | } 32 | }); 33 | } 34 | 35 | recreate_token(req, res) { 36 | var service = req.params.service; 37 | 38 | WSAA.generateToken(service) 39 | .then((tokens) => res.send(tokens)) 40 | .catch((err) => { 41 | res.send({ 42 | result: false, 43 | err: err.message 44 | }); 45 | }); 46 | } 47 | 48 | endpoint(req, res) { 49 | var service = req.params.service; 50 | var endpoint = req.params.endpoint; 51 | 52 | WSAA.generateToken(service).then((tokens) => { 53 | 54 | this.createClientForService(service).then((client) => { 55 | var params = { ...req.body.params }; 56 | console.info(req.body); 57 | 58 | params[`${req.body.auth.key}`] = { 59 | //Token: tokens.token, 60 | //Sign: tokens.sign 61 | }; 62 | 63 | params[`${req.body.auth.key}`][`${req.body.auth.token}`] = tokens.token; 64 | params[`${req.body.auth.key}`][`${req.body.auth.sign}`] = tokens.sign; 65 | 66 | console.info(params); 67 | 68 | client[endpoint](params, (err, result) => { 69 | try { 70 | res.send(result[`${endpoint}Result`]); 71 | } catch (e) { 72 | res.send(result); 73 | } 74 | }); 75 | }).catch(err => { 76 | console.info(err); 77 | res.send({ result: false }); 78 | }); 79 | 80 | }).catch((err) => { 81 | res.send({ 82 | result: false, 83 | err: err.message 84 | }); 85 | }); 86 | } 87 | 88 | describe(req, res) { 89 | var service = req.params.service; 90 | 91 | WSAA.generateToken(service).then((tokens) => { 92 | 93 | this.createClientForService(service).then((client) => { 94 | res.send(client.describe()); 95 | }); 96 | 97 | }).catch((err) => { 98 | res.send({ 99 | result: false, 100 | err: err.message 101 | }); 102 | }); 103 | } 104 | 105 | } 106 | 107 | module.exports = Endpoints; 108 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | const cors = require('cors'), 2 | bodyParser = require('body-parser'); 3 | 4 | // Setup Route Bindings 5 | exports = module.exports = (app, isServerless = false) => { 6 | !isServerless && app.use(cors()); 7 | 8 | // Add Middlewares 9 | app.use(bodyParser.json()); 10 | app.use(bodyParser.urlencoded({ extended: true })); 11 | 12 | app.get('/', (req, res) => { 13 | res.end(':D'); 14 | }); 15 | 16 | // API 17 | var Endpoint = require('./api/endpoints'); 18 | new Endpoint(app); 19 | }; 20 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const restana = require('restana'), 2 | path = require('path'), 3 | index = require('./routes/index'); 4 | 5 | global.keys = { 6 | private: path.join(__dirname, 'keys', 'afip.key'), 7 | public: path.join(__dirname, 'keys', 'afip.pem') 8 | }; 9 | (async function () { 10 | // Lets create Restana 11 | // This is the HTTP Version check next links to upgrde to HTTPS or even HTTP/2 12 | // HTTPS: https://github.com/jkyberneees/ana/blob/master/demos/https-service.js 13 | // HTTP/2: https://github.com/jkyberneees/ana/blob/master/demos/http2-service.js 14 | const app = restana(); 15 | 16 | // Start Routes 17 | index(app, false); 18 | 19 | await app.start(process.env.PORT || 3000); 20 | 21 | console.log( 22 | "AFIP API Corriendo en el puerto " + 23 | (process.env.PORT || 3000) 24 | ); 25 | })() 26 | -------------------------------------------------------------------------------- /tools/keygen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir keys 4 | 5 | openssl genrsa -out keys/afip.key 2048 6 | 7 | openssl req -new -key keys/afip.key -subj $1 -out keys/afip.csr 8 | --------------------------------------------------------------------------------