├── .editorconfig ├── .gitignore ├── LICENCE ├── README.md ├── index.js ├── lib ├── eet.js ├── util.js ├── validate.js └── ws-security.js ├── package.json ├── test ├── keys │ ├── certificate.pem │ └── private.pem └── tests.js └── wsdl ├── EETXMLSchema.xsd └── WSDL.wsdl /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [package.json] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.md] 17 | trim_trailing_whitespace = false 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Jakub Mrozek 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Balíček je archivován a už neplánuji jeho správu. 2 | 3 | --- 4 | 5 | # eet 6 | 7 | Node.js knihovna pro EET ([elektronickou evidenci tržeb](http://www.etrzby.cz/cs/technicka-specifikace)). 8 | 9 | ## Instalace 10 | 11 | Je nutné mít nainstalovaný Node.js v4+. 12 | 13 | ``` 14 | npm install eet 15 | ``` 16 | 17 | ## Příklad 18 | 19 | ```javascript 20 | const eet = require('eet') 21 | 22 | // privatni klic a certifikat podnikatele 23 | const options = { 24 | privateKey: '...', 25 | certificate: '...', 26 | playground: true 27 | } 28 | 29 | // polozky, ktere se posilaji do EET 30 | const items = { 31 | dicPopl: 'CZ1212121218', 32 | idPokl: '/5546/RO24', 33 | poradCis: '0/6460/ZQ42', 34 | datTrzby: new Date(), 35 | celkTrzba: 34113, 36 | idProvoz: '273' 37 | } 38 | 39 | // ziskani FIK (kod uctenky) pomoci async/await (Node.js 8+ / Babel) 40 | const {fik} = await eet(options, items) 41 | 42 | // ziskani FIK v Node.js 6+ 43 | eet(options, items).then(response => { 44 | // response.fik 45 | }) 46 | ``` 47 | 48 | ## Převod .p12 na .pem 49 | 50 | Balíček pracuje s klíči v textovém formátu, z binárního .p12 je lze převést např. pomocí balíčku [pem](https://github.com/andris9/pem): 51 | 52 | ```javascript 53 | // npm install pem 54 | const pem = require('pem') 55 | 56 | const file = require('fs').readFileSync('cesta/k/souboru.p12') 57 | const password = '' //pro testovací certifikáty EET je heslo 'eet' 58 | 59 | pem.readPkcs12(file, {p12Password: password}, (err, result) => { 60 | if (err) ... 61 | // result.key je privátní klíč 62 | // result.cert je certifikát 63 | }) 64 | 65 | ``` 66 | 67 | ## Nastavení 68 | 69 | ### eet (options, items) 70 | 71 | * *options* - Volby pro odesílání požadavku (pro SOAP). 72 | * *options.privateKey* (string) - Privátní klíč. 73 | * *options.certificate* (string) - Certifikát. 74 | * *options.playground* (bool) - Posílat požadavky na playground? Def. false (ne). 75 | * *options.httpClient* - Viz [soap options](https://github.com/vpulim/node-soap#options), slouží pro testování. 76 | * *options.timeout* (number) - Nastavení max. timeoutu (defaultně 2000 ms) 77 | * *options.offline* (bool) - Do chybové hlášky vkládat PKP a BKP 78 | * *items* - Položky, které se posílají do EET. Mají stejný název jako ve specifikaci EET, jen používají cammel case (tedy místo dic_popl se používá dicPopl). 79 | 80 | ## Časté chyby 81 | 82 | ### Neplatny podpis SOAP zpravy (4) 83 | 84 | Na 99% půjde o problém s certifikátem, více je popsáno v issue [#1](https://github.com/JakubMrozek/eet/issues/1#issuecomment-256877574). 85 | 86 | 87 | ## Changelog 88 | 89 | ### v0.8 (3. 9. 2019) 90 | - upgrade balíčku soap na nejnovější verzi (podpora Node.js 12+) 91 | 92 | ### v0.7 (6. 3. 2017) 93 | - vrácena podpora pro Node.js v4 ([#16](https://github.com/JakubMrozek/eet/pull/16)) 94 | - oprava regulárního výrazu pro kontrolu formátu pokladny ([#13](https://github.com/JakubMrozek/eet/pull/13)) 95 | 96 | ### v0.6 (6. 2. 2017) 97 | - doplněna volba `options.offline` 98 | - balíček uuid aktualizován na 3.0 99 | 100 | ### v0.5 (2. 12. 2016) + v0.5.1 101 | - doplněna možnost určit timeout 102 | 103 | ### v0.4 (13. 11. 2016) 104 | - oprava generování PKP ([#6](https://github.com/JakubMrozek/eet/issues/6)) 105 | - privátní klíč není potřeba převádět na buffer ([#4](https://github.com/JakubMrozek/eet/pull/4)) 106 | 107 | ### v0.3 (13. 11. 2016) 108 | - doplněny validace 109 | - v odpovědi se vrací warningy 110 | 111 | ### v0.2 (30. 10. 2016) 112 | - podpora verze Node.js 4+ 113 | - doplněna dokumentace (časté chyby a převod z .p12 na .pem pomocí balíčku `pem`) 114 | 115 | ### v0.1 (20. 10. 2016) 116 | - první veřejná verze 117 | 118 | ## Licence 119 | 120 | MIT 121 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const doRequest = require('./lib/eet').doRequest 2 | module.exports = doRequest 3 | -------------------------------------------------------------------------------- /lib/eet.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const crypto = require('crypto') 3 | const path = require('path') 4 | const soap = require('soap') 5 | const uuid = require('uuid') 6 | const WSSecurity = require('./ws-security') 7 | const util = require('./util') 8 | const validate = require('./validate') 9 | 10 | // node v4 11 | const formatDate = util.formatDate 12 | const formatBool = util.formatBool 13 | const formatNumber = util.formatNumber 14 | const isDefinedAndNotNull = util.isDefinedAndNotNull 15 | 16 | const WSDL = path.join(__dirname, '..', 'wsdl/WSDL.wsdl') 17 | const PG_WSDL_URL = 'https://pg.eet.cz:443/eet/services/EETServiceSOAP/v3/' 18 | 19 | /** 20 | * Vygeneruje podpisovy kod poplatnika. 21 | * 22 | * @see http://www.etrzby.cz/assets/cs/prilohy/EET_popis_rozhrani_v3.1.1.pdf (sekce 4.1) 23 | * 24 | */ 25 | function generatePKP (privateKey, dicPopl, idProvoz, idPokl, poradCis, datTrzby, celkTrzba) { 26 | const options = [dicPopl, idProvoz, idPokl, poradCis, datTrzby, celkTrzba] 27 | const strToHash = options.join('|') 28 | const sign = crypto.createSign('RSA-SHA256') 29 | sign.write(strToHash) 30 | sign.end() 31 | return sign.sign(privateKey, 'base64') 32 | } 33 | 34 | /** 35 | * Vygeneruje bezpecnostni kod poplatnika. 36 | * 37 | * @see http://www.etrzby.cz/assets/cs/prilohy/EET_popis_rozhrani_v3.1.1.pdf (sekce 4.2) 38 | * 39 | */ 40 | function generateBKP (pkp) { 41 | const buffer = new Buffer(pkp, 'base64') 42 | const hash = crypto.createHash('sha1') 43 | hash.update(buffer) 44 | const sha1str = hash.digest('hex').toUpperCase() 45 | return sha1str.match(/(.{1,8})/g).join('-') 46 | } 47 | 48 | /** 49 | * Odeslani platby do EET. 50 | * 51 | * Volby: 52 | * - options.playground (boolean): povolit testovaci prostredi, def. false 53 | * - options.offline (boolean): při chybě vygeneruje PKP a BKP, def. false 54 | 55 | * Polozky (items) jsou popsany u funkce getItemsForBody(). 56 | * 57 | * Vraci Promise. Pokud je vse v poradku, vrati FIK, v opacnem pripade vraci info o chybe. 58 | */ 59 | function doRequest (options, items) { 60 | const uid = options.uid || uuid.v4() 61 | const date = options.currentDate || new Date() 62 | const soapOptions = {} 63 | if (options.playground) { 64 | soapOptions.endpoint = PG_WSDL_URL 65 | } 66 | if (options.httpClient) { 67 | soapOptions.httpClient = options.httpClient 68 | } 69 | const timeout = options.timeout || 2000 70 | const offline = options.offline || false 71 | 72 | return new Promise((resolve, reject) => { 73 | const body = getBodyItems(options.privateKey, date, uid, items) 74 | soap.createClient(WSDL, soapOptions, (err, client) => { 75 | if (err) return reject(err) 76 | client.setSecurity(new WSSecurity(options.privateKey, options.certificate, uid)) 77 | client.OdeslaniTrzby(body, (err, response) => { 78 | if (err) return reject(err) 79 | try { 80 | validate.httpResponse(response) 81 | resolve(getResponseItems(response)) 82 | } catch (e) { 83 | reject(e) 84 | } 85 | }, {timeout: timeout}) 86 | }) 87 | }).catch(err => { 88 | if (!offline) return Promise.reject(err) 89 | let code = getFooterItems(options.privateKey, items) 90 | let bkp = code.bkp 91 | let pkp = code.pkp 92 | return Promise.resolve({pkp: pkp.$value, bkp: bkp.$value, err}) 93 | }) 94 | } 95 | 96 | /** 97 | * Vrati vsechny polozky pro obsah SOAP body. 98 | * 99 | * Polozky: 100 | * 101 | */ 102 | function getBodyItems (privateKey, currentDate, uid, items) { 103 | return { 104 | Hlavicka: getHeaderItems(uid, currentDate, items.prvniZaslani, items.overeni), 105 | Data: getDataItems(items), 106 | KontrolniKody: getFooterItems(privateKey, items) 107 | } 108 | } 109 | 110 | /** 111 | * Vygeneruje polozky pro element Hlavicka. 112 | * 113 | */ 114 | function getHeaderItems (uid, currentDate, prvniZaslani, overeni) { 115 | return { 116 | attributes: { 117 | uuid_zpravy: uid, 118 | dat_odesl: formatDate(currentDate), 119 | prvni_zaslani: formatBool(prvniZaslani, true), 120 | overeni: formatBool(overeni, false) 121 | } 122 | } 123 | } 124 | 125 | /** 126 | * Vygeneruje položky pro XML element Data. 127 | * 128 | */ 129 | function getDataItems (items) { 130 | validate.requiredItems(items) 131 | 132 | const data = {} 133 | validate.vatIdNumber(items.dicPopl) 134 | data.dic_popl = items.dicPopl 135 | 136 | validate.businessPremisesId(items.idProvoz) 137 | data.id_provoz = items.idProvoz 138 | 139 | validate.cashRegisterId(items.idPokl) 140 | data.id_pokl = items.idPokl 141 | 142 | validate.receiptNumber(items.poradCis) 143 | data.porad_cis = items.poradCis 144 | 145 | validate.date(items.datTrzby) 146 | data.dat_trzby = formatDate(items.datTrzby) 147 | 148 | if (isDefinedAndNotNull(items.rezim)) { 149 | validate.regime(items.rezim) 150 | data.rezim = items.rezim 151 | } else { 152 | data.rezim = 0 153 | } 154 | 155 | if (isDefinedAndNotNull(items.dicPoverujiciho)) { 156 | validate.vatIdNumber(items.dicPoverujiciho) 157 | data.dic_poverujiciho = items.dicPoverujiciho 158 | } 159 | 160 | validate.financialNumber(items.celkTrzba) 161 | data.celk_trzba = formatNumber(items.celkTrzba) 162 | 163 | const map = { 164 | zaklNepodlDph: 'zakl_nepodl_dph', 165 | zaklDan1: 'zakl_dan1', 166 | dan1: 'dan1', 167 | zaklDan2: 'zakl_dan2', 168 | dan2: 'dan2', 169 | zaklDan3: 'zakl_dan3', 170 | dan3: 'dan3', 171 | cestSluz: 'cest_sluz', 172 | pouzitZboz1: 'pouzit_zboz1', 173 | pouzitZboz2: 'pouzit_zboz2', 174 | pouzitZboz3: 'pouzit_zboz3', 175 | urcenoCerpZuct: 'urceno_cerp_zuct', 176 | cerpZuct: 'cerp_zuct' 177 | } 178 | Object.keys(map) 179 | .filter(key => isDefinedAndNotNull(items[key])) 180 | .forEach(key => { 181 | validate.financialNumber(items[key]) 182 | data[map[key]] = formatNumber(items[key]) 183 | }) 184 | 185 | return { 186 | attributes: data 187 | } 188 | } 189 | 190 | /** 191 | * Vygeneruje polozky pro element KontrolniKody. 192 | * 193 | */ 194 | function getFooterItems (privateKey, items) { 195 | const pkp = generatePKP( 196 | privateKey, 197 | items.dicPopl, 198 | items.idProvoz, 199 | items.idPokl, 200 | items.poradCis, 201 | formatDate(items.datTrzby), 202 | formatNumber(items.celkTrzba) 203 | ) 204 | const bkp = generateBKP(pkp) 205 | 206 | return { 207 | pkp: { 208 | attributes: { 209 | digest: 'SHA256', 210 | cipher: 'RSA2048', 211 | encoding: 'base64' 212 | }, 213 | $value: pkp 214 | }, 215 | bkp: { 216 | attributes: { 217 | digest: 'SHA1', 218 | encoding: 'base16' 219 | }, 220 | $value: bkp 221 | } 222 | } 223 | } 224 | 225 | /** 226 | * Zpracuje OK odpoved ze serveru EET. 227 | * 228 | */ 229 | function getResponseItems (response) { 230 | const header = response.Hlavicka.attributes 231 | const body = response.Potvrzeni.attributes 232 | return { 233 | uuid: header.uuid_zpravy, 234 | bkp: header.bkp, 235 | date: new Date(header.dat_prij), 236 | test: body.test === 'true', 237 | fik: body.fik, 238 | warnings: getWarnings(response.Varovani) 239 | } 240 | } 241 | 242 | function getWarnings (warnings) { 243 | if (!warnings || !warnings.length) { 244 | return [] 245 | } 246 | return warnings.map(warning => warning.$value) 247 | } 248 | 249 | exports.generatePKP = generatePKP 250 | exports.generateBKP = generateBKP 251 | exports.doRequest = doRequest 252 | exports.getBodyItems = getBodyItems 253 | exports.getHeaderItems = getHeaderItems 254 | exports.getDataItems = getDataItems 255 | exports.getFooterItems = getFooterItems 256 | exports.getResponseItems = getResponseItems 257 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /** 3 | * Prevede objekt Date na retezec. 4 | * 5 | * Datum je potreba prevest na ISO 8601, je ale potreba dat pryc ms, 6 | * protoze jinak vraci EET servery chybu "spatny format". 7 | * 8 | */ 9 | function formatDate (date) { 10 | return date.toISOString().split('.')[0] + 'Z' 11 | } 12 | 13 | function formatBool (value, defaultValue) { 14 | if (value === undefined) { 15 | value = defaultValue 16 | } 17 | return value ? 'true' : 'false' 18 | } 19 | 20 | function formatNumber (num) { 21 | return !isNaN(+num) ? (+num).toFixed(2) : num 22 | } 23 | 24 | function isDefinedAndNotNull (value) { 25 | return value !== undefined && value !== null 26 | } 27 | 28 | exports.formatDate = formatDate 29 | exports.formatBool = formatBool 30 | exports.formatNumber = formatNumber 31 | exports.isDefinedAndNotNull = isDefinedAndNotNull 32 | -------------------------------------------------------------------------------- /lib/validate.js: -------------------------------------------------------------------------------- 1 | const util = require('./util') 2 | const isDefinedAndNotNull = util.isDefinedAndNotNull 3 | 4 | /** 5 | * Zkontroluje, zda jsou zadany všechny povinné položky. 6 | * 7 | */ 8 | function requiredItems (items) { 9 | [ 10 | 'dicPopl', 11 | 'idPokl', 12 | 'idProvoz', 13 | 'poradCis', 14 | 'datTrzby', 15 | 'celkTrzba' 16 | ].forEach(item => { 17 | if (!isDefinedAndNotNull(items[item])) { 18 | throw new Error(`'${item}' is required.`) 19 | } 20 | }) 21 | } 22 | 23 | /** 24 | * Validace DIČ. 25 | * 26 | */ 27 | function vatIdNumber (value) { 28 | if (!/^CZ[0-9]{8,10}$/.test(value)) { 29 | throw new Error(`Value '${value}' doesn't match pattern for vat identification number.`) 30 | } 31 | } 32 | 33 | /** 34 | * Validace označení provozovny. 35 | * 36 | */ 37 | function businessPremisesId (value) { 38 | if (!/^[1-9][0-9]{0,5}$/.test(value)) { 39 | throw new Error(`Value '${value}' doesn't match pattern for business premises ID.`) 40 | } 41 | } 42 | 43 | /** 44 | * Validace označení pokladny. 45 | * 46 | */ 47 | function cashRegisterId (value) { 48 | if (!/^[0-9a-zA-Z\.,:;/#\-_ ]{1,20}$/.test(value)) { 49 | throw new Error(`Value '${value}' doesn't match pattern for cash register ID.`) 50 | } 51 | } 52 | 53 | /** 54 | * Validace čísla účtenky. 55 | * 56 | */ 57 | function receiptNumber (value) { 58 | if (!/^[0-9a-zA-Z\.,:;/#\-_]{1,20}$/.test(value)) { 59 | throw new Error(`Value '${value}' doesn't match pattern for serial number of receipt.`) 60 | } 61 | } 62 | 63 | /** 64 | * Validace data. 65 | * 66 | */ 67 | function date (value) { 68 | if (Object.prototype.toString.call(value) !== '[object Date]' || isNaN(value)) { 69 | throw new Error(`Value '${value}' is not a date object.`) 70 | } 71 | } 72 | 73 | /** 74 | * Validace režimu odeslání. 75 | * 76 | */ 77 | function regime (value) { 78 | if (!/^[01]$/.test(value)) { 79 | throw new Error(`Value '${value}' doesn't match pattern for sale regime.`) 80 | } 81 | } 82 | 83 | /** 84 | * Kontrola číselných hodnot. 85 | * 86 | */ 87 | function financialNumber (value) { 88 | const num = Number(value) 89 | if (value !== num || num < -99999999.99 || num > 99999999.99) { 90 | throw new Error(`Value '${value}' is not a valid number.`) 91 | } 92 | } 93 | 94 | /** 95 | * Zpracuje chybnou odpoved. 96 | * 97 | */ 98 | function httpResponse (response) { 99 | if (!response) { 100 | throw new Error('Unable to parse response.') 101 | } 102 | const errorAttrs = response.Chyba && response.Chyba.attributes 103 | if (errorAttrs) { 104 | throw new Error(`${response.Chyba.$value} (${errorAttrs.kod})`) 105 | } 106 | const body = response.Potvrzeni && response.Potvrzeni.attributes 107 | const header = response.Hlavicka && response.Hlavicka.attributes 108 | if (!body || !header) { 109 | throw new Error('Unable to read response.') 110 | } 111 | } 112 | 113 | exports.requiredItems = requiredItems 114 | exports.vatIdNumber = vatIdNumber 115 | exports.businessPremisesId = businessPremisesId 116 | exports.cashRegisterId = cashRegisterId 117 | exports.receiptNumber = receiptNumber 118 | exports.date = date 119 | exports.regime = regime 120 | exports.financialNumber = financialNumber 121 | exports.httpResponse = httpResponse 122 | -------------------------------------------------------------------------------- /lib/ws-security.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const SignedXml = require('xml-crypto').SignedXml 3 | 4 | const SIGNATURE_ALGORITHM = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' 5 | const TRANSFORM_ALGORITHMS = [ 6 | 'http://www.w3.org/2000/09/xmldsig#enveloped-signature', 7 | 'http://www.w3.org/2001/10/xml-exc-c14n#' 8 | ] 9 | const DIGEST_ALGORITHM = 'http://www.w3.org/2001/04/xmlenc#sha256' 10 | 11 | function insertStr (src, dst, pos) { 12 | return [dst.slice(0, pos), src, dst.slice(pos)].join('') 13 | } 14 | 15 | function removeCertHeaderAndFooter (publicP12PEM) { 16 | return publicP12PEM.toString() 17 | .replace('-----BEGIN CERTIFICATE-----', '') 18 | .replace('-----END CERTIFICATE-----', '') 19 | .replace(/(\r\n|\n|\r)/gm, '') 20 | } 21 | 22 | function getTokenXml (id) { 23 | return ( 24 | ` 25 | 26 | ` 27 | ) 28 | } 29 | 30 | function getSecurityXml (id, binaryToken) { 31 | return ( 32 | ` 35 | ${binaryToken} 39 | ` 40 | ) 41 | } 42 | 43 | class WSSecurityCert { 44 | 45 | constructor (privatePEM, publicP12PEM, uid) { 46 | this.publicP12PEM = removeCertHeaderAndFooter(publicP12PEM) 47 | this.x509Id = 'x509-' + uid.replace(/-/gm, '') 48 | 49 | this.signer = new SignedXml() 50 | this.signer.signingKey = privatePEM 51 | this.signer.signatureAlgorithm = SIGNATURE_ALGORITHM 52 | this.signer.addReference("//*[local-name(.)='Body']", TRANSFORM_ALGORITHMS, DIGEST_ALGORITHM) 53 | this.signer.keyInfoProvider = {} 54 | this.signer.keyInfoProvider.getKeyInfo = () => getTokenXml(this.x509Id) 55 | } 56 | 57 | postProcess (xml) { 58 | const secHeader = getSecurityXml(this.x509Id, this.publicP12PEM) 59 | const xmlWithSec = insertStr(secHeader, xml, xml.indexOf('')) 60 | this.signer.computeSignature(xmlWithSec) 61 | return insertStr(this.signer.getSignatureXml(), xmlWithSec, xmlWithSec.indexOf('')) 62 | } 63 | } 64 | 65 | module.exports = WSSecurityCert 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eet", 3 | "version": "0.8.0", 4 | "description": "Node.js knihovna pro EET.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "ava test/tests.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/JakubMrozek/eet.git" 12 | }, 13 | "keywords": [ 14 | "eet", 15 | "elektronická evidence tržeb", 16 | "babiš" 17 | ], 18 | "author": { 19 | "name": "Jakub Mrozek", 20 | "email": "jakub.mrozek@gmail.com", 21 | "url": "www.ronnieweb.net" 22 | }, 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/JakubMrozek/eet/issues" 26 | }, 27 | "homepage": "https://github.com/JakubMrozek/eet#readme", 28 | "devDependencies": { 29 | "ava": "^0.16.0" 30 | }, 31 | "dependencies": { 32 | "soap": "^0.29.0", 33 | "uuid": "^3.3.3", 34 | "xml-crypto": "^0.8.4" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/keys/certificate.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEmDCCA4CgAwIBAgIEVjaXMDANBgkqhkiG9w0BAQsFADB3MRIwEAYKCZImiZPy 3 | LGQBGRYCQ1oxQzBBBgNVBAoMOsSMZXNrw6EgUmVwdWJsaWthIOKAkyBHZW5lcsOh 4 | bG7DrSBmaW5hbsSNbsOtIMWZZWRpdGVsc3R2w60xHDAaBgNVBAMTE0VFVCBDQSAx 5 | IFBsYXlncm91bmQwHhcNMTYwOTMwMDkwMjQ0WhcNMTkwOTMwMDkwMjQ0WjBDMRIw 6 | EAYKCZImiZPyLGQBGRYCQ1oxFTATBgNVBAMTDENaMTIxMjEyMTIxODEWMBQGA1UE 7 | DRMNZnl6aWNrYSBvc29iYTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB 8 | AIY6O5tIJmB+GFrZsIAjZukigWqFWm9JR6y+O23BFSFIsNxLXlSr+o8PMlvc2xn3 9 | 25R2mlBmfWGSeNVC+VzNj0lUnXt5xkFAQTzUAGy5Vw395w0gjffP0a0aEOJbpP/j 10 | /NKVwMmcNCgmR7TMdrHFY+iVlUeBXayShQUi5iwkioSJ7lVHnZpo/vPEuGK1P9ZC 11 | br60HwyRrsgmE+ZPtlBUi5zPtNj0tFVRQ6p31fgDBFNKS+vRL8p9pBI0u2x+Ju64 12 | j2LBm4wbyX1tlgqNV0Eg/B+aHIi5LJNfX4AKEVQggso4ymD6RLP84UsYR03gRxGR 13 | VdrVx45LW0zslUg2M/OFFl8CAwEAAaOCAV4wggFaMAkGA1UdEwQCMAAwHQYDVR0O 14 | BBYEFJPcMF6yIt00KetjxoNkR6lS1Sc7MB8GA1UdIwQYMBaAFHwwdqzM1ofR7Mkf 15 | 4nAILONf3gwHMA4GA1UdDwEB/wQEAwIGwDBjBgNVHSAEXDBaMFgGCmCGSAFlAwIB 16 | MAEwSjBIBggrBgEFBQcCAjA8DDpUZW50byBjZXJ0aWZpa8OhdCBieWwgdnlkw6Fu 17 | IHBvdXplIHBybyB0ZXN0b3ZhY8OtIMO6xI1lbHkuMIGXBgNVHR8EgY8wgYwwgYmg 18 | gYaggYOGKWh0dHA6Ly9jcmwuY2ExLXBnLmVldC5jei9lZXRjYTFwZy9hbGwuY3Js 19 | hipodHRwOi8vY3JsMi5jYTEtcGcuZWV0LmN6L2VldGNhMXBnL2FsbC5jcmyGKmh0 20 | dHA6Ly9jcmwzLmNhMS1wZy5lZXQuY3ovZWV0Y2ExcGcvYWxsLmNybDANBgkqhkiG 21 | 9w0BAQsFAAOCAQEAOd3TksJlO4Cq6BfuAoWUqJP28p10f11W60X2TZ0LLEIeJHvl 22 | Z2to6Pht8Pf50ZE4XPKyJclUDhT4dEoR0JcCiFZci8Oei35p6PAZ/dFEXBLHylMO 23 | 5JOY5JNwhUJNkhE2oSoCDBWpZ+tF6sPPeQv+dR9Zcj6vy767D0XGz6zyrxB3Lb1t 24 | 03SO+pGac/1C7dc3rOkBkqxz7b7dVRl7hT31ct/TTSMBBvPqStiUNF375nKb1pRT 25 | SZtj5jt8m8UHChmu6bWyFGYLqil9XFHr3xeIGK8hRb4pPdjMEOY6HULZwImPg3Sn 26 | P8fInbXA47hWoHb7pGwpdE5Jybveo6ae8HNx4w== 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /test/keys/private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAhjo7m0gmYH4YWtmwgCNm6SKBaoVab0lHrL47bcEVIUiw3Ete 3 | VKv6jw8yW9zbGffblHaaUGZ9YZJ41UL5XM2PSVSde3nGQUBBPNQAbLlXDf3nDSCN 4 | 98/RrRoQ4luk/+P80pXAyZw0KCZHtMx2scVj6JWVR4FdrJKFBSLmLCSKhInuVUed 5 | mmj+88S4YrU/1kJuvrQfDJGuyCYT5k+2UFSLnM+02PS0VVFDqnfV+AMEU0pL69Ev 6 | yn2kEjS7bH4m7riPYsGbjBvJfW2WCo1XQSD8H5ociLksk19fgAoRVCCCyjjKYPpE 7 | s/zhSxhHTeBHEZFV2tXHjktbTOyVSDYz84UWXwIDAQABAoIBAAtnZykKODh6fhc5 8 | 54T7fQLRq60hJ03NLAnBH8tzXBg31M7imGEZO3BsGhsz6GmMZVt3uCSckIp5p0p3 9 | Jjh8PnA8gCS9c9qXWvOQD3ktRfgqWjcDTQyA24+ZZ1XT83DKLfC2SGrYmpXOqsjx 10 | liQEYTihfM1WcF75E9Qb4vJdKGeaMODtcYRM7IZEGF9kcEbYazpSLNfer+XiNUEN 11 | Hv/n3qxDg1ym8i7IeRowh3p4m0I+2S2qwJ2mfOlc42wejYff6wXfyvW5Lbq2AXEY 12 | Ik0lTtV3y7iblfnGORwN0kbkSkXYJm+0JQPnvpECQ33csB7IZ+guX+o6jRxpQ4D5 13 | YGm/ugECgYEAwAU/6d0towSeQPV1/LynsqMqQEn3p2l1ZiEN5KAR3+qmFRsfnZDV 14 | xwbVLNhCLVMubKYoJZPK/gEFmOMQwPfFzyOm6JS6PttZI+gjLFkvweFzO6ofQom+ 15 | iOVB3cDIfd2wdVDdvbBmNh6UsWbpHSBpjL0982dTjMxvfrFeTu9/8j0CgYEAsvNq 16 | 5mrpqoAKkzhtn0VXsmsbMikPX2+DSwvEwmB/DtD1aUwSMzGaBVgoeBEJZ2vIOHGe 17 | FZjyKcCMtT/F91MFAi/k9ae+EovW1GH5dbKgfik/1dEGHgVjpfK9PmSycs2GS9/P 18 | Ea2rJxfgzc3yG3AHHDcNKFq3eBzinADq9Q5QAMsCgYBRg6j/MVqHQpWfw9Pjh361 19 | Mdjk08GjOvyQb2XDcURFRwpcGViRfgOSas8iK2fkb9RcYSjX0uawb2Sd436gPE6n 20 | wV3AjCqINnuf55LO58mDR56wbGa6y1isKo6MgaNa8zpOgxauyOyK+u5qZOJ+79x+ 21 | 13MuGeX49mw6XzdG0RsCjQKBgQCO6RqsHSGyykKnw9633Xblw+eaLXRbcQDXQ/TE 22 | jq+ps3LpvpiiTbCFKBxZlrF96HOjsAjhkp2CfoVgkieVFrXfQ0SEcou23/qW7g98 23 | 0USevG6AYUeTXhZwhuRfGafxSvU/TuNdaOZQeB9N/HSnONZU0Bov6hKvV9IZaBo7 24 | zkxR9QKBgDtlte0DSiKG6eGLjKkLbNHNA3hLbwu/Z/CyD7O9LzmfNtfVwEg8TvcZ 25 | Kj890DufoJ+QLwUxNdKSn8zkw5Q1SbreGHbCVUEqSvMRP8SQIW1Wc5DOxuRD+gTA 26 | wFsI76+Hf/5JGpnzHq/t+CLABAb4fX8ojimEQxXdv6Y1eCR1q7NK 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /test/tests.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import test from 'ava' 3 | import eet from '../lib/eet' 4 | import util from '../lib/util' 5 | import validate from '../lib/validate' 6 | 7 | const PRIVATE_KEY = fs.readFileSync('./keys/private.pem') 8 | const CERTIFICATE = fs.readFileSync('./keys/certificate.pem') 9 | const TEST_PKP = 'JvCv0lXfT74zuviJaHeO91guUfum1MKhq0NNPxW0YlBGvIIt+I4QxEC3QP6BRwEkIS14n2WN+9oQ8nhQPYwZX7L4W9Ie7CYv1ojcl/YiF4560EdB3IpRNRj3UjQlwSZ5ucSM9vWqp0UTbhJDSUk5/WjC/CEiSYv7OQIqa0NJ0f0+ldzGveLRSF34eu2iqAhs/yfDnENlnMDPVB5ko/zQO0vcC93k5DEWEoytTIAsKd6jKSO7eama8Qe+d0wq9vBzudkfLgCe2C1iERJuyHknhjo9KOx10h5wk99QqVGX8tthpAmryDcX2N0ZGkzJHuzzebnYsxXFYI2tKOJLiLLoLQ==' 10 | 11 | test('generate PKP', t => { 12 | const result = eet.generatePKP( 13 | PRIVATE_KEY, 14 | 'CZ1212121218', 15 | '273', 16 | '/5546/RO24', 17 | '0/6460/ZQ42', 18 | '2016-08-05T00:30:12+02:00', 19 | '34113.00' 20 | ) 21 | t.is(result, TEST_PKP) 22 | }) 23 | 24 | test('generate BKP', t => { 25 | t.is(eet.generateBKP(TEST_PKP), '3F9119C1-FBF34535-D30B60F8-9859E4A6-C8C8AAFA') 26 | }) 27 | 28 | test('format date', t => { 29 | const date = new Date('2016-08-05T00:30:12+02:00') 30 | t.is(util.formatDate(date), '2016-08-04T22:30:12Z') 31 | }) 32 | 33 | test('format number', t => { 34 | t.is(util.formatNumber(12), '12.00') 35 | }) 36 | 37 | test('validate required', t => { 38 | t.notThrows(() => validate.requiredItems({ 39 | dicPopl: 'CZ1212121218', 40 | idPokl: 1, 41 | idProvoz: 1, 42 | poradCis: '2016-0001s', 43 | datTrzby: new Date(), 44 | celkTrzba: 1000 45 | })) 46 | t.throws(() => validate.requiredItems({ 47 | idPokl: 1 48 | })) 49 | }) 50 | 51 | test('validate vat id number', t => { 52 | t.notThrows(() => validate.vatIdNumber('CZ1212121218')) 53 | t.throws(() => validate.vatIdNumber(1212121218)) 54 | }) 55 | 56 | test('validate business premises id', t => { 57 | t.notThrows(() => validate.businessPremisesId(25)) 58 | t.throws(() => validate.businessPremisesId(12345678)) 59 | }) 60 | 61 | test('validate cash register id', t => { 62 | t.notThrows(() => validate.cashRegisterId('1aZ.,:;/#-_')) 63 | t.throws(() => validate.cashRegisterId('@@@')) 64 | }) 65 | 66 | test('validate receipt number', t => { 67 | t.notThrows(() => validate.receiptNumber('0aA.,:;/#-_')) 68 | t.throws(() => validate.receiptNumber('@@@')) 69 | }) 70 | 71 | test('validate date', t => { 72 | t.notThrows(() => validate.date(new Date())) 73 | t.throws(() => validate.date(new Date('test'))) 74 | t.throws(() => validate.date('test')) 75 | }) 76 | 77 | test('validate regime', t => { 78 | t.notThrows(() => validate.regime(1)) 79 | t.notThrows(() => validate.regime('1')) 80 | t.throws(() => validate.regime('test')) 81 | }) 82 | 83 | test('validate financial number', t => { 84 | t.notThrows(() => validate.financialNumber(1000)) 85 | t.notThrows(() => validate.financialNumber(0)) 86 | t.notThrows(() => validate.financialNumber(-1000)) 87 | t.throws(() => validate.financialNumber('1000,00')) 88 | t.throws(() => validate.financialNumber('test')) 89 | }) 90 | 91 | test('get data items', t => { 92 | const result = eet.getDataItems({ 93 | dicPopl: 'CZ1212121218', 94 | idPokl: '/5546/RO24', 95 | poradCis: '0/6460/ZQ42', 96 | datTrzby: new Date('2016-08-05T00:30:12+02:00'), 97 | celkTrzba: -34113.8, 98 | idProvoz: '273' 99 | }) 100 | const expected = { 101 | dic_popl: 'CZ1212121218', 102 | id_pokl: '/5546/RO24', 103 | porad_cis: '0/6460/ZQ42', 104 | dat_trzby: '2016-08-04T22:30:12Z', 105 | celk_trzba: '-34113.80', 106 | id_provoz: '273', 107 | rezim: 0 108 | } 109 | t.deepEqual(result.attributes, expected) 110 | }) 111 | 112 | test('do request', async t => { 113 | const data = { 114 | prvniZaslani: true, 115 | overeni: false, 116 | dicPopl: 'CZ1212121218', 117 | idPokl: '/5546/RO24', 118 | poradCis: '0/6460/ZQ42', 119 | datTrzby: new Date('2016-08-05T00:30:12+02:00'), 120 | celkTrzba: 34113.00, 121 | idProvoz: '273' 122 | } 123 | const options = { 124 | playground: true, 125 | privateKey: PRIVATE_KEY, 126 | certificate: CERTIFICATE 127 | } 128 | const response = await eet.doRequest(options, data) 129 | // TODO offline 130 | t.truthy(response.fik.length === 39) 131 | }) 132 | 133 | 134 | -------------------------------------------------------------------------------- /wsdl/EETXMLSchema.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /wsdl/WSDL.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ucel : Sluzba pro odeslani datove zpravy evidovane trzby Verze : 3.1 Vlastnik : Generalni financni reditelstvi 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | --------------------------------------------------------------------------------