Completed
Push — master ( 5114b2...409a55 )
by Marcel
08:59
created

VatCalculator::setBusinessCountryCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
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
9
class VatCalculator
10
{
11
    /**
12
     * VAT Service check URL provided by the EU.
13
     */
14
    const VAT_SERVICE_URL = 'http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl';
15
16
    /**
17
     * We're using the free ip2c service to lookup IP 2 country.
18
     */
19
    const GEOCODE_SERVICE_URL = 'http://ip2c.org/';
20
21
    protected $soapClient;
22
23
    /**
24
     * All available tax rules.
25
     *
26
     * @var array
27
     */
28
    protected $taxRules = [
29
        'AT' => 0.20,
30
        'BE' => 0.21,
31
        'BG' => 0.20,
32
        'CY' => 0.19,
33
        'CZ' => 0.21,
34
        'DE' => 0.19,
35
        'DK' => 0.25,
36
        'EE' => 0.20,
37
        'EL' => 0.23,
38
        'ES' => 0.21,
39
        'FI' => 0.24,
40
        'FR' => 0.20,
41
        'GB' => 0.20,
42
        'GR' => 0.23,
43
        'IE' => 0.23,
44
        'IT' => 0.22,
45
        'HR' => 0.25,
46
        'HU' => 0.27,
47
        'LV' => 0.21,
48
        'LT' => 0.21,
49
        'LU' => 0.17,
50
        'MT' => 0.18,
51
        'NL' => 0.21,
52
        'NO' => 0.25,
53
        'PL' => 0.23,
54
        'PT' => 0.23,
55
        'RO' => 0.20,
56
        'SE' => 0.25,
57
        'SK' => 0.20,
58
        'SI' => 0.22,
59
    ];
60
61
    /**
62
     * @var float
63
     */
64
    protected $netPrice = 0.0;
65
66
    /**
67
     * @var string
68
     */
69
    protected $countryCode;
70
71
    /**
72
     * @var Repository
73
     */
74
    protected $config;
75
76
    /**
77
     * @var float
78
     */
79
    protected $taxValue = 0;
80
81
    /**
82
     * @var float
83
     */
84
    protected $taxRate = 0;
85
86
    /**
87
     * The calculate net + tax value.
88
     *
89
     * @var float
90
     */
91
    protected $value = 0;
92
93
    /**
94
     * @var bool
95
     */
96
    protected $company = false;
97
98
    /**
99
     * @var string
100
     */
101
    protected $businessCountryCode;
102
103
    /**
104
     * @param \Illuminate\Contracts\Config\Repository
105
     */
106
    public function __construct($config = null)
107
    {
108
        $this->config = $config;
109
110
        $businessCountryKey = 'vat_calculator.business_country_code';
111
        if (isset($this->config) && $this->config->has($businessCountryKey)) {
112
            $this->setBusinessCountryCode($this->config->get($businessCountryKey, ''));
113
        }
114
115
        $this->soapClient = new SoapClient(self::VAT_SERVICE_URL);
116
    }
117
118
    /**
119
     * Finds the client IP address.
120
     *
121
     * @return mixed
122
     */
123
    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...
124
    {
125
        if (isset($_SERVER[ 'HTTP_X_FORWARDED_FOR' ]) && $_SERVER[ 'HTTP_X_FORWARDED_FOR' ]) {
126
            $clientIpAddress = $_SERVER[ 'HTTP_X_FORWARDED_FOR' ];
127
        } elseif (isset($_SERVER[ 'REMOTE_ADDR' ]) && $_SERVER[ 'REMOTE_ADDR' ]) {
128
            $clientIpAddress = $_SERVER[ 'REMOTE_ADDR' ];
129
        } else {
130
            $clientIpAddress = '';
131
        }
132
133
        return $clientIpAddress;
134
    }
135
136
    /**
137
     * Returns the ISO 3166-1 alpha-2 two letter
138
     * country code for the client IP. If the
139
     * IP can't be resolved it returns false.
140
     *
141
     * @return bool|string
142
     */
143
    public function getIPBasedCountry()
144
    {
145
        $ip = $this->getClientIP();
146
        $url = self::GEOCODE_SERVICE_URL.$ip;
147
        $result = file_get_contents($url);
148
        switch ($result[ 0 ]) {
149
            case '1':
150
                $data = explode(';', $result);
151
152
                return $data[ 1 ];
153
                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...
154
            default:
155
                return false;
156
        }
157
    }
158
159
    /**
160
     * Calculate the VAT based on the net price, country code and indication if the
161
     * customer is a company or not.
162
     *
163
     * @param int|float   $netPrice    The net price to use for the calculation
164
     * @param null|string $countryCode The country code to use for the rate lookup
165
     * @param null|bool   $company
166
     *
167
     * @return float
168
     */
169
    public function calculate($netPrice, $countryCode = null, $company = null)
170
    {
171
        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...
172
            $this->setCountryCode($countryCode);
173
        }
174
        if (!is_null($company) && $company !== $this->isCompany()) {
175
            $this->setCompany($company);
176
        }
177
        $this->netPrice = floatval($netPrice);
178
        $this->taxRate = $this->getTaxRateForCountry($this->getCountryCode(), $this->isCompany());
179
        $this->taxValue = $this->taxRate * $this->netPrice;
180
        $this->value = $this->netPrice + $this->taxValue;
181
182
        return $this->value;
183
    }
184
185
    /**
186
     * @return float
187
     */
188
    public function getNetPrice()
189
    {
190
        return $this->netPrice;
191
    }
192
193
    /**
194
     * @return string
195
     */
196
    public function getCountryCode()
197
    {
198
        return strtoupper($this->countryCode);
199
    }
200
201
    /**
202
     * @param mixed $countryCode
203
     */
204
    public function setCountryCode($countryCode)
205
    {
206
        $this->countryCode = $countryCode;
207
    }
208
209
    /**
210
     * @return float
211
     */
212
    public function getTaxRate()
213
    {
214
        return $this->taxRate;
215
    }
216
217
    /**
218
     * @return bool
219
     */
220
    public function isCompany()
221
    {
222
        return $this->company;
223
    }
224
225
    /**
226
     * @param bool $company
227
     */
228
    public function setCompany($company)
229
    {
230
        $this->company = $company;
231
    }
232
233
    /**
234
     * @param string $businessCountryCode
235
     */
236
    public function setBusinessCountryCode($businessCountryCode)
237
    {
238
        $this->businessCountryCode = $businessCountryCode;
239
    }
240
241
    /**
242
     * Returns the tax rate for the given country.
243
     *
244
     * @param string     $countryCode
245
     * @param bool|false $company
246
     *
247
     * @return float
248
     */
249
    public function getTaxRateForCountry($countryCode, $company = false)
250
    {
251
        if ($company && strtoupper($countryCode) !== strtoupper($this->businessCountryCode)) {
252
            return 0;
253
        }
254
        $taxKey = 'vat_calculator.rules.'.strtoupper($countryCode);
255
        if (isset($this->config) && $this->config->has($taxKey)) {
256
            return $this->config->get($taxKey, 0);
257
        }
258
259
        return isset($this->taxRules[ strtoupper($countryCode) ]) ? $this->taxRules[ strtoupper($countryCode) ] : 0;
260
    }
261
262
    /**
263
     * @return float
264
     */
265
    public function getTaxValue()
266
    {
267
        return $this->taxValue;
268
    }
269
270
    /**
271
     * @param $vatNumber
272
     *
273
     * @throws VATCheckUnavailableException
274
     *
275
     * @return bool
276
     */
277
    public function isValidVATNumber($vatNumber)
278
    {
279
        $vatNumber = str_replace([' ', '-', '.', ','], '', trim($vatNumber));
280
        $countryCode = substr($vatNumber, 0, 2);
281
        $vatNumber = substr($vatNumber, 2);
282
283
        $client = $this->soapClient;
284
        if ($client) {
285
            try {
286
                $result = $client->checkVat([
287
                    'countryCode' => $countryCode,
288
                    'vatNumber'   => $vatNumber,
289
                ]);
290
291
                return $result->valid;
292
            } catch (\SoapFault $e) {
293
                return false;
294
            }
295
        }
296
        throw new VATCheckUnavailableException('The VAT check service is currently unavailable. Please try again later.');
297
    }
298
299
    /**
300
     * @param SoapClient $soapClient
301
     */
302
    public function setSoapClient($soapClient)
303
    {
304
        $this->soapClient = $soapClient;
305
    }
306
}
307