Test Failed
Push — query-string ( c17fc7...166cc4 )
by
unknown
01:37
created

TransactionStatusRequest::sendData()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 3
Bugs 0 Features 2
Metric Value
cc 4
eloc 18
c 3
b 0
f 2
nc 5
nop 1
dl 0
loc 29
ccs 0
cts 18
cp 0
crap 20
rs 9.6666
1
<?php
2
3
namespace Omnipay\IcepayPayments\Message;
4
5
use Omnipay\Common\Message\ResponseInterface;
6
use Omnipay\IcepayPayments\Exception\PostBackException;
7
use Symfony\Component\HttpFoundation\Request;
8
9
/**
10
 * The request for getting the transaction status at Icepay.
11
 */
12
class TransactionStatusRequest extends AbstractRequest
13
{
14
    /**
15
     * {@inheritdoc}
16
     */
17 1
    public function getData(): array
18
    {
19 1
        $data = parent::getData();
20
21 1
        $data['ContractProfileId'] = $this->getContractProfileId();
22
23 1
        return $data;
24
    }
25
26
    /**
27
     * {@inheritdoc}
28
     */
29
    public function sendData($data): ResponseInterface
30
    {
31
        try {
32
            $transactionStatusResponse = $this->getTransactionStatusFromPostBack();
33
        } catch (PostBackException $exception) {
34
            // Optional parameter to throw the error instead of fallback.
35
            if (isset($data['throwOnPostBackError'])) {
36
                throw $exception;
37
            }
38
            $transactionStatusResponse = false;
39
        }
40
41
        if ($transactionStatusResponse !== false) {
42
            return $transactionStatusResponse;
43
        }
44
45
        $this->sendRequest(
46
            Request::METHOD_POST,
47
            sprintf(
48
                '/transaction/%s',
49
                $this->getTransactionReference()
50
            ),
51
            $data
52
        );
53
54
        return new TransactionStatusResponse(
55
            $this,
56
            $this->getResponseBody(),
57
            $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

57
            $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...
58
        );
59
    }
60
61
    /**
62
     * Use the data sent by Icepay in the post back to check the status.
63
     * This is necessary because Icepay has a delay in their backend if you request the status immediately after the signal.
64
     *
65
     * @see http://docs2.icepay.com/payment-process/handling-the-postback/postback-sample/
66
     *
67
     * @return TransactionStatusResponse|bool False when the data is is not sent or not correct.
68
     *
69
     * @throws PostBackException by call to self::validateSecurityHashMatch()
70
     */
71
    private function getTransactionStatusFromPostBack(): ?TransactionStatusResponse
72
    {
73
        if (stripos($this->httpRequest->getContentType(), 'json') === false) {
74
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the type-hinted return Omnipay\IcepayPayments\M...tionStatusResponse|null.
Loading history...
75
        }
76
77
        try {
78
            $contentAsArray = json_decode($this->httpRequest->getContent(), true);
79
        } catch(\LogicException $exception) {
80
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the type-hinted return Omnipay\IcepayPayments\M...tionStatusResponse|null.
Loading history...
81
        }
82
83
        if (is_array($contentAsArray) === false || isset($contentAsArray['StatusCode']) === false) {
84
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the type-hinted return Omnipay\IcepayPayments\M...tionStatusResponse|null.
Loading history...
85
        }
86
87
        $this->validateSecurityHashMatch($this->httpRequest, $contentAsArray);
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
     * Will throw an exception if it does not match.
104
     * Needs the POSTed Json as a php array.
105
     *
106
     * @param Request         $request
107
     * @param $contentAsArray
108
     *
109
     * @return bool
110
     *
111
     * @throws PostBackException
112
     */
113
    private function validateSecurityHashMatch(Request $request, $contentAsArray): bool
114
    {
115
        $generatedSecurityHash = $this->getSecurityHash(
116
            Request::METHOD_POST,
117
            $request->getPathInfo(),
118
            $contentAsArray
119
        );
120
121
        $sentSecurityHash = $request->headers->get('checksum');
122
123
        $possibleHashes = $this->getPossibleValidHashes($request, $contentAsArray);
124
125
        foreach ($possibleHashes as $generatedHash) {
126
            if ($generatedHash === $sentSecurityHash) {
127
                return true;
128
            }
129
        }
130
131
        throw new PostBackException(
132
            sprintf(
133
                'Sent security hash %s did not match generated hash %s',
134
                $sentSecurityHash,
135
                $generatedSecurityHash
136
            )
137
        );
138
    }
139
140
    /**
141
     * They way Icepay generates the hash is by using our notification url.
142
     * Though, they might add a trailing slash. Unsure of this at this point, so check both.
143
     *
144
     * @param Request $request
145
     * @param $contentAsArray
146
     *
147
     * @return array
148
     */
149
    private function getPossibleValidHashes(Request $request, $contentAsArray): array
150
    {
151
        $notifyUrls = [
152
            $request->getSchemeAndHttpHost() . $request->getRequestUri(),
153
            $request->getSchemeAndHttpHost() . $request->getRequestUri() . '/',
154
        ];
155
156
        $hashes = [];
157
        foreach ($notifyUrls as $notifyUrl) {
158
            $hashes[] = $this->getSecurityHash(
159
                $request->getMethod(),
160
                $notifyUrl,
161
                $contentAsArray,
162
                true
163
            );
164
        }
165
166
        return $hashes;
167
    }
168
}
169