├── .github └── workflows │ └── php.yml ├── .gitignore ├── .sensiolabs.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENCE ├── README.md ├── bin └── vatcheck ├── build.xml ├── composer.json ├── docs └── images │ └── vies-web-screenshot.png ├── examples ├── EURailwayVATChecker.php ├── async_processing │ ├── .gitignore │ ├── README.md │ ├── list.php │ ├── queue.php │ ├── vatqueue.sql │ └── worker.php ├── checkVat.php ├── checkVatBatch.php ├── checkVatWithDetails.php └── vatins.php ├── infection.json.dist ├── phpcs.xml ├── phpunit.xml ├── sonar-project.properties ├── src ├── Vies │ ├── CheckVatResponse.php │ ├── HeartBeat.php │ ├── Validator │ │ ├── ValidatorAT.php │ │ ├── ValidatorAbstract.php │ │ ├── ValidatorBE.php │ │ ├── ValidatorBG.php │ │ ├── ValidatorCY.php │ │ ├── ValidatorCZ.php │ │ ├── ValidatorDE.php │ │ ├── ValidatorDK.php │ │ ├── ValidatorEE.php │ │ ├── ValidatorEL.php │ │ ├── ValidatorES.php │ │ ├── ValidatorEU.php │ │ ├── ValidatorFI.php │ │ ├── ValidatorFR.php │ │ ├── ValidatorGB.php │ │ ├── ValidatorHR.php │ │ ├── ValidatorHU.php │ │ ├── ValidatorIE.php │ │ ├── ValidatorIT.php │ │ ├── ValidatorInterface.php │ │ ├── ValidatorLT.php │ │ ├── ValidatorLU.php │ │ ├── ValidatorLV.php │ │ ├── ValidatorMT.php │ │ ├── ValidatorNL.php │ │ ├── ValidatorPL.php │ │ ├── ValidatorPT.php │ │ ├── ValidatorRO.php │ │ ├── ValidatorSE.php │ │ ├── ValidatorSI.php │ │ ├── ValidatorSK.php │ │ └── ValidatorXI.php │ ├── Vies.php │ ├── ViesException.php │ └── ViesServiceException.php └── autoload.php └── tests └── Vies ├── CheckVatResponseTest.php ├── HeartBeatTest.php ├── Validator ├── AbstractValidatorTest.php ├── ValidatorATTest.php ├── ValidatorBETest.php ├── ValidatorBGTest.php ├── ValidatorCYTest.php ├── ValidatorCZTest.php ├── ValidatorDETest.php ├── ValidatorDKTest.php ├── ValidatorEETest.php ├── ValidatorELTest.php ├── ValidatorESTest.php ├── ValidatorEUTest.php ├── ValidatorFITest.php ├── ValidatorFRTest.php ├── ValidatorGBTest.php ├── ValidatorHRTest.php ├── ValidatorHUTest.php ├── ValidatorIETest.php ├── ValidatorITTest.php ├── ValidatorLTTest.php ├── ValidatorLUTest.php ├── ValidatorLVTest.php ├── ValidatorMTTest.php ├── ValidatorNLTest.php ├── ValidatorPLTest.php ├── ValidatorPTTest.php ├── ValidatorROTest.php ├── ValidatorSETest.php ├── ValidatorSITest.php ├── ValidatorSKTest.php └── ValidatorXITest.php ├── ValidatorTest.php ├── ViesTest.php └── _files ├── checkVatService.wsdl ├── checkVatTestService.wsdl └── soapVersionCheck.code /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: PHP Composer 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | name: ViesBuild 12 | runs-on: ${{ matrix.operating-system }} 13 | strategy: 14 | matrix: 15 | operating-system: [ubuntu-latest] 16 | php-versions: ['7.4', '8.0', '8.1'] 17 | 18 | steps: 19 | - name: Set default PHP version ${{ matrix.php-versions }} 20 | uses: shivammathur/setup-php@v2 21 | with: 22 | php-version: "${{ matrix.php-versions }}" 23 | 24 | - name: Display current PHP version 25 | run: php --version 26 | 27 | - uses: actions/checkout@v2 28 | 29 | - name: Validate composer.json and composer.lock 30 | run: composer validate 31 | 32 | - name: Cache Composer packages 33 | id: composer-cache 34 | uses: actions/cache@v2 35 | with: 36 | path: vendor 37 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 38 | restore-keys: | 39 | ${{ runner.os }}-php- 40 | 41 | - name: Install dependencies 42 | if: steps.composer-cache.outputs.cache-hit != 'true' 43 | run: | 44 | composer config --no-plugins allow-plugins.infection/extension-installer true 45 | composer install --prefer-dist --no-progress --no-suggest 46 | 47 | - name: Prepare for Quality 48 | run: /bin/sh -c 'mkdir -p ${{ github.workspace }}/build/logs && touch ${{ github.workspace }}/build/logs/checkstyle.xml' 49 | 50 | - name: Run PHP CodeSniffer 51 | run: ${{ github.workspace }}/vendor/bin/phpcs 52 | 53 | - name: Run PHPUnit 54 | run: php -dxdebug.mode=coverage ${{ github.workspace }}/vendor/bin/phpunit 55 | 56 | - name: Run PMD-Naming 57 | run: ${{ github.workspace }}/vendor/bin/phpmd src xml naming --reportfile build/logs/pmd-naming.xml --strict --ignore-violations-on-exit 58 | 59 | - name: Run PMD-Unused 60 | run: ${{ github.workspace }}/vendor/bin/phpmd src xml unusedcode --reportfile build/logs/pmd-unusedcode.xml --strict --ignore-violations-on-exit 61 | 62 | - name: Run PMD-Codesize 63 | run: ${{ github.workspace }}/vendor/bin/phpmd src xml codesize --reportfile build/logs/pmd-codesize.xml --strict --ignore-violations-on-exit 64 | 65 | - name: Run PMD-Cleancode 66 | run: ${{ github.workspace }}/vendor/bin/phpmd src xml cleancode --reportfile build/logs/pmd-cleancode.xml --strict --ignore-violations-on-exit 67 | 68 | - name: Run PDepend 69 | run: ${{ github.workspace }}/vendor/bin/pdepend --jdepend-chart=build/logs/jdepend-chart.svg --jdepend-xml=build/logs/jdepend.xml --overview-pyramid=build/logs/pyramid.svg src 70 | 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | build/ 3 | .phpunit.result.cache 4 | composer.lock 5 | composer.phar 6 | .scannerwork/ 7 | .idea/ 8 | 9 | -------------------------------------------------------------------------------- /.sensiolabs.yml: -------------------------------------------------------------------------------- 1 | global_exclude_dirs: 2 | - vendor 3 | - examples 4 | - tests 5 | 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [dragonbe+github@gmail.com](mailto:dragonbe+github@gmail.com). All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct/ 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | First off, thank you for considering contributing to VIES. It's people 4 | like you that make VIES such a great tool for anyone who needs to validate 5 | VAT ID's within the European Union. 6 | 7 | ### 1. Where do I go from here? 8 | 9 | If you've noticed a bug or have a question, [search the issue tracker](https://github.com/dragonbe/vies/issues?q=something) 10 | to see if someone else in the community has already created a ticket. 11 | If not, go ahead and [make one](https://github.com/dragonbe/vies/issues/new)! 12 | 13 | ### 2. Fork the project and create a branch 14 | 15 | If this is something you think you can fix, then 16 | [fork VIES](https://help.github.com/articles/fork-a-repo) 17 | and create a branch with a descriptive name. 18 | 19 | A good branch name would be (where issue #123 is the ticket you're working on): 20 | 21 | ```sh 22 | git checkout -b 123-update-be-vat-numbers 23 | ``` 24 | 25 | ### 3. Get the test suite running 26 | 27 | Make sure you're using the most recent PHP version: 28 | 29 | - 1.0 branch requires PHP 5.6.x or higher 30 | - 2.0 branch requires PHP 7.1.x or higher 31 | 32 | Now install PHP packages using [composer](https://getcomposer.org): 33 | 34 | ```sh 35 | composer install 36 | ``` 37 | 38 | At this point you should be able to run the entire test suite using: 39 | 40 | ```sh 41 | ./vendor/bin/phpunit 42 | ``` 43 | 44 | ### 4. Did you find a bug? 45 | 46 | * **Ensure the bug was not already reported** by [searching all 47 | issues](https://github.com/dragonbe/vies/issues?q=). 48 | 49 | * If you're unable to find an open issue addressing the problem, [open a new 50 | one](https://github.com/dragonbe/vies/issues/new). Be sure to 51 | include a **title and clear description**, as much relevant information as 52 | possible, and a **code sample** or an **executable test case** demonstrating 53 | the expected behavior that is not occurring. 54 | 55 | ### 5. Implement your fix or feature 56 | 57 | At this point, you're ready to make your changes! Feel free to ask for help; 58 | everyone is a beginner at first :smile_cat: 59 | 60 | ### 6. Make a Pull Request 61 | 62 | At this point, you should switch back to your master branch and make sure it's 63 | up to date with VIES's master branch: 64 | 65 | ```sh 66 | git remote add upstream git@github.com:dragonbe/vies.git 67 | git checkout master 68 | git pull upstream master 69 | ``` 70 | 71 | Then update your feature branch from your local copy of master, and push it! 72 | 73 | ```sh 74 | git checkout 123-update-be-vat-numbers 75 | git rebase master 76 | git push --set-upstream origin 123-update-be-vat-numbers 77 | ``` 78 | 79 | Replace `123-update-be-vat-numbers` with the branch name you have given yourself. 80 | 81 | Finally, go to GitHub and 82 | [make a Pull Request](https://help.github.com/articles/creating-a-pull-request) 83 | :D 84 | 85 | Travis CI will run our test suite against all supported PHP versions. We care 86 | about quality, so your PR won't be merged until all tests pass. It's unlikely, 87 | but it's possible that your changes pass tests in one PHP version but fails in 88 | another. In that case, you'll have to setup your development environment (as 89 | explained in step 3) to use the problematic PHP version, and investigate 90 | what's going on! 91 | 92 | The [PHP containers on Docker HUB](https://hub.docker.com/_/php) might be 93 | convenient for this purpose. You might want to make use of them. 94 | 95 | ### 7. Keeping your Pull Request updated 96 | 97 | If a maintainer asks you to "rebase" your PR, they're saying that a lot of code 98 | has changed, and that you need to update your branch so it's easier to merge. 99 | 100 | To learn more about rebasing in Git, there are a lot of 101 | [good](http://git-scm.com/book/en/Git-Branching-Rebasing) 102 | [resources](https://help.github.com/articles/interactive-rebase), 103 | but here's the suggested workflow: 104 | 105 | ```sh 106 | git checkout 123-update-be-vat-numbers 107 | git pull --rebase upstream master 108 | git push --force-with-lease 123-update-be-vat-numbers 109 | ``` 110 | 111 | ### 8. Merging a PR (maintainers only) 112 | 113 | A PR can only be merged into master by a maintainer if: 114 | 115 | * It is passing CI. 116 | * It has been approved by at least two maintainers. If it was a maintainer who 117 | opened the PR, only one extra approval is needed. 118 | * It has no requested changes. 119 | * It is up to date with current master. 120 | 121 | Any maintainer is allowed to merge a PR if all of these conditions are 122 | met. 123 | 124 | ### 9. Shipping a release (maintainers only) 125 | 126 | Maintainers need to do the following to push out a release: 127 | 128 | * Make sure all pull requests are in 129 | * Create a stable branch for that release: 130 | 131 | This example explains the process to tag a new version on the 2.0 branch, where 132 | `upstream` references [dragonbe/vies](https://github.com/dragonbe/vies)and 2.0.8 133 | is our latest tag. 134 | 135 | ```sh 136 | git fetch upstream 137 | git checkout master 138 | git pull --rebase upstream master 139 | git checkout 2.0 140 | git merge master 141 | git tag -a 2.0.9 142 | git push upstream 2.0.9 143 | ``` 144 | 145 | That's all there is to it. 146 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Michelangelo van Dam 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VIES 2 | 3 | Component using the European Commission (EC) VAT Information Exchange System (VIES) to verify and validate VAT registration numbers in the EU, using PHP and Composer. 4 | 5 | The `Vies` class provides functionality to make a SOAP call to VIES and returns an object `CheckVatResponse` containing the following information: 6 | 7 | - Country code (string): a 2-character notation of the country code 8 | - VAT registration number (string): contains the complete registration number without the country code 9 | - Date of request (DateTime): the date when the request was made 10 | - Valid (boolean): flag indicating the registration number was valid (TRUE) or not (FALSE) 11 | - Name (string): registered company name (if provided by EC member state) 12 | - Address (string): registered company address (if provided by EC member state) 13 | 14 | Stated on the European Commission website: 15 | > To make an intra-Community supply without charging VAT, you **should ensure** that the person to whom you are supplying the goods is a taxable person in another Member State, and that the goods in question have left, or will leave your Member State to another MS. VAT-number should also be in the invoice. 16 | 17 | More information at http://ec.europa.eu/taxation_customs/vies/faqvies.do#item16 18 | 19 | [![Actions Status](https://github.com/DragonBe/vies/workflows/PHP%20Composer/badge.svg?branch=master)](https://github.com/DragonBe/vies/actions) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=dragonbe-vies&metric=alert_status)](https://sonarcloud.io/dashboard?id=dragonbe-vies) 20 | 21 | ## GDPR and privacy regulation of VAT within the EU 22 | 23 | On May 25, 2018 the General Data Protection Regulation or GDPR becomes law within all 28 European Member States. Is this VIES service package going to be compliant with GDPR? 24 | 25 | In short: yes. 26 | 27 | The longer answer is that this VIES package only interacts with the service for VAT ID verification provided by the European Commission. VAT validation is mandatory in European countries and therefor this service is allowed as lawfulness and legal basis. Please read more about this in [European DPO-3816.1](http://ec.europa.eu/dpo-register/details.htm?id=40647). This service does not store any data itself or collects more information than what's strictly required by law and provided by the EC VIES service. 28 | 29 | When you have implemented this service package in your own project, be sure that you're making sure you're just store the VAT ID, the timestamp of validation, the result of validation and optionally the given validation ID provided by the EC VIES service. 30 | 31 | ## Requirements 32 | 33 | - Minimum PHP version: 7.3 34 | - Recommended PHP version: 7.4 35 | - Extension: soap 36 | - Extension: pcntl 37 | - Extension: ctype 38 | 39 | Please read the [release notes](https://github.com/DragonBe/vies/releases) for details. 40 | 41 | ## Installation 42 | 43 | This project is on [Packagist](https://packagist.org/packages/dragonbe/vies)! 44 | 45 | To install the latest stable version use `composer require dragonbe/vies`. 46 | 47 | To install specifically a version (e.g. 2.2.0), just add it to the command above, for example `composer require dragonbe/vies:2.2.0` 48 | 49 | ## Usage 50 | 51 | Here's a usage example you can immediately execute on the command line (or in cron, worker or whatever) as this will most likely be your most common usecase. 52 | 53 | ### 1. Setting it up 54 | 55 | ```php 56 | getHeartBeat()->isAlive()) { 71 | 72 | echo 'Service is not available at the moment, please try again later.' . PHP_EOL; 73 | exit(1); 74 | } 75 | ``` 76 | 77 | #### If using a proxy, you can now use the following approach 78 | 79 | ```php 80 | $vies = new Vies(); 81 | $options = [ 82 | 'proxy_host' => '127.0.0.1', 83 | 'proxy_port' => '8888', 84 | ]; 85 | $vies->setOptions($options); 86 | 87 | $heartBeat = new \DragonBe\Vies\HeartBeat('tcp://' . $options['proxy_host'], $options['proxy_port']); 88 | $vies->setHeartBeat($heartBeat); 89 | 90 | $isAlive = $vies->getHeartBeat()->isAlive(); 91 | ``` 92 | 93 | ### 3. Validate VAT 94 | 95 | Now that we know the service is alive, we can start validating VAT ID's 96 | 97 | #### 3.1. Simple usage 98 | 99 | ```php 100 | $vatResult = $vies->validateVat( 101 | 'BE', // Trader country code 102 | '0203430576', // Trader VAT ID 103 | 'BE', // Requester country code 104 | '0811231190' // Requester VAT ID 105 | ); 106 | ``` 107 | 108 | #### 3.2. Advanced usage 109 | 110 | ```php 111 | $vatResult = $vies->validateVat( 112 | 'BE', // Trader country code 113 | '0203430576', // Trader VAT ID 114 | 'BE', // Requester country code 115 | '0811231190' // Requester VAT ID 116 | 'B-Rail', // Trader name 117 | 'NV', // Trader company type 118 | 'Frankrijkstraat 65', // Trader street address 119 | '1060', // Trader postcode 120 | 'Sint-Gillis' // Trader city 121 | ); 122 | ``` 123 | 124 | #### 3.3. Result methods 125 | 126 | ##### 3.3.1. Is the VAT ID valid? 127 | 128 | The most important functionality is to see if the VAT ID is valid 129 | 130 | ```php 131 | echo ($vatResult->isValid() ? 'Valid' : 'Not valid') . PHP_EOL; 132 | 133 | // Result: Valid 134 | ``` 135 | 136 | ##### 3.3.2. Retrieve the VAT validation identifier 137 | 138 | ```php 139 | echo 'Identifier: ' . $vatResult->getIdentifier() . PHP_EOL; 140 | 141 | // Result: Identifier: WAPIAAAAWaXGj4Ra 142 | ``` 143 | 144 | ##### 3.3.3. Retrieve validation date 145 | 146 | **Note: VIES service returns date and timezone, but no time** 147 | 148 | ```php 149 | echo 'Date and time: ' . $vatResult->getRequestDate()->format('r') . PHP_EOL; 150 | 151 | // Result: Date and time: Sat, 31 Aug 2019 00:00:00 +0200 152 | ``` 153 | 154 | ##### 3.3.4. Retrieve official trader name (not always available) 155 | 156 | ```php 157 | echo 'Company name: ' . $vatResult->getName() . PHP_EOL; 158 | 159 | // Result: Company name: NV OR NATIONALE MAATSCHAPPIJ DER BELGISCHE SPOORWEGEN 160 | ``` 161 | 162 | ##### 3.3.5. Retrieve official trader street (not always available) 163 | 164 | ```php 165 | echo 'Company address: ' . $vatResult->getAddress() . PHP_EOL; 166 | 167 | // Result: Company address: FRANKRIJKSTRAAT 56 168 | 1060 SINT-GILLIS (BIJ-BRUSSEL) 169 | ``` 170 | 171 | ##### 3.3.6. Retrieve a match for trader name (not always available) 172 | 173 | ```php 174 | echo 'Trader name match: ' . $vatResult->getNameMatch() . PHP_EOL; 175 | 176 | // Result: Trader name match: 177 | ``` 178 | 179 | ##### 3.3.7. Retrieve a match for trader company type (not always available) 180 | 181 | ```php 182 | echo 'Trader company type match: ' . $vatResult->getCompanyTypeMatch() . PHP_EOL; 183 | 184 | // Result: Trader company type match: 185 | ``` 186 | 187 | ##### 3.3.8. Retrieve a match for trader street (not always available) 188 | 189 | ```php 190 | echo 'Trader street match: ' . $vatResult->getStreetMatch() . PHP_EOL; 191 | 192 | // Result: Trader street match: 193 | ``` 194 | 195 | ##### 3.3.9. Retrieve a match for trader postcode (not always available) 196 | 197 | ```php 198 | echo 'Trader postcode match: ' . $vatResult->getPostcodeMatch() . PHP_EOL; 199 | 200 | // Result: Trader postcode match: 201 | ``` 202 | 203 | ##### 3.3.10. Retrieve a match for trader city (not always available) 204 | 205 | ```php 206 | echo 'Trader city match: ' . $vatResult->getCityMatch() . PHP_EOL; 207 | 208 | // Result: Trader city match: 209 | ``` 210 | 211 | ### Example code 212 | 213 | ```php 214 | 'BE', 226 | 'vat_id' => '0203430576', 227 | 'trader_name' => 'B-Rail', 228 | 'trader_company_type' => 'NV', 229 | 'trader_street' => 'Frankrijkstraat 65', 230 | 'trader_postcode' => '1060', 231 | 'trader_city' => 'Sint-Gillis', 232 | ]; 233 | 234 | try { 235 | $vatResult = $vies->validateVat( 236 | $company['country_code'], // Trader country code 237 | $company['vat_id'], // Trader VAT ID 238 | 'BE', // Requester country code (your country code) 239 | '0811231190', // Requester VAT ID (your VAT ID) 240 | $company['trader_name'], // Trader name 241 | $company['trader_company_type'], // Trader company type 242 | $company['trader_street'], // Trader street address 243 | $company['trader_postcode'], // Trader postcode 244 | $company['trader_city'] // Trader city 245 | ); 246 | } catch (ViesException $viesException) { 247 | echo 'Cannot process VAT validation: ' . $viesException->getMessage(); 248 | exit (2); 249 | } catch (ViesServiceException $viesServiceException) { 250 | echo 'Cannot process VAT validation: ' . $viesServiceException->getMessage(); 251 | exit (2); 252 | } 253 | 254 | echo ($vatResult->isValid() ? 'Valid' : 'Not valid') . PHP_EOL; 255 | echo 'Identifier: ' . $vatResult->getIdentifier() . PHP_EOL; 256 | echo 'Date and time: ' . $vatResult->getRequestDate()->format('d/m/Y H:i') . PHP_EOL; 257 | echo 'Company name: ' . $vatResult->getName() . PHP_EOL; 258 | echo 'Company address: ' . $vatResult->getAddress() . PHP_EOL; 259 | 260 | echo 'Trader name match: ' . $vatResult->getNameMatch() . PHP_EOL; 261 | echo 'Trader company type match: ' . $vatResult->getCompanyTypeMatch() . PHP_EOL; 262 | echo 'Trader street match: ' . $vatResult->getStreetMatch() . PHP_EOL; 263 | echo 'Trader postcode match: ' . $vatResult->getPostcodeMatch() . PHP_EOL; 264 | echo 'Trader city match: ' . $vatResult->getCityMatch() . PHP_EOL; 265 | echo PHP_EOL; 266 | ``` 267 | 268 | When you run this, you will get the following result: 269 | 270 | ``` 271 | Valid 272 | Identifier: WAPIAAAAWaYR0O8D 273 | Date and time: 21/10/2018 02:00 274 | Company name: NV OR NATIONALE MAATSCHAPPIJ DER BELGISCHE SPOORWEGEN 275 | Company address: FRANKRIJKSTRAAT 56 276 | 1060 SINT-GILLIS (BIJ-BRUSSEL) 277 | Trader name match: 278 | Trader company type match: 279 | Trader street match: 280 | Trader postcode match: 281 | Trader city match: 282 | 283 | ``` 284 | 285 | ## Community involvement 286 | 287 | Here's a list of products or projects that have included this VIES package 288 | 289 | - [Symfony bundle](https://github.com/MyOnlineStore/ViesBundle) by [MyOnlineStore](https://www.myonlinestore.com) 290 | - [sandwich/vies-bundle](https://packagist.org/packages/sandwich/vies-bundle) 291 | 292 | If you have a product or a project that's using this package and you want some attribution for your work, send me an [email](mailto://dragonbe+github@gmail.com) or ping me on [Twitter](https://www.twitter.com/DragonBe) or [Facebook](https://www.facebook.com/dragonbe). 293 | 294 | ## Docker containers 295 | 296 | If you like to have Docker containers, you can now make use of a container designed for that purpose. 297 | 298 | ```shell 299 | docker run --rm -d -p 8000:18080 dragonbe/vies-web 300 | ``` 301 | 302 | Point your browser to [localhost:8000](http://localhost:8000) to use the web interface for validating VAT. 303 | 304 | ![A screenshot of VIES web application](docs/images/vies-web-screenshot.png) 305 | 306 | ## Referenced on the web 307 | 308 | - [Microsoft Dynamics GP - Dynamics GP real time EU tax registration number validation using VIES](http://timwappat.info/post/2013/08/22/Dynamics-GP-real-time-EU-tax-registration-number-validation-using-VIES) 309 | - [Popular RIA law eu projects](https://libraries.io/search?keywords=RIA%2Claw%2Ceu) 310 | - [PHP Code Examples - HotExamples.com](https://hotexamples.com/examples/dragonbe.vies/Vies/validateVat/php-vies-validatevat-method-examples.html) 311 | 312 | ## Clarification on exceptions 313 | 314 | For Greece the [international country ISO code](https://www.iso.org/obp/ui/#iso:code:3166:GR) is **GR**, but for VAT IDN's they use the prefix **EL**. Thanks to [Johan Wilfer](https://github.com/johanwilfer) for [reporting this](https://github.com/DragonBe/vies/issues/57). 315 | 316 | Since January 1, 2021 the UK is no longer a member of the European Union and as a result, the VIES service provided by the European Commission no longer validates VAT ID's for the UK. There is one exception though and that is for Northern Ireland (XI) for which VAT ID's can be validated using this library and the EC VIES service. 317 | 318 | ## Licence 319 | 320 | DragonBe\Vies is released under the MIT Licence. See the bundled [LICENCE](LICENCE) file for details. 321 | -------------------------------------------------------------------------------- /bin/vatcheck: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | ' . PHP_EOL; 11 | exit(1); 12 | } 13 | 14 | $vies = new Vies(); 15 | if (false === $vies->getHeartBeat()->isAlive()) { 16 | echo 'EU VIES Service not available at this moment, please try again later' . PHP_EOL; 17 | exit(2); 18 | } 19 | 20 | $vatId = $vies->splitVatId($argv[1]); 21 | 22 | $result = null; 23 | try { 24 | $result = $vies->validateVat($vatId['country'], $vatId['id']); 25 | } catch (ViesServiceException $vse) { 26 | echo $vse->getMessage() . PHP_EOL; 27 | exit(3); 28 | } catch (ViesException $ve) { 29 | echo $ve->getMessage() . PHP_EOL; 30 | exit(4); 31 | } 32 | 33 | echo sprintf( 34 | 'VAT ID %s %s is %s', 35 | $result->getCountryCode(), 36 | $result->getVatNumber(), 37 | $result->isValid() ? 'VALID' : 'NOT VALID' 38 | ) . PHP_EOL; 39 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 32 | 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dragonbe/vies", 3 | "type": "tool", 4 | "description": "EU VAT numbers validation using the VIES Service of the European Commission", 5 | "keywords": ["VIES","VAT","EU","EC"], 6 | "homepage": "https://github.com/DragonBe/vies", 7 | "minimum-stability": "stable", 8 | "license": "MIT", 9 | "authors": [ 10 | { 11 | "name": "Michelangelo van Dam", 12 | "email": "dragonbe@gmail.com", 13 | "homepage": "http://dragonbe.com", 14 | "role": "Developer" 15 | } 16 | ], 17 | "autoload": { 18 | "psr-4": { 19 | "DragonBe\\Vies\\": "src/Vies" 20 | } 21 | }, 22 | "autoload-dev": { 23 | "psr-4": { 24 | "DragonBe\\Tests\\Vies\\": "tests/Vies" 25 | } 26 | }, 27 | "require": { 28 | "php": ">=7.3", 29 | "ext-soap": "*", 30 | "ext-ctype": "*" 31 | }, 32 | "require-dev": { 33 | "ext-pcntl": "*", 34 | "phpunit/phpunit": "^9.4", 35 | "phpunit/php-invoker": "^3.1", 36 | "phploc/phploc": "^7.0", 37 | "phpmd/phpmd": "^2.6", 38 | "sebastian/phpcpd": "^6.0", 39 | "pdepend/pdepend": "^2.5", 40 | "squizlabs/php_codesniffer": "^3.2", 41 | "phing/phing": "^2.16", 42 | "infection/infection": "^0.25", 43 | "enlightn/security-checker": "^1.2" 44 | }, 45 | "scripts": { 46 | "phpcs": "phpcs", 47 | "phpunit": "phpunit", 48 | "test": [ 49 | "@phpcs", 50 | "@phpunit" 51 | ], 52 | "pmd": [ 53 | "@pmd-naming", 54 | "@pmd-unused", 55 | "@pmd-codesize", 56 | "@pmd-cleancode", 57 | "@phpcpd" 58 | ], 59 | "pmd-naming": "phpmd src xml naming --reportfile build/logs/pmd-naming.xml --strict --ignore-violations-on-exit", 60 | "pmd-unused": "phpmd src xml unusedcode --reportfile build/logs/pmd-unusedcode.xml --strict --ignore-violations-on-exit", 61 | "pmd-codesize": "phpmd src xml codesize --reportfile build/logs/pmd-codesize.xml --strict --ignore-violations-on-exit", 62 | "pmd-cleancode": "phpmd src xml cleancode --reportfile build/logs/pmd-cleancode.xml --strict --ignore-violations-on-exit", 63 | "pdepend": "pdepend --jdepend-chart=build/logs/jdepend-chart.svg --jdepend-xml=build/logs/jdepend.xml --overview-pyramid=build/logs/pyramid.svg src", 64 | "phpcpd": "phpcpd --log-pmd=build/logs/pmd-cpd.xml --min-lines=3 --min-tokens=50 --fuzzy --progress src", 65 | "test-all": [ 66 | "@test", 67 | "@pmd" 68 | ] 69 | }, 70 | "config": { 71 | "allow-plugins": { 72 | "infection/extension-installer": true 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /docs/images/vies-web-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DragonBe/vies/d9193cbaba7e2faefbdc228fb1bf5670f20acf30/docs/images/vies-web-screenshot.png -------------------------------------------------------------------------------- /examples/EURailwayVATChecker.php: -------------------------------------------------------------------------------- 1 | getHeartBeat()->isAlive()) { 20 | 21 | echo 'Back-end VIES service is not available at the moment, please try again later.' . PHP_EOL; 22 | exit(1); 23 | 24 | } 25 | 26 | $companies = [ 27 | [ 28 | 'country_code' => 'BE', 29 | 'vat_id' => '0203430576', 30 | 'trader_name' => 'B-Rail', 31 | 'trader_company_type' => 'NV', 32 | 'trader_street' => 'Frankrijkstraat 65', 33 | 'trader_postcode' => '1060', 34 | 'trader_city' => 'Sint-Gillis', 35 | ], 36 | [ 37 | 'country_code' => 'NL', 38 | 'vat_id' => '803979988B01', 39 | 'trader_name' => 'NS Groep', 40 | 'trader_company_type' => 'NV', 41 | 'trader_street' => 'Laan van Puntenburg 100', 42 | 'trader_postcode' => '3511ER', 43 | 'trader_city' => 'Utrecht', 44 | ], 45 | [ 46 | 'country_code' => 'DE', 47 | 'vat_id' => '811569869', 48 | 'trader_name' => 'Deutsche Bahn', 49 | 'trader_company_type' => 'AG', 50 | 'trader_street' => 'Potsdamer Platz 2', 51 | 'trader_postcode' => '10785', 52 | 'trader_city' => 'Berlin', 53 | ], 54 | [ 55 | 'country_code' => 'FR', 56 | 'vat_id' => '35552049447', 57 | 'trader_name' => 'Societe Nationale des Chemins de fer Francais', 58 | 'trader_company_type' => '', 59 | 'trader_street' => '2, place aux Étoiles', 60 | 'trader_postcode' => '93200', 61 | 'trader_city' => 'Saint-Denis', 62 | ], 63 | [ 64 | 'country_code' => 'ES', 65 | 'vat_id' => 'A86868189', 66 | 'trader_name' => 'Renfe Viajeros', 67 | 'trader_company_type' => 'SA', 68 | 'trader_street' => 'Avda. Ciudad de Barcelona, 8', 69 | 'trader_postcode' => '28007', 70 | 'trader_city' => 'Madrid', 71 | ], 72 | [ 73 | 'country_code' => 'PT', 74 | 'vat_id' => '500498601', 75 | 'trader_name' => 'Comboios de Portugal', 76 | 'trader_company_type' => 'SA', 77 | 'trader_street' => 'Calçada do Duque, n.º 20', 78 | 'trader_postcode' => '1249-109', 79 | 'trader_city' => 'Lisboa', 80 | ], 81 | [ 82 | 'country_code' => 'IT', 83 | 'vat_id' => '04983351000', 84 | 'trader_name' => 'POL-RAIL', 85 | 'trader_company_type' => 'SRL', 86 | 'trader_street' => 'Viale dello Scalo San Lorenzo, 16', 87 | 'trader_postcode' => '00185', 88 | 'trader_city' => 'Roma', 89 | ], 90 | [ 91 | 'country_code' => 'AT', 92 | 'vat_id' => 'U58044244', 93 | 'trader_name' => 'Rail Cargo Austria', 94 | 'trader_company_type' => 'AG', 95 | 'trader_street' => 'Hauptbahnhof 2', 96 | 'trader_postcode' => '1100', 97 | 'trader_city' => 'Vienna', 98 | ], 99 | [ 100 | 'country_code' => 'EL', 101 | 'vat_id' => '999645865', 102 | 'trader_name' => 'Trainose', 103 | 'trader_company_type' => 'SA', 104 | 'trader_street' => '1-3 Karolou', 105 | 'trader_postcode' => '10437', 106 | 'trader_city' => 'Athens', 107 | ], 108 | [ 109 | 'country_code' => 'PL', 110 | 'vat_id' => '1132316427', 111 | 'trader_name' => 'PKP POLSKIE LINIE KOLEJOWE SPÓŁKA AKCYJNA', 112 | 'trader_company_type' => '', 113 | 'trader_street' => 'TARGOWA 74', 114 | 'trader_postcode' => '03-734', 115 | 'trader_city' => 'WARSZAWA', 116 | ], 117 | [ 118 | 'country_code' => 'LV', 119 | 'vat_id' => '40003032065', 120 | 'trader_name' => 'Valsts akciju sabiedrība "Latvijas dzelzceļš"', 121 | 'trader_company_type' => '', 122 | 'trader_street' => 'Gogoļa iela 3,', 123 | 'trader_postcode' => 'LV-1050', 124 | 'trader_city' => 'Riga', 125 | ], 126 | ]; 127 | 128 | foreach ($companies as $company) { 129 | try { 130 | $vatResult = $vies->validateVat( 131 | $company['country_code'], // Trader country code 132 | $company['vat_id'], // Trader VAT ID 133 | 'BE', // Requester country code 134 | '0811231190', // Requester VAT ID 135 | $company['trader_name'], // Trader name 136 | $company['trader_company_type'], // Trader company type 137 | $company['trader_street'], // Trader street address 138 | $company['trader_postcode'], // Trader postcode 139 | $company['trader_city'] // Trader city 140 | ); 141 | } catch (ViesException $viesException) { 142 | echo 'Cannot process VAT validation: ' . $viesException->getMessage(); 143 | continue; 144 | } catch (ViesServiceException $viesServiceException) { 145 | echo 'Cannot process VAT validation: ' . $viesServiceException->getMessage(); 146 | continue; 147 | } 148 | 149 | echo ($vatResult->isValid() ? 'Valid' : 'Not valid') . PHP_EOL; 150 | echo 'Identifier: ' . $vatResult->getIdentifier() . PHP_EOL; 151 | echo 'Date and time: ' . $vatResult->getRequestDate()->format('d/m/Y H:i') . PHP_EOL; 152 | echo 'Company name: ' . $vatResult->getName() . PHP_EOL; 153 | echo 'Company address: ' . $vatResult->getAddress() . PHP_EOL; 154 | 155 | echo 'Trader name match: ' . $vatResult->getNameMatch() . PHP_EOL; 156 | echo 'Trader company type match: ' . $vatResult->getCompanyTypeMatch() . PHP_EOL; 157 | echo 'Trader street match: ' . $vatResult->getStreetMatch() . PHP_EOL; 158 | echo 'Trader postcode match: ' . $vatResult->getPostcodeMatch() . PHP_EOL; 159 | echo 'Trader city match: ' . $vatResult->getCityMatch() . PHP_EOL; 160 | echo PHP_EOL; 161 | } 162 | 163 | exit (0); 164 | -------------------------------------------------------------------------------- /examples/async_processing/.gitignore: -------------------------------------------------------------------------------- 1 | vatqueue.db 2 | -------------------------------------------------------------------------------- /examples/async_processing/README.md: -------------------------------------------------------------------------------- 1 | # Async processing of VAT ID validation 2 | 3 | A common question we got is how we deal with VAT ID validation when the official [European Commission (EC) VAT Information Exchange System (VIES)](http://ec.europa.eu/taxation_customs/vies) is down or not operational. Often we explain that we have several tools in place to perform many validations offline like `validateVatSum` and our `getHeartBeat()->isAlive()`, but this doesn't solve the problem for many users. 4 | 5 | Therefor I've created this simple Async VAT Validation setup using a `queue.php` for adding VAT ID's into a simple SQLite DB queue and `worker.php` which will run as a daemon constantly checking the queue to see if there's a new VAT ID to validate. This worker also implements the "heart beat" checker which puts it into sleep mode for 30 minutes. 6 | 7 | The benefit is you can run the worker as a process and have the queue script listening onto a seperate port to which you can easily send your VAT ID's. 8 | 9 | **THIS IS A PROOF-OF-CONCEPT!!! DO NOT USE AS-IS IN PRODUCTION!!!** 10 | 11 | ## Step 1: Initialise the database 12 | 13 | We're making use of SQLite for this PoC, but feel free to set it up for whatever backend you feel comfortable with. 14 | 15 | ``` 16 | cd examples/async_processing 17 | sqlite3 vatqueue.db < vatqueue.sql 18 | ``` 19 | 20 | ## Step 2: Starting the worker 21 | 22 | This will run the worker as a daemon and puts it in the background. You can also run it in Screen or even in Docker if you want. 23 | 24 | ``` 25 | php worker.php & 26 | ``` 27 | 28 | ## Step 3: Start the queue listener 29 | 30 | We also want a simple interface where we can just point the VAT ID to, and nothing is easier than to use it as a web service. In this case we're using the build-in PHP web server to handle requests, but you can also have it run in Apache, Nginx or even inside a Docker container. 31 | 32 | ``` 33 | php -S localhost:11984 queue.php 34 | ``` 35 | 36 | ## Step 4: Add VAT ID's to the queue 37 | 38 | Assuming that all steps have been followed as described above, you can just throw VAT ID's to the web service endpoint by using curl or ajax calls. 39 | 40 | ``` 41 | curl "http://localhost:11984/?vatid=BE0811231234" 42 | curl "http://localhost:11984/?vatid=BE0811231235" 43 | curl "http://localhost:11984/?vatid=BE0811231236" 44 | ``` 45 | 46 | ## Step 5: See the validation 47 | 48 | Now you just need to read from the backend to see which VAT ID was valid and which one did not. And as long the `worker.php` is running, it will keep reading the queue until all things are processed. 49 | -------------------------------------------------------------------------------- /examples/async_processing/list.php: -------------------------------------------------------------------------------- 1 | query('SELECT * FROM `vat_validation` ORDER BY `id` DESC'); 8 | 9 | $vatIdList = $listStmt->fetchAll(\PDO::FETCH_ASSOC); 10 | 11 | ?> 12 | 13 | 14 | 15 | Listing VAT ID's 16 | 17 | 18 |

Listing VAT ID's

19 | 20 |

Add new VAT ID

21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
VAT IDDateStatus
format('d-m-Y') ?>
36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/async_processing/queue.php: -------------------------------------------------------------------------------- 1 | prepare('SELECT `id` FROM `vat_validation` WHERE `vat_id` = ?'); 7 | $addToQueueStmt = $pdo->prepare('INSERT INTO `vat_validation` (`vat_id`) VALUES (?)'); 8 | $updateQueueStmt = $pdo->prepare('UPDATE `vat_validation` SET `validated` = "" WHERE `id` = ?'); 9 | 10 | if ([] !== $_GET && array_key_exists('vatid', $_GET)) { 11 | $vatId = filter_var($_GET['vatid'], FILTER_SANITIZE_SPECIAL_CHARS, FILTER_FLAG_ENCODE_HIGH); 12 | $vatId = str_replace(['.', '-', ' ', '_'], '', $vatId); 13 | 14 | $isAlreadyValidatedStmt->bindValue(1, $vatId); 15 | $isAlreadyValidatedStmt->execute(); 16 | 17 | $result = $isAlreadyValidatedStmt->fetchColumn(0); 18 | if (false === $result) { 19 | $addToQueueStmt->bindValue(1, $vatId); 20 | $addToQueueStmt->execute(); 21 | } else { 22 | $updateQueueStmt->bindValue(1, $result); 23 | $updateQueueStmt->execute(); 24 | } 25 | header('Location: ' . $_SERVER['PHP_SELF']); 26 | } 27 | ?> 28 | 29 | 30 | 31 | Add a new VAT ID to the queue 32 | 33 | 34 |

Add a new VAT ID to the queue

35 | 36 |

Back to list

37 | 38 |
39 | : 40 | 41 | 42 |
43 | 44 | 45 | -------------------------------------------------------------------------------- /examples/async_processing/vatqueue.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS `vat_validation`; 2 | CREATE TABLE `vat_validation` ( 3 | `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 4 | `vat_id` TEXT NOT NULL, 5 | `validated` TEXT NOT NULL DEFAULT '', 6 | `result` TEXT NOT NULL DEFAULT '', 7 | `reason` TEXT NOT NULL DEFAULT '', 8 | `name` TEXT NOT NULL DEFAULT '', 9 | `address` TEXT NOT NULL DEFAULT '', 10 | `identifier` TEXT NOT NULL DEFAULT '' 11 | ); 12 | -------------------------------------------------------------------------------- /examples/async_processing/worker.php: -------------------------------------------------------------------------------- 1 | prepare('SELECT `id`, `vat_id` FROM `vat_validation` WHERE `validated` = "" ORDER BY `id` DESC LIMIT 1'); 13 | $updateVatId = $pdo->prepare('UPDATE `vat_validation` SET `validated` = ?, `result` = ?, `reason` = ?, `name` = ?, `address` = ?, `identifier` = ? WHERE `id` = ?'); 14 | 15 | function debug($message = ''): void 16 | { 17 | $debug = false; 18 | if ($debug) { 19 | echo '[DEBUG] >>> ' . $message . PHP_EOL; 20 | } 21 | } 22 | 23 | $vies = new Vies(); 24 | do { 25 | if (!$vies->getHeartBeat()->isAlive()) { 26 | debug('Service is down, sleeping for 30 minutes'); 27 | sleep(1800); // sleep for 30 minutes 28 | continue; 29 | } 30 | $validationStmt->execute(); 31 | 32 | $row = $validationStmt->fetch(\PDO::FETCH_ASSOC); 33 | if (false === $row) { 34 | debug('No new data found'); 35 | sleep(60); 36 | continue; 37 | } 38 | $id = (int) $row['id']; 39 | $vatin = $row['vat_id']; 40 | $vatCountry = substr($vatin, 0, 2); 41 | $vatNumber = substr($vatin, 2); 42 | $vatNumber = $vies->filterVat($vatNumber); 43 | $reason = ''; 44 | 45 | try { 46 | $result = $vies->validateVat($vatCountry, $vatNumber); 47 | debug('Result: ' . var_export($result, 1)); 48 | } catch (ViesServiceException $viesServiceException) { 49 | // Connection was broken or other weird things went on, let's wait a few 50 | debug('ViesServiceException: ' . $viesServiceException->getMessage()); 51 | sleep(60); 52 | continue; 53 | } catch (ViesException $viesException) { 54 | // Something went wrong with the validation, add to log and notify service operators 55 | debug('ViesException: ' . $viesException->getMessage()); 56 | $reason = $viesException->getMessage(); 57 | } 58 | 59 | $updateVatId->bindValue(1, $result->getRequestDate()->format('Y-m-d H:i:s O')); 60 | $updateVatId->bindValue(2, $result->isValid() ? 'valid' : 'invalid'); 61 | $updateVatId->bindValue(3, $reason); 62 | $updateVatId->bindValue(4, $result->getName()); 63 | $updateVatId->bindValue(5, $result->getAddress()); 64 | $updateVatId->bindValue(6, $result->getIdentifier()); 65 | $updateVatId->bindValue(7, $id); 66 | 67 | $updateVatId->execute(); 68 | 69 | } while (true); 70 | -------------------------------------------------------------------------------- /examples/checkVat.php: -------------------------------------------------------------------------------- 1 | getHeartBeat()->isAlive()) { 12 | 13 | echo 'Back-end VIES service is not available at the moment, please try again later.' . PHP_EOL; 14 | 15 | } else { 16 | 17 | $vatNumberProvider = [ 18 | [ 19 | 'BE' => '0811231190', // valid 20 | 'HR' => '20649144807' 21 | ], 22 | [ 23 | 'BE' => '1234567890', // invalid 24 | 'ES' => '9999999999', 25 | ], 26 | [ 27 | 'AA' => '1234567890', // invalid country code 28 | 'NO' => '1234567890' 29 | ], 30 | ]; 31 | 32 | foreach ($vatNumberProvider as $vatNumbers) { 33 | 34 | foreach ($vatNumbers as $countryCode => $vatNumber) { 35 | 36 | echo 'Validating ' . $countryCode . $vatNumber . '... '; 37 | 38 | try { 39 | $result = $vies->validateVat($countryCode, $vatNumber); // - Validation routine worked as expected. 40 | echo ($result->isValid()) ? 'Valid' : 'Invalid'; // 41 | } catch (ViesServiceException $e) { // - Recoverable exception. 42 | echo $e->getMessage(); // There is probably a temporary problem with back-end VIES service. 43 | } catch (ViesException $e) { // - Unrecoverable exception. 44 | echo $e->getMessage(); // Invalid country code etc. 45 | } 46 | 47 | echo PHP_EOL; 48 | 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/checkVatBatch.php: -------------------------------------------------------------------------------- 1 | getHeartBeat()->isAlive()) { 12 | 13 | echo 'Back-end VIES service is not available at the moment, please try again later.' . PHP_EOL; 14 | 15 | } else { 16 | 17 | $splQueue = new SplQueue(); 18 | 19 | foreach (vatinProvider() as $vatin) { 20 | $splQueue->enqueue($vatin); 21 | } 22 | 23 | foreach ($splQueue as $item) { 24 | 25 | $vatin = $splQueue->dequeue(); 26 | 27 | echo 'Validating ' . $vatin . '... '; 28 | 29 | $countryCode = substr($vatin, 0, 2); 30 | $vatNumber = substr($vatin, 2); 31 | $vatNumber = $vies->filterVat($vatNumber); 32 | 33 | try { 34 | $result = $vies->validateVat($countryCode, $vatNumber); // Validation routine worked as expected. 35 | echo ($result->isValid()) ? 'VALID' : 'INVALID'; 36 | } catch (ViesServiceException $e) { // Recoverable exception. There is probably a temporary problem 37 | echo $e->getMessage(); // with back-end VIES service. Try again. Add VATIN back to queue. 38 | $splQueue->enqueue($vatin); 39 | } catch (ViesException $e) { // Unrecoverable exception. Invalid country code etc. 40 | echo $e->getMessage(); // Do not try again. 41 | } 42 | 43 | echo PHP_EOL; 44 | } 45 | } 46 | 47 | 48 | /** 49 | * Load an array of VATINs (country code + VAT number) 50 | * 51 | * Source: 52 | * http://www.braemoor.co.uk/software/vattest.php 53 | * 54 | * @return array 55 | */ 56 | function vatinProvider(): array 57 | { 58 | $filename = realpath(__DIR__ . '/vatins.php'); 59 | 60 | if (is_readable($filename)) { 61 | $ret = include $filename; 62 | } else { 63 | $ret = []; 64 | } 65 | 66 | return $ret; 67 | } 68 | -------------------------------------------------------------------------------- /examples/checkVatWithDetails.php: -------------------------------------------------------------------------------- 1 | getHeartBeat()->isAlive()) { 11 | 12 | echo 'Back-end VIES service is not available at the moment, please try again later.' . PHP_EOL; 13 | 14 | } else { 15 | $vatDataProvider = [ 16 | [ 17 | 'countryCode' => 'BE', // Country code 18 | 'vatId' => '0811231190', // VAT ID 19 | 'traderName' => 'In2it', // Trader name 20 | 'traderCompanyType' => 'VOF', // Trader company type 21 | 'traderStreet' => 'Battelsesteenweg 134', // Trader street address 22 | 'traderPostcode' => '2800', // Trader postcode 23 | 'traderCity' => 'Mechelen' // Trader city 24 | ], 25 | [ 26 | 'countryCode' => 'HR', // Country code 27 | 'vatId' => '20649144807', // VAT ID 28 | 'traderName' => 'Aning Usluge', // Trader name 29 | 'traderCompanyType' => 'DOO', // Trader company type 30 | 'traderStreet' => 'Zeleni Trg 4', // Trader street address 31 | 'traderPostcode' => '10000', // Trader postcode 32 | 'traderCity' => 'Zagreb' // Trader city 33 | ], 34 | ]; 35 | 36 | foreach ($vatDataProvider as $trader) { 37 | try { 38 | $response = $vies->validateVat( 39 | $trader['countryCode'], 40 | $trader['vatId'], 41 | $trader['countryCode'], // duplicate for requester VAT country code 42 | $trader['vatId'], // duplicate for requester VAT ID 43 | $trader['traderName'], 44 | $trader['traderCompanyType'], 45 | $trader['traderStreet'], 46 | $trader['traderPostcode'], 47 | $trader['traderCity'] 48 | ); 49 | 50 | echo sprintf('Company %s with VAT ID %s%s is %s (validation ID: %s)', 51 | $response->getName(), 52 | $response->getCountryCode(), 53 | $response->getVatNumber(), 54 | $response->isValid() ? 'Valid' : 'Not valid', 55 | $response->getIdentifier() 56 | ) . PHP_EOL; 57 | echo sprintf('Company name: %s (%s)', 58 | $response->getName(), 59 | '' === $response->getNameMatch() ? 'No match returned' : $response->getNameMatch() 60 | ) . PHP_EOL; 61 | echo sprintf('Company address: %s (%s)', 62 | $response->getAddress(), 63 | '' === $response->getStreetMatch() ? 'No match returned' : $response->getStreetMatch() 64 | ) . PHP_EOL; 65 | } catch (ViesException $viesException) { 66 | echo 'An error occurred: ' . $viesException->getMessage(); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /infection.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "timeout": 5, 3 | "source": { 4 | "directories": [ 5 | "src" 6 | ] 7 | }, 8 | "logs": { 9 | "text": "build\/logs\/infection.txt" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | VIES Coding Standard 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | src 26 | tests 27 | 28 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./src 16 | 17 | 18 | ./src/autoload.php 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | ./tests 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.organization=dragonbe-github 2 | sonar.projectKey=dragonbe-vies 3 | sonar.projectName=VIES QA 4 | sonar.projectVersion=2.1.13 5 | sonar.sources=. 6 | sonar.host.url=https://sonarcloud.io 7 | 8 | # ===================================================== 9 | # Meta-data for the project 10 | # ===================================================== 11 | 12 | sonar.links.homepage=https://github.com/DragonBe/vies 13 | sonar.links.ci=https://travis-ci.org/DragonBe/vies 14 | sonar.links.scm=https://github.com/DragonBe/vies 15 | sonar.links.issue=https://github.com/DragonBe/vies/issues 16 | 17 | 18 | # ===================================================== 19 | # Properties that will be shared amongst all modules 20 | # ===================================================== 21 | 22 | # SQ standard properties 23 | sonar.sources=src 24 | sonar.tests=tests 25 | 26 | # Properties specific to language plugins: 27 | sonar.java.checkstyle.reportPaths=build/logs/checkstyle.xml 28 | sonar.java.pmd.reportPaths=build/logs/pmd-*.xml 29 | sonar.php.coverage.reportPath=build/logs/coverage/coverage-xml/index.xml 30 | sonar.php.coverage.reportPaths=build/logs/clover.xml 31 | sonar.php.tests.reportPath=build/logs/junit.xml 32 | sonar.php.file.suffixes=php 33 | sonar.php.exclusions=**/vendor/**,src/autoload.php,**/examples/**,**/tests/** 34 | sonar.cpd.exclusions=src/Vies/Validator/ValidatorXI.php 35 | -------------------------------------------------------------------------------- /src/Vies/CheckVatResponse.php: -------------------------------------------------------------------------------- 1 | 12 | * @license MIT 13 | * 14 | */ 15 | namespace DragonBe\Vies; 16 | 17 | use DateTime; 18 | use InvalidArgumentException; 19 | use stdClass; 20 | 21 | /** 22 | * CheckVatResponse 23 | * 24 | * This is the response object from the VIES web service for validation of 25 | * VAT numbers of companies registered in the European Union. 26 | * 27 | * @see \DragonBe\Vies\Exception 28 | * @category DragonBe 29 | * @package \DragonBe\Vies 30 | */ 31 | class CheckVatResponse 32 | { 33 | public const VIES_DATETIME_FORMAT = 'Y-m-dP'; 34 | 35 | /** 36 | * @var string The country code for a member of the European Union 37 | */ 38 | protected $countryCode; 39 | /** 40 | * @var string The VAT number of a registered European company 41 | */ 42 | protected $vatNumber; 43 | /** 44 | * @var DateTime The date of the request 45 | */ 46 | protected $requestDate; 47 | /** 48 | * @var bool Flag indicating the VAT number is valid 49 | */ 50 | protected $valid; 51 | /** 52 | * @var string The registered name of a validated company (optional) 53 | */ 54 | protected $name; 55 | /** 56 | * @var string The registered address of a validated company (optional) 57 | */ 58 | protected $address; 59 | /** 60 | * @var string The request Identifier (optional) 61 | */ 62 | protected $identifier; 63 | 64 | /** 65 | * @var string If optional name was provided, a check on name will be returned 66 | */ 67 | protected $nameMatch; 68 | 69 | /** 70 | * @var string If optional company type was provided, a check on type will be returned 71 | */ 72 | protected $companyTypeMatch; 73 | 74 | /** 75 | * @var string If optional street was provided, a check on street will be returned 76 | */ 77 | protected $streetMatch; 78 | 79 | /** 80 | * @var string If optional postcode was provided, a check on postcode will be returned 81 | */ 82 | protected $postcodeMatch; 83 | 84 | /** 85 | * @var string If optional city was provided, a check on city will be returned 86 | */ 87 | protected $cityMatch; 88 | 89 | /** 90 | * Constructor for this response object 91 | * 92 | * @param null|array|stdClass $params 93 | */ 94 | public function __construct($params = null) 95 | { 96 | if (null !== $params) { 97 | $this->populate($params); 98 | } 99 | } 100 | /** 101 | * Sets the two-character country code for a member of the European Union 102 | * 103 | * @param string $countryCode 104 | * 105 | * @return self 106 | */ 107 | public function setCountryCode(string $countryCode): self 108 | { 109 | $this->countryCode = $countryCode; 110 | 111 | return $this; 112 | } 113 | /** 114 | * Retrieves the two-character country code from a member of the European 115 | * Union. 116 | * 117 | * @return string 118 | */ 119 | public function getCountryCode(): string 120 | { 121 | return $this->countryCode; 122 | } 123 | /** 124 | * Sets the VAT number of a company within the European Union 125 | * 126 | * @param string $vatNumber 127 | * 128 | * @return self 129 | */ 130 | public function setVatNumber(string $vatNumber): self 131 | { 132 | $this->vatNumber = $vatNumber; 133 | 134 | return $this; 135 | } 136 | /** 137 | * Retrieves the VAT number from a company within the European Union 138 | * 139 | * @return string 140 | */ 141 | public function getVatNumber(): string 142 | { 143 | return $this->vatNumber; 144 | } 145 | 146 | /** 147 | * Sets the date- and timestamp when the VIES service response was created 148 | * 149 | * @param DateTime $requestDate 150 | * 151 | * @return self 152 | */ 153 | public function setRequestDate(DateTime $requestDate): self 154 | { 155 | $this->requestDate = $requestDate; 156 | 157 | return $this; 158 | } 159 | 160 | /** 161 | * Retrieves the date- and timestamp the VIES service response was created 162 | * 163 | * @return DateTime 164 | */ 165 | public function getRequestDate(): DateTime 166 | { 167 | $this->requestDate = $this->requestDate ?? date_create(); 168 | 169 | return $this->requestDate; 170 | } 171 | /** 172 | * Sets the flag to indicate the provided details were valid or not 173 | * 174 | * @param bool $flag 175 | * 176 | * @return self 177 | */ 178 | public function setValid(bool $flag): self 179 | { 180 | $this->valid = $flag; 181 | 182 | return $this; 183 | } 184 | /** 185 | * Checks to see if a request is valid with given parameters 186 | * 187 | * @return bool 188 | */ 189 | public function isValid(): bool 190 | { 191 | return $this->valid; 192 | } 193 | /** 194 | * Sets optionally the registered name of the company 195 | * 196 | * @param string $name 197 | * 198 | * @return self 199 | */ 200 | public function setName(string $name): self 201 | { 202 | $this->name = $name; 203 | 204 | return $this; 205 | } 206 | /** 207 | * Retrieves the registered name of the company 208 | * 209 | * @return string 210 | */ 211 | public function getName(): string 212 | { 213 | return $this->name; 214 | } 215 | 216 | /** 217 | * Sets the registered address of a company 218 | * 219 | * @param string $address 220 | * 221 | * @return self 222 | */ 223 | public function setAddress(string $address): self 224 | { 225 | $this->address = $address; 226 | 227 | return $this; 228 | } 229 | 230 | /** 231 | * Retrieves the registered address of a company 232 | * 233 | * @return string 234 | */ 235 | public function getAddress(): string 236 | { 237 | return $this->address; 238 | } 239 | 240 | /** 241 | * Sets request Identifier 242 | * 243 | * @param string $identifier 244 | * 245 | * @return self 246 | */ 247 | public function setIdentifier(string $identifier): self 248 | { 249 | $this->identifier = $identifier; 250 | 251 | return $this; 252 | } 253 | /** 254 | * get request Identifier 255 | * 256 | * @return string 257 | */ 258 | public function getIdentifier(): string 259 | { 260 | return $this->identifier; 261 | } 262 | 263 | /** 264 | * @return string 265 | */ 266 | public function getNameMatch(): string 267 | { 268 | return $this->nameMatch; 269 | } 270 | 271 | /** 272 | * @param string $nameMatch 273 | * @return CheckVatResponse 274 | */ 275 | public function setNameMatch(string $nameMatch): self 276 | { 277 | $this->nameMatch = $nameMatch; 278 | 279 | return $this; 280 | } 281 | 282 | /** 283 | * @return string 284 | */ 285 | public function getCompanyTypeMatch(): string 286 | { 287 | return $this->companyTypeMatch; 288 | } 289 | 290 | /** 291 | * @param string $companyTypeMatch 292 | * @return CheckVatResponse 293 | */ 294 | public function setCompanyTypeMatch(string $companyTypeMatch): self 295 | { 296 | $this->companyTypeMatch = $companyTypeMatch; 297 | 298 | return $this; 299 | } 300 | 301 | /** 302 | * @return string 303 | */ 304 | public function getStreetMatch(): string 305 | { 306 | return $this->streetMatch; 307 | } 308 | 309 | /** 310 | * @param string $streetMatch 311 | * @return CheckVatResponse 312 | */ 313 | public function setStreetMatch(string $streetMatch): self 314 | { 315 | $this->streetMatch = $streetMatch; 316 | 317 | return $this; 318 | } 319 | 320 | /** 321 | * @return string 322 | */ 323 | public function getPostcodeMatch(): string 324 | { 325 | return $this->postcodeMatch; 326 | } 327 | 328 | /** 329 | * @param string $postcodeMatch 330 | * @return CheckVatResponse 331 | */ 332 | public function setPostcodeMatch(string $postcodeMatch): self 333 | { 334 | $this->postcodeMatch = $postcodeMatch; 335 | 336 | return $this; 337 | } 338 | 339 | /** 340 | * @return string 341 | */ 342 | public function getCityMatch(): string 343 | { 344 | return $this->cityMatch; 345 | } 346 | 347 | /** 348 | * @param string $cityMatch 349 | * @return CheckVatResponse 350 | */ 351 | public function setCityMatch(string $cityMatch): self 352 | { 353 | $this->cityMatch = $cityMatch; 354 | 355 | return $this; 356 | } 357 | 358 | /** 359 | * Populates this response object with external data 360 | * 361 | * @param array|stdClass $row 362 | */ 363 | public function populate($row): void 364 | { 365 | if (is_array($row)) { 366 | $row = (object) $row; 367 | } 368 | 369 | $requiredFields = ['countryCode', 'vatNumber', 'requestDate', 'valid']; 370 | foreach ($requiredFields as $requiredField) { 371 | if (! isset($row->{$requiredField})) { 372 | throw new InvalidArgumentException('Required field "' . $requiredField . '" is missing'); 373 | } 374 | } 375 | 376 | $requestDateTime = $row->requestDate; 377 | if (! $row->requestDate instanceof DateTime) { 378 | // prepare request date 379 | $requestDateTime = date_create_from_format( 380 | self::VIES_DATETIME_FORMAT, 381 | $row->requestDate 382 | ); 383 | // Need to set time to zero 384 | // otherwise datetime would use current system time (which is not the response time) 385 | $requestDateTime->setTime(0, 0, 0, 0); 386 | } 387 | 388 | $this 389 | // required parameters 390 | ->setCountryCode($row->countryCode) 391 | ->setVatNumber($row->vatNumber) 392 | ->setRequestDate($requestDateTime) 393 | ->setValid($row->valid) 394 | // optional parameters 395 | ->setName($row->traderName ?? '---') 396 | ->setAddress($row->traderAddress ?? '---') 397 | ->setIdentifier($row->requestIdentifier ?? '') 398 | ->setNameMatch($row->traderNameMatch ?? '') 399 | ->setCompanyTypeMatch($row->traderCompanyTypeMatch ?? '') 400 | ->setStreetMatch($row->traderStreetMatch ?? '') 401 | ->setPostcodeMatch($row->traderPostcodeMatch ?? '') 402 | ->setCityMatch($row->traderCityMatch ?? '') 403 | ; 404 | } 405 | 406 | /** 407 | * Return this object as an array 408 | * 409 | * @return array 410 | */ 411 | public function toArray(): array 412 | { 413 | return [ 414 | 'countryCode' => $this->getCountryCode(), 415 | 'vatNumber' => $this->getVatNumber(), 416 | 'requestDate' => $this->getRequestDate()->format('Y-m-d'), 417 | 'valid' => $this->isValid(), 418 | 'name' => $this->getName(), 419 | 'address' => $this->getAddress(), 420 | 'identifier' => $this->getIdentifier(), 421 | 'nameMatch' => $this->getNameMatch(), 422 | 'companyTypeMatch' => $this->getCompanyTypeMatch(), 423 | 'streetMatch' => $this->getStreetMatch(), 424 | 'postcodeMatch' => $this->getPostcodeMatch(), 425 | 'cityMatch' => $this->getCityMatch(), 426 | ]; 427 | } 428 | } 429 | -------------------------------------------------------------------------------- /src/Vies/HeartBeat.php: -------------------------------------------------------------------------------- 1 | 12 | * @license MIT 13 | * 14 | */ 15 | namespace DragonBe\Vies; 16 | 17 | use DomainException; 18 | 19 | /** 20 | * Class HeartBeat 21 | * 22 | * This class provides a simple but essential heartbeat cheack 23 | * on the VIES service as it's known to have availability issues. 24 | * 25 | * @category DragonBe 26 | * @package \DragonBe\Vies 27 | * @link http://ec.europa.eu/taxation_customs/vies/faqvies.do#item16 28 | */ 29 | class HeartBeat 30 | { 31 | public const DEFAULT_TIMEOUT = 10; 32 | 33 | /** 34 | * @var string The host you want to verify 35 | */ 36 | protected $host; 37 | /** 38 | * @var int The port you want to verify 39 | */ 40 | protected $port; 41 | /** 42 | * @var ?string The path to append 43 | */ 44 | protected $path; 45 | 46 | /** 47 | * @var int The timeout in seconds 48 | */ 49 | protected $timeout; 50 | 51 | /** 52 | * @var bool Allow the service to be tested without integration of sockets 53 | */ 54 | public static $testingEnabled = false; 55 | 56 | /** 57 | * @var bool Allow to define the validation return setting 58 | */ 59 | public static $testingServiceIsUp = true; 60 | 61 | /** 62 | * @param string|null $host 63 | * @param int $port 64 | * @param int $timeout 65 | */ 66 | public function __construct( 67 | ?string $host = null, 68 | int $port = Vies::VIES_PORT, 69 | int $timeout = self::DEFAULT_TIMEOUT, 70 | ?string $path = null 71 | ) { 72 | if (null !== $host) { 73 | $this->setHost($host); 74 | } 75 | 76 | $this->setPath($path); 77 | $this->setPort($port); 78 | $this->setTimeout($timeout); 79 | } 80 | 81 | /** 82 | * @return string 83 | */ 84 | public function getHost(): string 85 | { 86 | if (null !== $this->host) { 87 | return $this->host; 88 | } 89 | 90 | throw new DomainException('A host is required'); 91 | } 92 | 93 | /** 94 | * @param string $host 95 | * @return self 96 | */ 97 | public function setHost(string $host): self 98 | { 99 | $this->host = $host; 100 | 101 | return $this; 102 | } 103 | 104 | /** 105 | * @return ?string 106 | */ 107 | public function getPath(): ?string 108 | { 109 | return $this->path; 110 | } 111 | 112 | /** 113 | * @param ?string $path 114 | * @return self 115 | */ 116 | public function setPath(?string $path = null): self 117 | { 118 | $this->path = $path; 119 | 120 | return $this; 121 | } 122 | 123 | /** 124 | * @return int 125 | */ 126 | public function getPort(): int 127 | { 128 | return $this->port; 129 | } 130 | 131 | /** 132 | * @param int $port 133 | * @return self 134 | */ 135 | public function setPort(int $port): self 136 | { 137 | $this->port = $port; 138 | 139 | return $this; 140 | } 141 | 142 | /** 143 | * @return int 144 | */ 145 | public function getTimeout(): int 146 | { 147 | return $this->timeout; 148 | } 149 | 150 | /** 151 | * @param int $timeout 152 | * @return HeartBeat 153 | */ 154 | public function setTimeout(int $timeout): HeartBeat 155 | { 156 | $this->timeout = $timeout; 157 | return $this; 158 | } 159 | 160 | /** 161 | * Checks if the VIES service is online and available 162 | * 163 | * @return bool 164 | */ 165 | public function isAlive(): bool 166 | { 167 | if (false === static::$testingEnabled) { 168 | return $this->reachOut(); 169 | } 170 | 171 | return static::$testingServiceIsUp; 172 | } 173 | 174 | /** 175 | * A private routine to send a request over a socket to 176 | * test if the remote service is responding with a status 177 | * code of 200 OK. Now supports also proxy connections. 178 | * 179 | * @return bool 180 | */ 181 | private function reachOut(): bool 182 | { 183 | try { 184 | $data = $this->getSecuredResponse(); 185 | } catch (\RuntimeException $runtimeException) { 186 | return false; 187 | } 188 | return ( 189 | (0 === strcmp('HTTP/1.1 200 OK', $data[0])) || 190 | (0 === strcmp('HTTP/1.1 307 Temporary Redirect', $data[0])) 191 | ); 192 | } 193 | 194 | /** 195 | * This method will make a simple request inside a stream 196 | * resource to retrieve its contents. Useful inside secured 197 | * streams. 198 | * 199 | * @param resource $handle 200 | * @return array 201 | */ 202 | private function readContents($handle): array 203 | { 204 | if (! is_resource($handle)) { 205 | throw new \InvalidArgumentException('Expecting a resource to be provided'); 206 | } 207 | $response = ''; 208 | $uri = sprintf('%s://%s%s', Vies::VIES_PROTO, $this->host, $this->path); 209 | $stream = [ 210 | 'GET ' . $uri . ' HTTP/1.0', 211 | 'Host: ' . $this->host, 212 | 'Connection: close', 213 | ]; 214 | fwrite($handle, implode("\r\n", $stream) . "\r\n\r\n"); 215 | while (! feof($handle)) { 216 | $response .= fgets($handle, 1024); 217 | } 218 | fclose($handle); 219 | $response = str_replace("\r\n", PHP_EOL, $response); 220 | $data = explode(PHP_EOL, $response); 221 | return $data; 222 | } 223 | 224 | /** 225 | * Will make a secured request over SSL/TLS where this 226 | * method will first create a secured stream before 227 | * making the request. 228 | * 229 | * @return array 230 | * @throws \RuntimeException 231 | * @see https://bytephunk.wordpress.com/2017/11/27/ssl-tls-stream-sockets-in-php-7/ 232 | */ 233 | private function getSecuredResponse(): array 234 | { 235 | $streamOptions = [ 236 | 'ssl' => [ 237 | 'verify_peer' => true, 238 | 'verify_peer_name' => true, 239 | 'allow_self_signed' => false, 240 | ], 241 | ]; 242 | $streamContext = stream_context_create($streamOptions); 243 | $socketAddress = sprintf( 244 | 'tls://%s:%d', 245 | $this->host, 246 | $this->port 247 | ); 248 | $error = null; 249 | $errno = null; 250 | $stream = stream_socket_client( 251 | $socketAddress, 252 | $errno, 253 | $error, 254 | self::DEFAULT_TIMEOUT, 255 | STREAM_CLIENT_CONNECT, 256 | $streamContext 257 | ); 258 | 259 | if (! $stream) { 260 | throw new \RuntimeException('Can not create socket stream: ' . $error); 261 | } 262 | 263 | return $this->readContents($stream); 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorAT.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorAT 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9] 19 | * 20 | * Range: 21 | * C1 Alphabetic 22 | * C2 ... C9 Numeric from 0 to 9 23 | * 24 | * Rules: 25 | * C1 26 | * U 27 | * C9 28 | * Si = INT(Ci / 5) + (Ci * 2) modulo10 29 | * R = S3 + S5 + S7 30 | * (10 – (R + C2 + C4 + C6 + C8 + 4) modulo 10) modulo 10 31 | * 32 | */ 33 | class ValidatorAT extends ValidatorAbstract 34 | { 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function validate(string $vatNumber): bool 39 | { 40 | if (strlen($vatNumber) != 9) { 41 | return false; 42 | } 43 | 44 | if (strtoupper($vatNumber[0]) != 'U') { 45 | return false; 46 | } 47 | 48 | $checkVal = 0; 49 | for ($i = 1; $i < 8; $i++) { 50 | $checkVal += $this->crossSum((int)$vatNumber[$i] * ($this->isEven($i) ? 2 : 1)); 51 | } 52 | 53 | $checkVal = substr((string)(96 - $checkVal), -1); 54 | 55 | return (int)$vatNumber[8] == $checkVal; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorAbstract.php: -------------------------------------------------------------------------------- 1 | sumWeights($weights, $vatNumber); 79 | if ($checkVal % 11 == 10) { 80 | $weights = [3, 4, 5, 6, 7, 8, 9, 10]; 81 | $checkVal = $this->sumWeights($weights, $vatNumber, $weightStart); 82 | 83 | $checkVal = ($checkVal % 11) == 10 ? 0 : ($checkVal % 11); 84 | } else { 85 | $checkVal = $checkVal % $restModulo; 86 | } 87 | return $checkVal == (int) $vatNumber[$vatNumberPosition]; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorBE.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorBE 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C0 C1 C2 C3 C4 C5 C6 C7 C8 C9] 19 | * 20 | * Range: 21 | * C0 Either 0 or 1 (starting september 2023) 22 | * C1 Numeric from 1 to 9 23 | * C2 ... C9 Numeric from 0 to 9 24 | * 25 | * Rules: 26 | * [C8 C9] 27 | * 97 - ([C0 C1 C2 C3 C4 C5 C6 C7] modulo 97) 28 | */ 29 | class ValidatorBE extends ValidatorAbstract 30 | { 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function validate(string $vatNumber): bool 35 | { 36 | if (strlen($vatNumber) != 10) { 37 | return false; 38 | } 39 | 40 | $checkVal = (int) substr($vatNumber, 0, -2); 41 | 42 | return 97 - ($checkVal % 97) == (int) substr($vatNumber, -2); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorBG.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorBG 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9] 19 | * 20 | * Range: 21 | * C1 ... C9 Numeric from 0 to 9 22 | * 23 | * Rules: 24 | * C9 25 | * A1 = 1*C1 + 2*C2 + 3*C3 + 4*C4 + 5*C5 + 6*C6 + 7*C7 + 8*C8 26 | * R1 = A1 modulo 11 27 | * If R1 = 10, then 28 | * A2 = 3*C1 + 4*C2 + 5*C3 + 6*C4 + 7*C5 + 8*C6 + 9*C7 + 10*C8 29 | * R2 = A2 modulo 11 30 | * If R2 = 10 then R = 0 31 | * Else R = R2 32 | * Else R = R1 33 | * C9 = R 34 | */ 35 | class ValidatorBG extends ValidatorAbstract 36 | { 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function validate(string $vatNumber): bool 41 | { 42 | $vatNumberLength = strlen($vatNumber); 43 | if (! in_array($vatNumberLength, [9, 10], true)) { 44 | return false; 45 | } 46 | 47 | if (10 === $vatNumberLength) { 48 | return $this->validateNaturalPerson($vatNumber) 49 | || $this->validateForeignNaturalPerson($vatNumber); 50 | } 51 | return $this->validateBusiness($vatNumber); 52 | } 53 | 54 | /** 55 | * Validation for business VAT ID's with 9 digits 56 | * 57 | * @param string $vatNumber 58 | * @return bool 59 | */ 60 | private function validateBusiness(string $vatNumber): bool 61 | { 62 | $weights = [1, 2, 3, 4, 5, 6, 7, 8]; 63 | return $this->checkValue($vatNumber, $weights, parent::DEFAULT_MODULO, 8); 64 | } 65 | 66 | /** 67 | * Validate VAT ID's for natural persons 68 | * 69 | * @param string $vatNumber 70 | * @return bool 71 | * @see https://github.com/yolk/valvat/blob/master/lib/valvat/checksum/bg.rb 72 | */ 73 | private function validateNaturalPerson(string $vatNumber): bool 74 | { 75 | $weights = [2, 4, 8, 5, 10, 9, 7, 3, 6]; 76 | return $this->checkValue($vatNumber, $weights, parent::DEFAULT_MODULO, parent::DEFAULT_VAT_POSITION); 77 | } 78 | 79 | /** 80 | * Validate VAT ID's for foreign natural persons 81 | * 82 | * @param string $vatNumber 83 | * @return bool 84 | * @see https://github.com/yolk/valvat/blob/master/lib/valvat/checksum/bg.rb 85 | */ 86 | private function validateForeignNaturalPerson(string $vatNumber): bool 87 | { 88 | $weights = [21, 19, 17, 13, 11, 9, 7, 3, 1]; 89 | return $this->checkValue($vatNumber, $weights, 10, parent::DEFAULT_VAT_POSITION); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorCY.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorCY 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9] 19 | * 20 | * Range: 21 | * C1 ... C8 Numeric from 0 to 9 22 | * C9 Alphabetic 23 | * C1 0, 1, 3, 4, 5, 6, 9 24 | * 25 | * Rules: 26 | * C1 C2 27 | * C1C2 cannot be 12 (e.g. 12000139V is invalid) 28 | */ 29 | class ValidatorCY extends ValidatorAbstract 30 | { 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function validate(string $vatNumber): bool 35 | { 36 | if (strlen($vatNumber) != 9) { 37 | return false; 38 | } 39 | 40 | if (intval(substr($vatNumber, 0, 2) == 12)) { 41 | return false; 42 | } 43 | 44 | return in_array((int) $vatNumber[0], [0, 1, 3, 4, 5, 6, 9], true) 45 | && ctype_alpha($vatNumber[8]); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorCZ.php: -------------------------------------------------------------------------------- 1 | 6 | * @license MIT 7 | */ 8 | 9 | namespace DragonBe\Vies\Validator; 10 | 11 | /** 12 | * Class ValidatorCZ 13 | * @package DragonBe\Vies\Validator 14 | * 15 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8] 16 | * 17 | * Range: 18 | * C1 ... C8 Numeric from 0 to 9 19 | * C1 <> 9 20 | * 21 | * Rules: 22 | * C8 23 | * A1 = 8*C1 + 7*C2 + 6*C3 + 5*C4 + 4*C5 + 3*C6 + 2*C7 24 | * A2 = nearest higher multiple of 11 25 | * 26 | * if A1 mod 11 = 0 27 | * then 28 | * A2= A1 + 11 29 | * else 30 | * A2 = CEIL1(A1/11, 1) * 11 31 | * 32 | * D = A2 -A1 33 | * C8 = D mod 10 34 | */ 35 | class ValidatorCZ extends ValidatorAbstract 36 | { 37 | /** 38 | * @var array 39 | */ 40 | protected $allowedD = [8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8]; 41 | 42 | /** 43 | * @param string $vatNumber 44 | * @return bool 45 | */ 46 | public function validate(string $vatNumber): bool 47 | { 48 | $vatLength = strlen($vatNumber); 49 | 50 | if ($vatLength === 8) { 51 | return $this->validateLegalEntities($vatNumber); 52 | } elseif ($vatLength === 10) { 53 | return $this->validateIndividualsLong($vatNumber); 54 | } elseif ($vatLength === 9) { 55 | if ($vatNumber[0] == "6") { 56 | return $this->validateIndividualsShortSpecial($vatNumber); 57 | } else { 58 | return $this->validateIndividualsShort($vatNumber); 59 | } 60 | } 61 | 62 | return false; 63 | } 64 | 65 | /** 66 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8] 67 | * 68 | * Range: 69 | * C1 ... C8 Numeric from 0 to 9 70 | * C1 <> 9 71 | * 72 | * Rules: 73 | * C8 74 | * A1 = 8*C1 + 7*C2 + 6*C3 + 5*C4 + 4*C5 + 3*C6 + 2*C7 75 | * A2 = nearest higher multiple of 11 76 | * 77 | * if A1 mod 11 = 0 78 | * then 79 | * A2= A1 + 11 80 | * else 81 | * A2 = CEIL1(A1/11, 1) * 11 82 | * 83 | * D = A2 -A1 84 | * C8 = D mod 10 85 | * 86 | * @param string $vatNumber 87 | * @return bool 88 | */ 89 | protected function validateLegalEntities($vatNumber) 90 | { 91 | $weights = [8, 7, 6, 5, 4, 3, 2]; 92 | $checksum = (int)$vatNumber[7]; 93 | $checkBase = $this->sumWeights($weights, $vatNumber); 94 | 95 | $checkval = ($checkBase % 11) ? intval(ceil($checkBase / 11) * 11) : intval($checkBase + 11); 96 | $checkval = ($checkval - $checkBase) % 10; 97 | 98 | if ($checksum != $checkval) { 99 | return false; 100 | } 101 | 102 | return true; 103 | } 104 | 105 | /** 106 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9] 107 | * 108 | * Range: 109 | * C1 ... C9 Numeric from 0 to 9 110 | * 111 | * Rules: 112 | * C1 C2 ... must 00-53 113 | * C3 C4 ... Month of birth 114 | * >= 1 <=12 115 | * >= 51 <= 62 116 | * C5 C6 ... Day of birth 117 | * 118 | * @param string $vatNumber 119 | * @return bool 120 | */ 121 | protected function validateIndividualsShort($vatNumber) 122 | { 123 | $monthBase = array_merge(range(1, 12), range(51, 62)); 124 | 125 | $yearOfBirth = (int)substr($vatNumber, 0, 2); 126 | $monthOfBirth = (int)substr($vatNumber, 2, 2); 127 | $dayOfBirth = (int)substr($vatNumber, 4, 2); 128 | 129 | //validate day 130 | if ($dayOfBirth < 1 || $dayOfBirth > 31) { 131 | return false; 132 | } 133 | 134 | //validate month 135 | if (in_array($monthOfBirth, $monthBase) === false) { 136 | return false; 137 | } 138 | 139 | //validate year 140 | if ($yearOfBirth > 53) { 141 | return false; 142 | } 143 | 144 | return true; 145 | } 146 | 147 | /** 148 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8] 149 | * 150 | * Range: 151 | * C1 ... C9 Numeric 152 | * C1 = 6 153 | * 154 | * Rules: 155 | * C9 = 156 | * A1 = 8*C2 + 7*C3 + 6*C4 + 5*C5 + 4*C6 + 3*C7 + 2*C8 157 | * A2 = nearest higher multiple of 11 158 | * 159 | * if A1 mod 11 = 0 160 | * then 161 | * A2 = A1 + 11 162 | * else 163 | * A2 = CEIL1(A1/11, 1) * 11 164 | * D = A2 -A1 165 | * 166 | * @param string $vatNumber 167 | * @return bool 168 | */ 169 | protected function validateIndividualsShortSpecial($vatNumber) 170 | { 171 | $weights = [0, 8, 7, 6, 5, 4, 3, 2]; 172 | $checkval = $this->sumWeights($weights, $vatNumber, 1); 173 | $checksum = ($checkval % 11); 174 | 175 | if ($checksum > 0) { 176 | $checksum = ceil($checkval / 11) * 11; 177 | } else { 178 | $checksum = $checkval + 11; 179 | } 180 | 181 | $checksum = $checksum - $checkval; 182 | $checkval = $this->allowedD[$checksum - 1]; 183 | 184 | if ($vatNumber[8] != $checkval) { 185 | return false; 186 | } 187 | 188 | return true; 189 | } 190 | 191 | /** 192 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9 C10] 193 | * 194 | * Range: 195 | * C1 ... C10 Numeric from 0 to 9 196 | * C1 C2 C3 C4 C5 C6 ... Represents a date of birth of an individual 197 | * 198 | * Rules: 199 | * C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 must be devided by 11 without remainder. 200 | * C1 C2 ... must be in the range 00-[last two digits of current date year] or 54-99 201 | * C3 C4 ... Month of birth 202 | * C5 C6 ... Day of birth 203 | * 204 | * C10 ... A1 = C1C2 + C3C4 + C5C6 + C7C8 + C9C10 205 | * A1 must be divisible by 11 with no remainder. 206 | * 207 | * @param string $vatNumber 208 | * @return bool 209 | */ 210 | public function validateIndividualsLong($vatNumber) 211 | { 212 | $monthBase = array_merge(range(1, 12), range(21, 32), range(51, 62), range(71, 82)); 213 | 214 | $yearOfBirth = (int)substr($vatNumber, 0, 2); 215 | $monthOfBirth = (int)substr($vatNumber, 2, 2); 216 | $dayOfBirth = (int)substr($vatNumber, 4, 2); 217 | 218 | //validate day 219 | if ($dayOfBirth < 1 || $dayOfBirth > 31) { 220 | return false; 221 | } 222 | 223 | //validate month 224 | if (in_array($monthOfBirth, $monthBase) === false) { 225 | return false; 226 | } 227 | 228 | //validate year 229 | if ($yearOfBirth > (int)date("y") && $yearOfBirth < 54) { 230 | return false; 231 | } 232 | 233 | $checkval = 0; 234 | 235 | for ($i = 0; $i <= 8; $i += 2) { 236 | $checkval += (int)substr($vatNumber, $i, 2); 237 | } 238 | 239 | $checkval = ($checkval % 11); 240 | 241 | if ($checkval) { 242 | return false; 243 | } 244 | 245 | return true; 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorDE.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorDE 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9] 19 | * 20 | * Range: 21 | * C1 ... C9 Numeric 22 | * C1 > 0 23 | * 24 | * Rules: 25 | * C9 26 | * P = 10 27 | * 28 | * For N = 1…8 (N = character position i.e. C1) 29 | * S = CN + P 30 | * M = S modulo 10 31 | * If M = 0 then M = 10 32 | * P = (2*M) modulo 11 33 | * 34 | * R = 11 – P 35 | * If R = 10 36 | * then 37 | * C9 = 0 38 | * else 39 | * C9 =R 40 | */ 41 | class ValidatorDE extends ValidatorAbstract 42 | { 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function validate(string $vatNumber): bool 47 | { 48 | if (strlen($vatNumber) != 9) { 49 | return false; 50 | } 51 | 52 | $prod = 10; 53 | for ($i = 0; $i < 8; $i++) { 54 | $checkVal = ((int)$vatNumber[$i] + $prod) % 10; 55 | $checkVal = ($checkVal == 0) ? 10 : $checkVal; 56 | $prod = ($checkVal * 2) % 11; 57 | } 58 | 59 | $prod = $prod == 1 ? 11 : $prod; 60 | 61 | return 11 - $prod == (int) substr($vatNumber, -1); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorDK.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorDK 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8] 19 | * 20 | * Range: 21 | * C1 > 0 22 | * 23 | * Rules: 24 | * R 25 | * R = (2*C1 + 7*C2 + 6*C3 + 5*C4 + 4*C5 + 3*C6 + 2*C7 + C8) 26 | * 27 | * R is divisible by 11 28 | */ 29 | class ValidatorDK extends ValidatorAbstract 30 | { 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function validate(string $vatNumber): bool 35 | { 36 | if (strlen($vatNumber) != 8) { 37 | return false; 38 | } 39 | 40 | $checksum = $this->sumWeights([2, 7, 6, 5, 4, 3, 2, 1], $vatNumber); 41 | 42 | return ($checksum % 11) === 0; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorEE.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorEE 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9] 19 | * 20 | * Range: 21 | * C1 .. C9 Numeric 22 | * C1C2 = 10 23 | * 24 | * Rules: 25 | * C9 26 | * A1 = 3*1 + 7*0 + 1*0 + 3*2 + 7*0 + 1*7 + 3*4 + 7*1 = 35 27 | * A2 = CEIL(35;10) = 40 28 | * C9 = 40 - 35 = 5 29 | */ 30 | class ValidatorEE extends ValidatorAbstract 31 | { 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function validate(string $vatNumber): bool 36 | { 37 | if (strlen($vatNumber) != 9) { 38 | return false; 39 | } 40 | 41 | $checkVal = $this->sumWeights([3, 7, 1, 3, 7, 1, 3, 7], $vatNumber); 42 | $checkVal = (ceil($checkVal / 10) * 10) - $checkVal; 43 | 44 | return $checkVal == (int)$vatNumber[8]; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorEL.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorEL 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9] 19 | * 20 | * Range: 21 | * C1 .. C9 Numeric 22 | * 23 | * Rules: 24 | * C9 25 | * A1 = 256*C1 + 128*C2 + 64*C3 + 32*C4 + 16*C5 + 8*C6 + 4*C7 + 2*C8 26 | * A2 = A1 modulo 11 27 | * C9 = A2 modulo 10 28 | */ 29 | class ValidatorEL extends ValidatorAbstract 30 | { 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function validate(string $vatNumber): bool 35 | { 36 | if (strlen($vatNumber) != 9) { 37 | return false; 38 | } 39 | 40 | $weights = [256, 128, 64, 32, 16, 8, 4, 2]; 41 | $checkVal = $this->sumWeights($weights, $vatNumber); 42 | $checkVal = ($checkVal % 11) > 9 ? 0 : ($checkVal % 11); 43 | 44 | return $checkVal === (int) $vatNumber[8]; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorES.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorES 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9] 19 | * 20 | * Range: 21 | * C1, C9 Alphanumeric from 0 to 9 or A to Z 22 | * C2 .. C8 Numeric from 0 to 9 23 | * 24 | * Rules: 25 | * C1 26 | * IF C9 Alphabetic then: 27 | * C1 = A, B, C, D, E, F, G, H, N, P, Q, R, S, W 28 | * IF C9 Numeric then: 29 | * C1 = A, B, C, D, E, F, G, H, J, U, V 30 | * 31 | * C9 32 | * IF C9 Alphabetic and C1 = A, B, C, D, E, F, G, H, N, P, Q, R, S, W then 33 | * S1 = C3 + C5 +C7 34 | * S2 = D2 + D4 + D6 + D8, where Di = int(Ci/5) + (2 * Ci)modulo10 35 | * R = 10 – (S1 + S2) modulo 10 36 | * C9 = Check Character(R) 37 | * Check Character: 1-A, 2-B, 3-C, 4-D, 5-E, 6-F, 7-G, 8-H, 9-I, 10-J 38 | * 39 | * IF C9 Alphabetic and C1 = K, L, M, X, Y, Z or numeric 40 | * If C1 = Y then set C1 = 1 41 | * If C1 = Z then set C1 = 2 42 | * If C1 numeric then R = ([C1 C2 C3 C4 C5 C6 C7 C8])modulo23 + 1 43 | * If C1 alphabetic then R = ([C2 C3 C4 C5 C6 C7 C8])modulo23 + 1 44 | * C9 = Check Character(R) 45 | * Check Character: 1-T, 2-R, 3-W, 4-A, 5-G, 6-M, 7-Y, 8-F, 9-P, 10-D, 11-X, 12-B, 46 | * 13-N, 14-J, 15-Z, 16-S, 17-Q, 18-V, 19-H, 20-L, 21-C, 22-K, 23-E 47 | * 48 | * If C9 numeric 49 | * S1 = C3 + C5 + C7 50 | * S2 = D2 + D4 + D6 + D8, where Di = int(Ci/5) + (2 * Ci)modulo10 51 | * R = 10 – (S1 + S2) modulo 1 52 | * C9 = (R)modulo10 53 | */ 54 | class ValidatorES extends ValidatorAbstract 55 | { 56 | /** 57 | * Allowed C1 if C9 is Alphabetic 58 | * 59 | * @var array 60 | */ 61 | protected $allowedC1Alphabetic = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'N', 'P', 'Q', 'R', 'S', 'W']; 62 | 63 | /** 64 | * Allowed C1 if C9 is Numeric for National juridical 65 | * 66 | * @var array 67 | */ 68 | protected $allowedC1Numeric = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'U', 'V']; 69 | 70 | /** 71 | * Allowed C1 if C9 is Numeric for physical person 72 | * 73 | * @var array 74 | */ 75 | protected $allowedC1Physical = ['K', 'L', 'M', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0']; 76 | 77 | /** 78 | * Check Character: 1-A, 2-B, 3-C, 4-D, 5-E, 6-F, 7-G, 8-H, 9-I, 10-J 79 | * 80 | * @var array 81 | */ 82 | protected $checkCharacter = [ 83 | 1 => 'A', 84 | 2 => 'B', 85 | 3 => 'C', 86 | 4 => 'D', 87 | 5 => 'E', 88 | 6 => 'F', 89 | 7 => 'G', 90 | 8 => 'H', 91 | 9 => 'I', 92 | 10 => 'J' 93 | ]; 94 | 95 | /** 96 | * Check Character: 1-T, 2-R, 3-W, 4-A, 5-G, 6-M, 7-Y, 8-F, 9-P, 10-D, 11-X, 12-B, 13-N, 97 | * 14-J, 15-Z, 16-S, 17-Q, 18-V, 19-H, 20-L, 21-C, 22-K, 23-E 98 | * 99 | * @var array 100 | */ 101 | protected $checkCharacterPhysical = [ 102 | 1 => 'T', 103 | 2 => 'R', 104 | 3 => 'W', 105 | 4 => 'A', 106 | 5 => 'G', 107 | 6 => 'M', 108 | 7 => 'Y', 109 | 8 => 'F', 110 | 9 => 'P', 111 | 10 => 'D', 112 | 11 => 'X', 113 | 12 => 'B', 114 | 13 => 'N', 115 | 14 => 'J', 116 | 15 => 'Z', 117 | 16 => 'S', 118 | 17 => 'Q', 119 | 18 => 'V', 120 | 19 => 'H', 121 | 20 => 'L', 122 | 21 => 'C', 123 | 22 => 'K', 124 | 23 => 'E', 125 | ]; 126 | 127 | /** 128 | * {@inheritdoc} 129 | */ 130 | public function validate(string $vatNumber): bool 131 | { 132 | if (strlen($vatNumber) != 9) { 133 | return false; 134 | } 135 | 136 | if (! is_numeric(substr($vatNumber, 1, 6))) { 137 | return false; 138 | } 139 | 140 | $checksum = $vatNumber[8]; 141 | $fieldC1 = $vatNumber[0]; 142 | 143 | // Juridical entities other than national ones 144 | if (ctype_alpha($checksum) && in_array($fieldC1, $this->allowedC1Alphabetic)) { 145 | return $checksum === $this->validateJuridical($vatNumber); 146 | } 147 | 148 | // Physical person 149 | if (ctype_alpha($checksum) && in_array($fieldC1, $this->allowedC1Physical)) { 150 | return $checksum === $this->validatePhysical($vatNumber); 151 | } 152 | 153 | // National juridical entities 154 | if (ctype_digit($checksum) && in_array($fieldC1, $this->allowedC1Numeric)) { 155 | return (int) $checksum === $this->validateNational($vatNumber); 156 | } 157 | 158 | return false; 159 | } 160 | 161 | /** 162 | * @param string $vatNumber 163 | * 164 | * @return string 165 | */ 166 | private function validateJuridical(string $vatNumber): string 167 | { 168 | $checkVal = 0; 169 | 170 | for ($i = 2; $i <= 8; $i++) { 171 | $checkVal += $this->crossSum((int)$vatNumber[9 - $i] * ($this->isEven($i) ? 2 : 1)); 172 | } 173 | 174 | $checkVal = 10 - ($checkVal % 10); 175 | 176 | return $this->checkCharacter[$checkVal]; 177 | } 178 | 179 | /** 180 | * @param string $vatNumber 181 | * 182 | * @return int 183 | */ 184 | private function validateNational(string $vatNumber): int 185 | { 186 | $checkVal = 0; 187 | 188 | for ($i = 2; $i <= 8; $i++) { 189 | $checkVal += $this->crossSum((int)$vatNumber[9 - $i] * ($this->isEven($i) ? 2 : 1)); 190 | } 191 | 192 | $checkVal = 10 - ($checkVal % 10); 193 | 194 | return $checkVal % 10; 195 | } 196 | 197 | /** 198 | * @param string $vatNumber 199 | * 200 | * @return string 201 | */ 202 | private function validatePhysical(string $vatNumber): string 203 | { 204 | $vatNumber[0] = str_replace(['Y', 'Z'], [1, 2], $vatNumber[0]); 205 | 206 | if (ctype_digit($vatNumber[0])) { 207 | $checkVal = ((int)substr($vatNumber, 0, 8) % 23) + 1; 208 | } else { 209 | $checkVal = ((int)substr($vatNumber, 1, 7) % 23) + 1; 210 | } 211 | 212 | return $this->checkCharacterPhysical[$checkVal]; 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorEU.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorEU 16 | * @package DragonBe\Vies\Validator 17 | */ 18 | class ValidatorEU extends ValidatorAbstract 19 | { 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | public function validate(string $vatNumber): bool 24 | { 25 | return false; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorFI.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorFI 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8] 19 | * 20 | * Range: 21 | * C2 .. C8 Numeric from 0 to 9 22 | * 23 | * Rules: 24 | * C8 25 | * R = 11 - (7*C1 + 9*C2 + 10*C3 + 5*C4 + 8*C5 + 4*C6 + 2*C7) modulo11 26 | * If R = 10 then, VAT number is invalid 27 | * If R = 11 then C8 = 0 28 | * Else C8 = R 29 | */ 30 | class ValidatorFI extends ValidatorAbstract 31 | { 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function validate(string $vatNumber): bool 36 | { 37 | if (strlen($vatNumber) != 8) { 38 | return false; 39 | } 40 | 41 | $weights = [7, 9, 10, 5, 8, 4, 2]; 42 | $checkVal = $this->sumWeights($weights, $vatNumber); 43 | 44 | return (0 === $checkVal % 11) 45 | ? (int) $vatNumber[7] === 0 46 | : 11 - ($checkVal % 11) == (int) $vatNumber[7]; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorFR.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorFR 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11] 19 | * 20 | * Range: 21 | * C1 .. C2 Alphanumeric from A to Z or 0 to 9 22 | * C2 .. C11 Numeric from 0 to 9 23 | * 24 | * Rules: 25 | * Case 1: Old Style 26 | * [C1 C2] = ([C3 C4 C5 C6 C7 C8 C9 C10 C11] [1 2])modulo 97 27 | * 28 | * Case 2 : New Style 29 | * S1 = Check Character (C1) 30 | * S2 = Check Character (C2) 31 | * 32 | * If C1 numeric then 33 | * C2 alphabetic 34 | * S = (S1 * 24) + (S2 – 10) 35 | * 36 | * IF C1 alphabetic then 37 | * S = (S1*34) + (S2-100) 38 | * 39 | * P = (S/11) + 1 40 | * R1 = (S)modulo11 41 | * R2 = ( [C3 C4 C5 C6 C7 C8 C9 C10 C11] + P)modulo11 42 | * R1 = R2 43 | * 44 | * Check Character 45 | * 0-0, 1-1, 2-2, 3-3, 4-4, 5-5, 6-6, 7-7, 8-8, 9-9, 10-A, 11-B, 12-C, 13-D, 14-E, 15-F, 16-G, 17-H, 18-J, 19-K, 46 | * 20-L, 21-M, 22-N, 23-P, 24-Q, 25-R, 26-S, 27-T, 28-U, 29-V, 30-W, 31-X, 32-Y, 33-Z. 47 | * 48 | */ 49 | class ValidatorFR extends ValidatorAbstract 50 | { 51 | /** 52 | * the valid characters for the first two digits (O and I are missing) 53 | * 54 | * @var string 55 | */ 56 | protected $alphabet = '0123456789ABCDEFGHJKLMNPQRSTUVWXYZ'; 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function validate(string $vatNumber): bool 62 | { 63 | if (strlen($vatNumber) != 11) { 64 | return false; 65 | } 66 | 67 | if (strpos($this->alphabet, $vatNumber[0]) === false) { 68 | return false; 69 | } 70 | 71 | if (strpos($this->alphabet, $vatNumber[1]) === false) { 72 | return false; 73 | } 74 | 75 | $checksum = substr($vatNumber, 0, 2); 76 | 77 | if (ctype_digit($checksum)) { 78 | return $checksum == $this->validateOld($vatNumber); 79 | } 80 | 81 | return $checksum == $this->validateNew($vatNumber); 82 | } 83 | 84 | /** 85 | * @param string $vatNumber 86 | * 87 | * @return string 88 | */ 89 | private function validateOld(string $vatNumber): string 90 | { 91 | $checkVal = substr($vatNumber, 2); 92 | if (! ctype_digit($checkVal)) { 93 | return ""; 94 | } 95 | $checkVal .= "12"; 96 | if (PHP_INT_SIZE === 4 && extension_loaded('bcmath')) { 97 | $checkVal = (int) bcmod($checkVal, "97"); 98 | } else { 99 | $checkVal = intval($checkVal) % 97; 100 | } 101 | 102 | return $checkVal == 0 ? "00" : (string) $checkVal; 103 | } 104 | 105 | /** 106 | * @param string $vatNumber 107 | * 108 | * @return bool 109 | */ 110 | private function validateNew(string $vatNumber): bool 111 | { 112 | $multiplier = 34; 113 | $subStractor = 100; 114 | if (ctype_digit($vatNumber[0])) { 115 | $multiplier = 24; 116 | $subStractor = 10; 117 | } 118 | 119 | $checkCharacter = array_flip(str_split($this->alphabet)); 120 | $checkVal = ($checkCharacter[$vatNumber[0]] * $multiplier) + $checkCharacter[$vatNumber[1]] - $subStractor; 121 | 122 | if (PHP_INT_SIZE === 4 && extension_loaded("bcmath")) { 123 | return (int) bcmod(bcadd(substr($vatNumber, 2), strval(($checkVal / 11) + 1)), "11") === $checkVal % 11; 124 | } else { 125 | return ((int) (intval(substr($vatNumber, 2)) + ($checkVal / 11) + 1) % 11) == $checkVal % 11; 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorGB.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorGB 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5] 19 | * [C1 C2 C3 C4 C5 C6 C7 C8 C9] 20 | * [C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 C12] 21 | * 22 | * Range: 23 | * C1 .. C2 Alphanumeric from A to Z or 0 to 9 24 | * C2 .. C11 Numeric from 0 to 9 25 | * 26 | * Rules: 27 | * Case 1: 28 | * [C1 C2] Alpha: “GD” or “HA” 29 | * C3 ... C5 Numeric from 0 to 9 30 | * 31 | * if [C1 C2] = “GD” 32 | * [C3 C4 C5] from 000 to 499 33 | * 34 | * If [C1 C2] = “HA” 35 | * [C3 C4 C5] from 500 to 999 36 | * 37 | * Case 2 38 | * [C1 C2 C3] from 000 to 009 are numbers for Isle of Man 39 | * [C10 C11 C12] > 000 40 | * [C1 C2 C3 C4 C5 C6 C7 C8 C9] >000000000 41 | * 42 | * [C8 C9] 43 | * R1 = (8*C1 + 7*C2 + 6*C3 + 5*C4 + 4*C5 + 3*C6 + 2*C7 + C8C9) modulo 97 44 | * R2 = ((8*C1 + 7*C2 + 6*C3 + 5*C4 + 4*C5 + 3*C6 + 2*C7 + C8C9) + 55) modulo 97 45 | * Either R1 or R2 must equal to zero. 46 | * 47 | */ 48 | class ValidatorGB extends ValidatorAbstract 49 | { 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function validate(string $vatNumber): bool 54 | { 55 | if (strlen($vatNumber) == 5) { 56 | return $this->validateGovernment($vatNumber); 57 | } 58 | 59 | if (strlen($vatNumber) != 9 && strlen($vatNumber) != 12) { 60 | return false; 61 | } 62 | 63 | $weights = [8, 7, 6, 5, 4, 3, 2]; 64 | $checkVal = $this->sumWeights($weights, $vatNumber); 65 | $checkVal += (int)substr($vatNumber, 7, 2); 66 | 67 | $Result1 = $checkVal % 97; 68 | $Result2 = ($Result1 + 55) % 97; 69 | 70 | return ! ($Result1 * $Result2); 71 | } 72 | 73 | /** 74 | * Validate Government VAT 75 | * 76 | * @param string $vatNumber 77 | * 78 | * @return bool 79 | */ 80 | private function validateGovernment(string $vatNumber): bool 81 | { 82 | $prefix = strtoupper(substr($vatNumber, 0, 2)); 83 | $number = (int) substr($vatNumber, 2, 3); 84 | 85 | // Government departments 86 | if ($prefix == 'GD') { 87 | return $number < 500; 88 | } 89 | 90 | // Health authorities 91 | if ($prefix == 'HA') { 92 | return $number > 499; 93 | } 94 | 95 | return false; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorHR.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorHR 16 | * @package DragonBe\Vies\Validator 17 | */ 18 | class ValidatorHR extends ValidatorAbstract 19 | { 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | public function validate(string $vatNumber): bool 24 | { 25 | if (strlen($vatNumber) != 11) { 26 | return false; 27 | } 28 | 29 | if (! ctype_digit($vatNumber)) { 30 | return false; 31 | } 32 | 33 | $product = 10; 34 | 35 | for ($i = 0; $i < 10; $i++) { 36 | $sum = ($vatNumber[$i] + $product) % 10; 37 | $sum = ($sum == 0) ? 10 : $sum; 38 | $product = (2 * $sum) % 11; 39 | } 40 | 41 | return ($product + (int) $vatNumber[10]) % 10 == 1; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorHU.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorHU 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8] 19 | * 20 | * Range: 21 | * C1 ... C8 Numeric from 0 to 9 22 | * 23 | * Rules: 24 | * C8 25 | * A1 = 9*C1 + 7*C2 + 3*C3 + 1*C4 + 9*C5 + 7*C6 + 3*C7 26 | * If the number in the right hand column of A1 is zero then C8 = 0 27 | * Otherwise, subtract the number in the right hand column of A1 from 10 28 | * C8 = A1 29 | */ 30 | class ValidatorHU extends ValidatorAbstract 31 | { 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function validate(string $vatNumber): bool 36 | { 37 | if (strlen($vatNumber) != 8) { 38 | return false; 39 | } 40 | 41 | $weights = [9, 7, 3, 1, 9, 7, 3]; 42 | $checksum = (int) $vatNumber[7]; 43 | $checkVal = $this->sumWeights($weights, $vatNumber); 44 | $checkVal = (int) substr((string) $checkVal, -1); 45 | $checkVal = ($checkVal > 0) ? 10 - $checkVal : 0; 46 | 47 | return $checksum == $checkVal; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorIE.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorIE 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8] 19 | * 20 | */ 21 | class ValidatorIE extends ValidatorAbstract 22 | { 23 | 24 | protected $alphabet = 'WABCDEFGHIJKLMNOPQRSTUV'; 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function validate(string $vatNumber): bool 30 | { 31 | if (strlen($vatNumber) != 8 && strlen($vatNumber) != 9) { 32 | return false; 33 | } 34 | 35 | return $this->validateIENew($vatNumber) 36 | || $this->validateIEOld($vatNumber); 37 | } 38 | 39 | /** 40 | * @param string $vatNumber 41 | * 42 | * @return bool 43 | */ 44 | private function validateIEOld(string $vatNumber): bool 45 | { 46 | $transform = ['0', substr($vatNumber, 2, 5), $vatNumber[0], $vatNumber[7]]; 47 | $vat_id = join('', $transform); 48 | 49 | return $this->validateIENew($vat_id); 50 | } 51 | 52 | /** 53 | * @param string $vatNumber 54 | * 55 | * @return bool 56 | */ 57 | private function validateIENew(string $vatNumber): bool 58 | { 59 | $checksum = strtoupper(substr($vatNumber, 7, 1)); 60 | $checkNumber = substr($vatNumber, 0, 8); 61 | $checkVal = 0; 62 | 63 | for ($i = 2; $i <= 8; $i++) { 64 | $checkVal += (int)$checkNumber[8 - $i] * $i; 65 | } 66 | 67 | if (strlen($vatNumber) == 9) { 68 | $checkVal += (9 * strpos($this->alphabet, $vatNumber[8])); 69 | } 70 | 71 | $checkVal = ($checkVal % 23); 72 | 73 | if ($checkVal == 0) { 74 | return $checksum == 'W'; 75 | } 76 | 77 | $checkChar = 'A'; 78 | for ($i = $checkVal - 1; $i > 0; $i--) { 79 | $checkChar++; 80 | } 81 | 82 | return $checkChar == $checksum; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorIT.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorIT 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11] 19 | * 20 | * Range: 21 | * C1 ... C11 Numeric from 0 to 9 22 | * [C8 C9 C10] (>000 and <101) or (=120) or (=121) or (=999) or (=888) 23 | * 24 | * Rules: 25 | * C11 26 | * S1 = C1 + C3 + C5 + C7 + C9 27 | * S2 = D2 + D4 + D6 + D8 + D10 28 | * where Di = int(Ci/5) + (2*Ci)modulo10 29 | * C11 = (10 – (S1+S2)modulo10)modulo10 30 | */ 31 | class ValidatorIT extends ValidatorAbstract 32 | { 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function validate(string $vatNumber): bool 38 | { 39 | if (strlen($vatNumber) != 11) { 40 | return false; 41 | } 42 | 43 | if (! ctype_digit($vatNumber)) { 44 | return false; 45 | } 46 | 47 | if (substr($vatNumber, 0, 7) == '0000000') { 48 | return false; 49 | } 50 | 51 | $checksum = (int) substr($vatNumber, -1); 52 | $Sum1 = 0; 53 | $Sum2 = 0; 54 | for ($i = 1; $i <= 10; $i++) { 55 | if (! $this->isEven($i)) { 56 | $Sum1 += $vatNumber[$i - 1]; 57 | } else { 58 | $Sum2 += (int)($vatNumber[$i - 1] / 5) + ((2 * $vatNumber[$i - 1]) % 10); 59 | } 60 | } 61 | 62 | $checkVal = (10 - ($Sum1 + $Sum2) % 10) % 10; 63 | 64 | return $checksum == $checkVal; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorInterface.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | interface ValidatorInterface 15 | { 16 | /** 17 | * @param string $vatNumber 18 | * 19 | * @return bool 20 | */ 21 | public function validate(string $vatNumber): bool; 22 | } 23 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorLT.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorLT 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9] 19 | * [C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 C12] 20 | * 21 | * Range: 22 | * C1 ... C12 Numeric from 0 to 9 23 | * C8 = 1 24 | * 25 | * Rules: 26 | * C8 27 | * A1 = 1*C1 + 2*C2 + 3*C3 + 4*C4 + 5*C5 + 6*C6 + 7*C7 + 8*C8 + 9*C9 + 1*C10 + 2*C11 28 | * R1 = A1 modulo 11 29 | * 30 | * If R1 <> 10, then C12 = R1 31 | * Else 32 | * 33 | * A2 = 3*C1 + 4*C2 + 5*C3 + 6*C4 + 7*C5 + 8*C6 + 9*C7 + 1*C8 + 2*C9 + 3*C10 + 4*C11 34 | * R2 = A2 modulo 11 35 | * If R2 = 10, then C12 = 0 36 | * Else C12 = R2 37 | */ 38 | class ValidatorLT extends ValidatorAbstract 39 | { 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function validate(string $vatNumber): bool 44 | { 45 | if (strlen($vatNumber) == 12) { 46 | return $this->validateTemporaryTaxpayer($vatNumber); 47 | } 48 | 49 | if (strlen($vatNumber) == 9) { 50 | return $this->validateLegal($vatNumber); 51 | } 52 | 53 | return false; 54 | } 55 | 56 | /** 57 | * Validate Temporary Tax Payer 58 | * 59 | * @param string $vatNumber 60 | * 61 | * @return bool 62 | */ 63 | private function validateTemporaryTaxpayer(string $vatNumber): bool 64 | { 65 | if ($vatNumber[10] != 1) { 66 | return false; 67 | } 68 | 69 | $weights = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2]; 70 | $checksum = (int)$vatNumber[11]; 71 | $checkVal = $this->sumWeights($weights, $vatNumber); 72 | 73 | if (($checkVal % 11) == 10) { 74 | $weights = [3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4]; 75 | $checkVal = $this->sumWeights($weights, $vatNumber); 76 | $checkVal = ($checkVal % 11 == 10) ? 0 : $checkVal % 11; 77 | 78 | return $checkVal == $checksum; 79 | } 80 | 81 | return $checkVal % 11 == $checksum; 82 | } 83 | 84 | /** 85 | * Validate Legal 86 | * 87 | * @param string $vatNumber 88 | * 89 | * @return bool 90 | */ 91 | private function validateLegal(string $vatNumber): bool 92 | { 93 | if ($vatNumber[7] != 1) { 94 | return false; 95 | } 96 | 97 | $weights = [1, 2, 3, 4, 5, 6, 7, 8]; 98 | $checksum = (int) $vatNumber[8]; 99 | $checkVal = $this->sumWeights($weights, $vatNumber); 100 | 101 | if (($checkVal % 11) == 10) { 102 | $weights = [3, 4, 5, 6, 7, 8, 9, 1]; 103 | $checkVal = $this->sumWeights($weights, $vatNumber); 104 | $checkVal = ($checkVal % 11 == 10) ? 0 : $checkVal % 11; 105 | 106 | return $checkVal == $checksum; 107 | } 108 | 109 | return $checkVal % 11 == $checksum; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorLU.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorLU 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8] 19 | * 20 | * Range: 21 | * C1 ... C8 Numeric from 0 to 9 22 | * 23 | * Rules: 24 | * [C7 C8] 25 | * ([C1 C2 C3 C4 C5 C6]) Modulo 89 26 | */ 27 | class ValidatorLU extends ValidatorAbstract 28 | { 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function validate(string $vatNumber): bool 33 | { 34 | if (strlen($vatNumber) != 8) { 35 | return false; 36 | } 37 | 38 | $checksum = (int)substr($vatNumber, -2); 39 | $checkVal = (int)substr($vatNumber, 0, 6); 40 | 41 | return ($checkVal % 89) == $checksum; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorLV.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorLV 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11] 19 | * 20 | * Range: 21 | * C1 ... C11 Numeric from 0 to 9 22 | * C1 > 3 23 | * 24 | * Rules: 25 | * C11 26 | * A1 = 9*C1 + 1*C2 + 4*C3 + 8*C4 + 3*C5 + 10*C6 + 2*C7 + 5*C8 + 7*C9 + 6*C1 27 | * R = 3 - (A1 modulo 11) 28 | * If R < -1, then C11 = R + 11 29 | * If R > -1, then C11 = R 30 | * If R = -1, then VAT number is invalid 31 | */ 32 | class ValidatorLV extends ValidatorAbstract 33 | { 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function validate(string $vatNumber): bool 38 | { 39 | if (strlen($vatNumber) != 11) { 40 | return false; 41 | } 42 | 43 | if ((int)$vatNumber[0] <= 3) { 44 | return false; 45 | } 46 | 47 | $weights = [9, 1, 4, 8, 3, 10, 2, 5, 7, 6]; 48 | $checksum = (int)substr($vatNumber, -1); 49 | $checkVal = $this->sumWeights($weights, $vatNumber); 50 | $checkVal = 3 - ($checkVal % 11); 51 | 52 | if ($checkVal == -1) { 53 | return false; 54 | } 55 | 56 | if ($checkVal < -1) { 57 | $checkVal += 11; 58 | } 59 | 60 | return $checksum == $checkVal; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorMT.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorMT 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8] 19 | * 20 | * Range: 21 | * C1 ... C8 Numeric from 0 to 9 22 | * C1 ... C6 > 100000 23 | * 24 | * Rules: 25 | * [C7 C8] 26 | * A1 = 3*C1 + 4*C2 + 6*C3 + 7*C4 + 8*C5 + 9*C6 27 | * R = 37 - (A1 modulo 37) 28 | * If R = 00, then C7 C8 = 37 29 | * C7 C8 = R 30 | */ 31 | class ValidatorMT extends ValidatorAbstract 32 | { 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function validate(string $vatNumber): bool 37 | { 38 | if (strlen($vatNumber) != 8) { 39 | return false; 40 | } 41 | 42 | if ((int)substr($vatNumber, 0, 6) <= 100000) { 43 | return false; 44 | } 45 | 46 | $weights = [3, 4, 6, 7, 8, 9]; 47 | $checksum = (int)substr($vatNumber, -2, 2); 48 | $checkVal = $this->sumWeights($weights, $vatNumber); 49 | $checkVal = intval(37 - ($checkVal % 37)); 50 | 51 | return $checkVal == $checksum; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorNL.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorNL 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 C12] 19 | */ 20 | class ValidatorNL extends ValidatorAbstract 21 | { 22 | /** 23 | * Check Character: 10-A, 11-B .... 35-Z, 36-+, 37-* 24 | * 25 | * @var array 26 | */ 27 | protected $checkCharacter = [ 28 | 'A' => 10, 29 | 'B' => 11, 30 | 'C' => 12, 31 | 'D' => 13, 32 | 'E' => 14, 33 | 'F' => 15, 34 | 'G' => 16, 35 | 'H' => 17, 36 | 'I' => 18, 37 | 'J' => 19, 38 | 'K' => 20, 39 | 'L' => 21, 40 | 'M' => 22, 41 | 'N' => 23, 42 | 'O' => 24, 43 | 'P' => 25, 44 | 'Q' => 26, 45 | 'R' => 27, 46 | 'S' => 28, 47 | 'T' => 29, 48 | 'U' => 30, 49 | 'V' => 31, 50 | 'W' => 32, 51 | 'X' => 33, 52 | 'Y' => 34, 53 | 'Z' => 35, 54 | '+' => 36, 55 | '*' => 37, 56 | ]; 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function validate(string $vatNumber): bool 62 | { 63 | if (strlen($vatNumber) != 12) { 64 | return false; 65 | } 66 | 67 | if (strtoupper($vatNumber[9]) != 'B') { 68 | return false; 69 | } 70 | 71 | return $this->validateCommercial($vatNumber) || $this->validateSoleProprietor($vatNumber); 72 | } 73 | 74 | /** 75 | * Range: 76 | * C1 ... C9 Numeric from 0 to 9 77 | * C10 Alphabetic “B” 78 | * C11 ... C12 Numeric from 0 to 9 79 | * 80 | * Rules: 81 | * C9 82 | * A1 = C1*9 + C2*8 + C3*7 + C4*6 + C5*5 + C6*4 + C7*3 + C8*2 83 | * A2 = A1 modulo 11 84 | * If A2 = 10 then number is invalid 85 | * else C9 = A2 86 | * 87 | * [C11 C12] 88 | * >00 89 | * 90 | * @param string $vatNumber 91 | * @return bool 92 | */ 93 | protected function validateCommercial(string $vatNumber): bool 94 | { 95 | if ((int)substr($vatNumber, -2) == 0) { 96 | return false; 97 | } 98 | 99 | $checksum = (int)$vatNumber[8]; 100 | $weights = [9, 8, 7, 6, 5, 4, 3, 2]; 101 | $checkVal = $this->sumWeights($weights, $vatNumber); 102 | $checkVal = ($checkVal % 11) > 9 ? 0 : ($checkVal % 11); 103 | 104 | return $checkVal == $checksum; 105 | } 106 | 107 | /** 108 | * Range: 109 | * C1 ... C9 0-9 A-Z + * 110 | * C10 Alphabetic “B” 111 | * C11 ... C12 Numeric from 0 to 9 112 | * 113 | * [C11 C12] 114 | * 02 - 98 115 | * 116 | * @param string $vatNumber 117 | * @return bool 118 | */ 119 | protected function validateSoleProprietor(string $vatNumber): bool 120 | { 121 | if (! preg_match("#^[A-Z0-9+*]{9}B[0-9]{2}$#u", $vatNumber)) { 122 | return false; 123 | } 124 | 125 | $sumBase = array_reduce(str_split($vatNumber), function ($acc, $e) { 126 | if (ctype_digit($e)) { 127 | return $acc.$e; 128 | } 129 | 130 | return $acc.$this->checkCharacter[$e]; 131 | }, '2321'); 132 | 133 | if (PHP_INT_SIZE === 4 && extension_loaded('bcmath')) { 134 | return bcmod($sumBase, '97') === '1'; 135 | } else { 136 | return ((int) $sumBase % 97) === 1; 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorPL.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorPL 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9 C10] 19 | * 20 | * Range: 21 | * C1 ... C9 Numeric from 0 to 9 22 | * 23 | * Rules: 24 | * C10 25 | * A1 = 6*C1 + 5*C2 + 7*C3 + 2*C4 + 3*C5 + 4*C6 + 5*C7 + 6*C8 + 7*C9 26 | * R = A1 modulo 11 27 | * If R = 10, then VAT number is invalid 28 | * C10 = R 29 | */ 30 | class ValidatorPL extends ValidatorAbstract 31 | { 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function validate(string $vatNumber): bool 36 | { 37 | if (strlen($vatNumber) != 10) { 38 | return false; 39 | } 40 | 41 | $weights = [6, 5, 7, 2, 3, 4, 5, 6, 7]; 42 | $checksum = (int)$vatNumber[9]; 43 | $checkVal = $this->sumWeights($weights, $vatNumber); 44 | 45 | return $checkVal % 11 == $checksum; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorPT.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorPT 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9] 19 | * 20 | * Range: 21 | * C1 > 0 22 | * 23 | * Rules: 24 | * C9 25 | * R = 11 – (9*C1 + 8*C2 + 7*C3 + 6*C4 + 5*C5 + 4*C6 + 3*C7 + 2*C8) modulo 11 26 | * If R= 10 or R= 11, Then R = 0 27 | * C9 = R 28 | */ 29 | class ValidatorPT extends ValidatorAbstract 30 | { 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function validate(string $vatNumber): bool 35 | { 36 | if (strlen($vatNumber) != 9) { 37 | return false; 38 | } 39 | 40 | $checksum = (int)$vatNumber[8]; 41 | $weights = [9, 8, 7, 6, 5, 4, 3, 2]; 42 | $checkVal = $this->sumWeights($weights, $vatNumber); 43 | $checkVal = (11 - ($checkVal % 11)) > 9 ? 0 : (11 - ($checkVal % 11)); 44 | 45 | return $checksum == $checkVal; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorRO.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorRO 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9 C10] 19 | * 20 | * Range: 21 | * C1 ... C10 Numeric 22 | * Note that if the length is less than 10 digits, leading zeros must be assumed to perform the computation. 23 | * 24 | * Rules: 25 | * C10 26 | * A1 = C1*7 + C2*5 + C3*3 + C4*2 + C5*1 + C6*7 + C7*5 + C8*3 + C9*2 27 | * A2 = A1 * 10 28 | * R1 = A2 modulo 11 29 | * If R1 = 10, then R = 0 30 | * Else R = R1 31 | * C10 = R 32 | */ 33 | class ValidatorRO extends ValidatorAbstract 34 | { 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function validate(string $vatNumber): bool 39 | { 40 | if (strlen($vatNumber) < 2 || strlen($vatNumber) > 10) { 41 | return false; 42 | } 43 | 44 | $vatNumber = str_pad($vatNumber, 10, "0", STR_PAD_LEFT); 45 | $checksum = (int)$vatNumber[9]; 46 | $weights = [7, 5, 3, 2, 1, 7, 5, 3, 2]; 47 | $checkVal = $this->sumWeights($weights, $vatNumber); 48 | $checkVal = ($checkVal * 10) % 11; 49 | if ($checkVal == 10) { 50 | $checkVal = 0; 51 | } 52 | 53 | return $checkVal == $checksum; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorSE.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorSE 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 C12] 19 | * 20 | * Range: 21 | * C1 ... C12 Numeric 22 | * [C11 C12] >=01 and <= 94 23 | * 24 | * Rules: 25 | * C10 26 | * = (10 – (R + C2 + C4 + C6 + C8) modulo 10) modulo 10 27 | * Where R = S1 + S3 + S5 + S7 + S9 28 | * Where Si = INT(Ci/5) + (Ci*2)modulo 10 29 | */ 30 | class ValidatorSE extends ValidatorAbstract 31 | { 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function validate(string $vatNumber): bool 36 | { 37 | if (strlen($vatNumber) != 12) { 38 | return false; 39 | } 40 | 41 | if ((int)substr($vatNumber, -2) < 1 || (int)substr($vatNumber, -2) > 94) { 42 | return false; 43 | } 44 | 45 | $checksum = (int)$vatNumber[9]; 46 | $checkVal = 0; 47 | 48 | for ($i = 1; $i < 10; $i++) { 49 | $checkVal += $this->crossSum((int)$vatNumber[9 - $i] * ($this->isEven($i) ? 1 : 2)); 50 | } 51 | 52 | return $checksum == (($checkVal % 10) == 0 ? 0 : 10 - ($checkVal % 10)); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorSI.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorSI 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8] 19 | * 20 | * Range: 21 | * C1 ... C8 Numeric 22 | * C1 ... C7 1000000 and <= 9999999 23 | * 24 | * Rules: 25 | * C8 26 | * A1 = C1*8 + C2*7 + C3*6 + C4*5 + C5*4 + C6*3 + C7*2 27 | * R = 11 - (A1 modulo 11) 28 | * If R = 10, then C8 = 0 29 | * else if R = 11 then number is invalid 30 | * else C8 = R 31 | */ 32 | class ValidatorSI extends ValidatorAbstract 33 | { 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function validate(string $vatNumber): bool 38 | { 39 | if (strlen($vatNumber) != 8) { 40 | return false; 41 | } 42 | 43 | if (intval($vatNumber[0]) == 0) { 44 | return false; 45 | } 46 | 47 | $checksum = (int)$vatNumber[7]; 48 | $weights = [8, 7, 6, 5, 4, 3, 2]; 49 | $checkVal = $this->sumWeights($weights, $vatNumber); 50 | 51 | $mod = 11 - ($checkVal % 11); 52 | if ($mod === 11) { 53 | return false; 54 | } 55 | 56 | $checkVal = ($mod == 10) ? 0 : $mod; 57 | 58 | return $checksum == $checkVal; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorSK.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * Class ValidatorSK 16 | * @package DragonBe\Vies\Validator 17 | * 18 | * VAT format: [C1 C2 C3 C4 C5 C6 C7 C8 C9 C10] 19 | * 20 | * Range: 21 | * C1 ... C10 Numeric 22 | * C1 In the range 1...9 23 | * C2, C4 ... C10 In the range 0...9 24 | * C3 One of 2, 3, 4, 7, 8, 9 25 | * 26 | * Rules: 27 | * [C1 C2 C3 C4 C5 C6 C7 C8 C9 C10] 28 | * [C1 C2 C3 C4 C5 C6 C7 C8 C9 C10] modulo 11 = 0 29 | */ 30 | class ValidatorSK extends ValidatorAbstract 31 | { 32 | /** 33 | * {@inheritdoc} 34 | */ 35 | public function validate(string $vatNumber): bool 36 | { 37 | if (strlen($vatNumber) != 10 38 | || intval($vatNumber[0]) == 0 39 | || ! in_array((int) $vatNumber[2], [2, 3, 4, 7, 8, 9]) 40 | ) { 41 | return false; 42 | } 43 | 44 | if (PHP_INT_SIZE === 4 && extension_loaded("bcmath")) { 45 | return bcmod($vatNumber, '11') === '0'; 46 | } else { 47 | return $vatNumber % 11 == 0; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Vies/Validator/ValidatorXI.php: -------------------------------------------------------------------------------- 1 | 9 | * @license MIT 10 | */ 11 | 12 | namespace DragonBe\Vies\Validator; 13 | 14 | /** 15 | * This validator was derived from the "ValidatorGB" validator to validate the "XI" VAT numbers of 16 | * "United Kingdom (Northern Ireland)" following the end of the Brexit transition period on December, 31 2020. 17 | * 18 | * It is based on the information at: 19 | * https://www.avalara.com/vatlive/en/vat-news/brexit-northern-ireland-vat-and-eoro--xi--number.html 20 | * 21 | * As more information is published, this validator may be made more precise: 22 | * 23 | * - Remove the logic to validate Isle of Man numbers? 24 | * - Remove the logic to validate 'GD' and 'HA' prefixes? 25 | * 26 | */ 27 | 28 | /** 29 | * Class ValidatorXI 30 | * @package DragonBe\Vies\Validator 31 | * 32 | * VAT format: [C1 C2 C3 C4 C5] 33 | * [C1 C2 C3 C4 C5 C6 C7 C8 C9] 34 | * [C1 C2 C3 C4 C5 C6 C7 C8 C9 C10 C11 C12] 35 | * 36 | * Range: 37 | * C1 .. C2 Alphanumeric from A to Z or 0 to 9 38 | * C2 .. C11 Numeric from 0 to 9 39 | * 40 | * Rules: 41 | * Case 1: 42 | * [C1 C2] Alpha: “GD” or “HA” 43 | * C3 ... C5 Numeric from 0 to 9 44 | * 45 | * if [C1 C2] = “GD” 46 | * [C3 C4 C5] from 000 to 499 47 | * 48 | * If [C1 C2] = “HA” 49 | * [C3 C4 C5] from 500 to 999 50 | * 51 | * Case 2 52 | * [C1 C2 C3] from 000 to 009 are numbers for Isle of Man 53 | * [C10 C11 C12] > 000 54 | * [C1 C2 C3 C4 C5 C6 C7 C8 C9] >000000000 55 | * 56 | * [C8 C9] 57 | * R1 = (8*C1 + 7*C2 + 6*C3 + 5*C4 + 4*C5 + 3*C6 + 2*C7 + C8C9) modulo 97 58 | * R2 = ((8*C1 + 7*C2 + 6*C3 + 5*C4 + 4*C5 + 3*C6 + 2*C7 + C8C9) + 55) modulo 97 59 | * Either R1 or R2 must equal to zero. 60 | * 61 | */ 62 | class ValidatorXI extends ValidatorAbstract 63 | { 64 | /** 65 | * {@inheritdoc} 66 | */ 67 | public function validate(string $vatNumber): bool 68 | { 69 | if (strlen($vatNumber) == 5) { 70 | return $this->validateGovernment($vatNumber); 71 | } 72 | 73 | if (strlen($vatNumber) != 9 && strlen($vatNumber) != 12) { 74 | return false; 75 | } 76 | 77 | $weights = [8, 7, 6, 5, 4, 3, 2]; 78 | $checkVal = $this->sumWeights($weights, $vatNumber); 79 | $checkVal += (int)substr($vatNumber, 7, 2); 80 | 81 | $Result1 = $checkVal % 97; 82 | $Result2 = ($Result1 + 55) % 97; 83 | 84 | return ! ($Result1 * $Result2); 85 | } 86 | 87 | /** 88 | * Validate Government VAT 89 | * 90 | * @param string $vatNumber 91 | * 92 | * @return bool 93 | */ 94 | private function validateGovernment(string $vatNumber): bool 95 | { 96 | $prefix = strtoupper(substr($vatNumber, 0, 2)); 97 | $number = (int) substr($vatNumber, 2, 3); 98 | 99 | // Government departments 100 | if ($prefix == 'GD') { 101 | return $number < 500; 102 | } 103 | 104 | // Health authorities 105 | if ($prefix == 'HA') { 106 | return $number > 499; 107 | } 108 | 109 | return false; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Vies/ViesException.php: -------------------------------------------------------------------------------- 1 | 12 | * @license MIT 13 | * 14 | */ 15 | namespace DragonBe\Vies; 16 | 17 | /** 18 | * ViesException 19 | * 20 | * This class provides an exception layer for usage of the VIES web service 21 | * provided by the European commission to validate VAT numbers of companies 22 | * registered within the European Union 23 | * 24 | * @see \Exception 25 | * @package \DragonBe\Vies 26 | */ 27 | class ViesException extends \Exception 28 | { 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/Vies/ViesServiceException.php: -------------------------------------------------------------------------------- 1 | 12 | * @license MIT 13 | * 14 | */ 15 | namespace DragonBe\Vies; 16 | 17 | /** 18 | * ViesServiceException 19 | * 20 | * This class provides an exception layer for usage of the VIES web service 21 | * provided by the European commission to validate VAT numbers of companies 22 | * registered within the European Union 23 | * 24 | * @see \Exception 25 | * @package \DragonBe\Vies 26 | */ 27 | class ViesServiceException extends \Exception 28 | { 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/autoload.php: -------------------------------------------------------------------------------- 1 | 'BE', 29 | 'vatNumber' => '123456749', 30 | 'requestDate' => date_create(date('Y-m-dP')), 31 | 'valid' => $isValid, 32 | 'traderName' => 'Testing Corp N.V.', 33 | 'traderAddress' => 'MARKT 1' . PHP_EOL . '1000 BRUSSEL', 34 | 'requestIdentifier' => 'XYZ1234567890' 35 | ]; 36 | } 37 | 38 | /** 39 | * @param bool $isValid 40 | * 41 | * @return stdClass 42 | */ 43 | protected function createViesResponse($isValid = true) 44 | { 45 | return (object) $this->createViesResponseArray($isValid); 46 | } 47 | 48 | public function validationProvider() 49 | { 50 | return [ 51 | [true], 52 | [false] 53 | ]; 54 | } 55 | 56 | /** 57 | * Test that a VAT response can be created 58 | * 59 | * @covers ::__construct 60 | * @covers ::populate 61 | * @covers ::setCountryCode 62 | * @covers ::getCountryCode 63 | * @covers ::setVatNumber 64 | * @covers ::getVatNumber 65 | * @covers ::setRequestDate 66 | * @covers ::getRequestDate 67 | * @covers ::setValid 68 | * @covers ::isValid 69 | * @covers ::setName 70 | * @covers ::getName 71 | * @covers ::setAddress 72 | * @covers ::getAddress 73 | * @covers ::setIdentifier 74 | * @covers ::getIdentifier 75 | * @covers ::setNameMatch 76 | * @covers ::getNameMatch 77 | * @covers ::setCompanyTypeMatch 78 | * @covers ::getCompanyTypeMatch 79 | * @covers ::setStreetMatch 80 | * @covers ::getStreetMatch 81 | * @covers ::setPostcodeMatch 82 | * @covers ::getPostcodeMatch 83 | * @covers ::setCityMatch 84 | * @covers ::getCityMatch 85 | * 86 | * @dataProvider validationProvider 87 | */ 88 | public function testCanCreateResponseAtConstruct($validCheck) 89 | { 90 | $response = $this->createViesResponse($validCheck); 91 | $checkVatResponse = new CheckVatResponse($response); 92 | $this->assertSame($response->countryCode, $checkVatResponse->getCountryCode()); 93 | $this->assertSame($response->vatNumber, $checkVatResponse->getVatNumber()); 94 | $this->assertSame($response->requestDate, $checkVatResponse->getRequestDate()); 95 | $this->assertSame($response->valid, $checkVatResponse->isValid()); 96 | $this->assertSame($response->traderName, $checkVatResponse->getName()); 97 | $this->assertSame($response->traderAddress, $checkVatResponse->getAddress()); 98 | $this->assertSame($response->requestIdentifier, $checkVatResponse->getIdentifier()); 99 | } 100 | 101 | /** 102 | * @covers ::__construct 103 | * @covers ::populate 104 | * @covers ::setCountryCode 105 | * @covers ::getCountryCode 106 | * @covers ::setVatNumber 107 | * @covers ::getVatNumber 108 | * @covers ::setRequestDate 109 | * @covers ::getRequestDate 110 | * @covers ::setValid 111 | * @covers ::isValid 112 | * @covers ::setName 113 | * @covers ::getName 114 | * @covers ::setAddress 115 | * @covers ::getAddress 116 | * @covers ::setIdentifier 117 | * @covers ::getIdentifier 118 | * @covers ::setNameMatch 119 | * @covers ::getNameMatch 120 | * @covers ::setCompanyTypeMatch 121 | * @covers ::getCompanyTypeMatch 122 | * @covers ::setStreetMatch 123 | * @covers ::getStreetMatch 124 | * @covers ::setPostcodeMatch 125 | * @covers ::getPostcodeMatch 126 | * @covers ::setCityMatch 127 | * @covers ::getCityMatch 128 | * 129 | * @dataProvider validationProvider 130 | */ 131 | public function testCanCreateResponseWithoutNameAndAddressAtConstruct($validCheck) 132 | { 133 | $response = $this->createViesResponse($validCheck); 134 | unset($response->traderName, $response->traderAddress); 135 | $checkVatResponse = new CheckVatResponse($response); 136 | $this->assertSame($response->countryCode, $checkVatResponse->getCountryCode()); 137 | $this->assertSame($response->vatNumber, $checkVatResponse->getVatNumber()); 138 | $this->assertSame($response->requestDate, $checkVatResponse->getRequestDate()); 139 | $this->assertSame($response->valid, $checkVatResponse->isValid()); 140 | $this->assertSame($response->requestIdentifier, $checkVatResponse->getIdentifier()); 141 | $this->assertSame('---', $checkVatResponse->getName()); 142 | $this->assertSame('---', $checkVatResponse->getAddress()); 143 | } 144 | 145 | /** 146 | * @covers ::__construct 147 | * @covers ::populate 148 | * @covers ::setCountryCode 149 | * @covers ::getCountryCode 150 | * @covers ::setVatNumber 151 | * @covers ::getVatNumber 152 | * @covers ::setRequestDate 153 | * @covers ::getRequestDate 154 | * @covers ::setValid 155 | * @covers ::isValid 156 | * @covers ::setName 157 | * @covers ::getName 158 | * @covers ::setAddress 159 | * @covers ::getAddress 160 | * @covers ::setIdentifier 161 | * @covers ::getIdentifier 162 | * @covers ::setNameMatch 163 | * @covers ::getNameMatch 164 | * @covers ::setCompanyTypeMatch 165 | * @covers ::getCompanyTypeMatch 166 | * @covers ::setStreetMatch 167 | * @covers ::getStreetMatch 168 | * @covers ::setPostcodeMatch 169 | * @covers ::getPostcodeMatch 170 | * @covers ::setCityMatch 171 | * @covers ::getCityMatch 172 | * 173 | * @dataProvider validationProvider 174 | */ 175 | public function testCanCreateResponseWithArrayAtConstruct($validCheck) 176 | { 177 | $response = $this->createViesResponseArray($validCheck); 178 | $checkVatResponse = new CheckVatResponse($response); 179 | $this->assertSame($response['countryCode'], $checkVatResponse->getCountryCode()); 180 | $this->assertSame($response['vatNumber'], $checkVatResponse->getVatNumber()); 181 | $this->assertSame($response['requestDate'], $checkVatResponse->getRequestDate()); 182 | $this->assertSame($response['valid'], $checkVatResponse->isValid()); 183 | $this->assertSame($response['traderName'], $checkVatResponse->getName()); 184 | $this->assertSame($response['traderAddress'], $checkVatResponse->getAddress()); 185 | $this->assertSame($response['requestIdentifier'], $checkVatResponse->getIdentifier()); 186 | } 187 | 188 | /** 189 | * @covers ::__construct 190 | * @covers ::getRequestDate 191 | */ 192 | public function testDefaultDateIsNow() 193 | { 194 | $vatResponse = new CheckVatResponse(); 195 | $this->assertInstanceOf(DateTime::class, $vatResponse->getRequestDate()); 196 | $this->assertSame(date('Y-m-dP'), $vatResponse->getRequestDate()->format('Y-m-dP')); 197 | } 198 | 199 | /** 200 | * @covers ::__construct 201 | * @covers ::populate 202 | */ 203 | public function testExceptionIsThrownWhenRequiredParametersAreMissing() 204 | { 205 | $this->expectException(InvalidArgumentException::class); 206 | new CheckVatResponse([]); 207 | } 208 | 209 | public function requiredDataProvider() 210 | { 211 | return [ 212 | ['DE', '123456749', date('Y-m-dP'), true], 213 | ['ES', '987654321', date('Y-m-dP'), false], 214 | ]; 215 | } 216 | 217 | /** 218 | * @covers ::__construct 219 | * @covers ::populate 220 | * @covers ::setCountryCode 221 | * @covers ::getCountryCode 222 | * @covers ::setVatNumber 223 | * @covers ::getVatNumber 224 | * @covers ::setRequestDate 225 | * @covers ::getRequestDate 226 | * @covers ::setValid 227 | * @covers ::isValid 228 | * @covers ::setName 229 | * @covers ::getName 230 | * @covers ::setAddress 231 | * @covers ::getAddress 232 | * @covers ::setIdentifier 233 | * @covers ::getIdentifier 234 | * @covers ::setNameMatch 235 | * @covers ::getNameMatch 236 | * @covers ::setCompanyTypeMatch 237 | * @covers ::getCompanyTypeMatch 238 | * @covers ::setStreetMatch 239 | * @covers ::getStreetMatch 240 | * @covers ::setPostcodeMatch 241 | * @covers ::getPostcodeMatch 242 | * @covers ::setCityMatch 243 | * @covers ::getCityMatch 244 | * 245 | * @dataProvider requiredDataProvider 246 | */ 247 | public function testResponseContainsEmptyValuesWithOnlyRequiredArguments( 248 | $countryCode, 249 | $vatNumber, 250 | $requestDate, 251 | $valid 252 | ) { 253 | 254 | $expectedResult = [ 255 | 'countryCode' => $countryCode, 256 | 'vatNumber' => $vatNumber, 257 | 'requestDate' => substr($requestDate, 0, -6), 258 | 'valid' => $valid, 259 | 'name' => '---', 260 | 'address' => '---', 261 | 'identifier' => '', 262 | 'nameMatch' => '', 263 | 'companyTypeMatch' => '', 264 | 'streetMatch' => '', 265 | 'postcodeMatch' => '', 266 | 'cityMatch' => '', 267 | ]; 268 | 269 | $vatResponse = new CheckVatResponse([ 270 | 'countryCode' => $countryCode, 271 | 'vatNumber' => $vatNumber, 272 | 'requestDate' => date_create($requestDate), 273 | 'valid' => $valid, 274 | ]); 275 | 276 | $this->assertSame($expectedResult, $vatResponse->toArray()); 277 | } 278 | 279 | /** 280 | * @covers ::__construct 281 | * @covers ::populate 282 | * @covers ::setCountryCode 283 | * @covers ::getCountryCode 284 | * @covers ::setVatNumber 285 | * @covers ::getVatNumber 286 | * @covers ::setRequestDate 287 | * @covers ::getRequestDate 288 | * @covers ::setValid 289 | * @covers ::isValid 290 | * @covers ::setName 291 | * @covers ::getName 292 | * @covers ::setAddress 293 | * @covers ::getAddress 294 | * @covers ::setIdentifier 295 | * @covers ::getIdentifier 296 | * @covers ::setNameMatch 297 | * @covers ::getNameMatch 298 | * @covers ::setCompanyTypeMatch 299 | * @covers ::getCompanyTypeMatch 300 | * @covers ::setStreetMatch 301 | * @covers ::getStreetMatch 302 | * @covers ::setPostcodeMatch 303 | * @covers ::getPostcodeMatch 304 | * @covers ::setCityMatch 305 | * @covers ::getCityMatch 306 | * 307 | * @dataProvider requiredDataProvider 308 | */ 309 | public function testResponseAcceptsStringDates( 310 | $countryCode, 311 | $vatNumber, 312 | $requestDate, 313 | $valid 314 | ) { 315 | 316 | $expectedResult = [ 317 | 'countryCode' => $countryCode, 318 | 'vatNumber' => $vatNumber, 319 | 'requestDate' => substr($requestDate, 0, -6), 320 | 'valid' => $valid, 321 | 'name' => '---', 322 | 'address' => '---', 323 | 'identifier' => '', 324 | 'nameMatch' => '', 325 | 'companyTypeMatch' => '', 326 | 'streetMatch' => '', 327 | 'postcodeMatch' => '', 328 | 'cityMatch' => '', 329 | ]; 330 | 331 | $vatResponse = new CheckVatResponse([ 332 | 'countryCode' => $countryCode, 333 | 'vatNumber' => $vatNumber, 334 | 'requestDate' => $requestDate, 335 | 'valid' => $valid, 336 | ]); 337 | 338 | $this->assertSame($expectedResult, $vatResponse->toArray()); 339 | } 340 | 341 | /** 342 | * Generates trader details that can be submitted to VIES 343 | * as an additional check 344 | * 345 | * @return array 346 | */ 347 | public function traderDetailsProvider(): array 348 | { 349 | return [ 350 | [ 351 | 'BE', 352 | '0123456789', 353 | 'FooBar', 354 | 'bvba', 355 | 'Kerkstraat 1234', 356 | '2000', 357 | 'Antwerpen', 358 | ], 359 | [ 360 | 'NL', 361 | '0123456789', 362 | 'De vrolijke testers', 363 | 'BV', 364 | 'Kerkstraat 12', 365 | '1017 GC', 366 | 'Amsterdam', 367 | ], 368 | [ 369 | 'DE', 370 | '0123456789', 371 | 'Die fröhlichen Tester', 372 | 'GmbH', 373 | 'Kaiserstraße 14', 374 | '53113', 375 | 'Bonn', 376 | ], 377 | 378 | ]; 379 | } 380 | 381 | /** 382 | * @param string $countryCode 383 | * @param string $vatNumber 384 | * @param string $companyName 385 | * @param string $companyType 386 | * @param string $companyStreet 387 | * @param string $companyPostcode 388 | * @param string $companyCity 389 | * 390 | * @dataProvider traderDetailsProvider 391 | * @covers ::toArray 392 | * @covers ::populate 393 | * @covers ::setCountryCode 394 | * @covers ::getCountryCode 395 | * @covers ::setVatNumber 396 | * @covers ::getVatNumber 397 | * @covers ::setRequestDate 398 | * @covers ::getRequestDate 399 | * @covers ::setValid 400 | * @covers ::isValid 401 | * @covers ::setName 402 | * @covers ::getName 403 | * @covers ::setAddress 404 | * @covers ::getAddress 405 | * @covers ::setIdentifier 406 | * @covers ::getIdentifier 407 | * @covers ::setNameMatch 408 | * @covers ::getNameMatch 409 | * @covers ::setCompanyTypeMatch 410 | * @covers ::getCompanyTypeMatch 411 | * @covers ::setStreetMatch 412 | * @covers ::getStreetMatch 413 | * @covers ::setPostcodeMatch 414 | * @covers ::getPostcodeMatch 415 | * @covers ::setCityMatch 416 | * @covers ::getCityMatch 417 | */ 418 | public function testValidatingTraderDetails( 419 | string $countryCode, 420 | string $vatNumber, 421 | string $companyName, 422 | string $companyType, 423 | string $companyStreet, 424 | string $companyPostcode, 425 | string $companyCity 426 | ) { 427 | $requestDate = date('Y-m-dP'); 428 | $valid = true; 429 | $identifier = substr(md5('The world is not enough...'), 0, 16); 430 | 431 | $expectedResult = [ 432 | 'countryCode' => $countryCode, 433 | 'vatNumber' => $vatNumber, 434 | 'requestDate' => substr($requestDate, 0, -6), 435 | 'valid' => $valid, 436 | 'name' => strtoupper($companyType . ' ' . $companyName), 437 | 'address' => strtoupper($companyStreet . ' ' . $companyPostcode . ' ' . $companyCity), 438 | 'identifier' => $identifier, 439 | 'nameMatch' => '', 440 | 'companyTypeMatch' => '', 441 | 'streetMatch' => '', 442 | 'postcodeMatch' => '', 443 | 'cityMatch' => '', 444 | ]; 445 | 446 | $vatResponse = new CheckVatResponse([ 447 | 'countryCode' => $countryCode, 448 | 'vatNumber' => $vatNumber, 449 | 'requestDate' => date_create($requestDate), 450 | 'valid' => $valid, 451 | 'traderName' => strtoupper($companyType . ' ' . $companyName), 452 | 'traderAddress' => strtoupper($companyStreet . ' ' . $companyPostcode . ' ' . $companyCity), 453 | 'requestIdentifier' => $identifier, 454 | ]); 455 | 456 | $this->assertSame($expectedResult, $vatResponse->toArray()); 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /tests/Vies/HeartBeatTest.php: -------------------------------------------------------------------------------- 1 | expectException(DomainException::class); 25 | (new HeartBeat())->getHost(); 26 | } 27 | 28 | /** 29 | * @covers ::getPort 30 | */ 31 | public function testDefaultPortIsHttp() 32 | { 33 | $hb = new HeartBeat(); 34 | $port = $hb->getPort(); 35 | $this->assertSame(443, $port); 36 | } 37 | 38 | /** 39 | * @covers ::setHost 40 | * @covers ::getHost 41 | */ 42 | public function testCanSetHost() 43 | { 44 | $host = 'www.example.com'; 45 | $hb = new HeartBeat(); 46 | $hb->setHost($host); 47 | $this->assertSame($host, $hb->getHost()); 48 | } 49 | 50 | /** 51 | * @covers ::setPort 52 | * @covers ::getPort 53 | */ 54 | public function testCanSetPort() 55 | { 56 | $port = 443; 57 | $hb = new HeartBeat(); 58 | $hb->setPort($port); 59 | $this->assertSame($port, $hb->getPort()); 60 | } 61 | 62 | /** 63 | * @covers ::setTimeout 64 | * @covers ::getTimeout 65 | */ 66 | public function testCanSetTimeout() 67 | { 68 | $timeout = 300; 69 | $hb = new HeartBeat(); 70 | $hb->setTimeout($timeout); 71 | $this->assertSame($timeout, $hb->getTimeout()); 72 | } 73 | 74 | /** 75 | * @covers ::__construct 76 | */ 77 | public function testCanOverrideSettingsAtConstruct() 78 | { 79 | $host = 'www.example.com'; 80 | $port = 8080; 81 | $hb = new HeartBeat($host, $port); 82 | $this->assertSame($host, $hb->getHost()); 83 | $this->assertSame($port, $hb->getPort()); 84 | } 85 | 86 | /** 87 | * @covers ::isAlive 88 | */ 89 | public function testVerifyServicesIsAlive() 90 | { 91 | $host = '127.0.0.1'; 92 | $port = -1; 93 | HeartBeat::$testingEnabled = true; 94 | HeartBeat::$testingServiceIsUp = true; 95 | $hb = new HeartBeat($host, $port); 96 | $this->assertTrue($hb->isAlive()); 97 | } 98 | 99 | /** 100 | * @covers ::isAlive 101 | */ 102 | public function testVerifyServicesIsDown() 103 | { 104 | $host = '127.0.0.1'; 105 | $port = -1; 106 | HeartBeat::$testingEnabled = true; 107 | HeartBeat::$testingServiceIsUp = false; 108 | $hb = new HeartBeat($host, $port); 109 | $this->assertFalse($hb->isAlive()); 110 | } 111 | 112 | public function socketProvider(): array 113 | { 114 | return [ 115 | 'Non-existing socket on localhost' => ['127.0.0.1', -1, 10, null, false], 116 | 'Socket 443 on ec.europe.eu' => [Vies::VIES_DOMAIN, Vies::VIES_PORT, 10, null, false], 117 | 'Socket 443 on ec.europe.eu'.Vies::VIES_PATH => [ 118 | Vies::VIES_DOMAIN, 119 | Vies::VIES_PORT, 120 | 10, 121 | Vies::VIES_PATH, 122 | true 123 | ], 124 | ]; 125 | } 126 | 127 | /** 128 | * @dataProvider socketProvider 129 | * @covers ::isAlive 130 | * @covers ::reachOut 131 | * @covers ::getSecuredResponse 132 | * @covers ::readContents 133 | */ 134 | public function testIsAliveUsingSockets($host, $port, $timeout, $path, $expectedResult) 135 | { 136 | HeartBeat::$testingEnabled = false; 137 | $heartBeat = new HeartBeat($host, $port, $timeout, $path); 138 | $actualResult = $heartBeat->isAlive(); 139 | $this->assertSame($expectedResult, $actualResult); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /tests/Vies/Validator/AbstractValidatorTest.php: -------------------------------------------------------------------------------- 1 | assertSame($state, (new Vies())->validateVatSum($country, $vatNumber)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorATTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('AT', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['U10223006', true], 22 | ['U1022300', false], 23 | ['A10223006', false], 24 | ['U10223005', false], 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorBETest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('BE', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['776091951', false], 22 | ['0776091952', false], 23 | ['07760919', false], 24 | ['0417710407', true], 25 | ['0627515170', true], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorBGTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('BG', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['301004503', true], 22 | ['10100450', false], 23 | ['301004502', false], 24 | ['8311046307', true], 25 | ['3002779909', true], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorCYTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('CY', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['00532445O', true], 22 | ['005324451', false], 23 | ['0053244511', false], 24 | ['12000139V', false], 25 | ['72000139V', false], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorCZTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('CZ', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['46505334', true], 22 | ['7103192745', true], 23 | ['640903926', true], 24 | ['395601439', true], 25 | ['630903928', true], 26 | ['27082440', true], 27 | ['4650533', false], 28 | ['96505334', false], 29 | ['46505333', false], 30 | ['7103192743', false], 31 | ['5103192743', false], 32 | ['1903192745', false], 33 | ['7133192745', false], 34 | ['395632439', false], 35 | ['396301439', false], 36 | ['545601439', false], 37 | ['640903927', false], 38 | ['7103322745', false], 39 | ]; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorDETest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('DE', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['111111125', true], 22 | ['111111124', false], 23 | ['1234567', false], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorDKTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('DK', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['88146328', true], 22 | ['88146327', false], 23 | ['1234567', false], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorEETest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('EE', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['100207415', true], 22 | ['1002074', false], 23 | ['A12345678', false], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorELTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('EL', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['040127797', true], 22 | ['040127796', false], 23 | ['1234567', false], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorESTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('ES', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['A0011012B', true], 22 | ['A78304516', true], 23 | ['X5910266W', true], 24 | ['K0011012B', false], 25 | ['12345678', false], 26 | ['K001A012B', false], 27 | ['A0011012C', false], 28 | ['Z5910266W', false], 29 | ['J5910266W', false], 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorEUTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('EU', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['372009975', false], 22 | ['826409867', false], 23 | ['528003555', false], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorFITest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('FI', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['09853608', true], 22 | ['09853607', false], 23 | ['1234567', false], 24 | ['01089940', true], 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorFRTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('FR', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['00300076965', true], 22 | ['K7399859412', true], 23 | ['4Z123456782', true], 24 | ['0030007696A', false], 25 | ['1234567890', false], 26 | ['K6399859412', false], 27 | ['KO399859412', false], 28 | ['IO399859412', false], 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorGBTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('GB', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['434031494', true], 22 | ['GD001', true], 23 | ['HA500', true], 24 | ['434031493', false], 25 | ['12345', false], 26 | ['GD500', false], 27 | ['HA100', false], 28 | ['12345678', false], 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorHRTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('HR', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['38192148118', true], 22 | ['3819214811', false], 23 | ['1234567890A', false], 24 | ['AA123456789', false], 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorHUTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('HU', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['21376414', true], 22 | ['10597190', true], 23 | ['2137641', false], 24 | ['1234567A', false], 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorIETest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('IE', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['8Z49289F', true], 22 | ['3628739L', true], 23 | ['5343381W', true], 24 | ['6433435OA', true], 25 | ['8Z49389F', false], 26 | ['1234567', false], 27 | ['6433435OB', false], 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorITTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('IT', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['00000010215', true], 22 | ['00000010214', false], 23 | ['1234567890', false], 24 | ['00000001234', false], 25 | ['AA123456789', false], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorLTTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('LT', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['210061371310', true], 22 | ['213179412', true], 23 | ['290061371314', true], 24 | ['208640716', true], 25 | ['213179422', false], 26 | ['21317941', false], 27 | ['1234567890', false], 28 | ['1234567890AB', false], 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorLUTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('LU', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['10000356', true], 22 | ['10000355', false], 23 | ['1234567', false], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorLVTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('LV', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['40003009497', true], 22 | ['40013009497', false], 23 | ['40003009496', false], 24 | ['1234567890', false], 25 | ['00212345678', false], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorMTTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('MT', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['15121333', true], 22 | ['15121332', false], 23 | ['1234567', false], 24 | ['05121333', false], 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorNLTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('NL', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['010000446B01', true], 22 | ['010000436B01', false], 23 | ['12345678901', false], 24 | ['123456789A12', false], 25 | ['123456789B00', false], 26 | ['002342672B42', true], 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorPLTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('PL', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['5260001246', true], 22 | ['12342678090', false], 23 | ['1212121212', false], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorPTTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('PT', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['502757191', true], 22 | ['502757192', false], 23 | ['12345678', false], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorROTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('RO', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['11198699', true], 22 | ['14186770', true], 23 | ['11198698', false], 24 | ['1', false], 25 | ['12345678902', false], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorSETest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('SE', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['556188840401', true], 22 | ['556188840400', false], 23 | ['1234567890', false], 24 | ['556181140401', false], 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorSITest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('SI', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['15012557', true], 22 | ['15012556', false], 23 | ['12345670', false], 24 | ['01234567', false], 25 | ['1234567', false], 26 | ['95796550', true], 27 | ['95736220', false], 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorSKTest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('SK', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['4030000007', true], 22 | ['4030000006', false], 23 | ['123456789', false], 24 | ['0123456789', false], 25 | ['4060000007', false], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Vies/Validator/ValidatorXITest.php: -------------------------------------------------------------------------------- 1 | validateVatNumber('XI', $vatNumber, $state); 16 | } 17 | 18 | public function vatNumberProvider() 19 | { 20 | return [ 21 | ['925901618', true], 22 | ['GD001', true], 23 | ['HA500', true], 24 | ['434031493', false], 25 | ['12345', false], 26 | ['GD500', false], 27 | ['HA100', false], 28 | ['12345678', false], 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Vies/ValidatorTest.php: -------------------------------------------------------------------------------- 1 | ['U10223006', ['U1022300', 'A10223006', 'U10223005']], 16 | 'BE' => ['0776091951', ['0776091952', '07760919']], 17 | 'BG' => [['204514061', '301004503'], ['10100450', '301004502']], 18 | 'CY' => ['00532445O', ['005324451', '0053244511', '12000139V', '72000139V']], 19 | 'CZ' => [ 20 | ['46505334', '7103192745', '640903926', '395601439', '630903928', '27082440'], 21 | [ 22 | '4650533', '96505334', '46505333', '7103192743', '1903192745', '7133192745', 23 | '395632439', '396301439', '545601439', '640903927', '7103322745' 24 | ] 25 | ], 26 | 'DE' => ['111111125', ['111111124', '1234567']], 27 | 'DK' => ['88146328', ['88146327', '1234567']], 28 | 'EE' => ['100207415', ['1002074', 'A12345678']], 29 | 'EL' => ['040127797', ['040127796', '1234567']], 30 | 'ES' => [['A0011012B', 'A78304516'], ['K0011012B', '12345678', 'K001A012B', 'A0011012C']], 31 | 'FI' => ['09853608', ['09853607', '1234567']], 32 | 'FR' => [ 33 | ['00300076965', 'K7399859412', '4Z123456782'], 34 | ['0030007696A', '1234567890', 'K6399859412', 'KO399859412', 'IO399859412'] 35 | ], 36 | 'GB' => [['434031494', 'GD001', 'HA500'], ['434031493', '12345', 'GD500', 'HA100', '12345678']], 37 | 'HR' => ['38192148118', ['3819214811', '1234567890A']], 38 | 'HU' => [['21376414', '10597190'], ['2137641', '1234567A']], 39 | 'IE' => [['8Z49289F', '3628739L', '5343381W', '6433435OA'], ['8Z49389F', '1234567', '6433435OB']], 40 | 'IT' => ['00000010215', ['00000010214', '1234567890', '00000001234']], 41 | 'LT' => [ 42 | ['210061371310', '213179412', '290061371314', '208640716'], 43 | ['213179422', '21317941', '1234567890', '1234567890AB'] 44 | ], 45 | 'LU' => ['10000356', ['10000355', '1234567']], 46 | 'LV' => ['40003009497', ['40013009497', '40003009496', '1234567890', '00212345678']], 47 | 'MT' => ['15121333', ['15121332', '1234567', '05121333']], 48 | 'NL' => [ 49 | ['010000446B01', '000099998B57'], 50 | ['010000436B01', '12345678901', '123456789A12', '123456789B00', '0$0099998B57']], 51 | 'PL' => ['5260001246', ['12342678090', '1212121212']], 52 | 'PT' => ['502757191', ['502757192', '12345678']], 53 | 'RO' => [['11198699', '14186770'], ['11198698', '1', '12345678902']], 54 | 'SE' => ['556188840401', ['556188840400', '1234567890', '556181140401']], 55 | 'SI' => ['15012557', ['15012556', '12345670', '01234567', '1234567']], 56 | 'SK' => ['4030000007', ['4030000006', '123456789', '0123456789', '4060000007']] 57 | ]; 58 | } 59 | 60 | public function testVatNumberChecksumSuccess() 61 | { 62 | $vies = new Vies(); 63 | 64 | foreach ($this->vatNumberProvider() as $country => $numbers) { 65 | if (! is_array($numbers[0])) { 66 | $numbers[0] = [$numbers[0]]; 67 | } 68 | foreach ($numbers[0] as $number) { 69 | $result = $vies->validateVatSum($country, $number); 70 | $this->assertTrue( 71 | $result, 72 | 'VAT ID ' . $country . $number . ' should validate, but is not valid' 73 | ); 74 | } 75 | } 76 | } 77 | 78 | public function testVatNumberChecksumFailure() 79 | { 80 | $vies = new Vies(); 81 | 82 | foreach ($this->vatNumberProvider() as $country => $numbers) { 83 | foreach ($numbers[1] as $number) { 84 | $result = $vies->validateVatSum($country, $number); 85 | $this->assertFalse($result); 86 | } 87 | } 88 | } 89 | 90 | public function traderDataProvider() 91 | { 92 | return [ 93 | 'Belgian Trader Name' => [ 94 | [ 95 | 'countryCode' => 'BE', 96 | 'vatNumber' => '0203430576', 97 | 'requesterCountryCode' => 'BE', 98 | 'requesterVatNumber' => '0203430576', 99 | 'traderName' => 'B-Rail', 100 | 'traderCompanyType' => 'NV', 101 | 'traderStreet' => 'Frankrijkstraat 65', 102 | 'traderPostcode' => '1060', 103 | 'traderCity' => 'Sint-Gillis', 104 | ], 105 | ], 106 | 'German Trader Name' => [ 107 | [ 108 | 'countryCode' => 'DE', 109 | 'vatNumber' => '811569869', 110 | 'requesterCountryCode' => 'DE', 111 | 'requesterVatNumber' => '811569869', 112 | 'traderName' => 'Deutsche Bahn', 113 | 'traderCompanyType' => 'AG', 114 | 'traderStreet' => 'Potsdamer Platz 2', 115 | 'traderPostcode' => '10785', 116 | 'traderCity' => 'Berlin', 117 | ], 118 | ], 119 | 'Greek Trader Name' => [ 120 | [ 121 | 'countryCode' => 'EL', 122 | 'vatNumber' => '999645865', 123 | 'requesterCountryCode' => 'EL', 124 | 'requesterVatNumber' => '999645865', 125 | 'traderName' => 'ΤΡΑΙΝΟΣΕ', 126 | 'traderCompanyType' => 'AE', 127 | 'traderStreet' => 'ΚΑΡΟΛΟΥ 1-3', 128 | 'traderPostcode' => '10437', 129 | 'traderCity' => 'ΑΘΗΝΑ', 130 | ], 131 | ], 132 | 'Polish Trader Name' => [ 133 | [ 134 | 'countryCode' => 'PL', 135 | 'vatNumber' => '1132316427', 136 | 'requesterCountryCode' => 'PL', 137 | 'requesterVatNumber' => '1132316427', 138 | 'traderName' => 'PKP POLSKIE LINIE KOLEJOWE SPÓŁKA AKCYJNA', 139 | 'traderCompanyType' => '', 140 | 'traderStreet' => 'TARGOWA 74', 141 | 'traderPostcode' => '03-734', 142 | 'traderCity' => 'WARSZAWA', 143 | ], 144 | ], 145 | 'Ampesant Trader Name' => [ 146 | [ 147 | 'countryCode' => 'BE', 148 | 'vatNumber' => '0458591947', 149 | 'requesterCountryCode' => 'BE', 150 | 'requesterVatNumber' => '0458591947', 151 | 'traderName' => 'VAN AERDE & PARTNERS', 152 | 'traderCompanyType' => 'BVBA', 153 | 'traderStreet' => 'RIJSELSTRAAT 274', 154 | 'traderPostcode' => '8200', 155 | 'traderCity' => 'BRUGGE', 156 | ], 157 | ], 158 | 'Dot-dash Trader Name' => [ 159 | [ 160 | 'countryCode' => 'BE', 161 | 'vatNumber' => '0467609086', 162 | 'requesterCountryCode' => 'BE', 163 | 'requesterVatNumber' => '0467609086', 164 | 'traderName' => 'HAELTERMAN C.V.-KLIMA', 165 | 'traderCompanyType' => 'BVBA', 166 | 'traderStreet' => 'GERAARDSBERGSESTEENWEG 307', 167 | 'traderPostcode' => '9404', 168 | 'traderCity' => 'NINOVE', 169 | ], 170 | ], 171 | 'Accent Trader Name' => [ 172 | [ 173 | 'countryCode' => 'BE', 174 | 'vatNumber' => '0873284862', 175 | 'requesterCountryCode' => 'BE', 176 | 'requesterVatNumber' => '0873284862', 177 | 'traderName' => '\'t GERIEF', 178 | 'traderCompanyType' => 'CVBA', 179 | 'traderStreet' => 'LICHTAARTSEWEG(HRT) 22', 180 | 'traderPostcode' => '2200', 181 | 'traderCity' => 'HERENTALS', 182 | ], 183 | ], 184 | 'Plus Trader Name' => [ 185 | [ 186 | 'countryCode' => 'BE', 187 | 'vatNumber' => '0629758840', 188 | 'requesterCountryCode' => 'BE', 189 | 'requesterVatNumber' => '0629758840', 190 | 'traderName' => 'ARCHITECTUUR+', 191 | 'traderCompanyType' => 'BVBA', 192 | 'traderStreet' => 'STATIONSSTRAAT 28', 193 | 'traderPostcode' => '3930', 194 | 'traderCity' => 'HAMONT-ACHEL', 195 | ], 196 | ], 197 | ]; 198 | } 199 | 200 | /** 201 | * Testing that arguments that contain non-latin values are still 202 | * validated correctly 203 | * 204 | * @group issue-99 205 | * @covers \DragonBe\Vies\Vies::validateVat 206 | * @covers \DragonBe\Vies\Vies::validateArgument 207 | * @dataProvider TraderDataProvider 208 | */ 209 | public function testArgumentValidationSucceedsForNonLatinArgumentValues(array $traderData) 210 | { 211 | $vies = new Vies(); 212 | try { 213 | $vatResponse = $vies->validateVat( 214 | $traderData['countryCode'], 215 | $traderData['vatNumber'], 216 | $traderData['requesterCountryCode'], 217 | $traderData['requesterVatNumber'], 218 | $traderData['traderName'], 219 | $traderData['traderCompanyType'], 220 | $traderData['traderStreet'], 221 | $traderData['traderPostcode'], 222 | $traderData['traderCity'] 223 | ); 224 | $this->assertTrue($vatResponse->isValid()); 225 | } catch (ViesServiceException $viesServiceException) { 226 | $this->markTestSkipped('Service unavailable at the moment'); 227 | } 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /tests/Vies/_files/checkVatService.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | Specific disclaimer for this service ----------------------------------------- The 4 | objective of this Internet site is to allow persons involved in the intra-Community supply of 5 | goods or of services to obtain confirmation of the validity of the VAT identification number of 6 | any specified person, in accordance to article 27 of Council Regulation (EC) No. 1798/2003 of 7 7 | October 2003. Any other use and any extraction and use of the data which is not in conformity 8 | with the objective of this site is strictly forbidden. Any retransmission of the contents of 9 | this site, whether for a commercial purpose or otherwise, as well as any more general use other 10 | than as far as is necessary to support the activity of a legitimate user (for example: to draw 11 | up their own invoices) is expressly forbidden. In addition, any copying or reproduction of the 12 | contents of this site is strictly forbidden. The European Commission maintains this website to 13 | enhance the access by taxable persons making intra-Community supplies to verification of their 14 | customers VAT identification numbers. Our goal is to supply instantaneous and accurate 15 | information. However the Commission accepts no responsibility or liability whatsoever with 16 | regard to the information obtained using this site. This information: - is obtained from Member 17 | States databases over which the Commission services have no control and for which the Commission 18 | assumes no responsibility; it is the responsibility of the Member States to keep their databases 19 | complete, accurate and up to date; - is not professional or legal advice (if you need specific 20 | advice, you should always consult a suitably qualified professional); - does not in itself give 21 | a right to exempt intra-Community supplies from Value Added Tax; - does not change any 22 | obligations imposed on taxable persons in relation to intra-Community supplies. It is our goal 23 | to minimise disruption caused by technical errors. However some data or information on our site 24 | may have been created or structured in files or formats which are not error-free and we cannot 25 | guarantee that our service will not be interrupted or otherwise affected by such problems. The 26 | Commission accepts no responsibility with regard to such problems incurred as a result of using 27 | this site or any linked external sites. This disclaimer is not intended to limit the liability 28 | of the Commission in contravention of any requirements laid down in applicable national law nor 29 | to exclude its liability for matters which may not be excluded under that law. Usage: The 30 | countryCode input parameter must follow the pattern [A-Z]{2} The vatNumber input parameter must 31 | follow the [0-9A-Za-z\+\*\.]{2,12} In case of problem, the returned FaultString can take the 32 | following specific values: - INVALID_INPUT: The provided CountryCode is invalid or the VAT 33 | number is empty; - SERVICE_UNAVAILABLE: The SOAP service is unavailable, try again later; - 34 | MS_UNAVAILABLE: The Member State service is unavailable, try again later or with another Member 35 | State; - TIMEOUT: The Member State service could not be reach in time, try again later or with 36 | another Member State; - SERVER_BUSY: The service can't process your request. Try again latter. 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 | VALID 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | INVALID 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 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | -------------------------------------------------------------------------------- /tests/Vies/_files/checkVatTestService.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Specific disclaimer for this service ----------------------------------------- 5 | Here is the list of VAT Number to use to receive each kind of answer : 6 | 100 = Valid request with Valid VAT Number 7 | 200 = Valid request with an Invalid VAT Number 8 | 201 = Error : INVALID_INPUT 9 | 202 = Error : INVALID_REQUESTER_INFO 10 | 300 = Error : SERVICE_UNAVAILABLE 11 | 301 = Error : MS_UNAVAILABLE 12 | 302 = Error : TIMEOUT 13 | 400 = Error : VAT_BLOCKED 14 | 401 = Error : IP_BLOCKED 15 | 500 = Error : GLOBAL_MAX_CONCURRENT_REQ 16 | 501 = Error : GLOBAL_MAX_CONCURRENT_REQ_TIME 17 | 600 = Error : MS_MAX_CONCURRENT_REQ 18 | 601 = Error : MS_MAX_CONCURRENT_REQ_TIME 19 | 20 | For all the other cases, The web service will responds with a "SERVICE_UNAVAILABLE" error. 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 | VALID 92 | 93 | 94 | 95 | 96 | INVALID 97 | 98 | 99 | 100 | 101 | NOT_PROCESSED 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 | -------------------------------------------------------------------------------- /tests/Vies/_files/soapVersionCheck.code: -------------------------------------------------------------------------------- 1 | error_reporting(E_ALL|E_NOTICE|E_DEPRECATED); 2 | ini_set('display_errors', 1); 3 | return var_export([SOAP_1_1, SOAP_1_2], true); --------------------------------------------------------------------------------