Passed
Push — master ( e6cb61...3759bb )
by
unknown
01:27 queued 10s
created

TransactionStatusRequest::getPossibleValidHashes()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 2
eloc 11
c 3
b 0
f 0
nc 2
nop 2
dl 0
loc 18
ccs 0
cts 11
cp 0
crap 6
rs 9.9
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
        // Make sure we're updating the correct payment by reference, and not all of them.
81
        // Note: $contentAsArray['Reference'] is not the same as TransactionReference.
82
        if ($this->getTransactionReference() !== trim($contentAsArray['TransactionId'])) {
83
            return null;
84
        }
85
86
        if ($this->validateSecurityHashMatch($this->httpRequest, $contentAsStdObj) === false) {
87
            return null;
88
        }
89
90
        $camelCasedKeysContent = array_combine(
91
            array_map('lcfirst', array_keys($contentAsArray)),
92
            array_values($contentAsArray)
93
        );
94
95
        return new TransactionStatusResponse(
96
            $this,
97
            $camelCasedKeysContent,
98
            200
99
        );
100
    }
101
102
    /**
103
     * Get the security hash from the request and match it against a generated hash from the sent values.
104
     * Needs the POSTed JSON as stdClass.
105
     *
106
     * @param Request   $request
107
     * @param \stdClass $contentAsStdObj
108
     *
109
     * @return bool
110
     */
111
    private function validateSecurityHashMatch(Request $request, \stdClass $contentAsStdObj): bool
112
    {
113
        $sentSecurityHash = $request->headers->get('checksum');
114
115
        $possibleHashes = $this->getPossibleValidHashes($request, $contentAsStdObj);
116
117
        foreach ($possibleHashes as $generatedHash) {
118
            if ($generatedHash === $sentSecurityHash) {
119
                return true;
120
            }
121
        }
122
123
        return false;
124
    }
125
126
    /**
127
     * They way Icepay generates the hash is by using our notification url.
128
     * Though, they might add a trailing slash. Unsure of this at this point, so check both.
129
     *
130
     * @param Request   $request
131
     * @param \stdClass $contentAsStdObj
132
     *
133
     * @return array
134
     */
135
    private function getPossibleValidHashes(Request $request, $contentAsStdObj): array
136
    {
137
        $notifyUrls = [
138
            $request->getSchemeAndHttpHost().$request->getRequestUri(),
139
            $request->getSchemeAndHttpHost().$request->getRequestUri().'/',
140
        ];
141
142
        $hashes = [];
143
        foreach ($notifyUrls as $notifyUrl) {
144
            $hashes[] = $this->getSecurityHash(
145
                $request->getMethod(),
146
                $notifyUrl,
147
                $contentAsStdObj,
148
                true
149
            );
150
        }
151
152
        return $hashes;
153
    }
154
}
155