نظرة عامة
بعد إرسال طلب الشحن، استطلع 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);
import requests
import time
import os
def check_topup_status(ref):
"""Check top-up status by reference"""
response = requests.get(
f'https://api.oneclickdz.com/v3/mobile/check-ref/{ref}',
headers={'X-Access-Token': os.getenv('ONECLICKDZ_API_KEY')}
)
response.raise_for_status()
data = response.json()
if not data['success']:
raise Exception('Failed to check status')
return data['data']
def poll_topup_status(ref, max_attempts=60, interval_seconds=5):
"""Poll top-up status until completion"""
for attempt in range(1, max_attempts + 1):
try:
status = check_topup_status(ref)
print(f'[{attempt}/{max_attempts}] Status: {status["status"]}')
# Check if reached final state
final_states = ['FULFILLED', 'REFUNDED', 'UNKNOWN_ERROR']
if status['status'] in final_states:
return status
# Still processing, wait before next check
if attempt < max_attempts:
time.sleep(interval_seconds)
except Exception as error:
print(f'Attempt {attempt} failed: {error}')
# Continue polling even if one check fails
if attempt < max_attempts:
time.sleep(interval_seconds)
# Timeout
return {
'status': 'TIMEOUT',
'message': 'Status check timeout. Check again later.'
}
# Usage
status = poll_topup_status('order-123456')
print(f'Final status: {status["status"]}')
<?php
function checkTopUpStatus($ref) {
$ch = curl_init("https://api.oneclickdz.com/v3/mobile/check-ref/{$ref}");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'X-Access-Token: ' . getenv('ONECLICKDZ_API_KEY')
]
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
throw new Exception("HTTP error! status: $httpCode");
}
$data = json_decode($response, true);
if (!$data['success']) {
throw new Exception('Failed to check status');
}
return $data['data'];
}
function pollTopUpStatus($ref, $maxAttempts = 60, $intervalSeconds = 5) {
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
try {
$status = checkTopUpStatus($ref);
echo "[{$attempt}/{$maxAttempts}] Status: {$status['status']}\n";
// Check if reached final state
$finalStates = ['FULFILLED', 'REFUNDED', 'UNKNOWN_ERROR'];
if (in_array($status['status'], $finalStates)) {
return $status;
}
// Still processing, wait before next check
if ($attempt < $maxAttempts) {
sleep($intervalSeconds);
}
} catch (Exception $error) {
echo "Attempt {$attempt} failed: {$error->getMessage()}\n";
// Continue polling even if one check fails
if ($attempt < $maxAttempts) {
sleep($intervalSeconds);
}
}
}
// Timeout
return [
'status' => 'TIMEOUT',
'message' => 'Status check timeout. Check again later.'
];
}
// Usage
$status = pollTopUpStatus('order-123456');
echo "Final status: {$status['status']}\n";
#!/bin/bash
API_KEY="YOUR_API_KEY"
REF="$1"
MAX_ATTEMPTS=60
INTERVAL=5
check_status() {
curl -s "https://api.oneclickdz.com/v3/mobile/check-ref/$1" \
-H "X-Access-Token: $API_KEY" | jq -r '.data.status'
}
poll_status() {
for attempt in $(seq 1 $MAX_ATTEMPTS); do
status=$(check_status "$1")
echo "[$attempt/$MAX_ATTEMPTS] Status: $status"
# Check if final state
if [[ "$status" == "FULFILLED" ]] || \
[[ "$status" == "REFUNDED" ]] || \
[[ "$status" == "UNKNOWN_ERROR" ]]; then
echo "Final status: $status"
return 0
fi
# Wait before next check
if [ $attempt -lt $MAX_ATTEMPTS ]; then
sleep $INTERVAL
fi
done
echo "Timeout after $MAX_ATTEMPTS attempts"
return 1
}
poll_status "$REF"
التعامل مع حالات الحالة المختلفة
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;
}
}
async def handle_topup_status(order_id, ref, db):
"""Handle different top-up status outcomes"""
status = poll_topup_status(ref)
if status['status'] == 'FULFILLED':
# Success! Mark order as complete
db.orders.update_one(
{'_id': order_id},
{
'$set': {
'status': 'COMPLETED',
'completed_at': datetime.now()
}
}
)
# Notify user
notify_user(order_id, 'success', 'Top-up completed successfully')
print('✅ Top-up completed successfully')
elif status['status'] == 'REFUNDED':
# Failed, refund user
db.orders.update_one(
{'_id': order_id},
{
'$set': {
'status': 'REFUNDED',
'refund_message': status.get('refund_message'),
'refunded_at': datetime.now()
}
}
)
# Refund user balance
order = db.orders.find_one({'_id': order_id})
refund_user_balance(order['user_id'], order['cost'])
# Notify user
notify_user(order_id, 'failed', status.get('refund_message'))
# Show suggested offers
if status.get('suggested_offers'):
print('Suggested alternatives:', status['suggested_offers'])
show_suggested_offers(order_id, status['suggested_offers'])
print(f"❌ Top-up refunded: {status.get('refund_message')}")
elif status['status'] == 'UNKNOWN_ERROR':
# Status uncertain - DO NOT REFUND YET!
db.orders.update_one(
{'_id': order_id},
{
'$set': {
'status': 'PENDING_VERIFICATION',
'verification_message': status.get('refund_message')
}
}
)
# Notify user (don't say it failed!)
notify_user(order_id, 'pending', status.get('refund_message'))
# Schedule recheck in 1 hour
schedule_status_recheck(order_id, ref, time.time() + 3600)
print('⚠️ Status unknown, will verify within 24h')
elif status['status'] == 'TIMEOUT':
# Polling timeout
db.orders.update_one(
{'_id': order_id},
{'$set': {'status': 'CHECKING'}}
)
# Schedule recheck in 5 minutes
schedule_status_recheck(order_id, ref, time.time() + 300)
print('⏱️ Polling timeout, will check again later')
<?php
function handleTopUpStatus($orderId, $ref, $db) {
$status = pollTopUpStatus($ref);
switch ($status['status']) {
case 'FULFILLED':
// Success! Mark order as complete
$db->query(
"UPDATE orders SET status = 'COMPLETED', completed_at = NOW() WHERE id = ?",
[$orderId]
);
// Notify user
notifyUser($orderId, 'success', 'Top-up completed successfully');
echo "✅ Top-up completed successfully\n";
break;
case 'REFUNDED':
// Failed, refund user
$db->query(
"UPDATE orders
SET status = 'REFUNDED', refund_message = ?, refunded_at = NOW()
WHERE id = ?",
[$status['refund_message'] ?? null, $orderId]
);
// Refund user balance
$order = $db->query("SELECT * FROM orders WHERE id = ?", [$orderId])
->fetch(PDO::FETCH_ASSOC);
refundUserBalance($order['user_id'], $order['cost'], $db);
// Notify user
notifyUser($orderId, 'failed', $status['refund_message'] ?? 'Top-up failed');
// Show suggested offers
if (!empty($status['suggested_offers'])) {
echo "Suggested alternatives: " . json_encode($status['suggested_offers']) . "\n";
showSuggestedOffers($orderId, $status['suggested_offers']);
}
echo "❌ Top-up refunded: {$status['refund_message']}\n";
break;
case 'UNKNOWN_ERROR':
// Status uncertain - DO NOT REFUND YET!
$db->query(
"UPDATE orders
SET status = 'PENDING_VERIFICATION', verification_message = ?
WHERE id = ?",
[$status['refund_message'] ?? null, $orderId]
);
// Notify user (don't say it failed!)
notifyUser($orderId, 'pending', $status['refund_message'] ?? 'Verifying status...');
// Schedule recheck in 1 hour
scheduleStatusRecheck($orderId, $ref, time() + 3600);
echo "⚠️ Status unknown, will verify within 24h\n";
break;
case 'TIMEOUT':
// Polling timeout
$db->query("UPDATE orders SET status = 'CHECKING' WHERE id = ?", [$orderId]);
// Schedule recheck in 5 minutes
scheduleStatusRecheck($orderId, $ref, time() + 300);
echo "⏱️ Polling timeout, will check again later\n";
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 };
});
from celery import Celery
app = Celery('polling', broker='redis://localhost:6379/0')
@app.task(bind=True, max_retries=60)
def poll_status_task(self, order_id, ref):
"""Poll status asynchronously"""
try:
status = check_topup_status(ref)
print(f'Checking status (attempt {self.request.retries + 1})...')
final_states = ['FULFILLED', 'REFUNDED', 'UNKNOWN_ERROR']
if status['status'] in final_states:
# Reached final state
handle_topup_status(order_id, ref, db)
return {'done': True, 'status': status['status']}
# Still processing, retry in 5 seconds
raise self.retry(countdown=5)
except Exception as error:
if self.request.retries >= self.max_retries:
# Timeout
handle_topup_status(order_id, ref, db)
return {'done': True, 'status': 'TIMEOUT'}
# Retry
raise self.retry(countdown=5, exc=error)
# Start polling
def start_status_polling(order_id, ref):
poll_status_task.apply_async(args=[order_id, ref], countdown=5)
إعادة الفحص المجدولة لـ 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');
});
import schedule
import time
from datetime import datetime, timedelta
def recheck_uncertain_orders():
"""Daily cronjob to recheck uncertain orders"""
print('🔄 Checking uncertain orders...')
# Find uncertain orders older than 1 hour
one_hour_ago = datetime.now() - timedelta(hours=1)
uncertain_orders = db.orders.find({
'status': {'$in': ['UNKNOWN_ERROR', 'PENDING_VERIFICATION']},
'created_at': {'$lt': one_hour_ago}
})
uncertain_orders = list(uncertain_orders)
print(f'Found {len(uncertain_orders)} orders to recheck')
for order in uncertain_orders:
try:
status = check_topup_status(order['ref'])
print(f"Order {order['_id']}: {order['status']} → {status['status']}")
if status['status'] == 'FULFILLED':
db.orders.update_one(
{'_id': order['_id']},
{
'$set': {
'status': 'COMPLETED',
'completed_at': datetime.now()
}
}
)
notify_user(order['_id'], 'success', 'Top-up completed successfully')
elif status['status'] == 'REFUNDED':
db.orders.update_one(
{'_id': order['_id']},
{
'$set': {
'status': 'REFUNDED',
'refund_message': status.get('refund_message'),
'refunded_at': datetime.now()
}
}
)
# NOW we can refund
refund_user_balance(order['user_id'], order['cost'])
notify_user(order['_id'], 'failed', status.get('refund_message'))
except Exception as error:
print(f"Failed to recheck order {order['_id']}: {error}")
print('✅ Recheck completed')
# Schedule daily at midnight
schedule.every().day.at("00:00").do(recheck_uncertain_orders)
# Keep running
while True:
schedule.run_pending()
time.sleep(60)
#!/bin/bash
# Save as: /usr/local/bin/recheck-topups.sh
# Add to crontab: 0 0 * * * /usr/local/bin/recheck-topups.sh
echo "🔄 Checking uncertain orders..."
# Query database for uncertain orders (adjust based on your DB)
# This is a pseudo-example
uncertain_orders=$(mysql -u user -p'pass' -D db -se \
"SELECT id, ref FROM orders
WHERE status IN ('UNKNOWN_ERROR', 'PENDING_VERIFICATION')
AND created_at < DATE_SUB(NOW(), INTERVAL 1 HOUR)")
while IFS=$'\t' read -r order_id ref; do
echo "Checking order $order_id ($ref)..."
status=$(curl -s "https://api.oneclickdz.com/v3/mobile/check-ref/$ref" \
-H "X-Access-Token: $API_KEY" | jq -r '.data.status')
echo "Status: $status"
if [ "$status" = "FULFILLED" ]; then
mysql -u user -p'pass' -D db -e \
"UPDATE orders SET status='COMPLETED', completed_at=NOW() WHERE id=$order_id"
echo "✅ Order $order_id completed"
elif [ "$status" = "REFUNDED" ]; then
mysql -u user -p'pass' -D db -e \
"UPDATE orders SET status='REFUNDED', refunded_at=NOW() WHERE id=$order_id"
# Trigger refund process
echo "❌ Order $order_id refunded"
fi
done <<< "$uncertain_orders"
echo "✅ 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' };
}
def adaptive_poll_topup_status(ref):
"""Poll with adaptive intervals"""
intervals = [
{'attempts': 3, 'delay': 3}, # First 3 checks: every 3s
{'attempts': 5, 'delay': 5}, # Next 5 checks: every 5s
{'attempts': 12, 'delay': 10}, # Next 12 checks: every 10s
{'attempts': 40, 'delay': 30} # Final checks: every 30s
]
total_attempts = 0
for interval in intervals:
for i in range(interval['attempts']):
total_attempts += 1
status = check_topup_status(ref)
print(f"[{total_attempts}] Status: {status['status']} (delay: {interval['delay']}s)")
final_states = ['FULFILLED', 'REFUNDED', 'UNKNOWN_ERROR']
if status['status'] in final_states:
return status
# Wait before next check
time.sleep(interval['delay'])
return {'status': 'TIMEOUT'}
أفضل الممارسات
لا تسترد UNKNOWN_ERROR فوراً
لا تسترد UNKNOWN_ERROR فوراً
انتظر حتى تُحسم الحالة إلى FULFILLED أو REFUNDED خلال 24 ساعة قبل معالجة الاستردادات.
استخدام مهام الخلفية
استخدام مهام الخلفية
استطلع الحالة بشكل غير متزامن لتجنب حجب طلبات المستخدمين وتحسين الأداء.
تطبيق حماية من انتهاء المهلة
تطبيق حماية من انتهاء المهلة
حدد حداً أقصى لمحاولات الاستطلاع (عادةً 60 = 5 دقائق) وأعد جدولة الفحوصات عند الحاجة.
تخزين الحالة في قاعدة البيانات
تخزين الحالة في قاعدة البيانات
احفظ الحالة في قاعدة بياناتك لتقليل استدعاءات API. استعلم API فقط عندما لا تكون الحالة نهائية.
عرض الرسائل باللغة العربية
عرض الرسائل باللغة العربية
اعرض دائماً
refund_message للمستخدمين كما هو. إنه باللغة العربية ويشرح المشكلة بوضوح.التعامل مع العروض المقترحة
التعامل مع العروض المقترحة
عند توفر
suggested_offers، اعرض هذه البدائل لتحسين معدل التحويل.الخطوات التالية
استراتيجيات الاستطلاع
تعلّم تقنيات تحسين الاستطلاع المتقدمة
Webhooks
إعداد إشعارات الحالة الفورية بدلاً من الاستطلاع
API قائمة الشحنات
اطلع على جميع معاملات الشحن الخاصة بك
معالجة الأخطاء
مرجع معالجة الأخطاء الكامل

