Guide/بنية محوّل العملات

كيف تحافظ تطبيقات تحويل العملات على تحديث أسعار الصرف؟

يبقى محوّل العملات محدثاً عندما يخزّن مشاهدة سعر كاملة، ويحدّثها بجدول منضبط، ويحافظ على وقت البيانات الحقيقي عند فشل الطلب أو إغلاق السوق.

ERexchangerate.dev·Aug 23, 2026·8 دقائق قراءة

لا يطلب المحوّل الموثوق رقماً جديداً مع كل عرض للصفحة. بل يحفظ السعر مع source وmarket_session وdata_updated_at، ويعيد استخدام المشاهدة مدة قصيرة، ولا يستبدلها إلا بعد تحديث ناجح. وقت الاستجابة يبين متى أجابت API، ووقت المشاهدة يبين متى تغير السعر نفسه.

Key points
خزّن المشاهدة كاملة، بما فيها rates وsources وmarket_session وdata_updated_at.
استخدم data_updated_at لعمر البيانات؛ أما timestamp فهو وقت إنشاء الاستجابة.
قد تتحدث العملات النشطة خلال يوم التداول، بينما تتبع الأسعار المرجعية اليومية جدول نشرها.
أثناء عطلة نهاية الأسبوع أو الإغلاق، احتفظ بآخر مشاهدة صالحة ووقتها الأصلي.
عند فشل التحديث، أظهر آخر قيمة صالحة بتاريخها أو أعد خطأ إن لم توجد.

عامل الاستجابة كمشاهدة واحدة

السعر بلا بيانات وصفية غامض؛ فقد يكون مشاهدة فورية خلال اليوم، أو سعراً مرجعياً يومياً، أو تقاطعاً مشتقاً. احفظ الكائن كاملاً بدلاً من استخراج rates.EUR والتخلص من السياق. يعرّف مرجع API حقول الاستجابة، وتشرح المنهجية كيفية تعيين المصدر وحالة السوق.

json · abbreviated latest responsecopy
{
  "base": "USD",
  "source": "live",
  "market_session": "open",
  "timestamp": "2026-08-23T09:42:06Z",
  "data_updated_at": "2026-08-23T09:41:00Z",
  "rates": { "EUR": 0.86124, "GBP": 0.74281 },
  "sources": { "EUR": "live", "GBP": "live" },
  "derived_symbols": []
}

يلخص source أقل فئة حداثة في استجابة متعددة العملات. اقرأ sources لكل عملة، ويحدد derived_symbols الأسعار المحسوبة عبر عملات وسيطة.

طابعان زمنيان لمعنيين مختلفين
timestamp هو وقت تجهيز API للاستجابة. data_updated_at هو وقت كتابة المشاهدة الأساسية. استخدم الثاني للعرض وقرارات الحداثة.

حدد سياسة التحديث قبل كتابة المؤقت

لا توجد مدة واحدة تناسب كل المنتجات. يمكن لسعر متجر تقريبي مشاركة ذاكرة مؤقتة لبضع دقائق، وقد يتحدث مركز العمليات وهو ظاهر، بينما يكفي التقرير اليومي طلب مجدول بعد السعر المرجعي المطلوب. الطلب مع كل عرض يهدر الاستدعاءات وقد يعيد المشاهدة نفسها.

الاستخدامسياسة التطبيقما يعرض
عرض متجر تقريبيذاكرة مشتركة وتحديث كل بضع دقائقالعملة، صفة استرشادية، وقت المشاهدة
لوحة عملياتذاكرة قصيرة وتحديث عند الظهورالسعر، المصدر، الجلسة، الوقت
تقرير يوميطلب واحد بعد السعر المرجعيالسعر ووقت المشاهدة المخزن
تسوية فعليةلا تستخدم سعراً استرشادياًناتج مزود الدفع أو التداول

تكون market_session واحدة من open أوweekend أوinterbank_closed. قد تتحرك العملات النشطة خلال أسبوع التداول، بينما تتحدث السلاسل المرجعية وفق جدولها.

أنشئ ذاكرة مؤقتة صغيرة على الخادم

رتّب العملات للحصول على مفتاح ثابت، وخزّن الاستجابات الناجحة فقط، وامنع التحديثات المتزامنة المكررة. يجب أن تبقى الطلبات الموثقة ومفتاح API على الخادم.

typescript · latest-rates.tscopy
type RateSource = "live" | "ecb_daily" | "fred_daily";

type LatestRates = {
  base: string;
  source: RateSource;
  sources: Record<string, RateSource>;
  market_session: "open" | "weekend" | "interbank_closed";
  timestamp: string;
  data_updated_at: string;
  rates: Record<string, number>;
  derived_symbols: string[];
};

type CacheEntry = {
  value: LatestRates;
  fetchedAt: number;
};

const cache = new Map<string, CacheEntry>();
const pending = new Map<string, Promise<LatestRates>>();

function cacheKey(base: string, symbols: string[]): string {
  return `${base}:${[...symbols].sort().join(",")}`;
}

async function fetchLatest(base: string, symbols: string[]): Promise<LatestRates> {
  const url = new URL(`https://api.exchangerate.dev/v1/latest/${base}`);
  url.searchParams.set("symbols", [...symbols].sort().join(","));

  const response = await fetch(url, {
    headers: process.env.EXCHANGERATE_API_KEY
      ? { Authorization: `Bearer ${process.env.EXCHANGERATE_API_KEY}` }
      : {},
    signal: AbortSignal.timeout(8000),
  });

  if (!response.ok) {
    throw new Error(`FX refresh failed: ${response.status}`);
  }

  return response.json() as Promise<LatestRates>;
}

export async function getLatestRates(
  base: string,
  symbols: string[],
  maxAgeMs = 60_000,
): Promise<LatestRates> {
  const key = cacheKey(base, symbols);
  const existing = cache.get(key);

  if (existing && Date.now() - existing.fetchedAt < maxAgeMs) {
    return existing.value;
  }

  const inFlight = pending.get(key);
  if (inFlight) return inFlight;

  const refresh = fetchLatest(base, symbols)
    .then((value) => {
      cache.set(key, { value, fetchedAt: Date.now() });
      return value;
    })
    .finally(() => pending.delete(key));

  pending.set(key, refresh);
  return refresh;
}

في النشر متعدد النسخ، استخدم مخزناً مشتركاً أو ذاكرة إطار العمل. يبقى العقد واحداً: القيمة وبيانات مشاهدتها معاً، ولا تستبدلان إلا بعد نجاح الطلب.

فشل التحديث لا ينشئ سعراً جديداً

المهلة أو 429 أو خطأ المنبع ليست مشاهدة سوق. إن سمح المنتج بآخر قيمة صالحة، احتفظ بـdata_updated_at الأصلي وأظهر حالة الرجوع. من دون قيمة سابقة، أعد خطأ. احترم Retry-After واستخدم تراجعاً أسياً مع عشوائية للأخطاء المؤقتة؛ أما 401 فيتطلب إصلاح الإعداد.

typescript · explicit stale fallbackcopy
type ConverterResult =
  | { status: "fresh"; data: LatestRates }
  | { status: "last_known_good"; data: LatestRates; reason: string };

async function getConverterRates(
  base: string,
  symbols: string[],
): Promise<ConverterResult> {
  const key = cacheKey(base, symbols);

  try {
    return { status: "fresh", data: await getLatestRates(base, symbols) };
  } catch (error) {
    const previous = cache.get(key);
    if (!previous) throw error;

    return {
      status: "last_known_good",
      data: previous.value,
      reason: error instanceof Error ? error.message : "Refresh failed",
    };
  }
}

عند استجابة 429، احترم Retry-After إن وُجد، وامنع كل النسخ من إعادة المحاولة في الوقت نفسه. يناسب التراجع الأسي مع العشوائية الأعطال المؤقتة. أما أخطاء المصادقة فتتطلب إصلاح الإعداد، لا تكرار الطلب.

إغلاق السوق حالة صحيحة

قد تحمل استجابة market_session: weekend آخر مشاهدة من أسبوع التداول. لا تستبدل وقتها بوقت استجابة HTTP جديدة ولا تنشئ أسعاراً وهمية لعطلة نهاية الأسبوع.

يمكن للمحوّل إطالة مدة ذاكرة التطبيق أثناء إغلاق السوق، لكنه يجب أن يحدّث بعد استئناف أسبوع التداول. ابنِ القرار على حالة الجلسة المعادة ومتطلبات المنتج، لا على افتراض أن كل العملات تتبع جدولاً واحداً.

حوّل ثم قرّب مرة واحدة

عندما تكون القاعدة USD، يكون تحويل 25 USD إلى EUR هو 25 × rates.EUR. احتفظ بدقة السعر وقرّب المبلغ النهائي فقط وفق العملة الهدف.

استرشادي لا تنفيذي
يناسب سعر المحوّل العرض والتقدير والتحليل. يجب أن يأتي المبلغ المسدد من مزود الدفع أو جهة التداول مع الفارق والرسوم ووقت التنفيذ.

قائمة فحص الإنتاج

  • اطلب العملات التي تحتاجها الشاشة فقط.
  • كوّن مفتاحاً من العملة الأساس والرموز المرتبة.
  • خزّن الاستجابات الناجحة كاملة ولا تخزّن أخطاء HTTP.
  • امنع تكرار التحديثات المتزامنة.
  • اعرض data_updated_at عندما يهم عمر السعر.
  • احتفظ بـsources وmarket_session وderived_symbols.
  • أبق مفتاح API على الخادم.
  • عند استخدام آخر قيمة صالحة، احتفظ بوقتها الأصلي وأظهر حالتها.
ER
exchangerate.dev
أدلة بنية وتكامل للمطورين الذين يبنون باستخدام بيانات العملات.

تابع القراءة

Referenceقراءة source وmarket_sessionاقرأ Tutorialأسعار الصرف في Next.js وTypeScriptاقرأ Guideأسعار العملات الاسترشادية والتنفيذيةاقرأ
المزيد من المقارناتFixer vs exchangerate.devOpen Exchange Rates vs exchangerate.devCurrencylayer vs exchangerate.dev
تعلّمReading source and market_session in your pipelineIndicative vs executable FX rates: what a rates API actually gives youأسعار الصرف المرجعية للبنك المركزي الأوروبي، موضحة
أسعار مباشرةEUR/USDGBP/USDUSD/JPY