VatCalculator   F
last analyzed

Complexity

Total Complexity 63

Size/Duplication

Total Lines 724
Duplicated Lines 0.83 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 0
Metric Value
wmc 63
lcom 1
cbo 2
dl 6
loc 724
rs 3.236
c 0
b 0
f 0

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 3
A getClientIP() 0 12 5
A getIPBasedCountry() 0 17 3
A shouldCollectVAT() 0 6 3
A calculate() 0 18 5
A calculateNet() 0 19 6
A getNetPrice() 0 4 1
A getCountryCode() 0 4 1
A setCountryCode() 0 4 1
A getPostalCode() 0 4 1
A setPostalCode() 0 4 1
A getTaxRate() 0 4 1
A isCompany() 0 4 1
A setCompany() 0 4 1
A setBusinessCountryCode() 0 4 1
A getTaxRateForCountry() 0 4 1
C getTaxRateForLocation() 0 29 13
A getTaxValue() 0 4 1
A isValidVATNumber() 0 10 2
A getVATDetails() 3 24 5
A initSoapClient() 3 15 6
A setSoapClient() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like VatCalculator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use VatCalculator, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Mpociot\VatCalculator;
4
5
use Illuminate\Contracts\Config\Repository;
6
use Mpociot\VatCalculator\Exceptions\VATCheckUnavailableException;
7
use SoapClient;
8
use SoapFault;
9
10
class VatCalculator
11
{
12
    /**
13
     * VAT Service check URL provided by the EU.
14
     */
15
    const VAT_SERVICE_URL = 'https://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl';
16
17
    /**
18
     * We're using the free ip2c service to lookup IP 2 country.
19
     */
20
    const GEOCODE_SERVICE_URL = 'https://api.ip2country.info/ip?';
21
22
    protected $soapClient;
23
24
    /**
25
     * All available tax rules and their exceptions.
26
     *
27
     * Taken from: https://ec.europa.eu/taxation_customs/resources/documents/taxation/vat/how_vat_works/rates/vat_rates_en.pdf
28
     *
29
     * @var array
30
     */
31
    protected $taxRules = [
32
        'AT' => [ // Austria
33
            'rate'       => 0.20,
34
            'exceptions' => [
35
                'Jungholz'   => 0.19,
36
                'Mittelberg' => 0.19,
37
            ],
38
        ],
39
        'BE' => [ // Belgium
40
            'rate' => 0.21,
41
        ],
42
        'BG' => [ // Bulgaria
43
            'rate' => 0.20,
44
        ],
45
        'CY' => [ // Cyprus
46
            'rate' => 0.19,
47
        ],
48
        'CZ' => [ // Czech Republic
49
            'rate' => 0.21,
50
        ],
51
        'DE' => [ // Germany
52
            'rate'       => 0.19,
53
            'exceptions' => [
54
                'Heligoland'            => 0,
55
                'Büsingen am Hochrhein' => 0,
56
            ],
57
        ],
58
        'DK' => [ // Denmark
59
            'rate' => 0.25,
60
        ],
61
        'EE' => [ // Estonia
62
            'rate' => 0.20,
63
        ],
64
        'EL' => [ // Hellenic Republic (Greece)
65
            'rate'       => 0.24,
66
            'exceptions' => [
67
                'Mount Athos' => 0,
68
            ],
69
        ],
70
        'ES' => [ // Spain
71
            'rate'       => 0.21,
72
            'exceptions' => [
73
                'Canary Islands' => 0,
74
                'Ceuta'          => 0,
75
                'Melilla'        => 0,
76
            ],
77
        ],
78
        'FI' => [ // Finland
79
            'rate' => 0.24,
80
        ],
81
        'FR' => [ // France
82
            'rate'       => 0.20,
83
            'exceptions' => [
84
                // Overseas France
85
                'Reunion'    => 0.085,
86
                'Martinique' => 0.085,
87
                'Guadeloupe' => 0.085,
88
                'Guyane'     => 0,
89
                'Mayotte'    => 0,
90
            ],
91
        ],
92
        'GB' => [ // United Kingdom
93
            'rate'       => 0.20,
94
            'exceptions' => [
95
                // UK RAF Bases in Cyprus are taxed at Cyprus rate
96
                'Akrotiri' => 0.19,
97
                'Dhekelia' => 0.19,
98
            ],
99
        ],
100
        'GR' => [ // Greece
101
            'rate'       => 0.24,
102
            'exceptions' => [
103
                'Mount Athos' => 0,
104
            ],
105
        ],
106
        'HR' => [ // Croatia
107
            'rate' => 0.25,
108
        ],
109
        'HU' => [ // Hungary
110
            'rate' => 0.27,
111
        ],
112
        'IE' => [ // Ireland
113
            'rate' => 0.23,
114
        ],
115
        'IT' => [ // Italy
116
            'rate'       => 0.22,
117
            'exceptions' => [
118
                'Campione d\'Italia' => 0,
119
                'Livigno'            => 0,
120
            ],
121
        ],
122
        'LT' => [ // Lithuania
123
            'rate' => 0.21,
124
        ],
125
        'LU' => [ // Luxembourg
126
            'rate' => 0.17,
127
        ],
128
        'LV' => [ // Latvia
129
            'rate' => 0.21,
130
        ],
131
        'MT' => [ // Malta
132
            'rate' => 0.18,
133
        ],
134
        'NL' => [ // Netherlands
135
            'rate' => 0.21,
136
            'rates' => [
137
                'high' => 0.21,
138
                'low' => 0.09,
139
            ],
140
        ],
141
        'PL' => [ // Poland
142
            'rate' => 0.23,
143
        ],
144
        'PT' => [ // Portugal
145
            'rate'       => 0.23,
146
            'exceptions' => [
147
                'Azores'  => 0.18,
148
                'Madeira' => 0.22,
149
            ],
150
        ],
151
        'RO' => [ // Romania
152
            'rate' => 0.19,
153
        ],
154
        'SE' => [ // Sweden
155
            'rate' => 0.25,
156
        ],
157
        'SI' => [ // Slovenia
158
            'rate' => 0.22,
159
        ],
160
        'SK' => [ // Slovakia
161
            'rate' => 0.20,
162
        ],
163
164
        // Countries associated with EU countries that have a special VAT rate
165
        'MC' => [ // Monaco France
166
            'rate' => 0.20,
167
        ],
168
        'IM' => [ // Isle of Man - United Kingdom
169
            'rate' => 0.20,
170
        ],
171
172
        // Non-EU with their own VAT requirements
173
        'CH' => [ // Switzerland
174
            'rate' => 0.077,
175
            'rates' => [
176
                'high' => 0.077,
177
                'low' => 0.025,
178
            ],
179
        ],
180
        'TR' => [ // Turkey
181
            'rate' => 0.18,
182
        ],
183
        'NO' => [ // Norway
184
            'rate' => 0.25,
185
        ],
186
    ];
187
188
    /**
189
     * All possible postal code exceptions.
190
     *
191
     * @var array
192
     */
193
    protected $postalCodeExceptions = [
194
        'AT' => [
195
            [
196
                'postalCode' => '/^6691$/',
197
                'code'       => 'AT',
198
                'name'       => 'Jungholz',
199
            ],
200
            [
201
                'postalCode' => '/^699[123]$/',
202
                'city'       => '/\bmittelberg\b/i',
203
                'code'       => 'AT',
204
                'name'       => 'Mittelberg',
205
            ],
206
        ],
207
        'CH' => [
208
            [
209
                'postalCode' => '/^8238$/',
210
                'code'       => 'DE',
211
                'name'       => 'Büsingen am Hochrhein',
212
            ],
213
            [
214
                'postalCode' => '/^6911$/',
215
                'code'       => 'IT',
216
                'name'       => "Campione d'Italia",
217
            ],
218
            // The Italian city of Domodossola has a Swiss post office also
219
            [
220
                'postalCode' => '/^3907$/',
221
                'code'       => 'IT',
222
            ],
223
        ],
224
        'DE' => [
225
            [
226
                'postalCode' => '/^87491$/',
227
                'code'       => 'AT',
228
                'name'       => 'Jungholz',
229
            ],
230
            [
231
                'postalCode' => '/^8756[789]$/',
232
                'city'       => '/\bmittelberg\b/i',
233
                'code'       => 'AT',
234
                'name'       => 'Mittelberg',
235
            ],
236
            [
237
                'postalCode' => '/^78266$/',
238
                'code'       => 'DE',
239
                'name'       => 'Büsingen am Hochrhein',
240
            ],
241
            [
242
                'postalCode' => '/^27498$/',
243
                'code'       => 'DE',
244
                'name'       => 'Heligoland',
245
            ],
246
        ],
247
        'ES' => [
248
            [
249
                'postalCode' => '/^(5100[1-5]|5107[0-1]|51081)$/',
250
                'code'       => 'ES',
251
                'name'       => 'Ceuta',
252
            ],
253
            [
254
                'postalCode' => '/^(5200[0-6]|5207[0-1]|52081)$/',
255
                'code'       => 'ES',
256
                'name'       => 'Melilla',
257
            ],
258
            [
259
                'postalCode' => '/^(35\d{3}|38\d{3})$/',
260
                'code'       => 'ES',
261
                'name'       => 'Canary Islands',
262
            ],
263
        ],
264
        'FR' => [
265
            [
266
                'postalCode' => '/^971\d{2,}$/',
267
                'code'       => 'FR',
268
                'name'       => 'Guadeloupe',
269
            ],
270
            [
271
                'postalCode' => '/^972\d{2,}$/',
272
                'code'       => 'FR',
273
                'name'       => 'Martinique',
274
            ],
275
            [
276
                'postalCode' => '/^973\d{2,}$/',
277
                'code'       => 'FR',
278
                'name'       => 'Guyane',
279
            ],
280
            [
281
                'postalCode' => '/^974\d{2,}$/',
282
                'code'       => 'FR',
283
                'name'       => 'Reunion',
284
            ],
285
            [
286
                'postalCode' => '/^976\d{2,}$/',
287
                'code'       => 'FR',
288
                'name'       => 'Mayotte',
289
            ],
290
        ],
291
        'GB' => [
292
            // Akrotiri
293
            [
294
                'postalCode' => '/^BFPO57|BF12AT$/',
295
                'code'       => 'CY',
296
            ],
297
            // Dhekelia
298
            [
299
                'postalCode' => '/^BFPO58|BF12AU$/',
300
                'code'       => 'CY',
301
            ],
302
        ],
303
        'GR' => [
304
            [
305
                'postalCode' => '/^63086$/',
306
                'code'       => 'GR',
307
                'name'       => 'Mount Athos',
308
            ],
309
        ],
310
        'IT' => [
311
            [
312
                'postalCode' => '/^22060$/',
313
                'city'       => '/\bcampione\b/i',
314
                'code'       => 'IT',
315
                'name'       => "Campione d'Italia",
316
            ],
317
            [
318
                'postalCode' => '/^23030$/',
319
                'city'       => '/\blivigno\b/i',
320
                'code'       => 'IT',
321
                'name'       => 'Livigno',
322
            ],
323
        ],
324
        'PT' => [
325
            [
326
                'postalCode' => '/^9[0-4]\d{2,}$/',
327
                'code'       => 'PT',
328
                'name'       => 'Madeira',
329
            ],
330
            [
331
                'postalCode' => '/^9[5-9]\d{2,}$/',
332
                'code'       => 'PT',
333
                'name'       => 'Azores',
334
            ],
335
        ],
336
    ];
337
338
    /**
339
     * @var float
340
     */
341
    protected $netPrice = 0.0;
342
343
    /**
344
     * @var string
345
     */
346
    protected $countryCode;
347
348
    /**
349
     * @var string
350
     */
351
    protected $postalCode;
352
353
    /**
354
     * @var Repository
355
     */
356
    protected $config;
357
358
    /**
359
     * @var float
360
     */
361
    protected $taxValue = 0;
362
363
    /**
364
     * @var float
365
     */
366
    protected $taxRate = 0;
367
368
    /**
369
     * The calculate net + tax value.
370
     *
371
     * @var float
372
     */
373
    protected $value = 0;
374
375
    /**
376
     * @var bool
377
     */
378
    protected $company = false;
379
380
    /**
381
     * @var string
382
     */
383
    protected $businessCountryCode;
384
385
    /**
386
     * @param \Illuminate\Contracts\Config\Repository
387
     */
388
    public function __construct($config = null)
389
    {
390
        $this->config = $config;
391
392
        $businessCountryKey = 'vat_calculator.business_country_code';
393
        if (isset($this->config) && $this->config->has($businessCountryKey)) {
394
            $this->setBusinessCountryCode($this->config->get($businessCountryKey, ''));
395
        }
396
    }
397
398
    /**
399
     * Finds the client IP address.
400
     *
401
     * @return mixed
402
     */
403
    private function getClientIP()
404
    {
405
        if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR']) {
406
            $clientIpAddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
407
        } elseif (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR']) {
408
            $clientIpAddress = $_SERVER['REMOTE_ADDR'];
409
        } else {
410
            $clientIpAddress = '';
411
        }
412
413
        return $clientIpAddress;
414
    }
415
416
    /**
417
     * Returns the ISO 3166-1 alpha-2 two letter
418
     * country code for the client IP. If the
419
     * IP can't be resolved it returns false.
420
     *
421
     * @return bool|string
422
     */
423
    public function getIPBasedCountry()
424
    {
425
        if (! $ip = $this->getClientIP()) {
426
            return false;
427
        }
428
429
        $url = self::GEOCODE_SERVICE_URL.$ip;
430
        $result = file_get_contents($url);
431
432
        if ($result != false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $result of type string to the boolean false. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
433
            $json = json_decode($result);
434
435
            return $json->countryCode;
436
        }
437
438
        return false;
439
    }
440
441
    /**
442
     * Determines if you need to collect VAT for the given country code.
443
     *
444
     * @param $countryCode
445
     *
446
     * @return bool
447
     */
448
    public function shouldCollectVAT($countryCode)
449
    {
450
        $taxKey = 'vat_calculator.rules.'.strtoupper($countryCode);
451
452
        return isset($this->taxRules[strtoupper($countryCode)]) || (isset($this->config) && $this->config->has($taxKey));
453
    }
454
455
    /**
456
     * Calculate the VAT based on the net price, country code and indication if the
457
     * customer is a company or not.
458
     *
459
     * @param int|float   $netPrice    The net price to use for the calculation
460
     * @param null|string $countryCode The country code to use for the rate lookup
461
     * @param null|string $postalCode  The postal code to use for the rate exception lookup
462
     * @param null|bool   $company
463
     * @param null|string $type        The type can be low or high
464
     *
465
     * @return float
466
     */
467
    public function calculate($netPrice, $countryCode = null, $postalCode = null, $company = null, $type = null)
468
    {
469
        if ($countryCode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $countryCode of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
470
            $this->setCountryCode($countryCode);
471
        }
472
        if ($postalCode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $postalCode of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
473
            $this->setPostalCode($postalCode);
474
        }
475
        if (!is_null($company) && $company !== $this->isCompany()) {
476
            $this->setCompany($company);
477
        }
478
        $this->netPrice = floatval($netPrice);
479
        $this->taxRate = $this->getTaxRateForLocation($this->getCountryCode(), $this->getPostalCode(), $this->isCompany(), $type);
480
        $this->taxValue = $this->taxRate * $this->netPrice;
481
        $this->value = $this->netPrice + $this->taxValue;
482
483
        return $this->value;
484
    }
485
486
    /**
487
     * Calculate the net price on the gross price, country code and indication if the
488
     * customer is a company or not.
489
     *
490
     * @param int|float   $gross       The gross price to use for the calculation
491
     * @param null|string $countryCode The country code to use for the rate lookup
492
     * @param null|string $postalCode  The postal code to use for the rate exception lookup
493
     * @param null|bool   $company
494
     * @param null|string $type        The type can be low or high
495
     *
496
     * @return float
497
     */
498
    public function calculateNet($gross, $countryCode = null, $postalCode = null, $company = null, $type = null)
499
    {
500
        if ($countryCode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $countryCode of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
501
            $this->setCountryCode($countryCode);
502
        }
503
        if ($postalCode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $postalCode of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
504
            $this->setPostalCode($postalCode);
505
        }
506
        if (!is_null($company) && $company !== $this->isCompany()) {
507
            $this->setCompany($company);
508
        }
509
510
        $this->value = floatval($gross);
511
        $this->taxRate = $this->getTaxRateForLocation($this->getCountryCode(), $this->getPostalCode(), $this->isCompany(), $type);
512
        $this->taxValue = $this->taxRate > 0 ? $this->value / (1 + $this->taxRate) * $this->taxRate : 0;
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->taxRate > 0 ? $th...e) * $this->taxRate : 0 can also be of type integer. However, the property $taxValue is declared as type double. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
513
        $this->netPrice = $this->value - $this->taxValue;
514
515
        return $this->netPrice;
516
    }
517
518
    /**
519
     * @return float
520
     */
521
    public function getNetPrice()
522
    {
523
        return $this->netPrice;
524
    }
525
526
    /**
527
     * @return string
528
     */
529
    public function getCountryCode()
530
    {
531
        return strtoupper($this->countryCode);
532
    }
533
534
    /**
535
     * @param mixed $countryCode
536
     */
537
    public function setCountryCode($countryCode)
538
    {
539
        $this->countryCode = $countryCode;
540
    }
541
542
    /**
543
     * @return string
544
     */
545
    public function getPostalCode()
546
    {
547
        return $this->postalCode;
548
    }
549
550
    /**
551
     * @param mixed $postalCode
552
     */
553
    public function setPostalCode($postalCode)
554
    {
555
        $this->postalCode = $postalCode;
556
    }
557
558
    /**
559
     * @return float
560
     */
561
    public function getTaxRate()
562
    {
563
        return $this->taxRate;
564
    }
565
566
    /**
567
     * @return bool
568
     */
569
    public function isCompany()
570
    {
571
        return $this->company;
572
    }
573
574
    /**
575
     * @param bool $company
576
     */
577
    public function setCompany($company)
578
    {
579
        $this->company = $company;
580
    }
581
582
    /**
583
     * @param string $businessCountryCode
584
     */
585
    public function setBusinessCountryCode($businessCountryCode)
586
    {
587
        $this->businessCountryCode = $businessCountryCode;
588
    }
589
590
    /**
591
     * Returns the tax rate for the given country code.
592
     * This method is used to allow backwards compatibility.
593
     *
594
     * @param $countryCode
595
     * @param bool $company
596
     * @param string $type
597
     *
598
     * @return float
599
     */
600
    public function getTaxRateForCountry($countryCode, $company = false, $type = null)
601
    {
602
        return $this->getTaxRateForLocation($countryCode, null, $company, $type);
603
    }
604
605
    /**
606
     * Returns the tax rate for the given country code.
607
     * If a postal code is provided, it will try to lookup the different
608
     * postal code exceptions that are possible.
609
     *
610
     * @param string      $countryCode
611
     * @param string|null $postalCode
612
     * @param bool|false  $company
613
     * @param string|null $type
614
     *
615
     * @return float
616
     */
617
    public function getTaxRateForLocation($countryCode, $postalCode = null, $company = false, $type = null)
618
    {
619
        if ($company && strtoupper($countryCode) !== strtoupper($this->businessCountryCode)) {
620
            return 0;
621
        }
622
        $taxKey = 'vat_calculator.rules.'.strtoupper($countryCode);
623
        if (isset($this->config) && $this->config->has($taxKey)) {
624
            return $this->config->get($taxKey, 0);
625
        }
626
627
        if (isset($this->postalCodeExceptions[$countryCode]) && $postalCode !== null) {
628
            foreach ($this->postalCodeExceptions[$countryCode] as $postalCodeException) {
629
                if (!preg_match($postalCodeException['postalCode'], $postalCode)) {
630
                    continue;
631
                }
632
                if (isset($postalCodeException['name'])) {
633
                    return $this->taxRules[$postalCodeException['code']]['exceptions'][$postalCodeException['name']];
634
                }
635
636
                return $this->taxRules[$postalCodeException['code']]['rate'];
637
            }
638
        }
639
640
        if ($type !== null) {
641
            return isset($this->taxRules[strtoupper($countryCode)]['rates'][$type]) ? $this->taxRules[strtoupper($countryCode)]['rates'][$type] : 0;
642
        }
643
644
        return isset($this->taxRules[strtoupper($countryCode)]['rate']) ? $this->taxRules[strtoupper($countryCode)]['rate'] : 0;
645
    }
646
647
    /**
648
     * @return float
649
     */
650
    public function getTaxValue()
651
    {
652
        return $this->taxValue;
653
    }
654
655
    /**
656
     * @param $vatNumber
657
     *
658
     * @throws VATCheckUnavailableException
659
     *
660
     * @return bool
661
     */
662
    public function isValidVATNumber($vatNumber)
663
    {
664
        $details = self::getVATDetails($vatNumber);
665
666
        if ($details) {
667
            return $details->valid;
668
        } else {
669
            return false;
670
        }
671
    }
672
673
    /**
674
     * @param $vatNumber
675
     *
676
     * @throws VATCheckUnavailableException
677
     *
678
     * @return object|false
679
     */
680
    public function getVATDetails($vatNumber)
681
    {
682
        $vatNumber = str_replace([' ', "\xC2\xA0", "\xA0", '-', '.', ','], '', trim($vatNumber));
683
        $countryCode = substr($vatNumber, 0, 2);
684
        $vatNumber = substr($vatNumber, 2);
685
        $this->initSoapClient();
686
        $client = $this->soapClient;
687
        if ($client) {
688
            try {
689
                $result = $client->checkVat([
690
                    'countryCode' => $countryCode,
691
                    'vatNumber' => $vatNumber,
692
                ]);
693
                return $result;
694
            } catch (SoapFault $e) {
695 View Code Duplication
                if (isset($this->config) && $this->config->get('vat_calculator.forward_soap_faults')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
696
                    throw new VATCheckUnavailableException($e->getMessage(), $e->getCode(), $e->getPrevious());
697
                }
698
699
                return false;
700
            }
701
        }
702
        throw new VATCheckUnavailableException('The VAT check service is currently unavailable. Please try again later.');
703
    }
704
705
    /**
706
     * @throws VATCheckUnavailableException
707
     *
708
     * @return void
709
     */
710
    public function initSoapClient()
711
    {
712
        if (is_object($this->soapClient) || $this->soapClient === false) {
713
            return;
714
        }
715
        try {
716
            $this->soapClient = new SoapClient(self::VAT_SERVICE_URL);
717
        } catch (SoapFault $e) {
718 View Code Duplication
            if (isset($this->config) && $this->config->get('vat_calculator.forward_soap_faults')) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
719
                throw new VATCheckUnavailableException($e->getMessage(), $e->getCode(), $e->getPrevious());
720
            }
721
722
            $this->soapClient = false;
723
        }
724
    }
725
726
    /**
727
     * @param SoapClient $soapClient
728
     */
729
    public function setSoapClient($soapClient)
730
    {
731
        $this->soapClient = $soapClient;
732
    }
733
}
734