> ## Documentation Index
> Fetch the complete documentation index at: https://docs.oneclickdz.com/llms.txt
> Use this file to discover all available pages before exploring further.

# معالجة الأخطاء

> دليل شامل للتعامل مع الأخطاء بفعالية مع Flexy API

<div dir="rtl">
  ## بنية استجابة الخطأ

  تتبع جميع الأخطاء تنسيقًا موحدًا:

  ```json theme={null}
  {
    "success": false,
    "error": {
      "code": "ERROR_CODE",
      "message": "Human-readable error message",
      "details": {
        // additional context
      }
    },
    "requestId": "req_abc123"
  }
  ```

  ## رموز حالة HTTP

  | الحالة | الفئة      | الوصف                                 |
  | ------ | ---------- | ------------------------------------- |
  | `400`  | خطأ العميل | معلمات طلب غير صالحة أو خطأ في التحقق |
  | `401`  | المصادقة   | مفتاح API غير صالح أو مفقود           |
  | `403`  | التفويض    | صلاحيات أو رصيد غير كافٍ              |
  | `404`  | غير موجود  | المورد غير موجود                      |
  | `429`  | حد المعدل  | طلبات كثيرة جدًا                      |
  | `500`  | خطأ الخادم | خطأ داخلي في الخادم                   |
  | `503`  | غير متاح   | الخدمة غير متاحة مؤقتًا               |

  ## رموز الخطأ الشائعة

  <AccordionGroup>
    <Accordion title="أخطاء المصادقة (401)">
      ### MISSING\_ACCESS\_TOKEN

      * **الرسالة**: Access token is required
      * **السبب**: ترويسة `X-Access-Token` مفقودة
      * **الإجراء**: أدرج مفتاح API في الترويسة

      ```javascript theme={null}
      // ✅ Correct
      fetch(url, {
        headers: { 'X-Access-Token': 'YOUR_API_KEY' }
      })
      ```

      ### INVALID\_ACCESS\_TOKEN / ERR\_AUTH

      * **الرسالة**: The provided access token is invalid
      * **السبب**: مفتاح API غير صحيح أو منتهي الصلاحية أو ملغى
      * **الإجراء**:
        * تحقق من صحة مفتاح API
        * أنشئ مفتاحًا جديدًا إذا لزم الأمر
        * لا تسجّل قيم المفاتيح الحساسة
        * تواصل مع الدعم إذا استمرت المشكلة
    </Accordion>

    <Accordion title="أخطاء التفويض (403)">
      ### NO\_BALANCE / INSUFFICIENT\_BALANCE

      * **الرسالة**: Insufficient balance
      * **السبب**: رصيد الحساب منخفض جدًا
      * **الإجراء**:
        * تحقق من الرصيد عبر `/v3/account/balance` قبل العمليات
        * اعرض الرصيد الحالي للمستخدم
        * اعرض خيار الشحن
        * لا تعيد المحاولة دون إضافة أموال

      ```javascript theme={null}
      const balanceRes = await fetch('https://api.oneclickdz.com/v3/account/balance', {
        headers: { 'X-Access-Token': API_KEY }
      });
      const { data } = await balanceRes.json();

      if (data.balance < requiredAmount) {
        throw new Error('Insufficient balance');
      }
      ```

      ### DUPLICATED\_REF

      * **الرسالة**: This reference ID is already in use
      * **السبب**: تم استخدام المرجع في طلب سابق
      * **الإجراء**:
        * تحقق من حالة الطلب الموجود
        * أنشئ مرجعًا فريدًا جديدًا
        * لا تنشئ طلبات مكررة

      ```javascript theme={null}
      const ref = `order-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
      ```

      ### IP\_BLOCKED

      * **الرسالة**: Your IP has been temporarily blocked
      * **السبب**: محاولات مصادقة فاشلة كثيرة جدًا
      * **الإجراء**:
        * انتظر 15 دقيقة لإلغاء الحظر التلقائي
        * تحقق من صحة مفتاح API
        * تواصل مع الدعم إذا استمر الأمر

      ### IP\_NOT\_ALLOWED

      * **الرسالة**: Your IP address is not whitelisted
      * **السبب**: قائمة IP البيضاء مفعّلة
      * **الإجراء**: أضف عنوان IP الخاص بك إلى القائمة البيضاء في لوحة التحكم
    </Accordion>

    <Accordion title="أخطاء التحقق (400)">
      ### ERR\_VALIDATION

      * **الرسالة**: Validation error
      * **السبب**: معلمات الطلب لا تستوفي المتطلبات
      * **المشكلات الشائعة**: حقول مفقودة، أنواع بيانات غير صالحة، عدم تطابق النمط
      * **الإجراء**:
        * تحقق من المدخلات على جانب العميل أولًا
        * تحقق من `error.details` للمشكلات الخاصة بالحقول
        * لا تعيد المحاولة دون إصلاح المشكلة

      ```javascript theme={null}
      // Validate before sending
      function validateTopupRequest(data) {
        const errors = [];
        
        if (!data.plan_code) {
          errors.push({ field: 'plan_code', message: 'Plan code is required' });
        }
        
        if (!data.MSSIDN || !/^0[567][0-9]{8}$/.test(data.MSSIDN)) {
          errors.push({ field: 'MSSIDN', message: 'Invalid phone number format' });
        }
        
        if (isDynamicPlan(data.plan_code)) {
          if (!data.amount || data.amount < 50 || data.amount > 5000) {
            errors.push({ field: 'amount', message: 'Amount must be between 50 and 5000' });
          }
        }
        
        return errors.length > 0 ? { valid: false, errors } : { valid: true };
      }
      ```

      ### ERR\_PHONE

      * **الرسالة**: Invalid phone number
      * **السبب**: رقم الهاتف غير صحيح أو غير موجود
      * **الإجراء**: استخدم `/v3/internet/check-number` للتحقق أولًا

      ```javascript theme={null}
      const checkRes = await fetch(
        `https://api.oneclickdz.com/v3/internet/check-number?type=ADSL&number=${number}`,
        { headers: { 'X-Access-Token': API_KEY } }
      );

      if (!checkRes.ok) {
        throw new Error('Invalid phone number');
      }
      ```

      ### ERR\_STOCK

      * **الرسالة**: Product out of stock
      * **السبب**: المنتج/قيمة البطاقة المطلوبة غير متاحة
      * **الإجراء**:
        * تحقق من المخزون قبل الطلب
        * اعرض فئات بديلة
        * حاول مرة أخرى لاحقًا
    </Accordion>

    <Accordion title="أخطاء غير موجود (404)">
      ### NOT\_FOUND

      * **الرسالة**: Resource not found
      * **السبب**: المورد المطلوب غير موجود
      * **الحالات الشائعة**: معرّف طلب غير صالح، مرجع غير صالح، مورد محذوف
      * **الإجراء**: تحقق من صحة المعرّف/المرجع، تحقق من الأخطاء الإملائية، تعامل بأناقة في واجهة المستخدم
    </Accordion>

    <Accordion title="أخطاء حد المعدل (429)">
      ### RATE\_LIMIT\_EXCEEDED

      * **الرسالة**: Too many requests
      * **السبب**: تجاوز حد المعدل
      * **الحدود**: Sandbox: 60 طلب/دقيقة، الإنتاج: 120 طلب/دقيقة
      * **الإجراء**: نفّذ تراجعًا أسيًا مع منطق إعادة المحاولة

      ```javascript theme={null}
      async function fetchWithRetry(url, options, maxRetries = 3) {
        for (let i = 0; i < maxRetries; i++) {
          const response = await fetch(url, options);
          
          if (response.status === 429) {
            const retryAfter = response.headers.get('Retry-After') || Math.pow(2, i);
            await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
            continue;
          }
          
          return response;
        }
        
        throw new Error('Max retries exceeded');
      }
      ```
    </Accordion>

    <Accordion title="أخطاء الخادم (500/503)">
      ### INTERNAL\_SERVER\_ERROR / INTERNAL\_ERROR

      * **الرسالة**: Developer was notified and will check shortly
      * **السبب**: خطأ غير متوقع على خوادمنا
      * **الإجراء**:
        * **لا تستردّ الأموال فورًا - انتظر 24 ساعة**
        * احفظ requestId للدعم الفني
        * نفّذ منطق إعادة المحاولة مع تراجع
        * تواصل مع الدعم بالتفاصيل
        * نحن نُخطَر تلقائيًا

      ```javascript theme={null}
      if (response.status === 500) {
        console.error('Server error:', data.requestId);
        // Mark for review - don't refund yet
        await scheduleStatusCheck(orderId, 24 * 60 * 60 * 1000);
      }
      ```

      ### ERR\_SERVICE

      * **الرسالة**: Service temporarily unavailable
      * **السبب**: صيانة الخدمة أو مشكلة مؤقتة
      * **الإجراء**:
        * اعرض رسالة صيانة
        * أعد المحاولة بعد تأخير
        * راقب للحصول على حل
    </Accordion>
  </AccordionGroup>

  ## أفضل ممارسات التنفيذ

  ### 1. تحقق دائمًا من حقل success

  ```javascript theme={null}
  const response = await fetch(url, options);
  const data = await response.json();

  if (data.success) {
    return data.data;
  } else {
    throw new Error(`${data.error.code}: ${data.error.message}`);
  }
  ```

  ### 2. تعامل مع رموز الخطأ المحددة

  ```javascript theme={null}
  async function sendTopUp(params) {
    const response = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Access-Token": API_KEY,
      },
      body: JSON.stringify(params),
    });

    const data = await response.json();

    if (!data.success) {
      switch (data.error.code) {
        case "NO_BALANCE":
        case "INSUFFICIENT_BALANCE":
          throw new Error("Insufficient balance. Please add funds.");

        case "DUPLICATED_REF":
          return await checkOrderStatus(params.ref);

        case "ERR_VALIDATION":
          throw new Error(`Invalid input: ${data.error.message}`);

        case "INTERNAL_SERVER_ERROR":
        case "INTERNAL_ERROR":
          await scheduleStatusCheck(params.ref);
          throw new Error("Temporary error, checking status later");

        default:
          throw new Error(`${data.error.code}: ${data.error.message}`);
      }
    }

    return data.data;
  }
  ```

  ### 3. منطق إعادة المحاولة الذكي مع التراجع الأسي

  ```javascript theme={null}
  async function safeApiCall(apiFunction, options = {}) {
    const { maxRetries = 3, retryDelay = 1000, retryOn = [500, 503] } = options;

    let lastError;

    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      try {
        return await apiFunction();
      } catch (error) {
        lastError = error;

        // Don't retry on client errors (4xx)
        if (error.status >= 400 && error.status < 500) {
          throw error;
        }

        // Retry on specific status codes
        if (retryOn.includes(error.status) && attempt < maxRetries) {
          const delay = retryDelay * Math.pow(2, attempt - 1);
          console.log(`Retry ${attempt}/${maxRetries} after ${delay}ms`);
          await new Promise((resolve) => setTimeout(resolve, delay));
          continue;
        }

        throw error;
      }
    }

    throw lastError;
  }
  ```

  ### 4. رسائل خطأ مناسبة للمستخدم

  ```javascript theme={null}
  const errorMessages = {
    MISSING_ACCESS_TOKEN: "Authentication required. Please check your API key.",
    INVALID_ACCESS_TOKEN: "Invalid API key. Please verify your credentials.",
    ERR_AUTH: "Authentication failed. Please check your API key.",
    NO_BALANCE: "Insufficient balance. Please top up your account.",
    INSUFFICIENT_BALANCE: "Insufficient balance. Please top up your account.",
    DUPLICATED_REF: "This order was already processed.",
    ERR_VALIDATION: "Please check your input and try again.",
    ERR_PHONE: "Invalid phone number. Please verify and try again.",
    ERR_STOCK: "This product is currently out of stock.",
    NOT_FOUND: "The requested resource was not found.",
    RATE_LIMIT_EXCEEDED: "Too many requests. Please wait a moment and try again.",
    ERR_SERVICE: "Service temporarily unavailable. Please try again.",
    INTERNAL_SERVER_ERROR: "An error occurred. Our team has been notified.",
    INTERNAL_ERROR: "An error occurred. Our team has been notified.",
  };

  function getUserFriendlyMessage(errorCode, defaultMessage) {
    return (
      errorMessages[errorCode] ||
      defaultMessage ||
      "An unexpected error occurred."
    );
  }
  ```

  ### 5. سجّل Request IDs

  ```javascript theme={null}
  function logError(error, context) {
    const errorLog = {
      timestamp: new Date().toISOString(),
      requestId: error.requestId,
      code: error.code,
      message: error.message,
      context: {
        userId: context.userId,
        operation: context.operation,
      },
    };

    logger.error("API Error", errorLog);

    if (error.status >= 500) {
      alertOps("API Error", errorLog);
    }
  }
  ```

  ## أنماط متقدمة

  ### نمط قاطع الدائرة (Circuit Breaker)

  امنع الإخفاقات المتتالية بإيقاف الطلبات عند ارتفاع معدل الخطأ:

  ```javascript theme={null}
  class CircuitBreaker {
    constructor(threshold = 5, timeout = 60000) {
      this.failureCount = 0;
      this.threshold = threshold;
      this.timeout = timeout;
      this.state = "CLOSED"; // CLOSED, OPEN, HALF_OPEN
      this.nextAttempt = Date.now();
    }

    async execute(fn) {
      if (this.state === "OPEN") {
        if (Date.now() < this.nextAttempt) {
          throw new Error("Circuit breaker is OPEN");
        }
        this.state = "HALF_OPEN";
      }

      try {
        const result = await fn();
        this.onSuccess();
        return result;
      } catch (error) {
        this.onFailure();
        throw error;
      }
    }

    onSuccess() {
      this.failureCount = 0;
      this.state = "CLOSED";
    }

    onFailure() {
      this.failureCount++;
      if (this.failureCount >= this.threshold) {
        this.state = "OPEN";
        this.nextAttempt = Date.now() + this.timeout;
      }
    }
  }
  ```

  ### مراقبة الأخطاء

  تتبّع أنماط الأخطاء للكشف المبكر عن المشكلات:

  ```javascript theme={null}
  class ApiClient {
    constructor(apiKey) {
      this.apiKey = apiKey;
      this.errorStats = new Map();
    }

    trackError(errorCode) {
      const count = this.errorStats.get(errorCode) || 0;
      this.errorStats.set(errorCode, count + 1);
    }

    async request(url, options) {
      try {
        const response = await fetch(url, {
          ...options,
          headers: {
            ...options.headers,
            "X-Access-Token": this.apiKey,
          },
        });

        const data = await response.json();

        if (!data.success) {
          this.trackError(data.error.code);

          // Alert if too many errors
          if (this.errorStats.get(data.error.code) > 10) {
            this.sendAlert(`High error rate: ${data.error.code}`);
          }
        }

        return data;
      } catch (error) {
        this.trackError("NETWORK_ERROR");
        throw error;
      }
    }

    sendAlert(message) {
      console.error("ALERT:", message);
      // Send to your monitoring service
    }
  }
  ```

  ### معالجة UNKNOWN\_ERROR

  <Warning>
    **هام:** لا تُصدر أي استرداد فور ظهور UNKNOWN\_ERROR. انتظر دائماً 24 ساعة لحل المشكلة.
  </Warning>

  ```javascript theme={null}
  async function handleUnknownError(orderId, topupId) {
    // Mark for review
    await db.orders.update({
      where: { id: orderId },
      data: {
        status: "REVIEW_NEEDED",
        reviewReason: "UNKNOWN_ERROR from API",
        reviewScheduled: new Date(Date.now() + 24 * 60 * 60 * 1000),
      },
    });

    // Show message to user
    await notifyUser(orderId, {
      title: "Order Under Review",
      message: "Your order is being verified. Status will update within 24 hours.",
    });

    // Schedule automatic recheck
    await scheduleJob("recheck-unknown-error", {
      orderId,
      topupId,
      runAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
    });
  }
  ```

  ## اختبار سيناريوهات الخطأ

  استخدم وضع Sandbox لاختبار معالجة الأخطاء:

  ```javascript theme={null}
  // Test insufficient balance
  // Set your sandbox balance very low

  // Test validation errors
  await sendTopUp({
    plan_code: 'INVALID_PLAN',
    MSSIDN: '123456789',     // Invalid format
    amount: -100             // Negative amount
  });

  // Test duplicate reference
  const ref = 'test-duplicate-123';
  await sendTopUp({ ref, ... });  // First request
  await sendTopUp({ ref, ... });  // Should fail with DUPLICATED_REF
  ```

  ## مرجع سريع

  <CardGroup cols={2}>
    <Card title="التحقق المبكر" icon="circle-check">
      تحقق من المدخلات من جهة العميل قبل استدعاءات API
    </Card>

    <Card title="إعادة المحاولة بذكاء" icon="arrows-rotate">
      استخدم backoff الأسي لأخطاء 5xx، ولا تُعِد المحاولة أبداً لأخطاء 4xx
    </Card>

    <Card title="سجّل السياق" icon="file-lines">
      أدرج دائماً requestId والسياق في السجلات لتسهيل التصحيح
    </Card>

    <Card title="ردود فعل واضحة" icon="message">
      أظهر للمستخدمين رسائل خطأ واضحة وقابلة للتنفيذ
    </Card>
  </CardGroup>

  ## الخطوات التالية

  <CardGroup cols={2}>
    <Card title="Endpoints الأساسية" icon="code" href="/ar/api-reference/core/health-check">
      استكشف جميع الـ endpoints
    </Card>

    <Card title="تنسيق الاستجابة" icon="file-code" href="/ar/api-reference/response-format">
      فهم استجابات API
    </Card>
  </CardGroup>
</div>
