diff --git a/src/Message/AbstractRequest.php b/src/Message/AbstractRequest.php index 159c1da..93465fb 100755 --- a/src/Message/AbstractRequest.php +++ b/src/Message/AbstractRequest.php @@ -76,19 +76,31 @@ protected function getResponseBody(): array /** * Safety hash from icepay, to be generated after putting in all the data. * - * @param string $requestMethod - * @param string $urlPath - * @param array $data + * @param string $requestMethod + * @param string $urlPath + * @param array|\stdClass $data + * @param bool $urlIsFullUrl = false. True to have $urlPath be the absolute (full) url * * @return string */ - private function getSecurityHash(string $requestMethod, string $urlPath, array $data): string - { - $string = $this->getBaseUrl().$urlPath.$requestMethod.$this->getContractProfileId().json_encode($data); + protected function getSecurityHash( + string $requestMethod, + string $urlPath, + $data, + bool $urlIsFullUrl = false + ): string { + $contractProfileId = $this->getContractProfileId(); + + $fullUrl = $this->getBaseUrl().$urlPath; + if ($urlIsFullUrl) { + $fullUrl = $urlPath; + } + + $toBeHashed = $fullUrl.$requestMethod.$contractProfileId.json_encode($data); $hash = hash_hmac( 'sha256', - $string, + $toBeHashed, base64_decode($this->getSecretKey()), true ); diff --git a/src/Message/TransactionStatusRequest.php b/src/Message/TransactionStatusRequest.php index ff1eb65..ef28af1 100755 --- a/src/Message/TransactionStatusRequest.php +++ b/src/Message/TransactionStatusRequest.php @@ -27,6 +27,12 @@ public function getData(): array */ public function sendData($data): ResponseInterface { + $transactionStatusResponse = $this->getTransactionStatusFromPostBack(); + + if ($transactionStatusResponse !== null) { + return $transactionStatusResponse; + } + $this->sendRequest( Request::METHOD_POST, sprintf( @@ -42,4 +48,101 @@ public function sendData($data): ResponseInterface $this->getResponse()->getStatusCode() ); } + + /** + * Use the data sent by Icepay in the post back to check the status. + * This is necessary because Icepay has a delay in their backend if you request the status immediately after the signal. + * + * @see http://docs2.icepay.com/payment-process/handling-the-postback/postback-sample/ + * + * @return TransactionStatusResponse|null - Null when the data is is not sent or not correct + */ + private function getTransactionStatusFromPostBack(): ?TransactionStatusResponse + { + if (stripos($this->httpRequest->getContentType(), 'json') === false) { + return null; + } + + try { + $content = $this->httpRequest->getContent(); + $contentAsArray = json_decode($content, true); + $contentAsStdObj = json_decode($content); + } catch (\LogicException $exception) { + return null; + } + + if (is_array($contentAsArray) === false || isset($contentAsArray['StatusCode']) === false) { + return null; + } + + $this->setContractProfileId($contentAsStdObj->ContractProfileId); + + if ($this->validateSecurityHashMatch($this->httpRequest, $contentAsStdObj) === false) { + return null; + } + + $camelCasedKeysContent = array_combine( + array_map('lcfirst', array_keys($contentAsArray)), + array_values($contentAsArray) + ); + + return new TransactionStatusResponse( + $this, + $camelCasedKeysContent, + 200 + ); + } + + /** + * Get the security hash from the request and match it against a generated hash from the sent values. + * Needs the POSTed JSON as stdClass. + * + * @param Request $request + * @param \stdClass $contentAsStdObj + * + * @return bool + */ + private function validateSecurityHashMatch(Request $request, \stdClass $contentAsStdObj): bool + { + $sentSecurityHash = $request->headers->get('checksum'); + + $possibleHashes = $this->getPossibleValidHashes($request, $contentAsStdObj); + + foreach ($possibleHashes as $generatedHash) { + if ($generatedHash === $sentSecurityHash) { + return true; + } + } + + return false; + } + + /** + * They way Icepay generates the hash is by using our notification url. + * Though, they might add a trailing slash. Unsure of this at this point, so check both. + * + * @param Request $request + * @param \stdClass $contentAsStdObj + * + * @return array + */ + private function getPossibleValidHashes(Request $request, $contentAsStdObj): array + { + $notifyUrls = [ + $request->getSchemeAndHttpHost().$request->getRequestUri(), + $request->getSchemeAndHttpHost().$request->getRequestUri().'/', + ]; + + $hashes = []; + foreach ($notifyUrls as $notifyUrl) { + $hashes[] = $this->getSecurityHash( + $request->getMethod(), + $notifyUrl, + $contentAsStdObj, + true + ); + } + + return $hashes; + } }