Skip to main content

Overview

The products endpoint returns available internet recharge cards for ADSL or 4G LTE services with real-time pricing and stock availability. Products should be cached briefly to minimize API calls.
Cache product data for 5-10 minutes. Stock levels change more frequently than gift cards.

API Reference

GET /v3/internet/products

Complete endpoint documentation

Basic Product Loading

async function loadInternetProducts(type) {
  if (!['ADSL', '4G'].includes(type)) {
    throw new Error('Invalid type. Must be ADSL or 4G');
  }

  const response = await fetch(
    `https://api.oneclickdz.com/v3/internet/products?type=${type}`,
    {
      headers: {
        "X-Access-Token": process.env.API_KEY,
      },
    }
  );

  if (!response.ok) {
    throw new Error(`Failed to load products: ${response.status}`);
  }

  const result = await response.json();
  return result.data.products;
}

// Usage
const adslProducts = await loadInternetProducts('ADSL');
const lteProducts = await loadInternetProducts('4G');

console.log(`ADSL: ${adslProducts.length} products`);
console.log(`4G: ${lteProducts.length} products`);

Response Structure

{
  "success": true,
  "data": {
    "products": [
      {
        "value": 500,
        "cost": 490,
        "available": true
      },
      {
        "value": 1000,
        "cost": 980,
        "available": true
      },
      {
        "value": 2000,
        "cost": 1960,
        "available": true
      },
      {
        "value": 5000,
        "cost": 4900,
        "available": false
      }
    ]
  },
  "meta": {
    "timestamp": "2025-11-01T12:00:00.000Z",
    "type": "ADSL"
  }
}

Caching Implementation

class ProductCache {
  constructor(ttlMinutes = 10) {
    this.cache = new Map();
    this.ttl = ttlMinutes * 60 * 1000;
  }

  async get(type) {
    const cached = this.cache.get(type);
    const now = Date.now();
    
    // Return cached if still valid
    if (cached && (now - cached.timestamp) < this.ttl) {
      console.log(`Returning cached ${type} products`);
      return cached.data;
    }

    // Fetch fresh data
    console.log(`Fetching fresh ${type} products`);
    const products = await loadInternetProducts(type);
    
    this.cache.set(type, {
      data: products,
      timestamp: now,
    });
    
    return products;
  }

  invalidate(type) {
    if (type) {
      this.cache.delete(type);
    } else {
      this.cache.clear();
    }
  }
}

// Usage
const cache = new ProductCache(10); // 10 minutes TTL

// First call - fetches from API
const adsl1 = await cache.get('ADSL');

// Second call within 10 minutes - returns cached
const adsl2 = await cache.get('ADSL');

// Different type - fetches separately
const lte = await cache.get('4G');

// Force refresh
cache.invalidate('ADSL');
const adsl3 = await cache.get('ADSL'); // Fetches fresh

Filtering Available Products

Only show products that are in stock:
function getAvailableProducts(products) {
  return products.filter(p => p.available);
}

// Usage
const allProducts = await cache.get('ADSL');
const available = getAvailableProducts(allProducts);

console.log(`${available.length}/${allProducts.length} products in stock`);

if (available.length === 0) {
  console.log('No products available at the moment');
}

Applying Customer Markup

Add your profit margin to wholesale prices:
function applyMarkup(products, markupPercent = 5) {
  return products
    .filter(p => p.available)
    .map(p => ({
      value: p.value,
      wholesaleCost: p.cost, // Store but don't show
      customerPrice: Math.ceil(p.cost * (1 + markupPercent / 100)),
      available: true,
    }));
}

// Usage
const products = await cache.get('ADSL');
const customerPrices = applyMarkup(products, 5); // 5% markup

customerPrices.forEach(p => {
  console.log(`${p.value} DA card: ${p.customerPrice} DA`);
  // Don't log wholesaleCost in production!
});

Building Product UI

Example structure for displaying products to customers:
async function buildProductOptions(type, markupPercent = 5) {
  const products = await cache.get(type);
  
  return {
    serviceType: type,
    serviceName: type === 'ADSL' ? 'ADSL Internet' : '4G LTE Internet',
    options: products
      .filter(p => p.available)
      .map(p => ({
        value: p.value,
        label: `${p.value} DA`,
        price: Math.ceil(p.cost * (1 + markupPercent / 100)),
        inStock: true,
      }))
      .sort((a, b) => a.value - b.value) // Sort by value ascending
  };
}

// Usage
const adslOptions = await buildProductOptions('ADSL', 5);
const lteOptions = await buildProductOptions('4G', 5);

// Returns UI-ready structure:
// {
//   serviceType: 'ADSL',
//   serviceName: 'ADSL Internet',
//   options: [
//     { value: 500, label: '500 DA', price: 515, inStock: true },
//     { value: 1000, label: '1000 DA', price: 1029, inStock: true },
//     ...
//   ]
// }

Loading Both Service Types

Fetch ADSL and 4G products in parallel:
async function loadAllInternetProducts() {
  const [adsl, lte] = await Promise.all([
    cache.get('ADSL'),
    cache.get('4G'),
  ]);
  
  return {
    adsl: applyMarkup(adsl, 5),
    lte: applyMarkup(lte, 5),
  };
}

// Usage
const allProducts = await loadAllInternetProducts();
console.log('ADSL:', allProducts.adsl.length, 'products');
console.log('4G:', allProducts.lte.length, 'products');

Best Practices

Cache 5-10 Minutes

Balance freshness with API load reduction

Filter Available Only

Only display products where available: true

Hide Wholesale Prices

Never show raw cost values to customers

Handle Errors Gracefully

Fall back to cached data if API is temporarily unavailable

Error Handling

async function loadProductsSafely(type) {
  try {
    return await cache.get(type);
  } catch (error) {
    console.error(`Failed to load ${type} products:`, error);
    
    // Return stale cached data if available
    const cached = cache.cache.get(type);
    if (cached) {
      console.log('Using stale cached data');
      return cached.data;
    }
    
    throw new Error(`${type} products unavailable`);
  }
}

Next Steps