Vue d’ensemble
Après l’envoi d’une demande de recharge, interrogez l’endpoint de statut jusqu’à ce que la transaction atteigne un état final. L’API traite généralement les demandes en 5 à 30 secondes, en passant par les états PENDING → HANDLING → FULFILLED/REFUNDED.Critique : Gérez correctement toutes les valeurs de statut, en particulier
UNKNOWN_ERROR. Ne remboursez jamais immédiatement en cas de statut inconnu !API Vérification par référence
GET /v3/mobile/check-ref/:ref - Suivi avec votre référence
API Vérification par ID
GET /v3/mobile/check-id/:id - Suivi avec l’ID de recharge
Valeurs de statut
La compréhension de chaque statut est essentielle pour une gestion correcte :Consultez la Référence API Vérification par ID pour des descriptions détaillées des statuts et leurs cas d’usage.
| Statut | Signification | Durée | Action |
|---|---|---|---|
| PENDING | En file d’attente pour traitement | 2-15 secondes | Continuer l’interrogation |
| HANDLING | En cours de traitement | 3-8 secondes | Continuer l’interrogation |
| FULFILLED | Terminé avec succès ✅ | Final | Marquer comme terminé, notifier l’utilisateur |
| REFUNDED | Échoué et remboursé ❌ | Final | Rembourser l’utilisateur, afficher l’erreur |
| UNKNOWN_ERROR | Statut incertain ⚠️ | Se résout dans 1-24h | Attendre, ne pas rembourser encore |
Implémentation de base de l’interrogation
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"
Gérer les différents états
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;
}
}
Interrogation de statut en arrière-plan
Pour de meilleures performances, interrogez le statut dans des tâches en arrière-plan :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)
Revérification planifiée pour UNKNOWN_ERROR
Configurez une tâche cron quotidienne pour revérifier les commandes incertaines :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"
Stratégie d’interrogation optimisée
Utilisez des intervalles d’interrogation intelligents :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'}
Bonnes pratiques
Ne jamais rembourser UNKNOWN_ERROR immédiatement
Ne jamais rembourser UNKNOWN_ERROR immédiatement
Attendez que le statut se résolve en FULFILLED ou REFUNDED dans les 24 heures avant de traiter les remboursements.
Utiliser des tâches en arrière-plan
Utiliser des tâches en arrière-plan
Interrogez le statut de manière asynchrone pour éviter de bloquer les requêtes utilisateur et améliorer les performances.
Implémenter une protection contre les délais d'expiration
Implémenter une protection contre les délais d'expiration
Définissez un nombre maximum de tentatives d’interrogation (généralement 60 = 5 minutes) et replanifiez les vérifications si nécessaire.
Mettre en cache le statut dans la base de données
Mettre en cache le statut dans la base de données
Enregistrez le statut dans votre base de données pour minimiser les appels API. N’interrogez l’API que lorsque le statut n’est pas final.
Afficher les messages en arabe
Afficher les messages en arabe
Affichez toujours le
refund_message tel quel aux utilisateurs. Il est en arabe et explique clairement le problème.Gérer les offres suggérées
Gérer les offres suggérées
Lorsque
suggested_offers est présent, affichez ces alternatives pour améliorer la conversion.Étapes suivantes
Stratégies d'interrogation
Apprenez les techniques avancées d’optimisation de l’interrogation
Webhooks
Configurez des notifications de statut en temps réel au lieu d’interroger
API Liste des recharges
Consultez toutes vos transactions de recharge
Gestion des erreurs
Référence complète de gestion des erreurs

