الانتقال إلى المحتوى الرئيسي

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.

بنية استجابة الخطأ

تتبع جميع الأخطاء تنسيقًا موحدًا:
{
  "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غير متاحالخدمة غير متاحة مؤقتًا

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

MISSING_ACCESS_TOKEN

  • الرسالة: Access token is required
  • السبب: ترويسة X-Access-Token مفقودة
  • الإجراء: أدرج مفتاح API في الترويسة
// ✅ Correct
fetch(url, {
  headers: { 'X-Access-Token': 'YOUR_API_KEY' }
})

INVALID_ACCESS_TOKEN / ERR_AUTH

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

NO_BALANCE / INSUFFICIENT_BALANCE

  • الرسالة: Insufficient balance
  • السبب: رصيد الحساب منخفض جدًا
  • الإجراء:
    • تحقق من الرصيد عبر /v3/account/balance قبل العمليات
    • اعرض الرصيد الحالي للمستخدم
    • اعرض خيار الشحن
    • لا تعيد المحاولة دون إضافة أموال
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
  • السبب: تم استخدام المرجع في طلب سابق
  • الإجراء:
    • تحقق من حالة الطلب الموجود
    • أنشئ مرجعًا فريدًا جديدًا
    • لا تنشئ طلبات مكررة
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 الخاص بك إلى القائمة البيضاء في لوحة التحكم

ERR_VALIDATION

  • الرسالة: Validation error
  • السبب: معلمات الطلب لا تستوفي المتطلبات
  • المشكلات الشائعة: حقول مفقودة، أنواع بيانات غير صالحة، عدم تطابق النمط
  • الإجراء:
    • تحقق من المدخلات على جانب العميل أولًا
    • تحقق من error.details للمشكلات الخاصة بالحقول
    • لا تعيد المحاولة دون إصلاح المشكلة
// 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 للتحقق أولًا
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
  • السبب: المنتج/قيمة البطاقة المطلوبة غير متاحة
  • الإجراء:
    • تحقق من المخزون قبل الطلب
    • اعرض فئات بديلة
    • حاول مرة أخرى لاحقًا

NOT_FOUND

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

RATE_LIMIT_EXCEEDED

  • الرسالة: Too many requests
  • السبب: تجاوز حد المعدل
  • الحدود: Sandbox: 60 طلب/دقيقة، الإنتاج: 120 طلب/دقيقة
  • الإجراء: نفّذ تراجعًا أسيًا مع منطق إعادة المحاولة
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');
}

INTERNAL_SERVER_ERROR / INTERNAL_ERROR

  • الرسالة: Developer was notified and will check shortly
  • السبب: خطأ غير متوقع على خوادمنا
  • الإجراء:
    • لا تستردّ الأموال فورًا - انتظر 24 ساعة
    • احفظ requestId للدعم الفني
    • نفّذ منطق إعادة المحاولة مع تراجع
    • تواصل مع الدعم بالتفاصيل
    • نحن نُخطَر تلقائيًا
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
  • السبب: صيانة الخدمة أو مشكلة مؤقتة
  • الإجراء:
    • اعرض رسالة صيانة
    • أعد المحاولة بعد تأخير
    • راقب للحصول على حل

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

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

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. تعامل مع رموز الخطأ المحددة

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. منطق إعادة المحاولة الذكي مع التراجع الأسي

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. رسائل خطأ مناسبة للمستخدم

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

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)

امنع الإخفاقات المتتالية بإيقاف الطلبات عند ارتفاع معدل الخطأ:
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;
    }
  }
}

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

تتبّع أنماط الأخطاء للكشف المبكر عن المشكلات:
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

هام: لا تُصدر أي استرداد فور ظهور UNKNOWN_ERROR. انتظر دائماً 24 ساعة لحل المشكلة.
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 لاختبار معالجة الأخطاء:
// 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

مرجع سريع

التحقق المبكر

تحقق من المدخلات من جهة العميل قبل استدعاءات API

إعادة المحاولة بذكاء

استخدم backoff الأسي لأخطاء 5xx، ولا تُعِد المحاولة أبداً لأخطاء 4xx

سجّل السياق

أدرج دائماً requestId والسياق في السجلات لتسهيل التصحيح

ردود فعل واضحة

أظهر للمستخدمين رسائل خطأ واضحة وقابلة للتنفيذ

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

Endpoints الأساسية

استكشف جميع الـ endpoints

تنسيق الاستجابة

فهم استجابات API