Passed
Push — master ( 61e5a6...e6cb61 )
by
unknown
01:05 queued 17s
created

validateSecurityHashMatch()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
c 4
b 0
f 0
nc 3
nop 2
dl 0
loc 13
ccs 0
cts 7
cp 0
crap 12
rs 10
1
<?php
2
3
namespace Omnipay\IcepayPayments\Message;
4
5
use Omnipay\Common\Message\ResponseInterface;
6
use Symfony\Component\HttpFoundation\Request;
7
8
/**
9
 * The request for getting the transaction status at Icepay.
10
 */
11
class TransactionStatusRequest extends AbstractRequest
12
{
13
    /**
14
     * {@inheritdoc}
15
     */
16 1
    public function getData(): array
17
    {
18 1
        $data = parent::getData();
19
20 1
        $data['ContractProfileId'] = $this->getContractProfileId();
21
22 1
        return $data;
23
    }
24
25
    /**
26
     * {@inheritdoc}
27
     */
28 1
    public function sendData($data): ResponseInterface
29
    {
30 1
        $transactionStatusResponse = $this->getTransactionStatusFromPostBack();
31
32 1
        if ($transactionStatusResponse !== null) {
33
            return $transactionStatusResponse;
34
        }
35
36 1
        $this->sendRequest(
37 1
            Request::METHOD_POST,
38 1
            sprintf(
39 1
                '/transaction/%s',
40 1
                $this->getTransactionReference()
41
            ),
42 1
            $data
43
        );
44
45 1
        return new TransactionStatusResponse(
46 1
            $this,
47 1
            $this->getResponseBody(),
48 1
            $this->getResponse()->getStatusCode()
0 ignored issues
show
Bug introduced by
The method getStatusCode() does not exist on Omnipay\Common\Message\ResponseInterface. ( Ignorable by Annotation )

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

48
            $this->getResponse()->/** @scrutinizer ignore-call */ getStatusCode()

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
49
        );
50
    }
51
52
    /**
53
     * Use the data sent by Icepay in the post back to check the status.
54
     * This is necessary because Icepay has a delay in their backend if you request the status immediately after the signal.
55
     *
56
     * @see http://docs2.icepay.com/payment-process/handling-the-postback/postback-sample/
57
     *
58
     * @return TransactionStatusResponse|null - Null when the data is is not sent or not correct
59
     */
60 1
    private function getTransactionStatusFromPostBack(): ?TransactionStatusResponse
61
    {
62 1
        if (stripos($this->httpRequest->getContentType(), 'json') === false) {
63 1
            return null;
64
        }
65
66
        try {
67
            $content = $this->httpRequest->getContent();
68
            $contentAsArray = json_decode($content, true);
69
            $contentAsStdObj = json_decode($content);
70
        } catch (\LogicException $exception) {
71
            return null;
72
        }
73
74
        if (is_array($contentAsArray) === false || isset($contentAsArray['StatusCode']) === false) {
75
            return null;
76
        }
77
78
        $this->setContractProfileId($contentAsStdObj->ContractProfileId);
79
80
        if ($this->validateSecurityHashMatch($this->httpRequest, $contentAsStdObj) === false) {
81
            return null;
82
        }
83
84
        $camelCasedKeysContent = array_combine(
85
            array_map('lcfirst', array_keys($contentAsArray)),
86
            array_values($contentAsArray)
87
        );
88
89
        return new TransactionStatusResponse(
90
            $this,
91
            $camelCasedKeysContent,
92
            200
93
        );
94
    }
95
96
    /**
97
     * Get the security hash from the request and match it against a generated hash from the sent values.
98
     * Needs the POSTed JSON as stdClass.
99
     *
100
     * @param Request   $request
101
     * @param \stdClass $contentAsStdObj
102
     *
103
     * @return bool
104
     */
105
    private function validateSecurityHashMatch(Request $request, \stdClass $contentAsStdObj): bool
106
    {
107
        $sentSecurityHash = $request->headers->get('checksum');
108
109
        $possibleHashes = $this->getPossibleValidHashes($request, $contentAsStdObj);
110
111
        foreach ($possibleHashes as $generatedHash) {
112
            if ($generatedHash === $sentSecurityHash) {
113
                return true;
114
            }
115
        }
116
117
        return false;
118
    }
119
120
    /**
121
     * They way Icepay generates the hash is by using our notification url.
122
     * Though, they might add a trailing slash. Unsure of this at this point, so check both.
123
     *
124
     * @param Request   $request
125
     * @param \stdClass $contentAsStdObj
126
     *
127
     * @return array
128
     */
129
    private function getPossibleValidHashes(Request $request, $contentAsStdObj): array
130
    {
131
        $notifyUrls = [
132
            $request->getSchemeAndHttpHost().$request->getRequestUri(),
133
            $request->getSchemeAndHttpHost().$request->getRequestUri().'/',
134
        ];
135
136
        $hashes = [];
137
        foreach ($notifyUrls as $notifyUrl) {
138
            $hashes[] = $this->getSecurityHash(
139
                $request->getMethod(),
140
                $notifyUrl,
141
                $contentAsStdObj,
142
                true
143
            );
144
        }
145
146
        return $hashes;
147
    }
148
}
149