Completed
Push — master ( 812c3a...4ec448 )
by Marcel
10:44
created

VatCalculator::shouldCollectVAT()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 5
rs 9.4285
cc 3
eloc 3
nc 3
nop 1
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 = 'http://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 = 'http://ip2c.org/';
21
22
    protected $soapClient;
23
24
    /**
25
     * All available tax rules.
26
     *
27
     * @var array
28
     */
29
    protected $taxRules = [
30
        'AT' => 0.20,
31
        'BE' => 0.21,
32
        'BG' => 0.20,
33
        'CY' => 0.19,
34
        'CZ' => 0.21,
35
        'DE' => 0.19,
36
        'DK' => 0.25,
37
        'EE' => 0.20,
38
        'EL' => 0.23,
39
        'ES' => 0.21,
40
        'FI' => 0.24,
41
        'FR' => 0.20,
42
        'GB' => 0.20,
43
        'GR' => 0.23,
44
        'IE' => 0.23,
45
        'IT' => 0.22,
46
        'HR' => 0.25,
47
        'HU' => 0.27,
48
        'LV' => 0.21,
49
        'LT' => 0.21,
50
        'LU' => 0.17,
51
        'MT' => 0.18,
52
        'NL' => 0.21,
53
        'NO' => 0.25,
54
        'PL' => 0.23,
55
        'PT' => 0.23,
56
        'RO' => 0.20,
57
        'SE' => 0.25,
58
        'SK' => 0.20,
59
        'SI' => 0.22,
60
    ];
61
62
    /**
63
     * @var float
64
     */
65
    protected $netPrice = 0.0;
66
67
    /**
68
     * @var string
69
     */
70
    protected $countryCode;
71
72
    /**
73
     * @var Repository
74
     */
75
    protected $config;
76
77
    /**
78
     * @var float
79
     */
80
    protected $taxValue = 0;
81
82
    /**
83
     * @var float
84
     */
85
    protected $taxRate = 0;
86
87
    /**
88
     * The calculate net + tax value.
89
     *
90
     * @var float
91
     */
92
    protected $value = 0;
93
94
    /**
95
     * @var bool
96
     */
97
    protected $company = false;
98
99
    /**
100
     * @var string
101
     */
102
    protected $businessCountryCode;
103
104
    /**
105
     * @param \Illuminate\Contracts\Config\Repository
106
     */
107
    public function __construct($config = null)
108
    {
109
        $this->config = $config;
110
111
        $businessCountryKey = 'vat_calculator.business_country_code';
112
        if (isset($this->config) && $this->config->has($businessCountryKey)) {
113
            $this->setBusinessCountryCode($this->config->get($businessCountryKey, ''));
114
        }
115
116
        try {
117
            $this->soapClient = new SoapClient(self::VAT_SERVICE_URL);
118
        } catch (SoapFault $e) {
119
            $this->soapClient = false;
120
        }
121
    }
122
123
    /**
124
     * Finds the client IP address.
125
     *
126
     * @return mixed
127
     */
128
    private function getClientIP()
0 ignored issues
show
Coding Style introduced by
getClientIP uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

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

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
129
    {
130
        if (isset($_SERVER[ 'HTTP_X_FORWARDED_FOR' ]) && $_SERVER[ 'HTTP_X_FORWARDED_FOR' ]) {
131
            $clientIpAddress = $_SERVER[ 'HTTP_X_FORWARDED_FOR' ];
132
        } elseif (isset($_SERVER[ 'REMOTE_ADDR' ]) && $_SERVER[ 'REMOTE_ADDR' ]) {
133
            $clientIpAddress = $_SERVER[ 'REMOTE_ADDR' ];
134
        } else {
135
            $clientIpAddress = '';
136
        }
137
138
        return $clientIpAddress;
139
    }
140
141
    /**
142
     * Returns the ISO 3166-1 alpha-2 two letter
143
     * country code for the client IP. If the
144
     * IP can't be resolved it returns false.
145
     *
146
     * @return bool|string
147
     */
148
    public function getIPBasedCountry()
149
    {
150
        $ip = $this->getClientIP();
151
        $url = self::GEOCODE_SERVICE_URL.$ip;
152
        $result = file_get_contents($url);
153
        switch ($result[ 0 ]) {
154
            case '1':
155
                $data = explode(';', $result);
156
157
                return $data[ 1 ];
158
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
159
            default:
160
                return false;
161
        }
162
    }
163
164
    /**
165
     * Determines if you need to collect VAT for the given country code
166
     *
167
     * @param $countryCode
168
     * @return bool
169
     */
170
    public function shouldCollectVAT($countryCode)
171
    {
172
        $taxKey = 'vat_calculator.rules.'.strtoupper($countryCode);
173
        return (isset($this->taxRules[ strtoupper($countryCode) ]) || (isset($this->config) && $this->config->has($taxKey)));
174
    }
175
176
    /**
177
     * Calculate the VAT based on the net price, country code and indication if the
178
     * customer is a company or not.
179
     *
180
     * @param int|float   $netPrice    The net price to use for the calculation
181
     * @param null|string $countryCode The country code to use for the rate lookup
182
     * @param null|bool   $company
183
     *
184
     * @return float
185
     */
186
    public function calculate($netPrice, $countryCode = null, $company = null)
187
    {
188
        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...
189
            $this->setCountryCode($countryCode);
190
        }
191
        if (!is_null($company) && $company !== $this->isCompany()) {
192
            $this->setCompany($company);
193
        }
194
        $this->netPrice = floatval($netPrice);
195
        $this->taxRate = $this->getTaxRateForCountry($this->getCountryCode(), $this->isCompany());
196
        $this->taxValue = $this->taxRate * $this->netPrice;
197
        $this->value = $this->netPrice + $this->taxValue;
198
199
        return $this->value;
200
    }
201
202
    /**
203
     * @return float
204
     */
205
    public function getNetPrice()
206
    {
207
        return $this->netPrice;
208
    }
209
210
    /**
211
     * @return string
212
     */
213
    public function getCountryCode()
214
    {
215
        return strtoupper($this->countryCode);
216
    }
217
218
    /**
219
     * @param mixed $countryCode
220
     */
221
    public function setCountryCode($countryCode)
222
    {
223
        $this->countryCode = $countryCode;
224
    }
225
226
    /**
227
     * @return float
228
     */
229
    public function getTaxRate()
230
    {
231
        return $this->taxRate;
232
    }
233
234
    /**
235
     * @return bool
236
     */
237
    public function isCompany()
238
    {
239
        return $this->company;
240
    }
241
242
    /**
243
     * @param bool $company
244
     */
245
    public function setCompany($company)
246
    {
247
        $this->company = $company;
248
    }
249
250
    /**
251
     * @param string $businessCountryCode
252
     */
253
    public function setBusinessCountryCode($businessCountryCode)
254
    {
255
        $this->businessCountryCode = $businessCountryCode;
256
    }
257
258
    /**
259
     * Returns the tax rate for the given country.
260
     *
261
     * @param string     $countryCode
262
     * @param bool|false $company
263
     *
264
     * @return float
265
     */
266
    public function getTaxRateForCountry($countryCode, $company = false)
267
    {
268
        if ($company && strtoupper($countryCode) !== strtoupper($this->businessCountryCode)) {
269
            return 0;
270
        }
271
        $taxKey = 'vat_calculator.rules.'.strtoupper($countryCode);
272
        if (isset($this->config) && $this->config->has($taxKey)) {
273
            return $this->config->get($taxKey, 0);
274
        }
275
276
        return isset($this->taxRules[ strtoupper($countryCode) ]) ? $this->taxRules[ strtoupper($countryCode) ] : 0;
277
    }
278
279
    /**
280
     * @return float
281
     */
282
    public function getTaxValue()
283
    {
284
        return $this->taxValue;
285
    }
286
287
    /**
288
     * @param $vatNumber
289
     *
290
     * @throws VATCheckUnavailableException
291
     *
292
     * @return bool
293
     */
294
    public function isValidVATNumber($vatNumber)
295
    {
296
        $vatNumber = str_replace([' ', '-', '.', ','], '', trim($vatNumber));
297
        $countryCode = substr($vatNumber, 0, 2);
298
        $vatNumber = substr($vatNumber, 2);
299
300
        $client = $this->soapClient;
301
        if ($client) {
302
            try {
303
                $result = $client->checkVat([
304
                    'countryCode' => $countryCode,
305
                    'vatNumber'   => $vatNumber,
306
                ]);
307
308
                return $result->valid;
309
            } catch (SoapFault $e) {
310
                return false;
311
            }
312
        }
313
        throw new VATCheckUnavailableException('The VAT check service is currently unavailable. Please try again later.');
314
    }
315
316
    /**
317
     * @param SoapClient $soapClient
318
     */
319
    public function setSoapClient($soapClient)
320
    {
321
        $this->soapClient = $soapClient;
322
    }
323
}
324