From 3d4281b27dd7ae144b7483fa9f60ba77826abbb3 Mon Sep 17 00:00:00 2001 From: okangr Date: Wed, 28 Feb 2024 12:18:09 +0200 Subject: [PATCH] Added services for verifying the validity of a VAT number. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Αναζήτηση Βασικών Στοιχείων Μητρώου Επιχειρήσεων - TaxisNet - Vies For more information please visit [our documentation](/docs/http/search-vat) --- composer.json | 4 +- docs/http/search-vat.md | 139 +++++++++++++++++++++++++++++ docs/http/send-invoices.md | 4 - docs/index.md | 1 + src/Http/MyDataRequest.php | 6 +- src/Http/Traits/HasRequestDom.php | 15 ++++ src/Http/Traits/HasResponseDom.php | 15 ++++ src/Models/Type.php | 4 +- src/Services/Vat/TaxisNet.php | 131 +++++++++++++++++++++++++++ src/Services/Vat/VIES.php | 55 ++++++++++++ src/Services/Vat/VatEntity.php | 99 ++++++++++++++++++++ src/Services/Vat/VatException.php | 10 +++ src/Xml/XMLWriter.php | 3 - src/helpers.php | 88 ++++++++++++++++++ 14 files changed, 561 insertions(+), 13 deletions(-) create mode 100644 docs/http/search-vat.md create mode 100644 src/Services/Vat/TaxisNet.php create mode 100644 src/Services/Vat/VIES.php create mode 100644 src/Services/Vat/VatEntity.php create mode 100644 src/Services/Vat/VatException.php create mode 100644 src/helpers.php diff --git a/composer.json b/composer.json index 4978ca3..64585eb 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ "require": { "php": "^8.1", "ext-dom": "*", + "ext-soap": "*", "guzzlehttp/guzzle": "^7.0.1" }, "require-dev": { @@ -28,7 +29,8 @@ "autoload": { "psr-4": { "Firebed\\AadeMyData\\": "src/" - } + }, + "files": ["src/helpers.php"] }, "autoload-dev": { "psr-4": { diff --git a/docs/http/search-vat.md b/docs/http/search-vat.md new file mode 100644 index 0000000..64a2f91 --- /dev/null +++ b/docs/http/search-vat.md @@ -0,0 +1,139 @@ +# Αναζήτηση Βασικών Στοιχείων Μητρώου Επιχειρήσεων + +Με τη χρήση αυτής της υπηρεσίας, τα νομικά πρόσωπα, οι νομικές οντότητες, +και τα φυσικά πρόσωπα με εισόδημα από επιχειρηματική δραστηριότητα μπορούν +να αναζητήσουν βασικές πληροφορίες, προκειμένου να διακριβώσουν τη φορολογική +ή την επαγγελματική υπόσταση άλλων νομικών προσώπων ή νομικών οντοτήτων ή +φορολογουμένων/φυσικών προσώπων που ασκούν επιχειρηματική δραστηριότητα. + +Το σύστημα παρέχει 2 τρόπους αναζήτησης βασικών στοιχείων μητρώου επιχειρήσεων: + +- Μέσω της Υπηρεσίας Αναζήτησης Βασικών Στοιχείων Μητρώου Επιχειρήσεων +- Μέσω της Υπηρεσίας Vat Information Exchange System (VIES) + +## Μέσω της Υπηρεσίας Αναζήτησης Βασικών Στοιχείων Μητρώου Επιχειρήσεων + +Η υπηρεσία αυτή επιτρέπει την αναζήτηση όλων των Ελληνικών ΑΦΜ. Για την αναζήτηση +θα χρειαστείτε ένα `username` και ένα `password`. + +Διαδικασία εγγραφής: + +- Εγγραφή στην [υπηρεσία](https://www1.aade.gr/webtax/wspublicreg/faces/pages/wspublicreg/menu.xhtml) κάνοντας χρήση των κωδικών TAXISnet. +- Απόκτηση ειδικών κωδικών πρόσβασης μέσω της εφαρμογής [Διαχείριση Ειδικών Κωδικών](https://www1.aade.gr/sgsisapps/tokenservices/protected/displayConsole.htm). + +Για περισσότερες λεπτομέρειες και για την εγγραφή επισκεφτείτε +την [Επίσημη Σελίδα του ΑΑΔΕ](https://www.aade.gr/anazitisi-basikon-stoiheion-mitrooy-epiheiriseon). + +Μετά την εγγραφή, θα έχετε τα `username` και `password` που θα χρειαστείτε για την +χρήση της υπηρεσίας. + +```php +use Firebed\AadeMyData\Services\Vat\TaxisNet; +use Firebed\AadeMyData\Services\Vat\VatException; + +$username = 'your-username'; +$password = 'your-password'; + +$taxis = new TaxisNet($username, $password); + +try { + $response = $taxis->handle('094014201'); + + dd($response); +} catch (VatException $exception) { + echo "Σφάλμα: " . $exception->getMessage(); +} +``` + +Το αποτέλεσμα της παραπάνω κλήσης: + +```php +Firebed\AadeMyData\Services\Vat\VatEntity { + +vatNumber: "094014201" + +tax_authority_id: "1159" + +tax_authority_name: "ΦΑΕ ΑΘΗΝΩΝ" + +flag_description: "ΜΗ ΦΠ" + +valid: true + +validity_description: "ΕΝΕΡΓΟΣ ΑΦΜ" + +firm_flag_description: "ΕΠΙΤΗΔΕΥΜΑΤΙΑΣ" + +legalName: "ΤΡΑΠΕΖΑ ΕΘΝΙΚΗ ΤΗΣ ΕΛΛΑΔΟΣ ΑΝΩΝΥΜΗ ΕΤΑΙΡΕΙΑ" + +commerce_title: "" + +legal_status_description: "ΑΕ" + +street: "ΑΙΟΛΟΥ" + +street_number: "86" + +postcode: "10559" + +city: "ΑΘΗΝΑ" + +registration_date: "1900-01-01" + +stop_date: "" + +normal_vat: true + +firms: array:2 [ + 0 => array:4 [ + "code" => "64191204" + "description" => "ΥΠΗΡΕΣΙΕΣ ΤΡΑΠΕΖΩΝ" + "kind" => "1" + "kind_description" => "ΚΥΡΙΑ" + ] + 1 => array:4 [ + "code" => "66221001" + "description" => "ΥΠΗΡΕΣΙΕΣ ΑΣΦΑΛΙΣΤΙΚΟΥ ΠΡΑΚΤΟΡΑ ΚΑΙ ΑΣΦΑΛΙΣΤΙΚΟΥ ΣΥΜΒΟΥΛΟΥ" + "kind" => "2" + "kind_description" => "ΔΕΥΤΕΡΕΥΟΥΣΑ" + ] + ] +} +``` + +Σε περίπτωση που το ΑΦΜ δεν είναι έγκυρο επιστρέφεται τιμή `null`. Αν υπήρξε κάποιο άλλο πρόβλημα το `VatException` θα +περιέχει το σχετικό μήνυμα σφάλματος. + +## Μέσω της Υπηρεσίας Vat Information Exchange System (VIES) + +Με τη χρήση της Υπηρεσία VIES μπορείτε να επαληθεύσετε την εγκυρότητα του ΑΦΜ, +που χορηγείται απο οποιοδήποτε κράτος μέλος της Ευρωπαϊκής Ένωσης. Οι λεπτομέρειες +που παρέχει είναι πιο περιορισμένες σε σχέση με την υπηρεσία της ΑΑΔΕ. + +Η Υπηρεσία παρέχεται δωρεάν χωρίς εγγραφή σε κάποιο φορέα. Δέχεται 2 παραμέτρους: +- Τον κωδικό της χώρας (π.χ. EL για Ελλάδα) +- Τον ΑΦΜ που θέλετε να επαληθεύσετε. + +```php +use Firebed\AadeMyData\Services\Vat\VIES; +use Firebed\AadeMyData\Services\Vat\VatException; + +$taxis = new VIES(); + +try { + $response = $taxis->handle('EL', '094014201'); + + dd($response); +} catch (VatException $exception) { + echo "Σφάλμα: " . $exception->getMessage(); +} +``` + +Το αποτέλεσμα της παραπάνω κλήσης: + +```php +Firebed\AadeMyData\Services\Vat\VatEntity { + +vatNumber: "094014201" + +tax_authority_id: null + +tax_authority_name: null + +flag_description: null + +valid: true + +validity_description: null + +firm_flag_description: null + +legalName: "ΤΡΑΠΕΖΑ ΕΘΝΙΚΗ ΤΗΣ ΕΛΛΑΔΟΣ ΑΝΩΝΥΜΗ ΕΤΑΙΡΕΙΑ" + +commerce_title: null + +legal_status_description: null + +street: "ΑΙΟΛΟΥ" + +street_number: "86" + +postcode: "10559" + +city: "ΑΘΗΝΑ" + +registration_date: null + +stop_date: null + +normal_vat: null + +firms: [] +} +``` + +Σε περίπτωση που το ΑΦΜ δεν είναι έγκυρο, η υπηρεσία επιστρέφει `null`. \ No newline at end of file diff --git a/docs/http/send-invoices.md b/docs/http/send-invoices.md index d098992..01d6e1e 100644 --- a/docs/http/send-invoices.md +++ b/docs/http/send-invoices.md @@ -76,10 +76,6 @@ $doc->add(new Invoice()) $request = new SendInvoices(); $response = $request->handle($doc); -// Alternatively -$doc = new InvoicesDoc([new Invoice(), new Invoice()]); -$request = new SendInvoices(); - try { $response = $request->handle($doc); } catch (MyDataException $e) { diff --git a/docs/index.md b/docs/index.md index 5afb5dc..19c86ac 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,6 +5,7 @@ | Εισαγωγή | Αναβάθμιση | upgrade-guide | | Εισαγωγή | Σφάλματα | exceptions | | Εισαγωγή | Contributing | contributing | +| Περιγραφή λειτουργιών | Αναζήτηση ΑΦΜ | http/search-vat | | Περιγραφή λειτουργιών | SendInvoices | http/send-invoices | | Περιγραφή λειτουργιών | CancelInvoice | http/cancel-invoice | | Περιγραφή λειτουργιών | RequestDocs | http/request-docs | diff --git a/src/Http/MyDataRequest.php b/src/Http/MyDataRequest.php index 55c959b..82e14cf 100644 --- a/src/Http/MyDataRequest.php +++ b/src/Http/MyDataRequest.php @@ -10,6 +10,7 @@ use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\HandlerStack; use Psr\Http\Message\ResponseInterface; +use ReflectionClass; abstract class MyDataRequest { @@ -180,9 +181,6 @@ private function getUrl(): string private function getAction(): string { - $action = get_class($this); - $action = substr($action, strrpos($action, '\\') + 1); - - return $this->action ?? $action; + return $this->action ?? (new ReflectionClass($this))->getShortName(); } } diff --git a/src/Http/Traits/HasRequestDom.php b/src/Http/Traits/HasRequestDom.php index 9f48e26..dab9d1e 100644 --- a/src/Http/Traits/HasRequestDom.php +++ b/src/Http/Traits/HasRequestDom.php @@ -12,4 +12,19 @@ public function getRequestDom(): ?DOMDocument { return $this->requestDom; } + + public function getRequestXml(): ?string + { + return $this->requestDom?->saveXML(); + } + + public function getRequestElement(string $localName, int $index): ?string + { + if ($this->requestDom === null) { + return null; + } + + $element = $this->requestDom->getElementsByTagName($localName)->item($index); + return $this->requestDom->saveXML($element); + } } \ No newline at end of file diff --git a/src/Http/Traits/HasResponseDom.php b/src/Http/Traits/HasResponseDom.php index 6cbe552..1e7a04e 100644 --- a/src/Http/Traits/HasResponseDom.php +++ b/src/Http/Traits/HasResponseDom.php @@ -12,4 +12,19 @@ public function getResponseDom(): ?DOMDocument { return $this->responseDom; } + + public function getResponseXML(): ?string + { + return $this->responseDom?->saveXML(); + } + + public function getResponseElement(string $localName, int $index): ?string + { + if ($this->responseDom === null) { + return null; + } + + $element = $this->responseDom->getElementsByTagName($localName)->item($index); + return $this->responseDom->saveXML($element); + } } \ No newline at end of file diff --git a/src/Models/Type.php b/src/Models/Type.php index a714f0c..999e91e 100644 --- a/src/Models/Type.php +++ b/src/Models/Type.php @@ -47,8 +47,10 @@ protected function castValue(string $key, $value) } // Cast value to enum if it is not already an enum + // If the value doesn't correspond to an enum, it + // will return null. if ($this->isEnum($key) && !is_object($value)) { - return $cast::from($value); + return $cast::tryFrom($value); } return $value; diff --git a/src/Services/Vat/TaxisNet.php b/src/Services/Vat/TaxisNet.php new file mode 100644 index 0000000..0b71cc0 --- /dev/null +++ b/src/Services/Vat/TaxisNet.php @@ -0,0 +1,131 @@ +username = $username; + $this->password = $password; + } + + /** + * @throws VatException + */ + public function handle(string $vatToSearch, string $vatCalledBy = null): ?VatEntity + { + if (blank($vatToSearch)) { + throw new VatException("Please provide a VAT number"); + } + + try { + $response = $this->request($vatToSearch, $vatCalledBy); + + $error = $response->error_rec; + if (filled($error->error_code)) { + if ($error->error_code === "RG_WS_PUBLIC_WRONG_AFM") { + return null; + } + + throw new VatException(trim($error->error_descr)); + } + + return $this->parseResponse($response); + } catch (Throwable $e) { + throw new VatException($e->getMessage()); + } + } + + /** + * @throws VatException|SoapFault + * @noinspection PhpUndefinedMethodInspection + */ + protected function request(string $vatToSearch, string $vatCalledBy = null) + { + $headers = $this->prepareHeaders($this->username, $this->password); + + $client = new SoapClient(self::WSDL, ['soap_version' => SOAP_1_2]); + $client->__setSoapHeaders($headers); + + $response = $client->rgWsPublic2AfmMethod([ + 'INPUT_REC' => [ + 'afm_called_by' => $vatCalledBy, + 'afm_called_for' => $vatToSearch + ] + ]); + + if (!isset($response->result->rg_ws_public2_result_rtType)) { + $this->invalidResponse(); + } + + return $response->result->rg_ws_public2_result_rtType; + } + + /** + * @throws VatException + */ + protected function invalidResponse() + { + throw new VatException("Invalid response from TaxisNet"); + } + + protected function prepareHeaders(string $username, string $password): SoapHeader + { + $header = new stdClass(); + $header->UsernameToken = new stdClass(); + $header->UsernameToken->Username = $username; + $header->UsernameToken->Password = $password; + + return new SoapHeader(self::XSD, 'Security', $header); + } + + protected function parseResponse(object $data): VatEntity + { + $rec = $data->basic_rec; + + $vat = new VatEntity(); + $vat->vatNumber = trim($rec->afm); + $vat->tax_authority_id = trim($rec->doy); + $vat->tax_authority_name = trim($rec->doy_descr); + $vat->flag_description = trim($rec->i_ni_flag_descr); + $vat->valid = trim($rec->deactivation_flag) === "1"; + $vat->validity_description = trim($rec->deactivation_flag_descr); + $vat->firm_flag_description = trim($rec->firm_flag_descr); + $vat->legalName = preg_replace('!\s+!', ' ', trim($rec->onomasia)); + $vat->commerce_title = trim($rec->commer_title); + $vat->legal_status_description = trim($rec->legal_status_descr); + $vat->street = trim($rec->postal_address); + $vat->street_number = trim($rec->postal_address_no); + $vat->postcode = trim($rec->postal_zip_code); + $vat->city = trim($rec->postal_area_description); + $vat->registration_date = trim($rec->regist_date); + $vat->stop_date = trim($rec->stop_date); + $vat->normal_vat = trim($rec->normal_vat_system_flag) === 'Y'; + + $firms = wrapArray($data->firm_act_tab->item); + + foreach ($firms as $firm) { + $vat->firms[] = [ + 'code' => trim($firm->firm_act_code), + 'description' => trim($firm->firm_act_descr), + 'kind' => trim($firm->firm_act_kind), + 'kind_description' => trim($firm->firm_act_kind_descr), + ]; + } + + return $vat; + } +} \ No newline at end of file diff --git a/src/Services/Vat/VIES.php b/src/Services/Vat/VIES.php new file mode 100644 index 0000000..2a50718 --- /dev/null +++ b/src/Services/Vat/VIES.php @@ -0,0 +1,55 @@ +request($countryCode, $vatNumber); + + if (!$response) { + return null; + } + + $vat = new VatEntity(); + $vat->vatNumber = $response->vatNumber; + $vat->valid = $response->valid; + $vat->legalName = $response->name; + + # street street_number postcode - city + $address = trim(beforeLast($response->address, ' - ')); // street street_number postcode + $street = trim(beforeLast($address, ' ')); // street street_number + + $vat->postcode = trim(afterLast($address, ' ')); + $vat->street = trim(beforeLast($street, ' ')); + $vat->street_number = trim(afterLast($street, ' ')); + $vat->city = trim(afterLast($response->address, ' - ')); + return $vat; + } catch (Throwable $e) { + throw new VatException($e->getMessage()); + } + } + + /** @noinspection PhpUndefinedMethodInspection */ + protected function request(string $countryCode, string $vatNumber) + { + $client = new SoapClient(self::ENDPOINT); + $response = $client->checkVat(compact('countryCode', 'vatNumber')); + + if (!$response->valid) { + return false; + } + + return $response; + } +} \ No newline at end of file diff --git a/src/Services/Vat/VatEntity.php b/src/Services/Vat/VatEntity.php new file mode 100644 index 0000000..ca28e1e --- /dev/null +++ b/src/Services/Vat/VatEntity.php @@ -0,0 +1,99 @@ + 'string', 'description' => 'string', 'kind' => 'string', 'kind_description' => 'string']])] + public array $firms = []; +} \ No newline at end of file diff --git a/src/Services/Vat/VatException.php b/src/Services/Vat/VatException.php new file mode 100644 index 0000000..3619b7c --- /dev/null +++ b/src/Services/Vat/VatException.php @@ -0,0 +1,10 @@ +