Fetch the EUR to JPY rate with a cached helper, then multiply the requested amount in a Django view. Use Decimal for the calculation and round half up to whole yen. The helper keeps the rate in Django's cache for 300 seconds.
Install the packages and set your API key
Run the shell commands to install Django and requests, then set EXCHANGERATE_API_KEY in the environment. The helper reads that variable and sends its value in the Authorization header as a Bearer token.
Use the same environment variable name as the example. The request code reads it directly when it needs to fetch a rate, so the variable must be available to the app.
pip install django requestsexport EXCHANGERATE_API_KEY="your_key"Add the cached rate helper
Put the helper in shop/rates.py. The view calls it with EUR as the base and JPY as the quote. On a cache miss, it sends GET /v1/latest/EUR?symbols=JPY to https://api.exchangerate.dev with a 5 second timeout.
The helper raises on an HTTP error before it reads the response. It takes the JPY value from rates and caches that value for 300 seconds. The cache key includes both currency codes. A second request inside that period reads the cached rate with no network call.
import osimport requestsfrom django.core.cache import cacheAPI = "https://api.exchangerate.dev/v1/latest"def get_rate(base: str, quote: str) -> float: key = f"fx:{base}:{quote}" rate = cache.get(key) if rate is None: response = requests.get( f"{API}/{base}", params={"symbols": quote}, headers={"Authorization": f"Bearer {os.environ['EXCHANGERATE_API_KEY']}"}, timeout=5, ) response.raise_for_status() rate = response.json()["rates"][quote] cache.set(key, rate, 300) return rateValidate the amount and calculate whole yen
Put the conversion view in shop/views.py. It reads the amount query parameter, with a default of 1, and requires a finite number above zero. An invalid amount returns HTTP 400 with the error message amount must be a positive number.
The calculation uses Decimal for both the amount and the rate. The view converts the helper's rate through its string representation, multiplies it by the amount, then rounds half up to whole yen because JPY has no minor unit. Rounding happens after multiplication; the view does not round the rate first.
from decimal import ROUND_HALF_UP, Decimalfrom django.http import JsonResponsefrom .rates import get_ratedef convert(request): rate = Decimal(str(get_rate("EUR", "JPY"))) try: amount = Decimal(request.GET.get("amount", "1")) if not amount.is_finite() or amount <= 0: raise ValueError converted = (amount * rate).quantize(Decimal("1"), rounding=ROUND_HALF_UP) except (ArithmeticError, ValueError): return JsonResponse({"error": "amount must be a positive number"}, status=400) return JsonResponse({"amount": str(amount), "rate": str(rate), "jpy": int(converted)})Read the conversion response
The example response is the view's JSON for the sample request. amount is the EUR amount as a string, rate is the JPY per EUR rate as a string, and jpy is the converted amount as a whole number.
Multiply the EUR amount by the rate to get the yen amount before rounding. The jpy field contains the result after half-up rounding. When the helper uses a cached rate, the view still calculates the conversion from the amount in the current request.
{"amount": "1000", "rate": "175.604", "jpy": 175604}Check the rate's age separately from the cache
The 300 second cache period controls how long the helper reuses a rate. The API's data_updated_at field identifies the oldest contributing observation. A new fetch can therefore return a rate whose observation predates that request.
The API response also has result, base, source, sources, market_session, timestamp, effective_at, rates, stale, derived_symbols and notice. sources identifies the source per symbol, and stale is an object per symbol. The example helper retains only the requested rate, so the conversion JSON contains no API timestamps or per-symbol stale information.
Use the result as an indicative conversion
The calculated yen amount uses indicative mid-market figures. These rates are not for settlement or trading. Banks, cards and transfer services apply their own spread and fees, so the calculation does not establish a payout amount.
Whole-yen rounding follows JPY's lack of a minor unit. That rounding determines how the view expresses its calculated result. It does not account for a bank's, card's or transfer service's spread or fees.