Files

181 lines
4.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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;
}