Skip to main content

Overview

Once you’ve validated all inputs, you’re ready to send the top-up request to the OneClickDz API. This guide covers submitting requests, handling responses, and managing your reference system.

API Reference

View complete API documentation for POST /v3/mobile/send endpoint

Basic Top-Up Request

Simple Example

curl -X POST https://api.oneclickdz.com/v3/mobile/send \
  -H "Content-Type: application/json" \
  -H "X-Access-Token: YOUR_API_KEY" \
  -d '{
    "plan_code": "PREPAID_DJEZZY",
    "MSSIDN": "0778037340",
    "amount": 500,
    "ref": "order-1730462400-user123"
  }'

Success Response

{
  "success": true,
  "data": {
    "topupId": "6901616fe9e88196b4eb64b0",
    "topupRef": "order-1730462400-user123"
  },
  "meta": {
    "timestamp": "2025-11-01T11:00:00.454Z"
  },
  "requestId": "req_1730462400_abc123"
}

Generating Unique References

Always provide unique references to prevent duplicates and enable tracking:
function generateOrderRef(userId) {
  const timestamp = Date.now();
  const random = Math.random().toString(36).substring(2, 8);
  return `${userId}-${timestamp}-${random}`;
}

// Examples:
// user123-1730462400-x7k2p9
// user456-1730462401-m5n8q3

Checking Balance Before Sending

Always verify sufficient balance to prevent failed requests:

Balance API Reference

Learn more about GET /v3/account/balance endpoint
async function checkBalance() {
  const response = await fetch('https://api.oneclickdz.com/v3/account/balance', {
    headers: {
      'X-Access-Token': process.env.ONECLICKDZ_API_KEY
    }
  });
  
  const data = await response.json();
  
  if (!data.success) {
    throw new Error('Failed to check balance');
  }
  
  return data.data.balance;
}

async function sendTopUpWithBalanceCheck({ planCode, phone, amount, ref, requiredBalance }) {
  // Check balance first
  const balance = await checkBalance();
  
  if (balance < requiredBalance) {
    throw new Error(`Insufficient balance. Required: ${requiredBalance} DZD, Available: ${balance} DZD`);
  }
  
  // Proceed with top-up
  return await sendTopUp({ planCode, phone, amount, ref });
}

Error Handling

Handle common API errors:
See the Error Handling Reference for complete error codes and handling strategies.
async function sendTopUpSafe(data) {
  try {
    return await sendTopUp(data);
  } catch (error) {
    // Parse error response
    if (error.response) {
      const errorData = error.response.data;
      
      switch (errorData.error?.code) {
        case 'ERR_VALIDATION':
          console.error('Validation error:', errorData.error.message);
          console.error('Field:', errorData.error.details?.field);
          break;
          
        case 'INSUFFICIENT_BALANCE':
          console.error('Insufficient balance');
          console.error('Required:', errorData.error.details?.required);
          console.error('Available:', errorData.error.details?.available);
          break;
          
        case 'DUPLICATED_REF':
          console.error('Duplicate reference');
          console.error('Existing topup:', errorData.error.details?.existingTopupId);
          // Check status of existing topup
          break;
          
        case 'INTERNAL_ERROR':
          console.error('Server error, contact support');
          console.error('Request ID:', errorData.requestId);
          break;
          
        default:
          console.error('Unknown error:', errorData.error?.message);
      }
    }
    
    throw error;
  }
}

Database Integration

Store orders before and after API calls:
async function createTopUpOrder({ userId, planCode, phone, amount }) {
  // Start database transaction
  const transaction = await db.transaction();
  
  try {
    // 1. Generate reference
    const ref = generateOrderRef(userId);
    
    // 2. Get plan details
    const plan = await db.mobilePlans.findOne({
      where: { code: planCode }
    });
    
    const cost = amount * plan.wholesaleCost;
    
    // 3. Check user balance
    const user = await db.users.findOne({
      where: { id: userId },
      lock: true,  // Lock row for update
      transaction
    });
    
    if (user.balance < cost) {
      await transaction.rollback();
      throw new Error('Insufficient user balance');
    }
    
    // 4. Create order record
    const order = await db.orders.create({
      userId,
      ref,
      planCode,
      phone,
      amount,
      cost,
      status: 'PENDING',
      createdAt: new Date()
    }, { transaction });
    
    // 5. Deduct user balance
    await db.users.update({
      balance: user.balance - cost
    }, {
      where: { id: userId },
      transaction
    });
    
    // 6. Send to API
    const result = await sendTopUp({
      planCode,
      phone,
      amount,
      ref
    });
    
    // 7. Update order with API response
    await db.orders.update({
      apiTopupId: result.topupId,
      apiTopupRef: result.topupRef,
      status: 'PROCESSING'
    }, {
      where: { id: order.id },
      transaction
    });
    
    // Commit transaction
    await transaction.commit();
    
    return {
      orderId: order.id,
      topupId: result.topupId,
      ref: ref
    };
    
  } catch (error) {
    // Rollback on any error
    await transaction.rollback();
    throw error;
  }
}

Async Processing

For better performance, process top-ups asynchronously:
const Queue = require('bull');
const topupQueue = new Queue('topup-processing');

// Add job to queue
async function queueTopUp(orderData) {
  const order = await db.orders.create({
    ...orderData,
    status: 'QUEUED'
  });
  
  await topupQueue.add({
    orderId: order.id
  });
  
  return order;
}

// Process queue
topupQueue.process(async (job) => {
  const { orderId } = job.data;
  
  const order = await db.orders.findOne({ where: { id: orderId } });
  
  try {
    // Send top-up
    const result = await sendTopUp({
      planCode: order.planCode,
      phone: order.phone,
      amount: order.amount,
      ref: order.ref
    });
    
    // Update order
    await db.orders.update({
      apiTopupId: result.topupId,
      status: 'PROCESSING'
    }, {
      where: { id: orderId }
    });
    
    // Start status polling (another queue)
    await pollQueue.add({ topupId: result.topupId, orderId });
    
  } catch (error) {
    // Handle error
    await db.orders.update({
      status: 'FAILED',
      errorMessage: error.message
    }, {
      where: { id: orderId }
    });
    
    // Refund user
    await refundUser(order.userId, order.cost);
  }
});

Retry Logic

Implement retry for network errors:
async function sendTopUpWithRetry(data, maxRetries = 3) {
  let lastError;
  
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await sendTopUp(data);
    } catch (error) {
      lastError = error;
      
      // Don't retry on validation or duplicate errors
      if (error.code === 'ERR_VALIDATION' || error.code === 'DUPLICATED_REF') {
        throw error;
      }
      
      // Wait before retry (exponential backoff)
      if (attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000; // 2s, 4s, 8s
        console.log(`Retry ${attempt}/${maxRetries} after ${delay}ms...`);
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }
  
  throw lastError;
}

Best Practices

Generate unique ref for each request to prevent duplicates and enable tracking.
Verify sufficient balance before sending to avoid failed requests.
Wrap order creation and balance deduction in transactions for data consistency.
Use queues for better performance and user experience.
Retry network errors with exponential backoff, but not validation errors.
Log all requests and responses for debugging and auditing.

Next Steps