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

# Envoyer une Recharge Mobile

> Créer et envoyer une demande de recharge mobile

## Vue d'Ensemble

Envoyez des recharges mobiles instantanées aux opérateurs algériens (Mobilis, Djezzy, Ooredoo, Pixx) avec suivi du statut en temps réel.

<Note>
  **Recommandé :** Utilisez le paramètre `ref` unique pour prévenir les commandes en double et faciliter le suivi.
</Note>

## Corps de la Requête

<ParamField body="plan_code" type="string" required>
  Code de forfait depuis l'endpoint `/mobile/plans` (ex. : `PREPAID_DJEZZY`, `PIXX_500`)
</ParamField>

<ParamField body="MSSIDN" type="string" required>
  Numéro de téléphone au format `0[567][0-9]{8}` (doit être une chaîne, pas un nombre)
  **Exemples :** `"0778037340"`, `"0665983439"`, `"0556121212"`
</ParamField>

<ParamField body="amount" type="integer">
  Montant de recharge en DZD (requis pour les forfaits dynamiques, ignoré pour les forfaits fixes)
  Doit être compris entre `min_amount` et `max_amount` des détails du forfait
</ParamField>

<ParamField body="ref" type="string">
  Votre référence de commande unique pour le suivi (générée automatiquement si non fournie)
  **Important :** Prévient les requêtes en double si la même référence est soumise deux fois
</ParamField>

## Réponse

<ResponseField name="success" type="boolean" required>
  Indique si la requête a été soumise avec succès
</ResponseField>

<ResponseField name="data" type="object" required>
  <Expandable title="properties">
    <ResponseField name="topupId" type="string">
      Identifiant interne de la recharge pour le suivi du statut
    </ResponseField>

    <ResponseField name="topupRef" type="string">
      Votre référence (la référence fournie ou générée automatiquement)
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="meta" type="object" required>
  <Expandable title="properties">
    <ResponseField name="timestamp" type="string">
      Horodatage de la requête au format ISO 8601
    </ResponseField>
  </Expandable>
</ResponseField>

## Exemples

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.oneclickdz.com/v3/mobile/send \
    -X POST \
    -H "Content-Type: application/json" \
    -H "X-Access-Token: YOUR_API_KEY" \
    -d '{
      "plan_code": "PREPAID_DJEZZY",
      "MSSIDN": "0778037340",
      "amount": 500,
      "ref": "order-12345"
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://api.oneclickdz.com/v3/mobile/send", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Access-Token": "YOUR_API_KEY",
    },
    body: JSON.stringify({
      plan_code: "PREPAID_DJEZZY",
      MSSIDN: "0778037340",
      amount: 500,
      ref: "order-12345",
    }),
  });

  const data = await response.json();
  console.log(data.data.topupId);
  ```

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

  response = requests.post(
      'https://api.oneclickdz.com/v3/mobile/send',
      headers={
          'Content-Type': 'application/json',
          'X-Access-Token': 'YOUR_API_KEY'
      },
      json={
          'plan_code': 'PREPAID_DJEZZY',
          'MSSIDN': '0778037340',
          'amount': 500,
          'ref': 'order-12345'
      }
  )

  topup_id = response.json()['data']['topupId']
  ```

  ```php PHP theme={null}
  <?php
  $data = [
      'plan_code' => 'PREPAID_DJEZZY',
      'MSSIDN' => '0778037340',
      'amount' => 500,
      'ref' => 'order-12345'
  ];

  $ch = curl_init('https://api.oneclickdz.com/v3/mobile/send');
  curl_setopt($ch, CURLOPT_POST, true);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'Content-Type: application/json',
      'X-Access-Token: YOUR_API_KEY'
  ]);
  curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

  $response = json_decode(curl_exec($ch), true);
  $topupId = $response['data']['topupId'];
  ?>
  ```
</CodeGroup>

### Réponse de Succès

```json theme={null}
{
  "success": true,
  "data": {
    "topupId": "6901616fe9e88196b4eb64b0",
    "topupRef": "API-+213665983439-test-1761698159224-success"
  },
  "meta": {
    "timestamp": "2025-10-29T00:35:59.454Z"
  }
}
```

## Réponses d'Erreur

<AccordionGroup>
  <Accordion title="400 - Erreurs de Validation">
    **Corps ou paramètres de requête invalides**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "ERR_VALIDATION",
        "message": "body/MSSIDN must match pattern \"^0[567][0-9]{8}$\"",
        "details": {
          "field": "MSSIDN",
          "value": "778037340"
        }
      },
      "requestId": "req_1730160959_xyz789"
    }
    ```

    **Erreurs de validation courantes :**

    * `plan_code` ou `MSSIDN` manquant
    * Format de numéro de téléphone invalide
    * `amount` requis pour les forfaits dynamiques
    * `amount` hors plage min/max
    * MSSIDN ne correspond pas à l'opérateur du forfait
  </Accordion>

  <Accordion title="403 - Solde Insuffisant">
    **Solde insuffisant pour l'opération**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "INSUFFICIENT_BALANCE",
        "message": "Your balance is insufficient for this operation",
        "details": {
          "required": 500,
          "available": 250
        }
      },
      "requestId": "req_1730160959_def456"
    }
    ```
  </Accordion>

  <Accordion title="403 - Référence en Double">
    **Référence déjà utilisée**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "DUPLICATED-REF",
        "message": "This reference ID is already in use."
      },
      "requestId": "req_1730160959_ghi789"
    }
    ```

    **Solution :** Vérifiez le statut de la recharge existante en utilisant la même référence
  </Accordion>

  <Accordion title="500 - Erreur Interne">
    **Erreur serveur (rare)**

    ```json theme={null}
    {
      "success": false,
      "error": {
        "code": "INTERNAL_ERROR",
        "message": "Developer was notified and will check shortly"
      },
      "requestId": "req_1730160959_jkl012"
    }
    ```

    **Action :** Contactez le support avec le `requestId`. Ne remboursez PAS avant investigation.
  </Accordion>
</AccordionGroup>

## Prévention des Requêtes Dupliquées

<Info>
  Le champ `ref` garantit que la recharge est traitée une seule fois, même en cas de problèmes réseau.
</Info>

```javascript theme={null}
async function sendTopUpSafe(planCode, phone, amount, orderId) {
  // Use orderId as the ref for idempotency
  const ref = `topup-${orderId}`;

  try {
    const response = await fetch("https://api.oneclickdz.com/v3/mobile/send", {
      method: "POST",
      headers: {
        "X-Access-Token": API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        plan_code: planCode,
        MSSIDN: phone,
        amount,
        ref,
      }),
    });

    const data = await response.json();

    if (data.success) {
      return { success: true, topupId: data.data.topupId };
    }

    // Handle duplicate ref - order already exists
    if (data.error.code === "DUPLICATED-REF") {
      const status = await checkTopUpByRef(ref);
      return { success: true, topupId: status.data._id, fromExisting: true };
    }

    throw new Error(`${data.error.code}: ${data.error.message}`);
  } catch (networkError) {
    // Network error - check if order was created
    const status = await checkTopUpByRef(ref);
    if (status.success) {
      return { success: true, topupId: status.data._id, fromExisting: true };
    }
    throw networkError;
  }
}
```

## Format du Numéro de Téléphone

<Warning>
  Le numéro de téléphone doit être une **chaîne**, pas un nombre. Le zéro initial est obligatoire.
</Warning>

**Formats valides :**

* ✅ `"0778037340"` (chaîne avec zéro initial)
* ✅ `"0665983439"`
* ✅ `"0556121212"`

**Formats invalides :**

* ❌ `778037340` (zéro initial manquant)
* ❌ `0778037340` (type nombre au lieu de chaîne)
* ❌ `"+213778037340"` (format international)
* ❌ `"0778 037 340"` (contient des espaces)

## Cycle de Vie du Statut

Après l'envoi d'une recharge, elle passe par ces états :

<Steps>
  <Step title="PENDING">
    Commande créée et mise en file d'attente pour traitement **Durée :** 2 à 15 secondes (typique)
  </Step>

  {" "}

  <Step title="HANDLING">
    Actuellement en cours de traitement par l'opérateur **Durée :** 3 à 8 secondes (typique)
  </Step>

  <Step title="État Final">
    **FULFILLED :** Complété avec succès ✅ **REFUNDED :** Échoué et remboursé
    ❌ **UNKNOWN\_ERROR :** État incertain (se résout en 24h) ⚠️
  </Step>
</Steps>

[En savoir plus sur le Suivi du Statut →](/fr/api-reference/mobile/check-by-ref)

## Flux d'Intégration Recommandé

<Steps>
  <Step title="Créer la Commande dans Votre BD">
    ```javascript theme={null}
    const order = await db.orders.create({
      id: generateOrderId(),
      userId: user.id,
      phone: '0778037340',
      amount: 500,
      status: 'PROCESSING'
    });
    ```
  </Step>

  <Step title="Déduire le Solde Utilisateur">
    ```javascript theme={null}
    await db.users.update({
      id: user.id,
      balance: user.balance - 500
    });
    ```
  </Step>

  <Step title="Retourner la Commande à l'Utilisateur">
    ```javascript theme={null}
    res.json({
      orderId: order.id,
      status: 'PROCESSING',
      message: 'Top-up is being processed'
    });
    ```
  </Step>

  <Step title="Async : Envoyer à l'API">
    ```javascript theme={null}
    // In background job
    const response = await sendTopup({
      plan_code: 'PREPAID_DJEZZY',
      MSSIDN: order.phone,
      amount: order.amount,
      ref: order.id
    });

    await db.orders.update({
      id: order.id,
      apiTopupId: response.data.topupId
    });
    ```
  </Step>

  <Step title="Interroger le Statut et Mettre à Jour">
    ```javascript theme={null}
    const status = await checkTopupStatus(order.id);

    await db.orders.update({
      id: order.id,
      status: status.data.status
    });

    if (status.data.status === 'REFUNDED') {
      await refundUserBalance(user.id, order.amount);
    }
    ```
  </Step>
</Steps>

## Tests en Sandbox

Activez le mode sandbox pour tester sans transactions réelles :

<CardGroup cols={2}>
  <Card title="Flux Normal" icon="check">
    **Utilisez n'importe quel numéro normal** (ex. : `0778037340`) Flux : PENDING (5s) → HANDLING
    (15s) → FULFILLED
  </Card>

  {" "}

  <Card title="Remboursement avec Message" icon="message">
    **Utilisez :** `0600000001` Retourne : REFUNDED avec `refund_message`
  </Card>

  {" "}

  <Card title="Non-correspondance de Forfait" icon="list">
    **Utilisez :** `0600000002` Retourne : REFUNDED avec `suggested_offers`
  </Card>

  <Card title="Erreur Inconnue" icon="question">
    **Utilisez :** `0600000003` Retourne : statut UNKNOWN\_ERROR
  </Card>
</CardGroup>

## Bonnes Pratiques

<CardGroup cols={2}>
  <Card title="Utiliser des Références Uniques" icon="fingerprint">
    Fournissez toujours un `ref` unique pour prévenir les doublons et faciliter le suivi
  </Card>

  <Card title="Valider Avant Soumission" icon="circle-check">
    Vérifiez le forfait, le format du téléphone et le solde côté client pour réduire les erreurs
  </Card>

  <Card title="Gérer de Manière Asynchrone" icon="clock">
    Traitez les appels API de manière asynchrone pour éviter de bloquer les requêtes utilisateur
  </Card>

  <Card title="Implémenter une Logique de Nouvelle Tentative" icon="arrows-rotate">
    Réessayez les requêtes échouées avec un backoff exponentiel pour les erreurs réseau
  </Card>
</CardGroup>

## Endpoints Associés

<CardGroup cols={3}>
  <Card title="Lister les Forfaits" icon="list" href="/fr/api-reference/mobile/list-plans">
    Obtenir les forfaits disponibles
  </Card>

  <Card title="Vérifier par Référence" icon="magnifying-glass" href="/fr/api-reference/mobile/check-by-ref">
    Suivre avec votre référence
  </Card>

  <Card title="Vérifier par ID" icon="id-card" href="/fr/api-reference/mobile/check-by-id">
    Suivre avec l'ID de recharge
  </Card>
</CardGroup>
