diff --git a/README.md b/README.md index 45c7e6e..045c51b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,23 @@ [![](https://img.shields.io/badge/version-0.0.1-blue.svg)](https://shields.io/) [![](https://img.shields.io/badge/maintained-yes-green.svg)](https://shields.io/) -## Quick start +PHP package for Croatian fiscalization. + + +## Install the packages - `composer install` + + +## Update required fields in tests + +In order for tests to work, head to `tests\CroatianFiscalizationTest` and change the required fields which you will need to have in order for fiscalization to work: + +- Test certificate with path and password +- Company UID +- Operative user UID + + +## Run the tests + - `php vendor/phpunit/phpunit/phpunit` \ No newline at end of file diff --git a/composer.json b/composer.json index ca00ce8..8d23c55 100644 --- a/composer.json +++ b/composer.json @@ -6,7 +6,12 @@ ], "type": "library", "require": { - "php": "^7.4" + "php": "^7.4", + "ext-xmlwriter": "*", + "ext-openssl": "*", + "ext-dom": "*", + "ext-curl": "*", + "ext-simplexml": "*" }, "require-dev": { "phpunit/phpunit": "^8.5" diff --git a/src/Bill/Bill.php b/src/Bill/Bill.php deleted file mode 100644 index 5573bf6..0000000 --- a/src/Bill/Bill.php +++ /dev/null @@ -1,8 +0,0 @@ -path = $path; + $this->password = $password; + } +} \ No newline at end of file diff --git a/src/Company/Company.php b/src/Company/Company.php new file mode 100644 index 0000000..f367e9b --- /dev/null +++ b/src/Company/Company.php @@ -0,0 +1,51 @@ +uid = $uid; + $this->certificate = $certificate; + $this->demo = $demo; + $this->shouldBeTaxed = $shouldBeTaxed; + } +} \ No newline at end of file diff --git a/src/CroatianFiscalization.php b/src/CroatianFiscalization.php index 116a06e..17782f0 100644 --- a/src/CroatianFiscalization.php +++ b/src/CroatianFiscalization.php @@ -1,12 +1,479 @@ company = $company; + + if ($this->company->demo) { + $this->url = 'https://cistest.apis-it.hr:8449/FiskalizacijaServiceTest'; + } + + try { + $this->setCertificate($company->certificate->path, $company->certificate->password); + } catch (Exception $e) { + fwrite(STDERR, print_r('An exception happened: ' . $e->getMessage(), TRUE)); + } + } + + /** + * Validates required attributes. + * + * @param object $invoiceObject + * @return object + */ + protected function validate(object $invoiceObject): object + { + + if (!isset($invoiceObject->userUID) || empty($invoiceObject->userUID)) { + return (object) [ + 'success' => false, + 'message' => 'Invoice user UID is required.' + ]; + } + + if (!isset($invoiceObject->company) || empty($invoiceObject->company)) { + return (object) [ + 'success' => false, + 'message' => 'Invoice company is required.' + ]; + } + + if (!isset($invoiceObject->fiscal_data) || empty($invoiceObject->fiscal_data)) { + return (object) [ + 'success' => false, + 'message' => 'Invoice fiscal data is required.' + ]; + } + + if (!isset($invoiceObject->fiscal_abbreviation) || empty($invoiceObject->fiscal_abbreviation)) { + return (object) [ + 'success' => false, + 'message' => 'Fiscal abbreviation is required.' + ]; + } + + if (!isset($invoiceObject->gross) || empty($invoiceObject->gross)) { + return (object) [ + 'success' => false, + 'message' => 'Invoice gross is required.' + ]; + } + + if (!isset($invoiceObject->net) || empty($invoiceObject->net)) { + return (object) [ + 'success' => false, + 'message' => 'Invoice net is required.' + ]; + } + + if (!isset($invoiceObject->number) || empty($invoiceObject->number)) { + return (object) [ + 'success' => false, + 'message' => 'Invoice number is required.' + ]; + } + + if (!isset($invoiceObject->business_place_label) || empty($invoiceObject->business_place_label)) { + return (object) [ + 'success' => false, + 'message' => 'Invoice business place label is required.' + ]; + } + + if (!isset($invoiceObject->cash_register_label) || empty($invoiceObject->cash_register_label)) { + return (object) [ + 'success' => false, + 'message' => 'Invoice cash register label is required.' + ]; + } + + if (!isset($invoiceObject->created_at) || empty($invoiceObject->created_at)) { + return (object) [ + 'success' => false, + 'message' => 'Invoice created at field is required.' + ]; + } + + return (object) [ + 'success' => true, + 'message' => NULL + ]; + } + + /** + * Fiscalizes the given Invoice. + * + * @param object $invoiceObject + * @return object + * @throws Exception + */ + public function fiscalize(object $invoiceObject): object + { + $validation = $this->validate($invoiceObject); + + if (!$validation->success) { + return (object) [ + 'success' => $validation->success, + 'message' => $validation->message + ]; + } + + $this->createInvoiceNumber($invoiceObject); + + $fiscalizedInvoice = $this->createFiscalizedInvoice($invoiceObject); + + if ($invoiceObject->fiscal_data && $invoiceObject->fiscal_data->zki) { + $fiscalizedInvoice->setSecurityCode($invoiceObject->fiscal_data->zki); + $fiscalizedInvoice->setNoteOfRedelivery(true); + } else { + $fiscalizedInvoice->setSecurityCode( + $fiscalizedInvoice->formatSecurityCode( + $this->certificate['pkey'], + $fiscalizedInvoice->uid, + $fiscalizedInvoice->dateTime, + $this->invoiceNumber->numberNoteInvoice, + $this->invoiceNumber->noteOfBusinessArea, + $this->invoiceNumber->noteOfExchangeDevice, + $fiscalizedInvoice->totalValue + ) + ); + $fiscalizedInvoice->setNoteOfRedelivery(false); + $fiscalizedInvoice->zki = $fiscalizedInvoice->securityCode; + } + + $invoice_request = new InvoiceRequest($fiscalizedInvoice); + try { + $soapMessage = $this->signXML($invoice_request->toXML()); + $res = $this->sendSoap($soapMessage); + + $cleanXML = str_ireplace(['SOAP-ENV:', 'SOAP:', 'tns:'], '', $res); + $response = simplexml_load_string($cleanXML); + + if ($response->Body->RacunOdgovor->Greske) { + $error = $response->Body->RacunOdgovor->Greske->Greska; + return (object) [ + 'success' => false, + 'message' => "An error occurred with message: {$error->SifraGreske}: {$error->PorukaGreske}" + ]; + } + + $jir = (string) $response->Body->RacunOdgovor->Jir; + $date = (string) $response->Body->RacunOdgovor->Zaglavlje->DatumVrijeme; + + return (object) [ + 'success' => true, + 'zki' => $fiscalizedInvoice->zki, + 'jir' => $jir, + 'fiscal_at' => \DateTime::createFromFormat('d.m.Y\TH:i:s', $date)->format('Y-m-d H:i:s') + ]; + } catch (\Exception $e) { + return (object) [ + 'success' => false, + 'message' => 'An exception occurred with message: ' . $e->getMessage() + ]; + } + } + + /** + * Creates InvoiceNumber from Invoice data. + * + * @param object $invoiceObject + */ + public function createInvoiceNumber(object $invoiceObject): void + { + $this->invoiceNumber = new InvoiceNumber( + $invoiceObject->number, + $invoiceObject->business_place_label, + $invoiceObject->cash_register_label + ); + } + + /** + * Creates instance of a Invoice class for further fiscalization. + * + * @param object $invoiceObject + * @return Invoice + */ + public function createFiscalizedInvoice(object $invoiceObject): Invoice + { + $billedAt = \DateTime::createFromFormat('Y-m-d H:i:s', $invoiceObject->created_at)->format('d.m.Y\TH:i:s'); + $fiscalizedInvoice = new Invoice(); + $fiscalizedInvoice->setUID($this->company->uid); + $fiscalizedInvoice->setHaveTax($this->company->shouldBeTaxed); + $fiscalizedInvoice->setInvoiceNumber($this->invoiceNumber); + $fiscalizedInvoice->setNoteOfOrder('P'); + $fiscalizedInvoice->setDateTime($billedAt); + $taxRate = $this->company->shouldBeTaxed ? (100 * ($invoiceObject->gross / $invoiceObject->net - 1.0)) : 0; + $fiscalizedInvoice->setListTax([ + new TaxRate(round($taxRate, 2), $invoiceObject->net, ($invoiceObject->gross - $invoiceObject->net), null) + ]); + $fiscalizedInvoice->setTotalValue($invoiceObject->gross); + $fiscalizedInvoice->setTypeOfPayment($invoiceObject->fiscal_abbreviation); + $fiscalizedInvoice->setOperativeUID($invoiceObject->userUID); + + return $fiscalizedInvoice; + } + + /** + * Sets certificate data. + * + * @param $path + * @param $pass + * @throws Exception + */ + public function setCertificate($path, $pass) + { + $pkcs12 = $this->getCertificate($path); + openssl_pkcs12_read($pkcs12, $this->certificate, $pass); + $this->privateKeyResource = openssl_pkey_get_private($this->certificate['pkey'], $pass); + $this->publicCertificateData = openssl_x509_parse($this->certificate['cert']); + } + + /** + * Get certificate. + * + * @param $path + * @return false|string + * @throws Exception + */ + public function getCertificate($path) + { + if (!$cert = @file_get_contents($path)) { + throw new \Exception('Cannot read certificate from location: ' . $path, 1); + } + return $cert; + } + + /** + * Constructs SOAP message. + * + * @param $XMLRequest + * @return string + * @throws Exception + */ + public function signXML($XMLRequest) + { + $XMLRequestDOMDoc = new DOMDocument(); + $XMLRequestDOMDoc->loadXML($XMLRequest); + + $canonical = $XMLRequestDOMDoc->C14N(); + $DigestValue = base64_encode(hash('sha1', $canonical, true)); + + $rootElem = $XMLRequestDOMDoc->documentElement; + + $SignatureNode = $rootElem->appendChild(new DOMElement('Signature')); + $SignatureNode->setAttribute('xmlns', 'http://www.w3.org/2000/09/xmldsig#'); + + $SignedInfoNode = $SignatureNode->appendChild(new DOMElement('SignedInfo')); + $SignedInfoNode->setAttribute('xmlns', 'http://www.w3.org/2000/09/xmldsig#'); + + $CanonicalizationMethodNode = $SignedInfoNode->appendChild(new DOMElement('CanonicalizationMethod')); + $CanonicalizationMethodNode->setAttribute('Algorithm', 'http://www.w3.org/2001/10/xml-exc-c14n#'); + + $SignatureMethodNode = $SignedInfoNode->appendChild(new DOMElement('SignatureMethod')); + $SignatureMethodNode->setAttribute('Algorithm', 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'); + + $ReferenceNode = $SignedInfoNode->appendChild(new DOMElement('Reference')); + $ReferenceNode->setAttribute('URI', sprintf('#%s', $XMLRequestDOMDoc->documentElement->getAttribute('Id'))); + + $TransformsNode = $ReferenceNode->appendChild(new DOMElement('Transforms')); + + $Transform1Node = $TransformsNode->appendChild(new DOMElement('Transform')); + $Transform1Node->setAttribute('Algorithm', 'http://www.w3.org/2000/09/xmldsig#enveloped-signature'); + + $Transform2Node = $TransformsNode->appendChild(new DOMElement('Transform')); + $Transform2Node->setAttribute('Algorithm', 'http://www.w3.org/2001/10/xml-exc-c14n#'); + + $DigestMethodNode = $ReferenceNode->appendChild(new DOMElement('DigestMethod')); + $DigestMethodNode->setAttribute('Algorithm', 'http://www.w3.org/2000/09/xmldsig#sha1'); + + $ReferenceNode->appendChild(new DOMElement('DigestValue', $DigestValue)); + + $SignedInfoNode = $XMLRequestDOMDoc->getElementsByTagName('SignedInfo')->item(0); + + $X509Issuer = $this->publicCertificateData['issuer']; + $X509IssuerName = sprintf('O=%s,C=%s', $X509Issuer['O'], $X509Issuer['C']); + $X509IssuerSerial = $this->publicCertificateData['serialNumber']; + + $publicCertificatePureString = str_replace('-----BEGIN CERTIFICATE-----', '', $this->certificate['cert']); + $publicCertificatePureString = str_replace('-----END CERTIFICATE-----', '', $publicCertificatePureString); + + $signedInfoSignature = null; + + if (!openssl_sign($SignedInfoNode->C14N(true), $signedInfoSignature, $this->privateKeyResource, OPENSSL_ALGO_SHA1)) + throw new Exception('Unable to sign the SOAP request for CroatianFiscalization.'); + + $SignatureNode = $XMLRequestDOMDoc->getElementsByTagName('Signature')->item(0); + $SignatureValueNode = new DOMElement('SignatureValue', base64_encode($signedInfoSignature)); + $SignatureNode->appendChild($SignatureValueNode); + + $KeyInfoNode = $SignatureNode->appendChild(new DOMElement('KeyInfo')); + + $X509DataNode = $KeyInfoNode->appendChild(new DOMElement('X509Data')); + $X509CertificateNode = new DOMElement('X509Certificate', $publicCertificatePureString); + $X509DataNode->appendChild($X509CertificateNode); + + $X509IssuerSerialNode = $X509DataNode->appendChild(new DOMElement('X509IssuerSerial')); + + $X509IssuerNameNode = new DOMElement('X509IssuerName', $X509IssuerName); + $X509IssuerSerialNode->appendChild($X509IssuerNameNode); + + $X509SerialNumberNode = new DOMElement('X509SerialNumber', $X509IssuerSerial); + $X509IssuerSerialNode->appendChild($X509SerialNumberNode); + + $envelope = new DOMDocument(); + + $envelope->loadXML( + ' + + ' + ); + + $envelope->encoding = 'UTF-8'; + $envelope->version = '1.0'; + $XMLRequestType = $XMLRequestDOMDoc->documentElement->localName; + $XMLRequestTypeNode = $XMLRequestDOMDoc->getElementsByTagName($XMLRequestType)->item(0); + $XMLRequestTypeNode = $envelope->importNode($XMLRequestTypeNode, true); + + $envelope->getElementsByTagName('Body')->item(0)->appendChild($XMLRequestTypeNode); + return $envelope->saveXML(); + } + + /** + * Sends SOAP request to Croatian fiscalization service. + * + * @param $payload + * @return mixed + * @throws Exception + */ + public function sendSoap($payload) + { + $ch = curl_init(); + + $options = [ + CURLOPT_URL => $this->url, + CURLOPT_CONNECTTIMEOUT => 5, + CURLOPT_TIMEOUT => 5, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $payload, + CURLOPT_SSL_VERIFYHOST => 2, + CURLOPT_SSL_VERIFYPEER => false + ]; + + switch ($this->security) { + case 'SSL': + break; + case 'TLS': + curl_setopt($ch, CURLOPT_SSLVERSION, 6); + break; + default: + throw new \InvalidArgumentException('Third parameter in CroatianFiscalization constructor must be SSL or TLS!'); + } + + curl_setopt_array($ch, $options); + + $response = curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + + curl_close($ch); + if ($response) return $this->parseResponse($response, $code); + throw new Exception(curl_error($ch)); + } + + /** + * Parses response if an error happens. + * + * @param $response + * @param int $code + * @return mixed + * @throws Exception + */ + public function parseResponse($response, $code = 4) + { + if ($code === 200) return $response; + $DOMResponse = new DOMDocument(); + $DOMResponse->loadXML($response); + + $error_code = $DOMResponse->getElementsByTagName('SifraGreske')->item(0); + $error_message = $DOMResponse->getElementsByTagName('PorukaGreske')->item(0); + + if ($error_code && $error_message) throw new Exception(sprintf('%s: %s', $error_code->nodeValue, $error_message->nodeValue)); + throw new Exception(print_r($response, true), $code); + } +} diff --git a/src/Invoice/Invoice.php b/src/Invoice/Invoice.php new file mode 100644 index 0000000..f53b327 --- /dev/null +++ b/src/Invoice/Invoice.php @@ -0,0 +1,273 @@ +uid = $uid; + } + + /** + * Sets has tax. + * + * @param $haveTax + */ + public function setHaveTax($haveTax) + { + $this->haveTax = $haveTax; + } + + /** + * Sets date and time. + * + * @param $dateTime + */ + public function setDateTime($dateTime) + { + $this->dateTime = $dateTime; + } + + /** + * Sets note of order. + * + * @param $noteOfOrder + */ + public function setNoteOfOrder($noteOfOrder) + { + $this->noteOfOrder = $noteOfOrder; + } + + /** + * Sets invoice number. + * + * @param $invoiceNumber + */ + public function setInvoiceNumber($invoiceNumber) + { + $this->invoiceNumber = $invoiceNumber; + } + + /** + * Sets lists of taxes. + * + * @param $listTax + */ + public function setListTax($listTax) + { + $this->listTax = $listTax; + } + + /** + * Sets total value. + * + * @param $totalValue + */ + public function setTotalValue($totalValue) + { + $this->totalValue = $totalValue; + } + + /** + * Sets type of payment. + * + * @param $typeOfPayment + */ + public function setTypeOfPayment($typeOfPayment) + { + $this->typeOfPayment = $typeOfPayment; + } + + /** + * Sets operative UID. + * + * @param $operativeUID + */ + public function setOperativeUID($operativeUID) + { + $this->operativeUID = $operativeUID; + } + + /** + * Sets security code. + * + * @param $securityCode + */ + public function setSecurityCode($securityCode) + { + $this->securityCode = $securityCode; + } + + /** + * Sets note of redelivery. + * + * @param $noteOfRedelivery + */ + public function setNoteOfRedelivery($noteOfRedelivery) + { + $this->noteOfRedelivery = $noteOfRedelivery; + } + + /** + * Generates security code based on given parameters. + * + * @param $pkey + * @param $uid + * @param $dt + * @param $bor + * @param $opp + * @param $onu + * @param $uir + * @return string [type] md5 hash + * @throws \Exception + */ + public function formatSecurityCode($pkey, $uid, $dt, $bor, $opp, $onu, $uir) + { + $formattedSecurityCode = ''; + $formattedSecurityCode .= $uid; + $formattedSecurityCode .= $dt; + $formattedSecurityCode .= $opp; + $formattedSecurityCode .= $bor; + $formattedSecurityCode .= $onu; + $formattedSecurityCode .= $uir; + + $securityCodeSignature = null; + if (!openssl_sign($formattedSecurityCode, $securityCodeSignature, $pkey, OPENSSL_ALGO_SHA1)) { + throw new \Exception('Error creating security code.'); + } + + $this->securityCode = md5($securityCodeSignature); + + return $this->securityCode = md5($securityCodeSignature); + } + + /** + * Writes to XML. + * + * @return string + */ + public function toXML(): string { + $ns = 'tns'; + + $writer = new XMLWriter(); + $writer->openMemory(); + + $writer->setIndent(true); + $writer->setIndentString(" "); + $writer->startElementNs($ns, 'Racun', null); + $writer->writeElementNs($ns, 'Oib', null, $this->uid); + $writer->writeElementNs($ns, 'USustPdv', null, $this->haveTax ? "true" : "false"); + $writer->writeElementNs($ns, 'DatVrijeme', null, $this->dateTime); + $writer->writeElementNs($ns, 'OznSlijed', null, $this->noteOfOrder); + + $writer->writeRaw($this->invoiceNumber->toXML()); + + // Basic Tax (PDV) + if (!empty($this->listTax)) { + $writer->startElementNs($ns, 'Pdv', null); + foreach ($this->listTax as $tax) { + $writer->writeRaw($tax->toXML()); + } + $writer->endElement(); + } + + $writer->writeElementNs($ns, 'IznosOslobPdv', null, number_format(null, 2, '.', '')); + $writer->writeElementNs($ns, 'IznosMarza', null, number_format(null, 2, '.', '')); + $writer->writeElementNs($ns, 'IznosNePodlOpor', null, number_format(null, 2, '.', '')); + + $writer->writeElementNs($ns, 'IznosUkupno', null, number_format($this->totalValue, 2, '.', '')); + $writer->writeElementNs($ns, 'NacinPlac', null, $this->typeOfPayment); + $writer->writeElementNs($ns, 'OibOper', null, $this->operativeUID); + $writer->writeElementNs($ns, 'ZastKod', null, $this->securityCode); + $writer->writeElementNs($ns, 'NakDost', null, $this->noteOfRedelivery ? "true" : "false"); + + $writer->endElement(); + + return $writer->outputMemory(); + } +} diff --git a/src/Invoice/InvoiceNumber.php b/src/Invoice/InvoiceNumber.php new file mode 100644 index 0000000..357dedf --- /dev/null +++ b/src/Invoice/InvoiceNumber.php @@ -0,0 +1,65 @@ +numberNoteInvoice = $numberNoteInvoice; + $this->noteOfBusinessArea = $noteOfBusinessArea; + $this->noteOfExchangeDevice = $noteOfExchangeDevice; + } + + /** + * Writes to XML. + * + * @return string + */ + public function toXML(): string { + $ns = 'tns'; + + $writer = new XMLWriter(); + $writer->openMemory(); + + $writer->setIndent(true); + $writer->setIndentString(' '); + $writer->startElementNs($ns, 'BrRac', null); + $writer->writeElementNs($ns, 'BrOznRac', null, $this->numberNoteInvoice); + $writer->writeElementNs($ns, 'OznPosPr', null, $this->noteOfBusinessArea); + $writer->writeElementNs($ns, 'OznNapUr', null, $this->noteOfExchangeDevice); + $writer->endElement(); + + return $writer->outputMemory(); + } +} \ No newline at end of file diff --git a/src/Invoice/InvoiceRequest.php b/src/Invoice/InvoiceRequest.php new file mode 100644 index 0000000..7407b1f --- /dev/null +++ b/src/Invoice/InvoiceRequest.php @@ -0,0 +1,69 @@ +invoice = $invoice; + $this->requestName = 'RacunZahtjev'; + } + + /** + * Generates UUID. + * + * @return string + */ + public function generateUUID(): string { + return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + mt_rand(0, 0xffff), mt_rand(0, 0xffff), + mt_rand(0, 0xffff), + mt_rand(0, 0x0fff) | 0x4000, + mt_rand(0, 0x3fff) | 0x8000, + mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) + ); + } + + /** + * Writes to XML. + * + * @return string + */ + public function toXML(): string { + $ns = 'tns'; + + $writer = new XMLWriter(); + $writer->openMemory(); + + $writer->setIndent(true); + $writer->setIndentString(" "); + $writer->startElementNs($ns, $this->requestName, 'http://www.apis-it.hr/fin/2012/types/f73'); + $writer->writeAttribute('Id', uniqid()); + $writer->startElementNs($ns, 'Zaglavlje', null); + $writer->writeElementNs($ns, 'IdPoruke', null, $this->generateUUID()); + $writer->writeElementNs($ns, 'DatumVrijeme', null, date('d.m.Y\TH:i:s')); + $writer->endElement(); + + $writer->writeRaw($this->invoice->toXML()); + $writer->endElement(); + + return $writer->outputMemory(); + } +} \ No newline at end of file diff --git a/src/Invoice/TaxRate.php b/src/Invoice/TaxRate.php new file mode 100644 index 0000000..cf57b87 --- /dev/null +++ b/src/Invoice/TaxRate.php @@ -0,0 +1,74 @@ +name = $name; + $this->rate = number_format($rate, 2, '.', ''); + $this->baseValue = number_format($baseValue, 2, '.', ''); + $this->value = number_format($value, 2, '.', ''); + } + + /** + * Writes to XML. + * + * @return string + */ + public function toXML(): string { + $ns = 'tns'; + + $writer = new XMLWriter(); + $writer->openMemory(); + $writer->setIndent(true); + $writer->setIndentString(" "); + $writer->startElementNs($ns, 'Porez', null); + if ($this->name) $writer->writeElementNs($ns, 'Naziv', null, $this->name); + $writer->writeElementNs($ns, 'Stopa', null, $this->rate); + $writer->writeElementNs($ns, 'Osnovica', null, $this->baseValue); + $writer->writeElementNs($ns, 'Iznos', null, $this->value); + $writer->endElement(); + + return $writer->outputMemory(); + } +} \ No newline at end of file diff --git a/tests/CroatianFiscalizationTest.php b/tests/CroatianFiscalizationTest.php index 3368dc9..cad7c9f 100644 --- a/tests/CroatianFiscalizationTest.php +++ b/tests/CroatianFiscalizationTest.php @@ -3,15 +3,101 @@ require_once __DIR__ . '/../vendor/autoload.php'; use PHPUnit\Framework\TestCase; +use tonikresic\CroatianFiscalization\Company\Certificate; +use tonikresic\CroatianFiscalization\Company\Company; +use tonikresic\CroatianFiscalization\CroatianFiscalization; class CroatianFiscalizationTest extends TestCase { + /** + * Test certificate path. + * + * @var string + */ + public static string $certificatePath = 'CERTIFICATE_PATH'; + + /** + * Test certificate password. + * + * @var string + */ + public static string $certificatePassword = 'CERTIFICATE_PASSWORD'; + + /** + * Company UID. + * + * @var string + */ + public static string $companyUID = 'COMPANY_UID'; + + /** + * User UID. + * + * @var string + */ + public static string $userUID = 'USER_UID'; + /** * Asserts that the fiscalization runs properly. * + * @throws Exception */ public function testFiscalization() { - $this->assertEquals(true, true); + // Required. Add certificate path and password + $certificate = new Certificate(self::$certificatePath, self::$certificatePassword); + + // Required. Add company UID + $company = new Company(self::$companyUID, $certificate, true, true); + + // Required. Add user UID + $userUID = self::$userUID; + + // Set fiscal abbreviation. G is for cash, K is for credit card. + $fiscalAbbreviation = 'G'; + + // Invoice gross + $gross = 12.5; + + // Invoice net + $net = 10; + + // Invoice number + $number = 1; + + // Invoice business place label + $businessPlaceLabel = 1; + + // Invoice cash register label + $cashRegisterLabel = 1; + + // Set Croatian fiscalization data to null + $fiscalData = [ + 'zki' => NULL, + 'jir' => NULL + ]; + + $invoiceObject = (object) [ + 'userUID' => $userUID, + 'fiscal_abbreviation' => $fiscalAbbreviation, + 'company' => $company, + 'gross' => $gross, + 'net' => $net, + 'number' => $number, + 'business_place_label' => $businessPlaceLabel, + 'cash_register_label' => $cashRegisterLabel, + 'fiscal_data' => (object) $fiscalData, + 'created_at' => date('Y-m-d H:i:s'), + 'fiscal_at' => NULL, + ]; + + $cf = new CroatianFiscalization(); + $cf->configure($company); + + $response = $cf->fiscalize($invoiceObject); + + fwrite(STDERR, print_r($response, TRUE)); + + $this->assertEquals(true, $response->success); } } \ No newline at end of file