> ## 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.

# Loading Internet Products

> Fetch and cache available ADSL and 4G LTE internet cards

## 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.

<Info>
  Cache product data for **5-10 minutes**. Stock levels change more frequently than gift cards.
</Info>

## API Reference

<Card title="GET /v3/internet/products" icon="list" href="/en/api-reference/internet/list-products">
  Complete endpoint documentation
</Card>

## Basic Product Loading

<CodeGroup>
  ```javascript Node.js theme={null}
  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`);
  ```

  ```python Python theme={null}
  import requests
  import os

  def load_internet_products(service_type):
      if service_type not in ['ADSL', '4G']:
          raise ValueError('Invalid type. Must be ADSL or 4G')
      
      response = requests.get(
          'https://api.oneclickdz.com/v3/internet/products',
          headers={'X-Access-Token': os.getenv('API_KEY')},
          params={'type': service_type}
      )
      
      response.raise_for_status()
      return response.json()['data']['products']

  # Usage
  adsl_products = load_internet_products('ADSL')
  lte_products = load_internet_products('4G')

  print(f"ADSL: {len(adsl_products)} products")
  print(f"4G: {len(lte_products)} products")
  ```

  ```php PHP theme={null}
  <?php

  function loadInternetProducts($type) {
      if (!in_array($type, ['ADSL', '4G'])) {
          throw new InvalidArgumentException('Invalid type. Must be ADSL or 4G');
      }
      
      $url = "https://api.oneclickdz.com/v3/internet/products?type={$type}";
      
      $ch = curl_init($url);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_HTTPHEADER, [
          'X-Access-Token: ' . getenv('API_KEY')
      ]);
      
      $response = curl_exec($ch);
      $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
      curl_close($ch);
      
      if ($httpCode !== 200) {
          throw new Exception("Failed to load products: " . $httpCode);
      }
      
      $result = json_decode($response, true);
      return $result['data']['products'];
  }

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

  echo "ADSL: " . count($adslProducts) . " products\n";
  echo "4G: " . count($lteProducts) . " products\n";
  ?>
  ```

  ```bash cURL theme={null}
  # ADSL products
  curl "https://api.oneclickdz.com/v3/internet/products?type=ADSL" \
    -H "X-Access-Token: YOUR_API_KEY"

  # 4G products
  curl "https://api.oneclickdz.com/v3/internet/products?type=4G" \
    -H "X-Access-Token: YOUR_API_KEY"
  ```
</CodeGroup>

## Response Structure

```json theme={null}
{
  "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

<CodeGroup>
  ```javascript Node.js theme={null}
  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
  ```

  ```python Python theme={null}
  from datetime import datetime, timedelta
  import requests
  import os

  class ProductCache:
      def __init__(self, ttl_minutes=10):
          self.cache = {}
          self.ttl = timedelta(minutes=ttl_minutes)
      
      def get(self, service_type):
          cached = self.cache.get(service_type)
          now = datetime.now()
          
          # Return cached if still valid
          if cached and (now - cached['timestamp']) < self.ttl:
              print(f"Returning cached {service_type} products")
              return cached['data']
          
          # Fetch fresh data
          print(f"Fetching fresh {service_type} products")
          products = load_internet_products(service_type)
          
          self.cache[service_type] = {
              'data': products,
              'timestamp': now
          }
          
          return products
      
      def invalidate(self, service_type=None):
          if service_type:
              self.cache.pop(service_type, None)
          else:
              self.cache.clear()

  # Usage
  cache = ProductCache(ttl_minutes=10)

  # First call - fetches from API
  adsl1 = cache.get('ADSL')

  # Second call within 10 minutes - returns cached
  adsl2 = cache.get('ADSL')

  # Different type - fetches separately
  lte = cache.get('4G')

  # Force refresh
  cache.invalidate('ADSL')
  adsl3 = cache.get('ADSL')
  ```

  ```php PHP theme={null}
  <?php

  class ProductCache {
      private $cache = [];
      private $ttl;
      
      public function __construct($ttlMinutes = 10) {
          $this->ttl = $ttlMinutes * 60;
      }
      
      public function get($type) {
          $now = time();
          
          // Return cached if still valid
          if (isset($this->cache[$type]) && 
              ($now - $this->cache[$type]['timestamp']) < $this->ttl) {
              error_log("Returning cached {$type} products");
              return $this->cache[$type]['data'];
          }
          
          // Fetch fresh data
          error_log("Fetching fresh {$type} products");
          $products = loadInternetProducts($type);
          
          $this->cache[$type] = [
              'data' => $products,
              'timestamp' => $now
          ];
          
          return $products;
      }
      
      public function invalidate($type = null) {
          if ($type) {
              unset($this->cache[$type]);
          } else {
              $this->cache = [];
          }
      }
  }

  // Usage
  $cache = new ProductCache(10);

  // First call - fetches from API
  $adsl1 = $cache->get('ADSL');

  // Second call within 10 minutes - returns cached
  $adsl2 = $cache->get('ADSL');

  // Different type - fetches separately
  $lte = $cache->get('4G');

  // Force refresh
  $cache->invalidate('ADSL');
  $adsl3 = $cache->get('ADSL');
  ?>
  ```
</CodeGroup>

## Filtering Available Products

Only show products that are in stock:

<CodeGroup>
  ```javascript Node.js theme={null}
  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');
  }
  ```

  ```python Python theme={null}
  def get_available_products(products):
      return [p for p in products if p['available']]

  # Usage
  all_products = cache.get('ADSL')
  available = get_available_products(all_products)

  print(f"{len(available)}/{len(all_products)} products in stock")

  if not available:
      print('No products available at the moment')
  ```

  ```php PHP theme={null}
  <?php

  function getAvailableProducts($products) {
      return array_filter($products, function($p) {
          return $p['available'];
      });
  }

  // Usage
  $allProducts = $cache->get('ADSL');
  $available = array_values(getAvailableProducts($allProducts));

  echo count($available) . "/" . count($allProducts) . " products in stock\n";

  if (empty($available)) {
      echo "No products available at the moment\n";
  }
  ?>
  ```
</CodeGroup>

## Applying Customer Markup

Add your profit margin to wholesale prices:

<CodeGroup>
  ```javascript Node.js theme={null}
  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!
  });
  ```

  ```python Python theme={null}
  import math

  def apply_markup(products, markup_percent=5):
      available = [p for p in products if p['available']]
      
      return [
          {
              'value': p['value'],
              'wholesaleCost': p['cost'],  # Store but don't show
              'customerPrice': math.ceil(p['cost'] * (1 + markup_percent / 100)),
              'available': True
          }
          for p in available
      ]

  # Usage
  products = cache.get('ADSL')
  customer_prices = apply_markup(products, markup_percent=5)

  for p in customer_prices:
      print(f"{p['value']} DA card: {p['customerPrice']} DA")
  ```

  ```php PHP theme={null}
  <?php

  function applyMarkup($products, $markupPercent = 5) {
      $available = array_filter($products, function($p) {
          return $p['available'];
      });
      
      return array_map(function($p) use ($markupPercent) {
          return [
              'value' => $p['value'],
              'wholesaleCost' => $p['cost'], // Store but don't show
              'customerPrice' => ceil($p['cost'] * (1 + $markupPercent / 100)),
              'available' => true
          ];
      }, $available);
  }

  // Usage
  $products = $cache->get('ADSL');
  $customerPrices = applyMarkup($products, 5); // 5% markup

  foreach ($customerPrices as $p) {
      echo "{$p['value']} DA card: {$p['customerPrice']} DA\n";
  }
  ?>
  ```
</CodeGroup>

## Building Product UI

Example structure for displaying products to customers:

```javascript theme={null}
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:

```javascript theme={null}
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

<CardGroup cols={2}>
  <Card title="Cache 5-10 Minutes" icon="clock">
    Balance freshness with API load reduction
  </Card>

  <Card title="Filter Available Only" icon="filter">
    Only display products where `available: true`
  </Card>

  <Card title="Hide Wholesale Prices" icon="eye-slash">
    Never show raw `cost` values to customers
  </Card>

  <Card title="Handle Errors Gracefully" icon="triangle-exclamation">
    Fall back to cached data if API is temporarily unavailable
  </Card>
</CardGroup>

## Error Handling

```javascript theme={null}
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

<CardGroup cols={3}>
  <Card title="Validate Numbers" icon="check" href="/en/internet-topup-guides/2-validation">
    Verify phone numbers before ordering
  </Card>

  <Card title="Send Top-Ups" icon="paper-plane" href="/en/internet-topup-guides/3-sending-topups">
    Submit orders with error handling
  </Card>

  <Card title="API Reference" icon="book" href="/en/api-reference/internet/list-products">
    Complete endpoint documentation
  </Card>

  <Card title="Overview" icon="book-open" href="/en/internet-topup-guides/overview">
    Back to integration overview
  </Card>

  <Card title="Track Status" icon="chart-line" href="/en/internet-topup-guides/4-status-tracking">
    Monitor order fulfillment
  </Card>

  <Card title="Deliver Cards" icon="file-code" href="/en/internet-topup-guides/5-card-delivery">
    Securely deliver card codes
  </Card>
</CardGroup>
