# Intégration SMS - Documentation Technique

## 📋 Vue d'ensemble

Cette documentation décrit l'intégration complète de l'API SMS SendText dans le portail 2AB. Le système permet l'envoi de SMS individuels et groupés, avec suivi complet des envois, gestion du solde, et statistiques détaillées.

**Date de création:** 8 Mars 2026
**Version:** 1.0
**API Provider:** SendText (https://sendtext.sn)

---

## 🏗️ Architecture

### Schéma des Entités

```
SmsConfiguration (Configuration API)
├── id
├── name (nom descriptif)
├── apiKey (SNT-API-KEY)
├── apiSecret (SNT-API-SECRET)
├── senderName (nom expéditeur, max 11 car.)
├── apiEndpoint (URL API)
├── balanceEndpoint (URL solde)
├── isActive (bool, une seule active)
├── description (optionnel)
├── createdAt, updatedAt, deletedAt (Gedmo)
└── Relations:
    ├── OneToMany → SmsLog
    └── OneToMany → SmsCampaign

SmsLog (Historique SMS individuel)
├── id
├── recipient (numéro téléphone)
├── message (texte SMS)
├── smsType (normal | flash)
├── statusId (1-5, voir statuts ci-dessous)
├── statusDescription
├── messageId (ID fourni par SendText)
├── sendtextSmsCount (nombre de SMS consommés)
├── attemptCount (nombre de tentatives)
├── lastAttemptAt
├── failureReason (si échec)
├── cost (optionnel)
├── createdAt, updatedAt (Gedmo)
└── Relations:
    ├── ManyToOne → SmsConfiguration
    ├── ManyToOne → User (nullable)
    └── ManyToOne → SmsCampaign (nullable)

SmsCampaign (Campagnes d'envoi groupé)
├── id
├── name (nom de la campagne)
├── campaignId (ID SendText)
├── totalRecipients
├── successCount
├── failedCount
├── pendingCount
├── sentAt
├── description
├── createdAt, updatedAt, deletedAt (Gedmo)
└── Relations:
    ├── ManyToOne → SmsConfiguration
    ├── ManyToOne → User (createdBy)
    ├── OneToMany → SmsLog
    └── OneToMany → SmsCampaignLine

SmsCampaignLine (Lignes de campagne)
├── id
├── phone
├── message
├── messageId
├── sendtextSmsCount
├── statusId
├── statusDescription
├── createdAt, updatedAt (Gedmo)
└── Relations:
    ├── ManyToOne → SmsCampaign
    └── ManyToOne → User (nullable)
```

### Codes Statut SMS (statusId)

| ID | Constante | Label | Description |
|----|-----------|-------|-------------|
| 1 | STATUS_PENDING | Traité et envoyé à l'opérateur | Le message a été traité et envoyé à l'opérateur mobile |
| 2 | STATUS_SENT_NOT_DELIVERED | Envoyé mais non délivré | Le message a été envoyé à l'opérateur mobile mais pas délivré au destinataire |
| 3 | STATUS_DELIVERED | Délivré avec succès | Le message a été traité et livré avec succès |
| 4 | STATUS_NOT_RECEIVED | Envoyé mais non reçu | Le message a été envoyé à l'utilisateur mais pas reçu pour plusieurs raisons possibles |
| 5 | STATUS_REJECTED | Rejeté par l'opérateur | Le message a été rejeté par le système de l'opérateur |

### Services

#### 1. `SmsApiClient` (Communication HTTP)
**Localisation:** `src/Service/Sms/SmsApiClient.php`

Gère toute la communication HTTP avec l'API SendText.

**Méthodes principales:**
```php
// Envoi SMS unique
public function sendSingle(
    SmsConfiguration $config,
    string $phone,
    string $message,
    string $type = 'normal'
): array

// Envoi SMS groupé (campagne)
public function sendBulk(
    SmsConfiguration $config,
    string $campaignName,
    array $lines,
    string $type = 'normal'
): array

// Consultation du solde
public function getBalance(SmsConfiguration $config): array

// Test de connexion
public function testConnection(SmsConfiguration $config): array
```

**Headers HTTP requis:**
```php
'Content-Type' => 'application/json'
'SNT-API-KEY' => $config->getApiKey()
'SNT-API-SECRET' => $config->getApiSecret()
```

**Format de retour:**
```php
[
    'success' => bool,
    'data' => array,      // Réponse API si succès
    'error' => string,    // Message d'erreur si échec
    'status_code' => int  // Code HTTP
]
```

#### 2. `SmsService` (Logique métier)
**Localisation:** `src/Service/Sms/SmsService.php`

Service principal pour l'envoi de SMS avec logging automatique.

**Méthodes principales:**
```php
// Envoi SMS unique (avec User ou numéro)
public function sendSms(
    User|string $recipient,
    string $message,
    string $type = 'normal',
    ?SmsConfiguration $config = null
): ?SmsLog

// Envoi SMS groupé
public function sendBulkSms(
    string $campaignName,
    array $recipients,
    User $createdBy,
    string $type = 'normal',
    ?SmsConfiguration $config = null
): ?SmsCampaign

// Réessayer les SMS échoués
public function retryFailedSms(int $maxAttempts = 3): int

// Méthodes spécifiques sécurité
public function send2FACode(User $user, string $code): bool
public function sendIpValidationCode(User $user, string $code): bool
```

**Exemple d'utilisation:**
```php
// Injection du service
public function __construct(
    private SmsService $smsService
) {}

// Envoi SMS simple
$smsLog = $this->smsService->sendSms(
    $user,
    "Votre code de vérification: 1234",
    'normal'
);

if ($smsLog && !$smsLog->isFailed()) {
    // SMS envoyé avec succès
}

// Envoi campagne
$recipients = [
    ['user' => $user1, 'phone' => $user1->getPhone(), 'message' => 'Message 1'],
    ['user' => $user2, 'phone' => $user2->getPhone(), 'message' => 'Message 2'],
];

$campaign = $this->smsService->sendBulkSms(
    'Ma Campagne',
    $recipients,
    $currentUser,
    'normal'
);
```

#### 3. `SmsConfigurationService` (Gestion config)
**Localisation:** `src/Service/Sms/SmsConfigurationService.php`

Gestion CRUD des configurations SMS.

**Méthodes principales:**
```php
public function createConfiguration(SmsConfiguration $config): bool
public function updateConfiguration(SmsConfiguration $config): bool
public function deleteConfiguration(SmsConfiguration $config): bool
public function setActiveConfiguration(int $configId): bool
public function getActiveConfiguration(): ?SmsConfiguration
public function testConfiguration(SmsConfiguration $config): array
```

#### 4. `SmsBalanceService` (Gestion solde)
**Localisation:** `src/Service/Sms/SmsBalanceService.php`

Consultation du solde SMS avec cache (15 minutes).

**Méthodes principales:**
```php
// Récupérer le solde (avec cache)
public function getCurrentBalance(bool $forceRefresh = false): array

// Forcer le rafraîchissement
public function refreshBalance(): array

// Vérifier si le solde est faible
public function isBalanceLow(int $threshold = 100): bool

// Obtenir le statut du solde avec niveaux d'alerte
public function getBalanceStatus(): array
```

**Niveaux d'alerte du solde:**
- **Critical** (< 50 SMS): Rechargez immédiatement
- **Warning** (< 100 SMS): Rechargez bientôt
- **OK** (< 500 SMS): Solde suffisant
- **Good** (≥ 500 SMS): Solde optimal

---

## 🔌 API SendText - Référence

### Endpoint: Envoi SMS Unique

**URL:** `POST {SMS_API_ENDPOINT}`
**Exemple:** `POST https://api.sendtext.sn/v2/sms`

**Requête:**
```json
{
    "sender_name": "SENDTEXT",
    "sms_type": "normal",
    "phone": "221763983535",
    "text": "Bonjour. Votre code d'activation est 3725."
}
```

**Réponse:**
```json
{
    "type": "normal",
    "text": "Bonjour. Votre code d'activation est 3725.",
    "phone": "221763983535",
    "senderName": "SENDTEXT",
    "messageId": "SENDTEXT-MESSAGE-ID-35903981927237213851",
    "sendtextSmsCount": 1,
    "statusId": 1,
    "statusDescription": "PENDING|Message sent to next instance"
}
```

### Endpoint: Envoi SMS Groupé

**URL:** `POST {SMS_API_ENDPOINT}/bulk_sms`
**Exemple:** `POST https://api.sendtext.sn/v2/sms/bulk_sms`

**Requête:**
```json
{
    "sender_name": "Ecole SN",
    "sms_type": "flash",
    "campaign_name": "Admission 2025",
    "campaign_lines": [
        {
            "phone": "221701234567",
            "text": "Bonjour Monsier Sow..."
        },
        {
            "phone": "221761234567",
            "text": "Bonjour Madame Diop..."
        }
    ]
}
```

**Réponse:**
```json
{
    "type": "flash",
    "senderName": "Ecole SN",
    "campaignId": 4,
    "campaignLines": [
        {
            "text": "Bonjour Monsier Sow...",
            "phone": "221701234567",
            "messageId": "SENDTEXT-MESSAGE-ID-...",
            "sendtextSmsCount": 1,
            "statusId": 1,
            "statusDescription": "PENDING|Message sent to next instance"
        }
    ]
}
```

### Endpoint: Consultation Solde

**URL:** `GET {SMS_BALANCE_ENDPOINT}`
**Exemple:** `GET https://api.sendtext.sn/v2/balance`

**Réponse:**
```json
{
    "balance": 8478,
    "expires_at": "2024-12-31T00:00:00.000000Z",
    "updated_at": "2023-11-23T11:45:52.000000Z"
}
```

---

## 🎨 Interface Admin

### Routes Disponibles

#### Configuration SMS
- **Liste:** `GET /admin/sms` → `admin_sms_index`
- **Créer:** `GET/POST /admin/sms/create` → `admin_sms_create`
- **Voir:** `GET /admin/sms/{id}` → `admin_sms_show`
- **Éditer:** `GET/POST /admin/sms/{id}/edit` → `admin_sms_edit`
- **Activer:** `POST /admin/sms/{id}/activate` → `admin_sms_activate`
- **Tester:** `POST /admin/sms/{id}/test` → `admin_sms_test`
- **Supprimer:** `POST /admin/sms/{id}/delete` → `admin_sms_delete`

#### Envoi SMS
- **Envoi unique:** `GET/POST /admin/sms/send/single` → `admin_sms_send_single`
- **Solde:** `GET /admin/sms/balance/view` → `admin_sms_balance`

#### Campagnes
- **Liste:** `GET /admin/sms/campaigns` → `admin_sms_campaigns_index`
- **Créer:** `GET/POST /admin/sms/campaigns/create` → `admin_sms_campaigns_create`
- **Voir:** `GET /admin/sms/campaigns/{id}` → `admin_sms_campaigns_show`

#### Historique & Stats
- **Liste:** `GET /admin/sms/logs` → `admin_sms_logs_index`
- **Voir:** `GET /admin/sms/logs/{id}` → `admin_sms_logs_show`
- **Statistiques:** `GET /admin/sms/logs/statistics/view` → `admin_sms_statistics`

### Menu Admin

Le menu SMS est intégré dans le menu admin avec un sous-menu déroulant contenant:
- Configuration SMS
- Envoyer SMS
- Campagnes
- Historique
- Solde SMS
- Statistiques

---

## 🔐 Intégration Sécurité

### 1. Codes 2FA par SMS

Le service SMS est intégré dans `TwoFactorService` pour envoyer les codes de vérification par SMS.

**Utilisation:**
```php
// Dans TwoFactorService
public function send2FACode(User $user, string $code): bool
{
    return $this->smsService->send2FACode($user, $code);
}
```

**Message type:** "Code de vérification 2FA: {code}. Valide pendant 10 minutes. Ne partagez pas ce code."

### 2. Validation IP par SMS

Le service SMS est intégré dans `IpValidationService` pour envoyer les codes de validation IP.

**Utilisation:**
```php
// Dans IpValidationService
public function sendValidationCode(User $user, string $code): bool
{
    return $this->smsService->sendIpValidationCode($user, $code);
}
```

**Message type:** "Code de validation IP: {code}. Valide pendant 24 heures. Si vous n'avez pas demandé ce code, contactez le support."

---

## ⚙️ Configuration

### Variables d'Environnement

Fichier: `.env`

```env
# SMS SendText API Configuration
SMS_API_KEY=SNT_API_KEY_1f09daad-7b45-4025-9aae-28014a33f308
SMS_API_SECRET=SNT_API_SECRET_f554e24c-d67e-4b78-84c4-5b63cc91f2f8
SMS_API_ENDPOINT=https://api.sendtext.sn/v2/sms
SMS_BALANCE_ENDPOINT=https://api.sendtext.sn/v2/balance
SMS_SENDER_NAME=SENDTEXT
```

**⚠️ Important:** Ces valeurs sont des exemples. Utilisez vos propres clés API SendText.

### Configuration Initiale

**Étape 1: Créer une configuration SMS**
1. Accéder à `/admin/sms/create`
2. Remplir le formulaire:
   - Nom: "Production SendText"
   - Clé API: Votre clé SNT-API-KEY
   - Secret API: Votre secret SNT-API-SECRET
   - Nom expéditeur: "SENDTEXT" (ou votre nom personnalisé)
   - Endpoint API: `https://api.sendtext.sn/v2/sms`
   - Endpoint solde: `https://api.sendtext.sn/v2/balance`
3. Cocher "Définir comme configuration active"
4. Cliquer "Tester la connexion" pour valider
5. Enregistrer

**Étape 2: Vérifier le solde**
- Accéder à `/admin/sms/balance/view`
- Le solde doit s'afficher avec un indicateur de statut

**Étape 3: Test d'envoi**
- Accéder à `/admin/sms/send/single`
- Sélectionner un utilisateur test ou saisir votre numéro
- Envoyer un SMS de test
- Vérifier dans `/admin/sms/logs` que le SMS apparaît avec statusId=1 ou 3

---

## 📊 Monitoring & Statistiques

### Dashboard Statistiques

**Route:** `/admin/sms/logs/statistics/view`

**Données affichées:**
- **Aujourd'hui:** Total, délivrés, en attente, échoués
- **Ce mois:** Total, délivrés, en attente, échoués
- **Total (all time):** Statistiques globales
- **Graphique:** Évolution sur 30 derniers jours

### Logs d'Historique

**Route:** `/admin/sms/logs`

**Filtres disponibles:**
- Recherche (numéro, message, message ID)
- Statut (1-5)
- Période (date début/fin)

**Informations par SMS:**
- Destinataire
- Message
- Statut (label + description)
- Message ID SendText
- Nombre de tentatives
- Date d'envoi
- Configuration utilisée
- Utilisateur associé (si applicable)

---

## 🔧 Troubleshooting

### Problème: SMS non envoyé (statusId reste à 1)

**Diagnostic:**
1. Vérifier le solde: `/admin/sms/balance/view`
2. Vérifier les logs: `/admin/sms/logs` → Chercher le messageId
3. Consulter `failureReason` si statusId = 5

**Solutions:**
- Solde insuffisant → Recharger le compte SendText
- Numéro invalide → Vérifier le format (221XXXXXXXXX)
- API Key/Secret invalide → Vérifier la configuration

### Problème: Erreur "Aucune configuration SMS active"

**Solution:**
1. Aller dans `/admin/sms`
2. Cliquer "Activer" sur une configuration existante
3. Ou créer une nouvelle configuration avec "isActive" coché

### Problème: Erreur HTTP lors de l'envoi

**Diagnostic:**
1. Vérifier les logs Symfony: `var/log/dev.log`
2. Chercher `sms_api_http_error` ou `sms_api_transport_error`
3. Vérifier la connectivité réseau avec SendText

**Solutions:**
- Erreur 401/403 → Clés API incorrectes
- Erreur 500 → Problème côté SendText (contacter le support)
- Timeout → Vérifier la connexion internet

### Problème: Solde ne se met pas à jour

**Solution:**
1. Forcer le rafraîchissement: `/admin/sms/balance/view?refresh=true`
2. Vider le cache Symfony: `php bin/console cache:clear`

---

## 🚀 Utilisation Avancée

### Envoi SMS Programmé (à implémenter)

Pour envoyer des SMS de manière différée, utiliser Symfony Messenger:

```php
// Créer un message
$message = new SendSmsMessage($userId, $messageText, $type);

// Dispatcher avec délai
$this->messageBus->dispatch($message, [
    new DelayStamp(3600000) // 1 heure
]);
```

### Webhook de Statut (à implémenter)

Pour recevoir les mises à jour de statut en temps réel depuis SendText:

1. Créer un controller webhook: `POST /api/sms/webhook`
2. Configurer l'URL dans SendText
3. Valider la signature de la requête
4. Mettre à jour `SmsLog.statusId` en conséquence

### Export des Statistiques

Utiliser les repositories pour exporter les données:

```php
$stats = $this->smsLogRepository->getStatistics(
    new \DateTime('first day of this month'),
    new \DateTime('last day of this month')
);

// Exporter en CSV, Excel, etc.
```

---

## 📝 Notes Importantes

### Sécurité

1. **Secrets API:** Ne jamais commiter les clés API dans Git. Utiliser `.env.local` pour les valeurs réelles.
2. **Validation numéros:** Tous les numéros sont validés avant envoi (regex + format international)
3. **Rate limiting:** Implémenter un rate limiting pour éviter l'abus (à ajouter si nécessaire)
4. **Logging:** Tous les envois sont loggés avec FileLogger pour audit

### Performance

1. **Cache solde:** Le solde est mis en cache 15 minutes pour éviter les appels API répétés
2. **Bulk operations:** Utiliser `/bulk_sms` pour envois > 10 destinataires
3. **Async processing:** Pour gros volumes, passer en mode async avec Messenger

### Coûts

1. **Tarification:** Chaque SMS consomme des crédits (vérifier `sendtextSmsCount`)
2. **Messages longs:** Un SMS > 160 caractères consomme plusieurs crédits
3. **Monitoring:** Surveiller le solde régulièrement via le dashboard

---

## 📚 Références

- **Documentation SendText:** https://sendtext.sn/docs (si disponible)
- **Support SendText:** contact@sendtext.sn
- **Symfony HttpClient:** https://symfony.com/doc/current/http_client.html
- **Doctrine ORM:** https://www.doctrine-project.org/

---

**Dernière mise à jour:** 8 Mars 2026
**Auteur:** Claude Code AI
**Version:** 1.0
