Completed
Push — wip ( 681ec1 )
by z38
02:37
created

QRCode::parseAddress()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4.0378

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 13
cts 15
cp 0.8667
rs 8.9197
c 0
b 0
f 0
cc 4
eloc 15
nc 4
nop 1
crap 4.0378
1
<?php
2
3
namespace Z38\SwissPayment;
4
5
use InvalidArgumentException;
6
7
/**
8
 * QRCode contains the data of a Swiss QR Code.
9
 */
10
class QRCode
11
{
12
    /**
13
     * @var IBAN
14
     */
15
    protected $creditorAccount;
16
17
    /**
18
     * @var string
19
     */
20
    protected $creditorName;
21
22
    /**
23
     * @var StructuredPostalAddress
24
     */
25
    protected $creditorAddress;
26
27
    /**
28
     * @var string|null
29
     */
30
    protected $ultimateCreditorName;
31
32
    /**
33
     * @var StructuredPostalAddress|null
34
     */
35
    protected $ultimateCreditorAddress;
36
37
    /**
38
     * @var string
39
     */
40
    protected $currency;
41
42
    /**
43
     * @var Money\Money|null
44
     */
45
    protected $amount;
46
47
    /**
48
     * @var string|null
49
     */
50
    protected $dueDate;
51
52
    /**
53
     * @var string|null
54
     */
55
    protected $ultimateDebtorName;
56
57
    /**
58
     * @var StructuredPostalAddress|null
59
     */
60
    protected $ultimateDebtorAddress;
61
62
    /**
63
     * @var QRReference|CreditorReference|null
64
     */
65
    protected $reference;
66
67
    /**
68
     * @var string|null
69
     */
70
    protected $unstructuredMessage;
71
72
    /**
73
     * @var array
74
     */
75
    protected $alternativeSchemes;
76
77
    /**
78
     * Constructor
79
     *
80
     * @param string $code
81
     *
82
     * @throws \InvalidArgumentException When the QR code is malformed or otherwise invalid.
83
     */
84 5
    public function __construct($code)
85
    {
86 5
        $elements = explode("\r\n", $code);
87 5
        if (count($elements) < 28) {
88
            throw new InvalidArgumentException('QR code is malformed.');
89
        }
90 5
        if ($elements[0] !== 'SPC' || $elements[1] !== '0100' || $elements[2] !== '1') {
91
            throw new InvalidArgumentException('Unsupported version.');
92
        }
93
94 5
        $this->creditorAccount = new IBAN($elements[3]);
95 5
        if (!in_array($this->creditorAccount->getCountry(), ['CH', 'LI'])) {
96
            throw new \InvalidArgumentException('IBAN must be from Switzerland or Lichtenstein.');
97
        }
98
99 5
        list($this->creditorName, $this->creditorAddress) = $this->parseAddress(array_slice($elements, 4));
100 5
        if ($this->creditorName === null) {
101
            throw new InvalidArgumentException('Creditor is invalid.');
102
        }
103
104 5
        list($this->ultimateCreditorName, $this->ultimateCreditorAddress) = $this->parseAddress(array_slice($elements, 10));
105
106 5
        $this->currency = $elements[17];
107 5
        if ($this->currency !== 'CHF' && $this->currency !== 'EUR') {
108
            throw new InvalidArgumentException('Unsupported currency.');
109
        }
110
111 5
        if ($elements[16] !== '') {
112 4
            if (!preg_match('/^[0-9, ]+\.[0-9]{2}$/', $elements[16])) {
113
                throw new InvalidArgumentException('Amount is invalid.');
114
            }
115
            //$cents = intval(str_replace($elements[16], '.', ''));
116 4
            $cents = intval(preg_replace('/[^0-9]+/', '', $elements[16]));
117 4
            if ($this->currency === 'CHF') {
118 4
                $this->amount = new Money\CHF($cents);
119 4
            } else {
120
                $this->amount = new Money\EUR($cents);
121
            }
122 4
        }
123
124 5
        if ($elements[18] !== '') {
125 4
            if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $elements[18])) {
126
                throw new InvalidArgumentException('Due date is invalid.');
127
            }
128 4
            $this->dueDate = $elements[18];
129 4
        }
130
131 5
        list($this->ultimateDebtorName, $this->ultimateDebtorAddress) = $this->parseAddress(array_slice($elements, 19));
132
133 5
        switch ($elements[25]) {
134 5
            case 'QRR':
135 3
                $this->reference = new QRReference($elements[26]);
136 3
                break;
137 4
            case 'SCOR':
138 3
                $this->reference = new CreditorReference($elements[26]);
139 3
                break;
140 3
            case 'NON':
141 3
                if ($elements[26] !== '') {
142
                    throw new InvalidArgumentException('Reference number is not allowed.');
143
                }
144 3
                break;
145
            default:
146
                throw new InvalidArgumentException('Unsupported reference type.');
147 5
        }
148
149 5
        $this->unstructuredMessage = strlen($elements[27]) ? $elements[27] : null;
150
151 5
        $this->alternativeSchemes = [];
152 5
        foreach (array_slice($elements, 28) as $alternativeScheme) {
153 3
            $this->alternativeSchemes[] = $alternativeScheme;
154 5
        }
155 5
    }
156
157
    /**
158
     * Gets the IBAN of the creditor
159
     *
160
     * @return IBAN
161
     */
162 2
    public function getCreditorAccount()
163
    {
164 2
        return $this->creditorAccount;
165
    }
166
167
    /**
168
     * Gets the name of the creditor
169
     *
170
     * @return string A ISO 3166-1 alpha-2 country code
171
     */
172 2
    public function getCreditorName()
173
    {
174 2
        return $this->creditorName;
175
    }
176
177
    /**
178
     * Gets the address of the creditor
179
     *
180
     * @return StructuredPostalAddress
181
     */
182 2
    public function getCreditorAddress()
183
    {
184 2
        return $this->creditorAddress;
185
    }
186
187
    /**
188
     * Gets the name of the ultimate creditor
189
     *
190
     * @return string|null
191
     */
192 2
    public function getUltimateCreditorName()
193
    {
194 2
        return $this->ultimateCreditorName;
195
    }
196
197
    /**
198
     * Gets the address of the ultimate creditor
199
     *
200
     * @return StructuredPostalAddress|null
201
     */
202 2
    public function getUltimateCreditorAddress()
203
    {
204 2
        return $this->ultimateCreditorAddress;
205
    }
206
207
    /**
208
     * Gets the currency of the creditor's account
209
     *
210
     * @return string A ISO 4217 currency code
211
     */
212 2
    public function getCurrency()
213
    {
214 2
        return $this->currency;
215
    }
216
217
    /**
218
     * Gets the amount
219
     *
220
     * @return Money\Money|null
221
     */
222 2
    public function getAmount()
223
    {
224 2
        return $this->amount;
225
    }
226
227
    /**
228
     * Gets the due date
229
     *
230
     * @return string|null
231
     */
232
    public function getDueDate()
233
    {
234
        return $this->dueDate;
235
    }
236
237
    /**
238
     * Gets the name of the ultimate debtor
239
     *
240
     * @return string|null
241
     */
242 2
    public function getUltimateDebtorName()
243
    {
244 2
        return $this->ultimateDebtorName;
245
    }
246
247
    /**
248
     * Gets the address of the ultimate debtor
249
     *
250
     * @return StructuredPostalAddress|null
251
     */
252 2
    public function getUltimateDebtorAddress()
253
    {
254 2
        return $this->ultimateDebtorAddress;
255
    }
256
257
    /**
258
     * Gets the reference number
259
     *
260
     * @return CreditorReference|QRReference|null
261
     */
262 2
    public function getReference()
263
    {
264 2
        return $this->reference;
265
    }
266
267
    /**
268
     * Gets the additional information
269
     *
270
     * @return string|null
271
     */
272 2
    public function getUnstructuredMessage()
273
    {
274 2
        return $this->unstructuredMessage;
275
    }
276
277
    /**
278
     * Gets a list of subelements for a given identifier.
279
     *
280
     //* @param string $id The 2-char identifier
281
     * @param string $id The 3-char identifier
282
     *
283
     * @return array|null
284
     */
285 1
    public function getAlternativeScheme($id)
286
    {
287 1
        foreach ($this->alternativeSchemes as $scheme) {
288
            // if($id === substr($scheme, 0, 2) && isset($scheme[2])) {
289
            //     return explode($scheme[2], substr($scheme, 3));
290 1
            if ($id === substr($scheme, 0, 3) && isset($scheme[3])) {
291 1
                return explode($scheme[3], substr($scheme, 4));
292
            }
293 1
        }
294
295
        return null;
296
    }
297
298
    /**
299
     * Gets a list of all alternative schemes
300
     *
301
     * @return array
302
     */
303 2
    public function getAlternativeSchemes()
304
    {
305 2
        return $this->alternativeSchemes;
306
    }
307
308
    /**
309
     * Parses an address
310
     *
311
     * @param array $elements
312
     *
313
     * @returns StructuredPostalAddress|null
314
     *
315
     * @throws \InvalidArgumentException When the address is malformed.
316
     */
317 2
    protected function parseAddress(array $elements)
318
    {
319 2
        if (count($elements) < 6) {
320
            throw new InvalidArgumentException('Address is malformed.');
321
        }
322 2
        list($name, $street, $houseNumber, $postalCode, $city, $country) = $elements;
323 2
        $dependent = count(array_filter([$name, $postalCode, $city, $country], 'strlen'));
324 2
        if ($dependent === 0) {
325 2
            return [null, null];
326
        }
327 2
        if ($dependent !== 4) {
328
            throw new InvalidArgumentException('Address is incomplete.');
329
        }
330
331 2
        return [$name, new StructuredPostalAddress(
332 2
            $street,
333 2
            $houseNumber,
334 2
            $postalCode,
335 2
            $city,
336
            $country
337 2
        )];
338
    }
339
}
340