private function fetchOnlineRate($url, ExchangeRateQuery $exchangeRateQuery): ExchangeRate
    {
        $currencyPair = $exchangeRateQuery->getCurrencyPair();

        $response = $this->getResponse($url);

        if (200 !== $response->getStatusCode()) {
            throw new Exception("Unexpected response status {$response->getReasonPhrase()}, $url");
        }

        $responsePayload = StringUtil::jsonToArray($response->getBody()->__toString());

        $keyAsCurrencyPair = $this->stringifyCurrencyPair($currencyPair);

        if (empty($responsePayload['results'][$keyAsCurrencyPair]['val'])) {
            throw new Exception("Unexpected response body {$response->getReasonPhrase()}");
        }

        if ($responsePayload['results'][$keyAsCurrencyPair]['fr'] !== $currencyPair->getBaseCurrency()) {
            throw new Exception("Unexpected base currency {$responsePayload['results'][$keyAsCurrencyPair]['fr']}");
        }

        if ($responsePayload['results'][$keyAsCurrencyPair]['to'] !== $currencyPair->getQuoteCurrency()) {
            throw new Exception("Unexpected quote currency {$responsePayload['results'][$keyAsCurrencyPair]['to']}");
        }

        if ($exchangeRateQuery instanceof HistoricalExchangeRateQuery) {
            $dateStringified = $responsePayload['date'];
            $date = new \DateTime($dateStringified);
            $rate = $responsePayload['results'][$keyAsCurrencyPair]['val'][$dateStringified];
        } else {
            $date = new \DateTime('now');
            $rate = $responsePayload['results'][$keyAsCurrencyPair]['val'];
        }

        return $this->createRate($currencyPair, (float) $rate, $date);
    }