Passed
Pull Request — master (#15)
by Gaël
18:39
created

PurchaseResponse::toArray()   F

Complexity

Conditions 12
Paths 2048

Size

Total Lines 69
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 1
Metric Value
cc 12
eloc 42
c 2
b 1
f 1
nc 2048
nop 0
dl 0
loc 69
rs 2.8

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace DansMaCulotte\Monetico\Responses;
4
5
use DansMaCulotte\Monetico\Exceptions\AuthenticationException;
6
use DansMaCulotte\Monetico\Exceptions\Exception;
7
use DansMaCulotte\Monetico\Exceptions\PurchaseException;
8
use DansMaCulotte\Monetico\Resources\AuthenticationResource;
9
use DateTime;
10
11
class PurchaseResponse extends AbstractResponse
12
{
13
    /** @var string */
14
    public $eptCode;
15
16
    /** @var \DateTime */
17
    public $dateTime;
18
19
    /** @var string */
20
    public $amount;
21
22
    /** @var string */
23
    public $seal;
24
25
    /** @var string */
26
    public $cardVerificationStatus;
27
28
    /** @var string */
29
    public $cardExpirationDate;
30
31
    /** @var string */
32
    public $cardBrand;
33
34
    /** @var string */
35
    public $cardCountry;
36
37
    /** @var string */
38
    public $cardBIN;
39
40
    /** @var string */
41
    public $cardHash;
42
43
    /** @var bool */
44
    public $cardSaved = null;
45
46
    /** @var string */
47
    public $cardMask = null;
48
49
    /** @var string */
50
    public $rejectReason = null;
51
52
    /** @var string */
53
    public $authNumber;
54
55
    /** @var string */
56
    public $clientIp;
57
58
    /** @var string */
59
    public $transactionCountry;
60
61
    /** @var string */
62
    public $paymentMethod = null;
63
64
    /** @var string */
65
    public $commitmentAmount = null;
66
67
    /** @var int */
68
    public $filteredReason = null;
69
70
    /** @var string */
71
    public $filteredValue = null;
72
73
    /** @var string */
74
    public $filteredStatus = null;
75
76
    /** @var AuthenticationResource */
77
    public $authentication = null;
78
79
    /** @var string */
80
    public $authenticationHash = null;
81
82
    /** @var string */
83
    public $cardType;
84
85
    /** @var string */
86
    public $accountType;
87
88
    /** @var string */
89
    public $virtualCard;
90
91
    /** @var string */
92
    const DATETIME_FORMAT = 'd/m/Y_\a_H:i:s';
93
94
    /** @var array */
95
    const RETURN_CODES = [
96
        'payetest',
97
        'paiement',
98
        'Annulation',
99
        'paiement_pf2',
100
        'paiement_pf3',
101
        'paiement_pf4',
102
        'Annulation_pf2',
103
        'Annulation_pf3',
104
        'Annulation_pf4',
105
    ];
106
107
    /** @var array */
108
    const CARD_VERIFICATION_STATUSES = [
109
        'oui',
110
        'non',
111
    ];
112
113
    /** @var array */
114
    const CARD_BRANDS = [
115
        'AM' => 'American Express',
116
        'CB' => 'GIE CB',
117
        'MC' => 'Mastercard',
118
        'VI' => 'Visa',
119
        'na' => 'Non disponible',
120
    ];
121
122
    /** @var array  */
123
    const REJECT_REASONS = [
124
        'Appel Phonie',
125
        'Refus',
126
        'Interdit',
127
        'filtrage',
128
        'scoring',
129
        '3DSecure',
130
    ];
131
132
    /** @var array  */
133
    const PAYMENT_METHODS = [
134
        'CB',
135
        'paypal',
136
        '1euro',
137
        '3xcb',
138
        '4cb',
139
        'audiotel',
140
    ];
141
142
    /** @var array */
143
    const FILTERED_REASONS = [
144
        1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16,
145
    ];
146
147
    /**
148
     * OutputPayload constructor.
149
     *
150
     * @param array $data
151
     * @throws \Exception
152
     */
153
    public function __construct(array $data = [])
154
    {
155
        parent::__construct($data);
156
157
        $this->dateTime = DateTime::createFromFormat(self::DATETIME_FORMAT, $this->dateTime);
0 ignored issues
show
Documentation Bug introduced by
It seems like DateTime::createFromForm...ORMAT, $this->dateTime) can also be of type false. However, the property $dateTime is declared as type DateTime. 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...
Bug introduced by
$this->dateTime of type DateTime is incompatible with the type string expected by parameter $time of DateTime::createFromFormat(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

157
        $this->dateTime = DateTime::createFromFormat(self::DATETIME_FORMAT, /** @scrutinizer ignore-type */ $this->dateTime);
Loading history...
158
        if (!$this->dateTime instanceof DateTime) {
159
            throw Exception::invalidResponseDateTime();
160
        }
161
162
        if (!in_array($this->returnCode, self::RETURN_CODES, true)) {
163
            throw PurchaseException::invalidResponseReturnCode($this->returnCode);
164
        }
165
166
        if (!in_array($this->cardVerificationStatus, self::CARD_VERIFICATION_STATUSES, true)) {
167
            throw PurchaseException::invalidResponseCardVerificationStatus($this->cardVerificationStatus);
168
        }
169
170
        if (!array_key_exists($this->cardBrand, self::CARD_BRANDS)) {
171
            throw PurchaseException::invalidResponseCardBrand($this->cardBrand);
172
        }
173
174
        $this->setAuthentication($this->authenticationHash);
175
        $this->setOptions($data);
176
        $this->setErrorsOptions($data);
177
    }
178
179
    public function getRequiredKeys(): array
180
    {
181
        return [
182
            'TPE' => 'eptCode',
183
            'date' => 'dateTime',
184
            'montant' => 'amount',
185
            'reference' => 'reference',
186
            'MAC' => 'seal',
187
            'authentification' => 'authenticationHash',
188
            'texte-libre' => 'description',
189
            'code-retour' => 'returnCode',
190
            'cvx' => 'cardVerificationStatus',
191
            'vld' => 'cardExpirationDate',
192
            'brand' => 'cardBrand',
193
            'originecb' => 'cardCountry',
194
            'bincb' => 'cardBIN',
195
            'hpancb' => 'cardHash',
196
            'ipclient' => 'clientIp',
197
            'originetr' => 'transactionCountry',
198
            'usage' => 'cardType',
199
            'typecompte' => 'accountType',
200
            'ecard' => 'virtualCard',
201
        ];
202
    }
203
204
    /**
205
     * @param string $authentication
206
     * @throws AuthenticationException
207
     */
208
    private function setAuthentication(string $authentication): void
209
    {
210
        $authentication = base64_decode($authentication);
211
        $authentication = json_decode($authentication, true);
212
213
        $this->authentication = new AuthenticationResource(
214
            $authentication['protocol'],
215
            $authentication['status'],
216
            $authentication['version'],
217
            $authentication->details ?? []
218
        );
219
    }
220
221
    /**
222
     * @param array $data
223
     * @throws PurchaseException
224
     */
225
    private function setOptions(array $data): void
226
    {
227
        if (isset($data['numauto'])) {
228
            $this->authNumber = $data['numauto'];
229
        }
230
231
        if (isset($data['modepaiement'])) {
232
            $this->paymentMethod = $data['modepaiement'];
233
            if (!in_array($this->paymentMethod, self::PAYMENT_METHODS, true)) {
234
                throw PurchaseException::invalidResponsePaymentMethod($this->paymentMethod);
235
            }
236
        }
237
238
        // ToDo: Split amount and currency with ISO4217
239
        if (isset($data['montantech'])) {
240
            $this->commitmentAmount = $data['montantech'];
241
        }
242
243
        if (isset($data['cbenregistree'])) {
244
            $this->cardSaved = (bool) $data['cbenregistree'];
245
        }
246
247
        if (isset($data['cbmasquee'])) {
248
            $this->cardMask = $data['cbmasquee'];
249
        }
250
    }
251
252
    /**
253
     * @param array $data
254
     * @throws PurchaseException
255
     */
256
    private function setErrorsOptions(array $data): void
257
    {
258
        if (isset($data['filtragecause'])) {
259
            $this->filteredReason = (int) $data['filtragecause'];
260
            if (!in_array($this->filteredReason, self::FILTERED_REASONS, true)) {
261
                throw PurchaseException::invalidResponseFilteredReason($this->filteredReason);
262
            }
263
        }
264
265
        if (isset($data['motifrefus'])) {
266
            $this->rejectReason = $data['motifrefus'];
267
            if (!in_array($this->rejectReason, self::REJECT_REASONS, true)) {
268
                throw PurchaseException::invalidResponseRejectReason($this->rejectReason);
269
            }
270
        }
271
272
        if (isset($data['filtragevaleur'])) {
273
            $this->filteredValue = $data['filtragevaleur'];
274
        }
275
276
        if (isset($data['filtrage_etat'])) {
277
            $this->filteredStatus = $data['filtrage_etat'];
278
        }
279
    }
280
281
    /**
282
     * @return array
283
     */
284
    public function toArray(): array
285
    {
286
        $fields = [
287
            'authentification' => $this->authenticationHash,
288
            'bincb' => $this->cardBIN,
289
            'brand' => $this->cardBrand,
290
            'code-retour' => $this->returnCode,
291
            'cvx' => $this->cardVerificationStatus,
292
            'date' => $this->dateTime->format(self::DATETIME_FORMAT),
293
            'hpancb' => $this->cardHash,
294
            'ipclient' => $this->clientIp,
295
            'modepaiement' => $this->paymentMethod,
296
            'montant' => $this->amount,
297
            'originecb' => $this->cardCountry,
298
            'originetr' => $this->transactionCountry,
299
            'reference' => $this->reference,
300
            'texte-libre' => $this->description,
301
            'vld' => $this->cardExpirationDate,
302
            'usage' => $this->cardType,
303
            'typecompte' => $this->accountType,
304
            'ecard' => $this->virtualCard,
305
        ];
306
307
        if (isset($this->authNumber)) {
308
            $fields['numauto'] = $this->authNumber;
309
        }
310
311
        if (isset($this->rejectReason)) {
312
            $fields['motifrefus'] = $this->rejectReason;
313
        }
314
315
316
        if (isset($this->commitmentAmount)) {
317
            $fields['montantech'] = $this->commitmentAmount;
318
        }
319
320
        if (isset($this->folderNumber)) {
321
            $fields['numerodossier'] = $this->folderNumber;
322
        }
323
324
        if (isset($this->invoiceType)) {
325
            $fields['typefacture'] = $this->invoiceType;
326
        }
327
328
        if (isset($this->filteredReason)) {
329
            $fields['filtragecause'] = $this->filteredReason;
330
        }
331
332
        if (isset($this->filteredValue)) {
333
            $fields['filtragevaleur'] = $this->filteredValue;
334
        }
335
336
        if (isset($this->filteredStatus)) {
337
            $fields['filtrage_etat'] = $this->filteredStatus;
338
        }
339
340
        if (isset($this->cardSaved)) {
341
            $fields['cbenregistree'] = $this->cardSaved;
342
        }
343
344
        if (isset($this->cardMask)) {
345
            $fields['cbmasquee'] = $this->cardMask;
346
        }
347
348
        if (isset($this->paymentMethod)) {
349
            $fields['modepaiement'] = $this->paymentMethod;
350
        }
351
352
        return $fields;
353
    }
354
355
    /**
356
     * Validate seal to verify payment
357
     *
358
     * @param string $eptCode
359
     * @param string $securityKey
360
     * @return bool
361
     */
362
    public function validateSeal(string $eptCode, string $securityKey): bool
363
    {
364
        $fields = array_merge(
365
            [
366
                'TPE' => $eptCode,
367
            ],
368
            $this->toArray()
369
        );
370
371
        ksort($fields);
372
373
        $query = http_build_query($fields, null, '*');
374
        $query = urldecode($query);
375
376
        $hash = strtoupper(hash_hmac(
377
            'sha1',
378
            $query,
379
            $securityKey
380
        ));
381
382
        return $hash === $this->seal;
383
    }
384
}
385