> ## Documentation Index
> Fetch the complete documentation index at: https://doc.ashtechpay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Flux de paiement Mobile Money

> Gérez les flux USSD Push, Wave, OTP SMS et OTP USSD.

Une collecte Mobile Money n'a pas toujours le même parcours. Le flux dépend du pays, de l'opérateur et de la configuration active pour cet opérateur.

Ne codez pas le flux à partir du nom de la marque. Chargez [`GET /v1/countries`](https://doc.ashtechpay.com/docs/reference/countries), utilisez l'opérateur retourné par le catalogue, puis inspectez la réponse de `POST /v1/collect`.

Toutes les collectes et confirmations OTP exigent `user_id` dans le JSON. Utilisez l'identifiant du profil AshTech Pay associé à la clé Bearer. Les lectures de transaction exigent aussi `user_id` dans la query string.

## Résumé des flux

| Flux | Réponse initiale | Action côté client |
| - | - | - |
| USSD Push | `202` avec `status: "pending"` | Le client valide la demande sur son téléphone |
| Wave / redirection | `202` avec `flow: "wave"` et `wave_url` | Ouvrir `wave_url`, puis attendre le webhook |
| OTP SMS | `400` avec `error: "otp_required"` et `reference` | Lire le code reçu par SMS et relancer la requête avec `otp` et `reference` |
| OTP USSD | `400` avec `error: "otp_required"`, `reference` et `ussd_code` | Composer `ussd_code`, saisir le code obtenu, puis relancer avec le même `reference` et `otp` |

Dans tous les cas, la transaction reste `pending` jusqu'à la confirmation du paiement. Ne livrez jamais une commande uniquement parce que la requête initiale a répondu `202`.

## 1. USSD Push

Le flux USSD Push est utilisé par la plupart des opérateurs qui ne demandent pas une saisie OTP dans votre application.

1. Vous appelez `POST /v1/collect`.
2. AshTech Pay renvoie `202` et `status: "pending"`.
3. Le client reçoit une demande sur son téléphone et la valide avec son code secret opérateur.
4. Votre serveur reçoit le webhook final, ou vérifie périodiquement `GET /v1/transaction/:id?user_id=VOTRE_USER_ID`.

Exemple de réponse :

```json theme={null}
{
  "transaction_id": "uuid-de-transaction",
  "reference": "ASH-D-XXXXXXXXXXXXXX",
  "status": "pending",
  "amount": 5000,
  "credited_amount": 4750,
  "currency": "XAF",
  "operator": "MTN Money",
  "country_code": "CM"
}
```

L'application cliente doit afficher un état d'attente et permettre au client de terminer l'autorisation sur son téléphone.

## 2. Wave et autres redirections

Pour Wave, la réponse de collecte contient une URL de validation :

```json theme={null}
{
  "status": "pending",
  "flow": "wave",
  "wave_url": "https://...",
  "transaction_id": "uuid-de-transaction",
  "reference": "ASH-D-XXXXXXXXXXXXXX"
}
```

Ouvrez `wave_url` dans le navigateur ou dans l'application Wave si votre intégration mobile le permet. Ne remplacez pas cette URL par une URL construite manuellement.

Après la décision du client, utilisez le webhook `payment.completed` ou `payment.failed`, puis vérifiez le statut côté serveur avant de livrer.

Le flux Wave est actuellement détecté notamment pour Wave en Côte d'Ivoire et au Sénégal lorsque l'opérateur est actif dans le catalogue.

## 3. OTP envoyé par SMS

Selon le parcours configuré, un OTP peut être envoyé par SMS. La première requête ne finalise pas le paiement :

```json theme={null}
{
  "error": "otp_required",
  "message": "OTP requis. Un code a été envoyé par SMS. Relancez la requête avec les champs 'otp' et 'reference'.",
  "reference": "ASH-D-XXXXXXXXXXXXXX"
}
```

Conservez la valeur `reference` retournée. Après réception du SMS, relancez la même collecte avec le code OTP et cette référence :

```bash theme={null}
curl -X POST https://www.ashtechpay.com/v1/collect \
  -H "Authorization: Bearer $ASHTECH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "VOTRE_USER_ID",
    "amount": 5000,
    "currency": "XOF",
    "phone": "2250XXXXXXXX",
    "operator": "Orange Money",
    "country_code": "CI",
    "reference": "ASH-D-XXXXXXXXXXXXXX",
    "otp": "123456",
    "notify_url": "https://example.com/webhooks/ashtechpay"
  }'
```

Le champ `reference` de cette confirmation doit être celui reçu dans la réponse `400 otp_required`. Ne générez pas une nouvelle référence pour la confirmation.

## 4. OTP obtenu par USSD

Pour certains opérateurs Orange, le client doit composer un code USSD pour obtenir son OTP. L'API renvoie le code à utiliser :

```json theme={null}
{
  "error": "otp_required",
  "message": "OTP requis. Composez le code indiqué pour obtenir votre code OTP, puis relancez la requête avec les champs 'otp' et 'reference'.",
  "reference": "ASH-D-XXXXXXXXXXXXXX",
  "ussd_code": "#144*82#"
}
```

Affichez `ussd_code` directement au client. Après saisie de l'OTP, renvoyez aussi le même `reference` de session. Certains codes contiennent le montant exact, par exemple pour le Burkina Faso. N'utilisez donc pas une table codée en dur dans votre application.

Exemples de familles de flux actuellement configurées :

| Pays | Opérateur / cas | Parcours | Code à composer |
| - | - | - | - |
| Côte d'Ivoire | Orange | OTP obtenu par USSD | `#144*82#` |
| Côte d'Ivoire | Wave | Redirection Wave | Aucun : ouvrir la redirection |
| Côte d'Ivoire | MTN, Moov | USSD Push | Aucun : valider la notification |
| Sénégal | Orange | OTP obtenu par USSD | `#144#391#` |
| Sénégal | Wave | Redirection Wave | Aucun : ouvrir la redirection |
| Sénégal | Free, E-money | USSD Push | Aucun : valider la notification |
| Mali | Orange | OTP obtenu par USSD | `#144#77#` |
| Mali | Moov | USSD Push | Aucun : valider la notification |
| Burkina Faso | Orange | OTP USSD, avec montant dans certains codes | `*144*4*6*montant#` |
| Burkina Faso | Moov / Flooz | USSD Push | Aucun : valider la notification |
| Cameroun | MTN, Orange | USSD Push | Aucun : valider la notification |
| Congo RDC | Orange, Airtel, M-Pesa, Afrimoney | USSD Push | Aucun : valider la notification |
| Autres opérateurs actifs | Selon le catalogue et la configuration active | USSD Push, OTP ou redirection | Utiliser `ussd_code` ou l'URL retournée |

Cette matrice décrit le routage actuellement configuré pour les opérateurs concernés. Le statut actif peut changer. La réponse de [`GET /v1/countries`](https://doc.ashtechpay.com/docs/reference/countries) reste la source de vérité pour votre formulaire.

## Exemples cURL

### Requête commune

La requête de départ est la même pour un flux USSD Push, Wave ou OTP :

> [tab] Node.js
>
> ```javascript theme={null}
> const response = await fetch("https://www.ashtechpay.com/v1/collect", {
>   method: "POST",
>   headers: {
>     Authorization: `Bearer ${process.env.ASHTECH_API_KEY}`,
>     "Content-Type": "application/json",
>   },
>   body: JSON.stringify({
>     user_id: "VOTRE_USER_ID",
>     amount: 5000,
>     currency: "XOF",
>     phone: "2250XXXXXXXX",
>     operator: "Orange Money",
>     country_code: "CI",
>     reference: "order_ci_12345",
>     notify_url: "https://example.com/webhooks/ashtechpay",
>   }),
> });
> const payment = await response.json();
> ```

> [tab] Python
>
> ```python theme={null}
> import os
> import requests
>
> response = requests.post(
>     "https://www.ashtechpay.com/v1/collect",
>     headers={
>         "Authorization": f"Bearer {os.environ['ASHTECH_API_KEY']}",
>         "Content-Type": "application/json",
>     },
>     json={
>         "user_id": "VOTRE_USER_ID",
>         "amount": 5000,
>         "currency": "XOF",
>         "phone": "2250XXXXXXXX",
>         "operator": "Orange Money",
>         "country_code": "CI",
>         "reference": "order_ci_12345",
>         "notify_url": "https://example.com/webhooks/ashtechpay",
>     },
> )
> payment = response.json()
> ```

> [tab] cURL
>
> ```bash theme={null}
> curl -X POST https://www.ashtechpay.com/v1/collect \
>   -H "Authorization: Bearer $ASHTECH_API_KEY" \
>   -H "Content-Type: application/json" \
>   -d '{
>     "user_id": "VOTRE_USER_ID",
>     "amount": 5000,
>     "currency": "XOF",
>     "phone": "2250XXXXXXXX",
>     "operator": "Orange Money",
>     "country_code": "CI",
>     "reference": "order_ci_12345",
>     "notify_url": "https://example.com/webhooks/ashtechpay"
>   }'
> ```

> [tab] PHP / Laravel
>
> ```php theme={null}
> <?php
>
> $payload = [
>     "user_id" => "VOTRE_USER_ID",
>     "amount" => 5000,
>     "currency" => "XOF",
>     "phone" => "2250XXXXXXXX",
>     "operator" => "Orange Money",
>     "country_code" => "CI",
>     "reference" => "order_ci_12345",
>     "notify_url" => "https://example.com/webhooks/ashtechpay",
> ];
>
> $ch = curl_init("https://www.ashtechpay.com/v1/collect");
> curl_setopt_array($ch, [
>     CURLOPT_POST => true,
>     CURLOPT_RETURNTRANSFER => true,
>     CURLOPT_HTTPHEADER => [
>         "Authorization: Bearer " . getenv("ASHTECH_API_KEY"),
>         "Content-Type: application/json",
>     ],
>     CURLOPT_POSTFIELDS => json_encode($payload),
> ]);
> $payment = json_decode(curl_exec($ch), true);
> curl_close($ch);
> ```

Utilisez la devise et le nom exacts retournés par `/v1/countries`. Le champ `reference` est votre référence marchand et doit rester unique pour chaque commande.

### Exemple USSD Push : Cameroun

```bash theme={null}
curl -X POST https://www.ashtechpay.com/v1/collect \
  -H "Authorization: Bearer $ASHTECH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "VOTRE_USER_ID",
    "amount": 5000,
    "currency": "XAF",
    "phone": "2376XXXXXXXX",
    "operator": "MTN Money",
    "country_code": "CM",
    "reference": "order_cm_12345",
    "notify_url": "https://example.com/webhooks/ashtechpay"
  }'
```

Réponse attendue : `202`, `status: "pending"`. Le client valide ensuite la demande reçue sur son téléphone.

### Exemple Wave : Côte d'Ivoire

```bash theme={null}
curl -X POST https://www.ashtechpay.com/v1/collect \
  -H "Authorization: Bearer $ASHTECH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "VOTRE_USER_ID",
    "amount": 5000,
    "currency": "XOF",
    "phone": "2250XXXXXXXX",
    "operator": "Wave",
    "country_code": "CI",
    "reference": "order_wave_12345",
    "notify_url": "https://example.com/webhooks/ashtechpay"
  }'
```

Si le flux est Wave, la réponse `202` contient `flow: "wave"` et `wave_url`. Ouvrez cette URL pour que le client confirme le paiement.

### Exemple OTP Orange : première requête

```bash theme={null}
curl -X POST https://www.ashtechpay.com/v1/collect \
  -H "Authorization: Bearer $ASHTECH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "VOTRE_USER_ID",
    "amount": 5000,
    "currency": "XOF",
    "phone": "2250XXXXXXXX",
    "operator": "Orange Money",
    "country_code": "CI",
    "reference": "order_orange_12345",
    "notify_url": "https://example.com/webhooks/ashtechpay"
  }'
```

La réponse peut être `400 otp_required` avec un `ussd_code`, ou avec une `reference` si le code est envoyé par SMS. Affichez l'instruction retournée par l'API au client.

### Exemple OTP : confirmation

Après réception du code OTP, reprenez les mêmes paramètres et ajoutez `otp`. Si la première réponse contenait `reference`, envoyez exactement cette valeur :

> [tab] Node.js
>
> ```javascript theme={null}
> const response = await fetch("https://www.ashtechpay.com/v1/collect", {
>   method: "POST",
>   headers: {
>     Authorization: `Bearer ${process.env.ASHTECH_API_KEY}`,
>     "Content-Type": "application/json",
>   },
>   body: JSON.stringify({
>     user_id: "VOTRE_USER_ID",
>     amount: 5000,
>     currency: "XOF",
>     phone: "2250XXXXXXXX",
>     operator: "Orange Money",
>     country_code: "CI",
>     reference: "ASH-D-XXXXXXXXXXXXXX",
>     otp: "123456",
>     notify_url: "https://example.com/webhooks/ashtechpay",
>   }),
> });
> const payment = await response.json();
> ```

> [tab] Python
>
> ```python theme={null}
> import os
> import requests
>
> response = requests.post(
>     "https://www.ashtechpay.com/v1/collect",
>     headers={
>         "Authorization": f"Bearer {os.environ['ASHTECH_API_KEY']}",
>         "Content-Type": "application/json",
>     },
>     json={
>         "user_id": "VOTRE_USER_ID",
>         "amount": 5000,
>         "currency": "XOF",
>         "phone": "2250XXXXXXXX",
>         "operator": "Orange Money",
>         "country_code": "CI",
>         "reference": "ASH-D-XXXXXXXXXXXXXX",
>         "otp": "123456",
>         "notify_url": "https://example.com/webhooks/ashtechpay",
>     },
> )
> payment = response.json()
> ```

> [tab] cURL
>
> ```bash theme={null}
> curl -X POST https://www.ashtechpay.com/v1/collect \
>   -H "Authorization: Bearer $ASHTECH_API_KEY" \
>   -H "Content-Type: application/json" \
>   -d '{
>     "user_id": "VOTRE_USER_ID",
>     "amount": 5000,
>     "currency": "XOF",
>     "phone": "2250XXXXXXXX",
>     "operator": "Orange Money",
>     "country_code": "CI",
>     "reference": "ASH-D-XXXXXXXXXXXXXX",
>     "otp": "123456",
>     "notify_url": "https://example.com/webhooks/ashtechpay"
>   }'
> ```

> [tab] PHP / Laravel
>
> ```php theme={null}
> <?php
>
> $payload = [
>     "user_id" => "VOTRE_USER_ID",
>     "amount" => 5000,
>     "currency" => "XOF",
>     "phone" => "2250XXXXXXXX",
>     "operator" => "Orange Money",
>     "country_code" => "CI",
>     "reference" => "ASH-D-XXXXXXXXXXXXXX",
>     "otp" => "123456",
>     "notify_url" => "https://example.com/webhooks/ashtechpay",
> ];
>
> $ch = curl_init("https://www.ashtechpay.com/v1/collect");
> curl_setopt_array($ch, [
>     CURLOPT_POST => true,
>     CURLOPT_RETURNTRANSFER => true,
>     CURLOPT_HTTPHEADER => [
>         "Authorization: Bearer " . getenv("ASHTECH_API_KEY"),
>         "Content-Type: application/json",
>     ],
>     CURLOPT_POSTFIELDS => json_encode($payload),
> ]);
> $payment = json_decode(curl_exec($ch), true);
> curl_close($ch);
> ```

Ne remplacez pas la référence de session OTP par votre référence de commande. Une session expirée renvoie `400 otp_expired` : recommencez alors une nouvelle collecte sans réutiliser l'ancien OTP.

Après avoir affiché le code, demandez au client de saisir le code OTP reçu, puis relancez la collecte avec exactement les mêmes `amount`, `currency`, `phone`, `operator` et `country_code`, ainsi que le `reference` de session renvoyé par `otp_required` et le champ `otp`. Le serveur réserve atomiquement la session pendant la confirmation : une seconde confirmation concurrente renvoie `409 otp_confirmation_in_progress`. Un OTP explicitement invalide renvoie `400 invalid_otp` et peut être réessayé avec la même session. Si le résultat de la confirmation reste incertain, l'API renvoie `202 pending` : ne renvoyez pas le code et consultez le statut ou attendez le webhook.

## Gestion des réponses et des erreurs

### `202 pending`

Le paiement est accepté par AshTech Pay et attend la confirmation opérateur. Enregistrez `transaction_id` et `reference`, puis attendez le webhook ou interrogez le statut.

### `400 otp_required`

Le paiement attend une action du client. Vérifiez si la réponse contient :

* `reference` : OTP envoyé par SMS ou session OTP nécessitant une référence ;
* `ussd_code` : code à composer pour obtenir l'OTP ;
* les deux champs selon le parcours.

### `400 otp_expired`

La session OTP a expiré. Recommencez une nouvelle collecte sans réutiliser l'ancien code ou l'ancienne référence.

### `payment.completed` et `payment.failed`

Traitez ces événements de manière idempotente. Répondez rapidement `2xx`, vérifiez la transaction côté serveur et ne créditez ou ne livrez qu'une seule fois.

## Bonnes pratiques

* Utilisez le catalogue `/v1/countries` pour les pays et opérateurs actifs.
* N'associez pas vous-même un pays à un flux fixe.
* N'affichez pas `success` après la réponse initiale `202`.
* N'exposez jamais votre clé API dans le navigateur.
* Ne stockez pas les codes OTP plus longtemps que nécessaire.
* Utilisez le webhook et `GET /v1/transaction/:id?user_id=VOTRE_USER_ID` pour confirmer le résultat final.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.