TransactionStatusRequest::sendData()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 2.0017

Importance

Changes 5
Bugs 0 Features 2
Metric Value
cc 2
eloc 12
c 5
b 0
f 2
nc 2
nop 1
dl 0
loc 20
ccs 12
cts 13
cp 0.9231
crap 2.0017
rs 9.8666
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_GET,
38 1
            sprintf(
39 1
                '/transaction/%s',
40 1
                $this->getTransactionReference()
41
            )
42
        );
43
44 1
        return new TransactionStatusResponse(
45 1
            $this,
46 1
            $this->getResponseBody(),
47 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

47
            $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...
48
        );
49
    }
50
51
    /**
52
     * Use the data sent by Icepay in the post back to check the status.
53
     * This is necessary because Icepay has a delay in their backend if you request the status immediately after the signal.
54
     *
55
     * @see http://docs2.icepay.com/payment-process/handling-the-postback/postback-sample/
56
     *
57
     * @return TransactionStatusResponse|null - Null when the data is is not sent or not correct
58
     */
59 1
    private function getTransactionStatusFromPostBack(): ?TransactionStatusResponse
60
    {
61 1
        if (stripos($this->httpRequest->getContentType(), 'json') === false) {
0 ignored issues
show
Bug introduced by
It seems like $this->httpRequest->getContentType() can also be of type null; however, parameter $haystack of stripos() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

61
        if (stripos(/** @scrutinizer ignore-type */ $this->httpRequest->getContentType(), 'json') === false) {
Loading history...
62 1
            return null;
63
        }
64
65
        try {
66
            $content = $this->httpRequest->getContent();
67
            $contentAsArray = json_decode($content, true);
68
            $contentAsStdObj = json_decode($content);
69
        } catch (\LogicException $exception) {
70
            return null;
71
        }
72
73
        if (is_array($contentAsArray) === false || isset($contentAsArray['StatusCode']) === false) {
74
            return null;
75
        }
76
77
        $this->setContractProfileId($contentAsStdObj->ContractProfileId);
78
79
        // Make sure we're updating the correct payment by reference, and not all of them.
80
        // Note: $contentAsArray['Reference'] is not the same as TransactionReference.
81
        if ($this->getTransactionReference() !== trim($contentAsArray['TransactionId'])) {
82
            return null;
83
        }
84
85
        if ($this->validateSecurityHashMatch($this->httpRequest, $contentAsStdObj) === false) {
86
            return null;
87
        }
88
89
        $camelCasedKeysContent = array_combine(
90
            array_map('lcfirst', array_keys($contentAsArray)),
91
            array_values($contentAsArray)
92
        );
93
94
        return new TransactionStatusResponse(
95
            $this,
96
            $camelCasedKeysContent,
97
            200
98
        );
99
    }
100
101
    /**
102
     * Get the security hash from the request and match it against a generated hash from the sent values.
103
     * Needs the POSTed JSON as stdClass.
104
     *
105
     * @param Request   $request
106
     * @param \stdClass $contentAsStdObj
107
     *
108
     * @return bool
109
     */
110
    private function validateSecurityHashMatch(Request $request, \stdClass $contentAsStdObj): bool
111
    {
112
        $sentSecurityHash = $request->headers->get('checksum');
113
114
        $possibleHashes = $this->getPossibleValidHashes($request, $contentAsStdObj);
115
116
        foreach ($possibleHashes as $generatedHash) {
117
            if ($generatedHash === $sentSecurityHash) {
118
                return true;
119
            }
120
        }
121
122
        return false;
123
    }
124
125
    /**
126
     * They way Icepay generates the hash is by using our notification url.
127
     * Though, they might add a trailing slash. Unsure of this at this point, so check both.
128
     *
129
     * @param Request   $request
130
     * @param \stdClass $contentAsStdObj
131
     *
132
     * @return array
133
     */
134
    private function getPossibleValidHashes(Request $request, $contentAsStdObj): array
135
    {
136
        $notifyUrls = [
137
            $request->getSchemeAndHttpHost().$request->getRequestUri(),
138
            $request->getSchemeAndHttpHost().$request->getRequestUri().'/',
139
        ];
140
141
        $hashes = [];
142
        foreach ($notifyUrls as $notifyUrl) {
143
            $hashes[] = $this->getSecurityHash(
144
                $request->getMethod(),
145
                $notifyUrl,
146
                $contentAsStdObj,
147
                true
148
            );
149
        }
150
151
        return $hashes;
152
    }
153
}
154