Newer
Older
TillQliro / Model / Security / CallbackToken.php
@Jonas Jonsson Jonas Jonsson on 2 Apr 2024 4 KB Initial
<?php
/**
 * Copyright © Qliro AB. All rights reserved.
 * See LICENSE.txt for license details.
 */

namespace Qliro\QliroOne\Model\Security;

use Qliro\QliroOne\Model\Config;
use Qliro\QliroOne\Model\Logger\Manager;

/**
 * Notification Callback Token handling class
 */
class CallbackToken
{
    /**
     * @var \Qliro\QliroOne\Model\Security\Jwt
     */
    private $jwt;

    /**
     * @var \Qliro\QliroOne\Model\Config
     */
    private $qliroConfig;

    /**
     * @var \Qliro\QliroOne\Model\Logger\Manager
     */
    private $logManager;

    /**
     * Inject dependencies
     *
     * @param \Qliro\QliroOne\Model\Security\Jwt $jwt
     * @param \Qliro\QliroOne\Model\Config $qliroConfig
     * @param \Qliro\QliroOne\Model\Logger\Manager $logManager
     */
    public function __construct(
        Jwt $jwt,
        Config $qliroConfig,
        Manager $logManager
    ) {
        $this->jwt = $jwt;
        $this->qliroConfig = $qliroConfig;
        $this->logManager = $logManager;
    }

    /**
     * Get a new token that will expire in 2 hours
     *
     * @return string
     */
    public function getToken()
    {
        $payload = [
            'merchant' => $this->qliroConfig->getMerchantApiKey(),
            'expires' => date('Y-m-d H:i:s', $this->getExpirationTimestamp()),
            'additional_data' => $this->getAdditionalData(),
        ];

        return $this->jwt->encode($payload, $this->qliroConfig->getMerchantApiSecret());
    }

    /**
     * Verify if the token is valid
     *
     * @param string $token
     * @return bool
     */
    public function verifyToken($token)
    {
        try {
            $payload = $this->jwt->decode($token, $this->qliroConfig->getMerchantApiSecret(), true);
        } catch (\Exception $exception) {
            return false;
        }

        $merchant = $payload['merchant'] ?? null;
        $expiresAt = isset($payload['expires']) ? strtotime($payload['expires']) : 0;
        $additionalData = $payload['additional_data'] ?? null;

        $this->logManager->setMark('SECURITY TOKEN');
        $this->logManager->addTag('security');

        if ($merchant !== $this->qliroConfig->getMerchantApiKey()) {
            $this->logManager->debug(
                'merchant ID mismatch',
                [
                    'extra' => [
                        'request' => $merchant,
                        'configured' => $this->qliroConfig->getMerchantApiKey()
                    ]
                ]
            );

            $this->logManager->setMark(null);
            $this->logManager->removeTag('security');

            return false;
        }

        if ($additionalData != $this->getAdditionalData()) {
            $this->logManager->debug(
                'additional data mismatch',
                [
                    'extra' => [
                        'additional_data' => $additionalData,
                    ]
                ]
            );

            $this->logManager->setMark(null);
            $this->logManager->removeTag('security');

            return false;
        }

        if ($expiresAt - time() < 0) {
            $this->logManager->debug(
                'expired {expired} seconds ago',
                [
                    'expired' => time() - $expiresAt
                ]
            );

            $this->logManager->setMark(null);
            $this->logManager->removeTag('security');

            return false;
        }

        $this->logManager->setMark(null);
        $this->logManager->removeTag('security');

        return true;
    }

    /**
     * Get an expiration timestamp for the token
     *
     * @return int
     */
    public function getExpirationTimestamp()
    {
        /* Reason from QliroOne API documentation:
         * If the callback is not received by Qliro One, seven retry attempts are scheduled
         * at 30 seconds, 60 seconds, 2 minutes, 30 minutes, 1 hour, 24 hours and 3 days.
         * We also need an additional day for the shopper taking their time before placing the order.
         */
        return strtotime('+4 day');
    }

    /**
     * Get additional data used for modifying security token
     *
     * @return mixed
     */
    public function getAdditionalData()
    {
        return null;
    }
}