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

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.

نظرة عامة

بعد إرسال طلب الشحن، استطلع endpoint الحالة حتى تصل المعاملة إلى حالة نهائية. تُعالج API الطلبات عادةً في غضون 5-30 ثانية، وتمر بالحالات PENDING → HANDLING → FULFILLED/REFUNDED.
تحذير حرج: تعامل بشكل صحيح مع جميع قيم الحالة، وخاصةً UNKNOWN_ERROR. لا تُصدر استرداداً فورياً عند ظهور حالة غير معروفة!

API التحقق بالمرجع

GET /v3/mobile/check-ref/:ref - التتبع باستخدام مرجعك

API التحقق بالمعرّف

GET /v3/mobile/check-id/:id - التتبع باستخدام معرّف الشحن

قيم الحالة

فهم كل حالة أمر بالغ الأهمية للتعامل الصحيح:
راجع مرجع API التحقق بالمعرّف للاطلاع على أوصاف تفصيلية للحالات وحالات الاستخدام.
الحالةالمعنىالمدةالإجراء
PENDINGفي قائمة الانتظار للمعالجة2-15 ثانيةاستمر في الاستطلاع
HANDLINGقيد المعالجة3-8 ثوانياستمر في الاستطلاع
FULFILLEDاكتمل بنجاح ✅نهائيضع علامة مكتمل، أشعر المستخدم
REFUNDEDفشل وتم الاسترداد ❌نهائياسترد المبلغ للمستخدم، اعرض الخطأ
UNKNOWN_ERRORحالة غير مؤكدة ⚠️تُحسم خلال 1-24 ساعةانتظر، لا تسترد المبلغ بعد

التطبيق الأساسي للاستطلاع

async function checkTopUpStatus(ref) {
  const response = await fetch(
    `https://api.oneclickdz.com/v3/mobile/check-ref/${ref}`,
    {
      headers: {
        'X-Access-Token': process.env.ONECLICKDZ_API_KEY
      }
    }
  );
  
  if (!response.ok) {
    throw new Error(`HTTP error! status: ${response.status}`);
  }
  
  const data = await response.json();
  
  if (!data.success) {
    throw new Error('Failed to check status');
  }
  
  return data.data;
}

async function pollTopUpStatus(ref, maxAttempts = 60, intervalMs = 5000) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      const status = await checkTopUpStatus(ref);
      
      console.log(`[${attempt}/${maxAttempts}] Status: ${status.status}`);
      
      // Check if reached final state
      const finalStates = ['FULFILLED', 'REFUNDED', 'UNKNOWN_ERROR'];
      if (finalStates.includes(status.status)) {
        return status;
      }
      
      // Still processing, wait before next check
      if (attempt < maxAttempts) {
        await new Promise(resolve => setTimeout(resolve, intervalMs));
      }
    } catch (error) {
      console.error(`Attempt ${attempt} failed:`, error.message);
      
      // Continue polling even if one check fails
      if (attempt < maxAttempts) {
        await new Promise(resolve => setTimeout(resolve, intervalMs));
      }
    }
  }
  
  // Timeout
  return {
    status: 'TIMEOUT',
    message: 'Status check timeout. Check again later.'
  };
}

// Usage
const status = await pollTopUpStatus('order-123456');
console.log('Final status:', status.status);

التعامل مع حالات الحالة المختلفة

async function handleTopUpStatus(orderId, ref) {
  const status = await pollTopUpStatus(ref);
  
  switch (status.status) {
    case 'FULFILLED':
      // Success! Mark order as complete
      await db.orders.update({
        status: 'COMPLETED',
        completedAt: new Date()
      }, {
        where: { id: orderId }
      });
      
      // Notify user
      await notifyUser(orderId, 'success', 'Top-up completed successfully');
      
      console.log('✅ Top-up completed successfully');
      break;
      
    case 'REFUNDED':
      // Failed, refund user
      await db.orders.update({
        status: 'REFUNDED',
        refundMessage: status.refund_message,
        refundedAt: new Date()
      }, {
        where: { id: orderId }
      });
      
      // Refund user balance
      const order = await db.orders.findOne({ where: { id: orderId } });
      await refundUserBalance(order.userId, order.cost);
      
      // Notify user with error message (in Arabic)
      await notifyUser(orderId, 'failed', status.refund_message);
      
      // Show suggested offers if available
      if (status.suggested_offers && status.suggested_offers.length > 0) {
        console.log('Suggested alternatives:', status.suggested_offers);
        await showSuggestedOffers(orderId, status.suggested_offers);
      }
      
      console.log('❌ Top-up refunded:', status.refund_message);
      break;
      
    case 'UNKNOWN_ERROR':
      // Status uncertain - DO NOT REFUND YET!
      await db.orders.update({
        status: 'PENDING_VERIFICATION',
        verificationMessage: status.refund_message
      }, {
        where: { id: orderId }
      });
      
      // Show message to user (don't say it failed!)
      await notifyUser(orderId, 'pending', status.refund_message);
      
      // Schedule recheck in 1 hour
      await scheduleStatusRecheck(orderId, ref, Date.now() + 3600000);
      
      console.log('⚠️ Status unknown, will verify within 24h');
      break;
      
    case 'TIMEOUT':
      // Polling timeout - schedule recheck
      await db.orders.update({
        status: 'CHECKING'
      }, {
        where: { id: orderId }
      });
      
      // Schedule recheck in 5 minutes
      await scheduleStatusRecheck(orderId, ref, Date.now() + 300000);
      
      console.log('⏱️ Polling timeout, will check again later');
      break;
  }
}

استطلاع الحالة في الخلفية

للحصول على أداء أفضل، استطلع الحالة في مهام الخلفية:
const Queue = require('bull');
const pollQueue = new Queue('status-polling');

// Start polling job
async function startStatusPolling(orderId, ref) {
  await pollQueue.add({
    orderId,
    ref,
    attempt: 1
  }, {
    delay: 5000, // Start after 5 seconds
    attempts: 60,
    backoff: {
      type: 'fixed',
      delay: 5000
    }
  });
}

// Process polling jobs
pollQueue.process(async (job) => {
  const { orderId, ref, attempt } = job.data;
  
  console.log(`Checking status (attempt ${attempt})...`);
  
  const status = await checkTopUpStatus(ref);
  
  const finalStates = ['FULFILLED', 'REFUNDED', 'UNKNOWN_ERROR'];
  
  if (finalStates.includes(status.status)) {
    // Reached final state, handle it
    await handleTopUpStatus(orderId, ref);
    return { done: true, status: status.status };
  }
  
  // Still processing, continue polling
  if (attempt < 60) {
    await pollQueue.add({
      orderId,
      ref,
      attempt: attempt + 1
    }, {
      delay: 5000
    });
  } else {
    // Timeout
    await handleTopUpStatus(orderId, ref);
  }
  
  return { done: false, status: status.status };
});

إعادة الفحص المجدولة لـ UNKNOWN_ERROR

أنشئ مهمة cron يومية لإعادة فحص الطلبات غير المؤكدة:
const cron = require('node-cron');

// Run daily at midnight
cron.schedule('0 0 * * *', async () => {
  console.log('🔄 Checking uncertain orders...');
  
  // Find all orders with UNKNOWN_ERROR or PENDING_VERIFICATION
  // that are older than 1 hour
  const uncertainOrders = await db.orders.findAll({
    where: {
      status: {
        [db.Op.in]: ['UNKNOWN_ERROR', 'PENDING_VERIFICATION']
      },
      createdAt: {
        [db.Op.lt]: new Date(Date.now() - 3600000) // 1 hour ago
      }
    }
  });
  
  console.log(`Found ${uncertainOrders.length} orders to recheck`);
  
  for (const order of uncertainOrders) {
    try {
      const status = await checkTopUpStatus(order.ref);
      
      console.log(`Order ${order.id}: ${order.status}${status.status}`);
      
      // Update based on new status
      if (status.status === 'FULFILLED') {
        await db.orders.update({
          status: 'COMPLETED',
          completedAt: new Date()
        }, {
          where: { id: order.id }
        });
        
        await notifyUser(order.id, 'success', 'Top-up completed successfully');
        
      } else if (status.status === 'REFUNDED') {
        await db.orders.update({
          status: 'REFUNDED',
          refundMessage: status.refund_message,
          refundedAt: new Date()
        }, {
          where: { id: order.id }
        });
        
        // NOW we can refund
        await refundUserBalance(order.userId, order.cost);
        await notifyUser(order.id, 'failed', status.refund_message);
      }
      
    } catch (error) {
      console.error(`Failed to recheck order ${order.id}:`, error);
    }
  }
  
  console.log('✅ Recheck completed');
});

استراتيجية الاستطلاع المُحسَّنة

استخدم فترات استطلاع ذكية:
async function adaptivePollTopUpStatus(ref) {
  const intervals = [
    { attempts: 3, delay: 3000 },   // First 3 checks: every 3s
    { attempts: 5, delay: 5000 },   // Next 5 checks: every 5s
    { attempts: 12, delay: 10000 }, // Next 12 checks: every 10s
    { attempts: 40, delay: 30000 }  // Final checks: every 30s
  ];
  
  let totalAttempts = 0;
  
  for (const { attempts, delay } of intervals) {
    for (let i = 0; i < attempts; i++) {
      totalAttempts++;
      
      const status = await checkTopUpStatus(ref);
      
      console.log(`[${totalAttempts}] Status: ${status.status} (delay: ${delay}ms)`);
      
      const finalStates = ['FULFILLED', 'REFUNDED', 'UNKNOWN_ERROR'];
      if (finalStates.includes(status.status)) {
        return status;
      }
      
      // Wait before next check
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  
  return { status: 'TIMEOUT' };
}

أفضل الممارسات

انتظر حتى تُحسم الحالة إلى FULFILLED أو REFUNDED خلال 24 ساعة قبل معالجة الاستردادات.
استطلع الحالة بشكل غير متزامن لتجنب حجب طلبات المستخدمين وتحسين الأداء.
حدد حداً أقصى لمحاولات الاستطلاع (عادةً 60 = 5 دقائق) وأعد جدولة الفحوصات عند الحاجة.
احفظ الحالة في قاعدة بياناتك لتقليل استدعاءات API. استعلم API فقط عندما لا تكون الحالة نهائية.
اعرض دائماً refund_message للمستخدمين كما هو. إنه باللغة العربية ويشرح المشكلة بوضوح.
عند توفر suggested_offers، اعرض هذه البدائل لتحسين معدل التحويل.

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

استراتيجيات الاستطلاع

تعلّم تقنيات تحسين الاستطلاع المتقدمة

Webhooks

إعداد إشعارات الحالة الفورية بدلاً من الاستطلاع

API قائمة الشحنات

اطلع على جميع معاملات الشحن الخاصة بك

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

مرجع معالجة الأخطاء الكامل