Skip to content
intermediate Phase 91 · Multi-store

Multi-Currency

Multi-currency - currency configuration, exchange rates, currency display

45m
0 problems
Topic Progress 0%

Currency Configuration

Currency Setup

<!-- app/code/Vendor/Currency/etc/config.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
    <default>
        <currency>
            <options>
                <base>USD</base>
                <default>USD</default>
                <allow>USD,EUR,GBP,JPY</allow>
            </options>
        </currency>
    </default>
</config>

Currency Manager

namespace Vendor\Currency\Manager;

class CurrencyManager
{
    private CurrencyInterface $currency;
    private StoreManagerInterface $storeManager;

    public function getStoreCurrencies(int $storeId): array
    {
        $store = $this->storeManager->getStore($storeId);
        $allowed = explode(',', $store->getConfig('currency/options/allow'));

        return array_map(function ($code) {
            return [
                'code' => $code,
                'symbol' => $this->currency->getSymbol($code),
                'name' => $this->currency->getName($code),
            ];
        }, $allowed);
    }

    public function getBaseCurrency(int $storeId): string
    {
        return $this->storeManager->getStore($storeId)
            ->getConfig('currency/options/base');
    }

    public function getDefaultCurrency(int $storeId): string
    {
        return $this->storeManager->getStore($storeId)
            ->getConfig('currency/options/default');
    }
}

Exchange Rates

Exchange Rate Service

namespace Vendor\Currency\ExchangeRate;

class ExchangeRateService
{
    private RateRepositoryInterface $rateRepo;

    public function convert(
        float $amount,
        string $fromCurrency,
        string $toCurrency,
        int $storeId = null
    ): float {
        if ($fromCurrency === $toCurrency) {
            return $amount;
        }

        $rate = $this->getRate($fromCurrency, $toCurrency, $storeId);

        return round($amount * $rate, 4);
    }

    public function getRate(
        string $fromCurrency,
        string $toCurrency,
        int $storeId = null
    ): float {
        $rate = $this->rateRepo->getByCurrencies(
            $fromCurrency,
            $toCurrency,
            $storeId
        );

        if ($rate === null) {
            // Try inverse rate
            $inverseRate = $this->rateRepo->getByCurrencies(
                $toCurrency,
                $fromCurrency,
                $storeId
            );

            if ($inverseRate !== null) {
                return 1 / $inverseRate;
            }

            throw new \Magento\Framework\Exception\LocalizedException(
                __('Exchange rate not found for %1 to %2', $fromCurrency, $toCurrency)
            );
        }

        return $rate;
    }
}

Rate Import

namespace Vendor\Currency\ExchangeRate\Import;

class RateImporter
{
    private RateRepositoryInterface $rateRepo;
    private RateConverterFactory $converterFactory;

    public function importFromSource(
        string $source,
        array $currencyPairs
    ): ImportResult {
        $converter = $this->converterFactory->create($source);
        $rates = $converter->fetch($currencyPairs);

        $imported = 0;
        foreach ($rates as $pair => $rate) {
            [$from, $to] = explode('_', $pair);

            $rateEntity = new RateData([
                'currency_from' => $from,
                'currency_to' => $to,
                'rate' => $rate,
                'import_source' => $source,
                'imported_at' => new \DateTime(),
            ]);

            $this->rateRepo->save($rateEntity);
            $imported++;
        }

        return new ImportResult($imported);
    }
}

Rate Sources

namespace Vendor\Currency\ExchangeRate\Source;

interface RateSourceInterface
{
    public function fetch(array $pairs): array;
    public function getName(): string;
}

class ECBRateSource implements RateSourceInterface
{
    private HttpClientInterface $httpClient;

    public function fetch(array $pairs): array
    {
        $response = $this->httpClient->get(
            'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml'
        );

        return $this->parseXml($response->getBody(), $pairs);
    }
}

Currency Display

Price Formatter

namespace Vendor\Currency\Display;

class PriceFormatter
{
    private CurrencyInterface $currency;
    private PriceCurrencyInterface $priceCurrency;

    public function format(
        float $amount,
        string $currencyCode,
        bool $includeContainer = true
    ): string {
        $symbol = $this->currency->getSymbol($currencyCode);
        $formatted = number_format($amount, 2);

        if ($includeContainer) {
            return sprintf(
                '<span class="price" data-currency="%s">%s%s</span>',
                $currencyCode,
                $symbol,
                $formatted
            );
        }

        return $symbol . $formatted;
    }

    public function formatForStore(
        float $amount,
        int $storeId
    ): string {
        $currency = $this->priceCurrency->getCurrency($storeId);

        return $this->currency->format(
            $amount,
            ['container' => '<span class="price">%s</span>']
        );
    }
}

Currency Switcher

namespace Vendor\Currency\Switcher;

class CurrencySwitcher
{
    private StoreManagerInterface $storeManager;

    public function getAvailableCurrencies(int $storeId): array
    {
        $store = $this->storeManager->getStore($storeId);
        $allowed = explode(',', $store->getConfig('currency/options/allow'));

        return array_map(function ($code) use ($storeId) {
            return [
                'code' => $code,
                'symbol' => $this->currency->getSymbol($code),
                'label' => $this->getCurrencyLabel($code),
                'is_current' => $this->isCurrentCurrency($code, $storeId),
            ];
        }, $allowed);
    }

    public function switchCurrency(
        string $currencyCode,
        int $storeId
    ): void {
        $session = $this->customerSession;
        $session->setCurrencyCode($currencyCode);
    }
}

Currency Conversion in Pricing

Price Converter

namespace Vendor\Currency\Pricing;

class PriceConverter
{
    private ExchangeRateServiceInterface $rateService;
    private StoreManagerInterface $storeManager;

    public function convertPrice(
        float $price,
        string $fromCurrency,
        string $toCurrency,
        int $storeId = null
    ): float {
        return $this->rateService->convert(
            $price,
            $fromCurrency,
            $toCurrency,
            $storeId
        );
    }

    public function convertCatalogPrices(
        array $products, string $targetCurrency, int $storeId): array
    {
        $baseCurrency = $this->getBaseCurrency($storeId);

        foreach ($products as $product) {
            $product->setPrice(
                $this->convertPrice(
                    $product->getPrice(),
                    $baseCurrency,
                    $targetCurrency,
                    $storeId
                )
            );
        }

        return $products;
    }
}

Cart Currency Handling

namespace Vendor\Currency\Cart;

class CartCurrencyHandler
{
    public function recalculateCart(
        CartInterface $cart,
        string $newCurrency
    ): void {
        $baseCurrency = $cart->getQuote()->getCurrency();

        foreach ($cart->getItems() as $item) {
            $item->setPrice(
                $this->converter->convert(
                    $item->getBasePrice(),
                    $baseCurrency,
                    $newCurrency
                )
            );
        }

        $cart->recalculate();
    }
}

Quiz

1. What is the base currency?

Question 1 options

2. How do exchange rates work?

Question 2 options

3. What is the currency switcher?

Question 3 options

Flashcards

Question

What is base currency?

Answer

Currency used for internal price calculations

Question

What is default currency?

Answer

Default display currency for the store view

Question

How to import exchange rates?

Answer

Use RateImporter with external source (ECB, etc.)

Question

What is currency conversion?

Answer

Transforming prices between currencies using exchange rates

Revision Notes

Key Takeaways

  • 1. Base currency is for internal calculations
  • 2. Default currency is for display
  • 3. Exchange rates convert between currencies
  • 4. Currency switcher changes display currency
  • 5. Catalog prices stored in base currency, converted on display

Interview Tips

  • Explain base vs default vs allowed currencies
  • Discuss exchange rate import strategies
  • Describe currency conversion in cart/checkout
  • Talk about rounding and precision in conversions

Cheat Sheet

Currency Types:
  Base → internal calculations
  Default → primary display
  Allowed → customer switchable

Exchange Rate:
  from_currency → to_currency ratio
  Inverse: 1 / rate

Display:
  Symbol + formatted amount
  Currency switcher UI

Conversion:
  price × rate = converted_price
  Store in base, display in default