diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ef551f2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+nbproject/*
+vendor/
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..434b1a1
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Miloš Havlíček
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/README.md b/README.md
index 3eea3ce..bf878cd 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,52 @@
-# Ukázka implementace EET v PHP.
+# Example implementation of EET in PHP
+
+## Installation
+Install Ondrejnov/eet using [Composer](http://getcomposer.org/):
+
+```sh
+$ composer require cothema/eet
+```
+
+### Dependencies
+PHP >=5.6
+robrichards/wse-php
+
+Attached WSDL, key and certificate are intended for non-production usage (Playground).
+
+## Example Usage
+Sample codes are located in examples/ folder
+
+### License
+MIT
+
+---
+
+# Ukázka implementace EET v PHP
+
+## Instalace
+Install Ondrejnov/eet using [Composer](http://getcomposer.org/):
+
+```sh
+$ composer require cothema/eet
+```
### Závislosti
-Testováno na PHP 5.6
+PHP >=5.6
+robrichards/wse-php
-V adresáří vendor se očekává knihovna https://github.com/robrichards/wse-php
+Přiložené WSDL, klíč a certifikát jsou pro neprodukční prostředí (Playground).
-Přiložené WSDL, klíč a certifikát je pro neprodukční prostředí (Playground).
+## Ukázka použití
+Ukázky použití naleznete ve složce examples/
### Licence
-Kód je poskytován tak jak stojí a leží, můžete s ním dělat co chcete, klidně použít i pro EET. Za chyby nezodpovídám.
+MIT
+
+---
### Reklama
Komu se nechce do implementace, tak může použít on-line službu EETApp.cz, která má pokročilejší správu účtenek včetně tisku na tiskárnu.
-
+
### Bitcoin Donate
1LZuWFUHeVMrYvZWinxFjjkZtuq56TECot
diff --git a/eet.key b/_cert/eet.key
similarity index 100%
rename from eet.key
rename to _cert/eet.key
diff --git a/eet.pem b/_cert/eet.pem
similarity index 100%
rename from eet.pem
rename to _cert/eet.pem
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..ac98d5a
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,18 @@
+{
+ "name": "ondrejnov/eet",
+ "description": "EET (Electronic records of sales for Czech Ministry of Finance) Client for PHP",
+ "type": "project",
+ "license": ["MIT", "BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
+ "require": {
+ "php": ">=5.6.0",
+ "robrichards/wse-php": "*"
+ },
+ "require-dev": {
+ "nette/tester": "~1.4"
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "autoload": {
+ "classmap": ["src/"]
+ }
+}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..122f0fd
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,108 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
+ "This file is @generated automatically"
+ ],
+ "hash": "c658f2c59e9ea90ac5e176dac443cd80",
+ "content-hash": "db9c721cc0d3a114db5a58c6a7378e8f",
+ "packages": [
+ {
+ "name": "robrichards/wse-php",
+ "version": "dev-master",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/robrichards/wse-php.git",
+ "reference": "ed6c3cefaa80604de539e69b0affaca67e11195e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/robrichards/wse-php/zipball/ed6c3cefaa80604de539e69b0affaca67e11195e",
+ "reference": "ed6c3cefaa80604de539e69b0affaca67e11195e",
+ "shasum": ""
+ },
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "": "src/"
+ },
+ "files": [
+ "xmlseclibs.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD"
+ ],
+ "description": "Libraries for adding WS-* support to ext/soap in PHP.",
+ "time": "2016-01-29 10:34:55"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "nette/tester",
+ "version": "v1.7.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nette/tester.git",
+ "reference": "d97534578e8cf66eabe081e7d5eaa4dd527ab0c8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nette/tester/zipball/d97534578e8cf66eabe081e7d5eaa4dd527ab0c8",
+ "reference": "d97534578e8cf66eabe081e7d5eaa4dd527ab0c8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "bin": [
+ "src/tester"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.7-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause",
+ "GPL-2.0",
+ "GPL-3.0"
+ ],
+ "authors": [
+ {
+ "name": "David Grudl",
+ "homepage": "https://davidgrudl.com"
+ },
+ {
+ "name": "Nette Community",
+ "homepage": "https://nette.org/contributors"
+ }
+ ],
+ "description": "An easy-to-use PHP unit testing framework.",
+ "homepage": "https://tester.nette.org",
+ "keywords": [
+ "nette",
+ "testing",
+ "unit"
+ ],
+ "time": "2016-03-19 14:34:02"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "dev",
+ "stability-flags": [],
+ "prefer-stable": true,
+ "prefer-lowest": false,
+ "platform": {
+ "php": ">=5.6.0"
+ },
+ "platform-dev": []
+}
diff --git a/example.php b/example.php
deleted file mode 100644
index f3c64c6..0000000
--- a/example.php
+++ /dev/null
@@ -1,26 +0,0 @@
-uuid_zpravy = 'b3a09b52-7c87-4014-a496-4c7a53cf9120';
-$r->dic_popl = 'CZ72080043';
-$r->id_provoz = '181';
-$r->id_pokl = '1';
-$r->porad_cis = '1';
-$r->dat_trzby = new DateTime();
-$r->celk_trzba = 1000;
-$fik = $r->send();
-var_dump($fik);
-
-// ukazka chyby
-$r->dic_popl = 'x';
-try {
- $fik = $r->send();
-}
-catch (\eet\EETException $e) {
- var_dump($e->getMessage());
-}
\ No newline at end of file
diff --git a/examples/bootstrap.php b/examples/bootstrap.php
new file mode 100644
index 0000000..eadadf5
--- /dev/null
+++ b/examples/bootstrap.php
@@ -0,0 +1,10 @@
+uuid_zpravy = 'b3a09b52-7c87-4014-a496-4c7a53cf9120';
+$r->dic_popl = 'CZ72080043';
+$r->id_provoz = '181';
+$r->id_pokl = '1';
+$r->porad_cis = '1';
+$r->dat_trzby = new \DateTime();
+$r->celk_trzba = 1000;
+
+// Valid response should be returned
+echo '
---VALID REQUEST---
';
+try {
+ $fik = $dispatcher->send($r); // Send request
+ echo sprintf('Returned fik code: %s', $fik); // See response - should be returned
+} catch (ServerException $e) {
+ var_dump($e); // See exception
+} catch (\Exception $e) {
+ var_dump($e); // Fatal error
+}
+
+// Example of error message
+$r->dic_popl = 'x';
+
+// ServerException should be returned
+echo '---ERROR REQUEST---
';
+try {
+ $fik = $dispatcher->send($r); // Send request
+ var_dump($fik); // See response
+} catch (ServerException $e) {
+ echo sprintf('Error from server of Ministry of Finance: %s', $e->getMessage()); // See exception - should be returned
+} catch (\Exception $e) {
+ var_dump($e); // Fatal error
+}
\ No newline at end of file
diff --git a/lib/EETException.php b/lib/EETException.php
deleted file mode 100644
index ba72c59..0000000
--- a/lib/EETException.php
+++ /dev/null
@@ -1,7 +0,0 @@
-key = $key;
- $this->cert = $cert;
- }
-
- public function check()
- {
- try {
- return $this->send(TRUE);
- }
- catch (EETException $e) {
- return FALSE;
- }
- }
-
- public function send($check = FALSE)
- {
- $hlavicka = [
- 'uuid_zpravy' => $this->uuid_zpravy,
- 'dat_odesl' => time(),
- 'prvni_zaslani' => $this->prvni_zaslani,
- 'overeni' => $check
- ];
-
- $data = [
- 'dic_popl' => $this->dic_popl,
- 'dic_poverujiciho' => $this->dic_poverujiciho,
- 'id_provoz' => $this->id_provoz,
- 'id_pokl' => $this->id_pokl,
- 'porad_cis' => $this->porad_cis,
- 'dat_trzby' => $this->dat_trzby->format('c'),
- 'celk_trzba' => $this->priceFormat($this->celk_trzba),
- 'zakl_nepodl_dph' => $this->priceFormat($this->zakl_nepodl_dph),
- 'zakl_dan1' => $this->priceFormat($this->zakl_dan1),
- 'dan1' => $this->priceFormat($this->dan1),
- 'zakl_dan2' => $this->priceFormat($this->zakl_dan2),
- 'dan2' => $this->priceFormat($this->dan2),
- 'zakl_dan3' => $this->priceFormat($this->zakl_dan3),
- 'dan3' => $this->priceFormat($this->dan3),
- 'rezim' => $this->rezim
- ];
-
-
- $soapClient = new \eet\SoapClient($this->key, $this->cert);
- $response = $soapClient->OdeslaniTrzby([
- 'Hlavicka' => $hlavicka,
- 'Data' => $data,
- 'KontrolniKody' => $this->getCheckCodes()
- ]
- );
- if (isset($response->Chyba)) {
- $this->processError($response->Chyba);
- }
- if ($check) {
- return TRUE;
- }
- else {
- return $response->Potvrzeni->fik;
- }
- }
-
- public function getCheckCodes()
- {
- $objKey = new \XMLSecurityKey(\XMLSecurityKey::RSA_SHA256, ['type' => 'private']);
- $objKey->loadKey($this->key, TRUE);
-
- $arr = [
- $this->dic_popl,
- $this->id_provoz,
- $this->id_pokl,
- $this->porad_cis,
- $this->dat_trzby->format('c'),
- $this->priceFormat($this->celk_trzba)
- ];
- $sign = $objKey->signData(join('|', $arr));
-
- return [
- 'pkp' => [
- '_' => $sign,
- 'digest' => 'SHA256',
- 'cipher' => 'RSA2048',
- 'encoding' => 'base64'
- ],
- 'bkp' => [
- '_' => $this->formatBKB(sha1($sign)),
- 'digest' => 'SHA1',
- 'encoding' => 'base16'
- ]
- ];
- }
-
- private function formatBKB($code) {
- $r = '';
- for ($i = 0; $i < 40; $i++) {
- if ($i % 8 == 0 && $i != 0) {
- $r.= '-';
- }
- $r .= $code[$i];
- }
- return $r;
- }
-
- private function priceFormat($value) {
- return number_format($value, 2, '.', '');
- }
-
- private function processError($error) {
- if ($error->kod) {
- $msgs = [
- -1 => 'Docasna technicka chyba zpracovani – odeslete prosim datovou zpravu pozdeji',
- 2 => 'Kodovani XML neni platne',
- 3 => 'XML zprava nevyhovela kontrole XML schematu',
- 4 => 'Neplatny podpis SOAP zpravy',
- 5 => 'Neplatny kontrolni bezpecnostni kod poplatnika (BKP)',
- 6 => 'DIC poplatnika ma chybnou strukturu',
- 7 => 'Datova zprava je prilis velka',
- 8 => 'Datova zprava nebyla zpracovana kvuli technicke chybe nebo chybe dat',
- ];
- $msg = isset($msgs[$error->kod]) ? $msgs[$error->kod] : '';
- throw new EETException($msg, $error->kod);
- }
- }
-
-}
\ No newline at end of file
diff --git a/lib/SoapClient.php b/lib/SoapClient.php
deleted file mode 100644
index d9b3df7..0000000
--- a/lib/SoapClient.php
+++ /dev/null
@@ -1,33 +0,0 @@
- 1]);
- $this->key = $key;
- $this->cert = $cert;
- }
-
- public function __doRequest($request, $location, $saction, $version, $one_way = NULL)
- {
- $doc = new \DOMDocument('1.0');
- $doc->loadXML($request);
-
- $objWSSE = new \WSSESoap($doc);
- $objWSSE->addTimestamp();
-
- $objKey = new \XMLSecurityKey(\XMLSecurityKey::RSA_SHA256, ['type' => 'private']);
- $objKey->loadKey($this->key, TRUE);
- $objWSSE->signSoapDoc($objKey, ["algorithm" => \XMLSecurityDSig::SHA256]);
-
- $token = $objWSSE->addBinaryToken(file_get_contents($this->cert));
- $objWSSE->attachTokentoSig($token);
-
- return parent::__doRequest($objWSSE->saveXML(), $location, $saction, $version);
- }
-}
\ No newline at end of file
diff --git a/src/Dispatcher.php b/src/Dispatcher.php
new file mode 100644
index 0000000..f5c98be
--- /dev/null
+++ b/src/Dispatcher.php
@@ -0,0 +1,162 @@
+key = $key;
+ $this->cert = $cert;
+ $this->checkRequirements();
+ }
+
+ private function checkRequirements() {
+ if (!class_exists('\SoapClient')) {
+ throw new RequirementsException('Class SoapClient is not defined! Please, allow php extension php_soap.dll in php.ini');
+ }
+ }
+
+ /**
+ *
+ * @param Receipt $receipt
+ * @return boolean|string
+ */
+ public function check(Receipt $receipt) {
+ try {
+ return $this->send($receipt, TRUE);
+ } catch (ServerException $e) {
+ return FALSE;
+ }
+ }
+
+ /**
+ *
+ * @param Receipt $receipt
+ * @param boolean $check
+ * @return boolean|string
+ */
+ public function send(Receipt $receipt, $check = FALSE) {
+ $response = $this->processData($receipt, $check);
+
+ isset($response->Chyba) && $this->processError($response->Chyba);
+
+ return $check ? TRUE : $response->Potvrzeni->fik;
+ }
+
+ /**
+ *
+ * @param Receipt $receipt
+ * @param boolean $check
+ * @return object
+ */
+ private function processData(Receipt $receipt, $check = FALSE) {
+ $head = [
+ 'uuid_zpravy' => $receipt->uuid_zpravy,
+ 'dat_odesl' => time(),
+ 'prvni_zaslani' => $receipt->prvni_zaslani,
+ 'overeni' => $check
+ ];
+
+ $body = [
+ 'dic_popl' => $receipt->dic_popl,
+ 'dic_poverujiciho' => $receipt->dic_poverujiciho,
+ 'id_provoz' => $receipt->id_provoz,
+ 'id_pokl' => $receipt->id_pokl,
+ 'porad_cis' => $receipt->porad_cis,
+ 'dat_trzby' => $receipt->dat_trzby->format('c'),
+ 'celk_trzba' => Format::price($receipt->celk_trzba),
+ 'zakl_nepodl_dph' => Format::price($receipt->zakl_nepodl_dph),
+ 'zakl_dan1' => Format::price($receipt->zakl_dan1),
+ 'dan1' => Format::price($receipt->dan1),
+ 'zakl_dan2' => Format::price($receipt->zakl_dan2),
+ 'dan2' => Format::price($receipt->dan2),
+ 'zakl_dan3' => Format::price($receipt->zakl_dan3),
+ 'dan3' => Format::price($receipt->dan3),
+ 'rezim' => $receipt->rezim
+ ];
+
+
+ $soapClient = new SoapClient($this->key, $this->cert);
+ return $soapClient->OdeslaniTrzby([
+ 'Hlavicka' => $head,
+ 'Data' => $body,
+ 'KontrolniKody' => $this->getCheckCodes($receipt)
+ ]
+ );
+ }
+
+ public function getCheckCodes(Receipt $receipt) {
+ $objKey = new \XMLSecurityKey(\XMLSecurityKey::RSA_SHA256, ['type' => 'private']);
+ $objKey->loadKey($this->key, TRUE);
+
+ $arr = [
+ $receipt->dic_popl,
+ $receipt->id_provoz,
+ $receipt->id_pokl,
+ $receipt->porad_cis,
+ $receipt->dat_trzby->format('c'),
+ Format::price($receipt->celk_trzba)
+ ];
+ $sign = $objKey->signData(join('|', $arr));
+
+ return [
+ 'pkp' => [
+ '_' => $sign,
+ 'digest' => 'SHA256',
+ 'cipher' => 'RSA2048',
+ 'encoding' => 'base64'
+ ],
+ 'bkp' => [
+ '_' => Format::BKB(sha1($sign)),
+ 'digest' => 'SHA1',
+ 'encoding' => 'base16'
+ ]
+ ];
+ }
+
+ private function processError($error) {
+ if ($error->kod) {
+ $msgs = [
+ -1 => 'Docasna technicka chyba zpracovani – odeslete prosim datovou zpravu pozdeji',
+ 2 => 'Kodovani XML neni platne',
+ 3 => 'XML zprava nevyhovela kontrole XML schematu',
+ 4 => 'Neplatny podpis SOAP zpravy',
+ 5 => 'Neplatny kontrolni bezpecnostni kod poplatnika (BKP)',
+ 6 => 'DIC poplatnika ma chybnou strukturu',
+ 7 => 'Datova zprava je prilis velka',
+ 8 => 'Datova zprava nebyla zpracovana kvuli technicke chybe nebo chybe dat',
+ ];
+ $msg = isset($msgs[$error->kod]) ? $msgs[$error->kod] : '';
+ throw new ServerException($msg, $error->kod);
+ }
+ }
+
+}
diff --git a/src/Exceptions/RequirementsException.php b/src/Exceptions/RequirementsException.php
new file mode 100644
index 0000000..a72028a
--- /dev/null
+++ b/src/Exceptions/RequirementsException.php
@@ -0,0 +1,7 @@
+
-
-
- Ucel : Sluzba pro odeslani datove zpravy evidovane trzby
- Verze : 2.0
- Vlastnik : Generalni financni reditelstvi
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ Ucel : Sluzba pro odeslani datove zpravy evidovane trzby
+ Verze : 2.0
+ Vlastnik : Generalni financni reditelstvi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/EETXMLSchema.xsd b/src/Schema/EETXMLSchema.xsd
similarity index 97%
rename from EETXMLSchema.xsd
rename to src/Schema/EETXMLSchema.xsd
index 53b4390..abbdf7b 100644
--- a/EETXMLSchema.xsd
+++ b/src/Schema/EETXMLSchema.xsd
@@ -1,200 +1,200 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/SoapClient.php b/src/SoapClient.php
new file mode 100644
index 0000000..327dee6
--- /dev/null
+++ b/src/SoapClient.php
@@ -0,0 +1,33 @@
+ 1]);
+ $this->key = $key;
+ $this->cert = $cert;
+ }
+
+ public function __doRequest($request, $location, $saction, $version, $one_way = NULL) {
+ $doc = new \DOMDocument('1.0');
+ $doc->loadXML($request);
+
+ $objWSSE = new \WSSESoap($doc);
+ $objWSSE->addTimestamp();
+
+ $objKey = new \XMLSecurityKey(\XMLSecurityKey::RSA_SHA256, ['type' => 'private']);
+ $objKey->loadKey($this->key, TRUE);
+ $objWSSE->signSoapDoc($objKey, ["algorithm" => \XMLSecurityDSig::SHA256]);
+
+ $token = $objWSSE->addBinaryToken(file_get_contents($this->cert));
+ $objWSSE->attachTokentoSig($token);
+
+ return parent::__doRequest($objWSSE->saveXML(), $location, $saction, $version);
+ }
+
+}
diff --git a/src/Utils/Format.php b/src/Utils/Format.php
new file mode 100644
index 0000000..0da54b7
--- /dev/null
+++ b/src/Utils/Format.php
@@ -0,0 +1,22 @@
+getTestDispatcher()->send($this->getExampleReceipt());
+ Assert::type('string', $fik);
+ }
+
+ public function testSendError() {
+ $r = $this->getExampleReceipt();
+ $r->dic_popl = 'x';
+ Assert::exception(function() use ($r) {
+ $this->getTestDispatcher()->send($r);
+ }, ServerException::class);
+ }
+
+ /**
+ *
+ * @return Tested
+ */
+ private function getTestDispatcher() {
+ return new Tested(DIR_CERT . '/eet.key', DIR_CERT . '/eet.pem');
+ }
+
+ /**
+ * @return Receipt
+ */
+ private function getExampleReceipt() {
+ $r = new Receipt();
+ $r->uuid_zpravy = 'b3a09b52-7c87-4014-a496-4c7a53cf9120';
+ $r->dic_popl = 'CZ72080043';
+ $r->id_provoz = '181';
+ $r->id_pokl = '1';
+ $r->porad_cis = '1';
+ $r->dat_trzby = new \DateTime();
+ $r->celk_trzba = 1000;
+ return $r;
+ }
+
+}
+
+(new Dispatcher)->run();
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 0000000..eadadf5
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,10 @@
+