/** * Live EUR→toman pricing (TGJU free-market EUR). * Markups: <3€ +100%, 3–5€ +80%, >5€ +65%. */ const FALLBACK_EUR_TOMAN = 302700; const EUR_DETECT_MAX = 500; const CACHE_KEY = "kikomiko-eur-toman-v1"; const CACHE_TTL_MS = 5 * 60 * 1000; let cachedRate = 0; let inflight = null; export function markupForEuro(eur) { if (!Number.isFinite(eur) || eur <= 0) return 1; if (eur < 3) return 2; if (eur <= 5) return 1.8; return 1.65; } function parseTgjuRial(raw) { const n = Number(String(raw ?? "").replace(/,/g, "").trim()); return Number.isFinite(n) && n > 0 ? n : 0; } function rialToToman(rial) { return rial > 0 ? rial / 10 : 0; } function readBodyFallback() { const raw = Number(document.body?.dataset?.eurRate || ""); return Number.isFinite(raw) && raw > 0 ? raw : FALLBACK_EUR_TOMAN; } function readSessionCache() { try { const raw = sessionStorage.getItem(CACHE_KEY); if (!raw) return 0; const parsed = JSON.parse(raw); if ( parsed && Number(parsed.rate) > 0 && Date.now() - Number(parsed.at || 0) < CACHE_TTL_MS ) { return Number(parsed.rate); } } catch { /* ignore */ } return 0; } function writeSessionCache(rate) { try { sessionStorage.setItem( CACHE_KEY, JSON.stringify({ rate, at: Date.now() }) ); } catch { /* ignore */ } } function applyRateToDom(rate) { if (!(rate > 0) || !document.body) return; document.body.dataset.eurRate = String(Math.round(rate)); document.querySelectorAll("[data-eur-rate-display]").forEach((el) => { el.textContent = new Intl.NumberFormat("fa-IR", { maximumFractionDigits: 0, }).format(Math.round(rate)); }); } /** Prefer same-origin proxy (avoids CORS); fall back to TGJU ajax JSON. */ async function fetchTgjuEurRial() { const endpoints = ["/api/fx/eur", "https://call1.tgju.org/ajax.json"]; let lastErr; for (const url of endpoints) { try { const res = await fetch(url, { credentials: "omit", headers: { Accept: "application/json" }, }); if (!res.ok) throw new Error(`FX HTTP ${res.status}`); const data = await res.json(); const row = data?.current?.price_eur || data?.price_eur; const rial = parseTgjuRial(row?.p ?? row); if (rial > 0) return rial; throw new Error("TGJU price_eur missing"); } catch (err) { lastErr = err; } } throw lastErr || new Error("FX fetch failed"); } export function getEurToTomanRate() { if (cachedRate > 0) return cachedRate; const fromSession = readSessionCache(); if (fromSession > 0) { cachedRate = fromSession; return cachedRate; } return readBodyFallback(); } export async function ensureEurRate() { if (cachedRate > 0) return cachedRate; const fromSession = readSessionCache(); if (fromSession > 0) { cachedRate = fromSession; applyRateToDom(cachedRate); return cachedRate; } if (!inflight) { inflight = fetchTgjuEurRial() .then((rial) => { const toman = rialToToman(rial); cachedRate = toman > 0 ? toman : readBodyFallback(); writeSessionCache(cachedRate); applyRateToDom(cachedRate); return cachedRate; }) .catch(() => { cachedRate = readBodyFallback(); applyRateToDom(cachedRate); return cachedRate; }) .finally(() => { inflight = null; }); } return inflight; } export function euroBase(item) { if (!item) return null; for (const candidate of [item.priceEur, item.sourcePrice]) { const n = Number(candidate); if (Number.isFinite(n) && n > 0 && n < EUR_DETECT_MAX) return n; } const cur = String(item.sourceCurrency || "").toUpperCase(); const price = Number(item.price); if ( (cur === "EUR" || cur === "EU" || cur === "€") && Number.isFinite(price) && price > 0 && price < EUR_DETECT_MAX ) { return price; } return null; } export function eurToTomanSell(eur, rate = getEurToTomanRate()) { const r = rate > 0 ? rate : FALLBACK_EUR_TOMAN; return Math.round(Number(eur) * r * markupForEuro(Number(eur))); } export function sellPriceFromItem(item, rate = getEurToTomanRate()) { const eur = euroBase(item); if (eur != null) return eurToTomanSell(eur, rate); const n = Number(item?.price); return Number.isFinite(n) && n > 0 ? Math.round(n) : 0; } export function sellCompareFromItem(item, rate = getEurToTomanRate()) { const eur = euroBase(item); const compare = Number(item?.compareAtPrice); if ( eur != null && Number.isFinite(compare) && compare > 0 && compare < EUR_DETECT_MAX ) { return eurToTomanSell(compare, rate); } if (Number.isFinite(compare) && compare > 0) return Math.round(compare); return null; }