Passed
Push — master ( f4ebb6...c31285 )
by Gaël
11:14
created

PaymentResponse   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 336
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 162
dl 0
loc 336
rs 10
c 0
b 0
f 0
wmc 29

7 Methods

Rating   Name   Duplication   Size   Complexity  
A setAuthentication() 0 10 1
A __construct() 0 24 5
B setErrorsOptions() 0 22 7
A getRequiredKeys() 0 20 1
A validateSeal() 0 16 1
B fieldsToArray() 0 52 8
A setOptions() 0 20 6
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\PaymentException;
8
use DansMaCulotte\Monetico\Resources\AuthenticationResource;
9
use DateTime;
10
11
class PaymentResponse 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 $cardBookmarked = 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
    const DATETIME_FORMAT = 'd/m/Y_\a_H:i:s';
84
85
    /** @var array */
86
    const RETURN_CODES = [
87
        'payetest',
88
        'paiement',
89
        'Annulation',
90
        'paiement_pf2',
91
        'paiement_pf3',
92
        'paiement_pf4',
93
        'Annulation_pf2',
94
        'Annulation_pf3',
95
        'Annulation_pf4',
96
    ];
97
98
    /** @var array */
99
    const CARD_VERIFICATION_STATUSES = [
100
        'oui',
101
        'non',
102
    ];
103
104
    /** @var array */
105
    const CARD_BRANDS = [
106
        'AM' => 'American Express',
107
        'CB' => 'GIE CB',
108
        'MC' => 'Mastercard',
109
        'VI' => 'Visa',
110
        'na' => 'Non disponible',
111
    ];
112
113
    /** @var array  */
114
    const REJECT_REASONS = [
115
        'Appel Phonie',
116
        'Refus',
117
        'Interdit',
118
        'filtrage',
119
        'scoring',
120
        '3DSecure',
121
    ];
122
123
    /** @var array  */
124
    const PAYMENT_METHODS = [
125
        'CB',
126
        'paypal',
127
        '1euro',
128
        '3xcb',
129
        '4cb',
130
        'audiotel',
131
    ];
132
133
    /** @var array */
134
    const FILTERED_REASONS = [
135
        1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16,
136
    ];
137
138
    /**
139
     * OutputPayload constructor.
140
     *
141
     * @param array $data
142
     * @throws \Exception
143
     */
144
    public function __construct(array $data = [])
145
    {
146
        parent::__construct($data);
147
148
        $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

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