feat: synchronisation mode deconnecte fin chat et stats

This commit is contained in:
2025-08-31 18:21:20 +02:00
parent f5bef999df
commit 96af94ad13
129 changed files with 125731 additions and 110375 deletions

View File

@@ -0,0 +1,622 @@
# PLANNING STRIPE - DÉVELOPPEUR BACKEND PHP
## API PHP 8.3 - Intégration Stripe Connect + Terminal
### Période : 25/08/2024 - 05/09/2024
---
## 📅 LUNDI 25/08 - Setup et architecture (8h)
### 🌅 Matin (4h)
```bash
# Installation Stripe PHP SDK
cd api
composer require stripe/stripe-php
```
#### ✅ Configuration environnement
- [ ] Créer `config/stripe.php` avec clés TEST
- [ ] Ajouter variables `.env` :
```env
STRIPE_PUBLIC_KEY=pk_test_...
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_API_VERSION=2024-06-20
```
- [ ] Créer service `StripeService.php` singleton
- [ ] Configurer middleware authentification API
#### ✅ Base de données
```sql
-- Tables à créer
CREATE TABLE stripe_accounts (
id INT PRIMARY KEY AUTO_INCREMENT,
amicale_id INT NOT NULL,
stripe_account_id VARCHAR(255) UNIQUE,
charges_enabled BOOLEAN DEFAULT FALSE,
payouts_enabled BOOLEAN DEFAULT FALSE,
onboarding_completed BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (amicale_id) REFERENCES amicales(id)
);
CREATE TABLE payment_intents (
id INT PRIMARY KEY AUTO_INCREMENT,
stripe_payment_intent_id VARCHAR(255) UNIQUE,
amicale_id INT NOT NULL,
pompier_id INT NOT NULL,
amount INT NOT NULL, -- en centimes
currency VARCHAR(3) DEFAULT 'eur',
status VARCHAR(50),
application_fee INT,
metadata JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (amicale_id) REFERENCES amicales(id),
FOREIGN KEY (pompier_id) REFERENCES users(id)
);
CREATE TABLE terminal_readers (
id INT PRIMARY KEY AUTO_INCREMENT,
stripe_reader_id VARCHAR(255) UNIQUE,
amicale_id INT NOT NULL,
label VARCHAR(255),
location VARCHAR(255),
status VARCHAR(50),
device_type VARCHAR(50),
last_seen_at TIMESTAMP,
FOREIGN KEY (amicale_id) REFERENCES amicales(id)
);
CREATE TABLE android_certified_devices (
id INT PRIMARY KEY AUTO_INCREMENT,
manufacturer VARCHAR(100),
model VARCHAR(200),
model_identifier VARCHAR(200),
tap_to_pay_certified BOOLEAN DEFAULT FALSE,
certification_date DATE,
min_android_version INT,
country VARCHAR(2) DEFAULT 'FR',
last_verified TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_manufacturer_model (manufacturer, model)
);
```
### 🌆 Après-midi (4h)
#### ✅ Endpoints Connect - Onboarding
```php
// POST /api/amicales/{id}/stripe-account
public function createStripeAccount($amicaleId) {
$amicale = Amicale::find($amicaleId);
$account = \Stripe\Account::create([
'type' => 'express',
'country' => 'FR',
'email' => $amicale->email,
'capabilities' => [
'card_payments' => ['requested' => true],
'transfers' => ['requested' => true],
],
'business_type' => 'non_profit',
'business_profile' => [
'name' => $amicale->name,
'product_description' => 'Vente de calendriers des pompiers',
],
]);
// Sauvegarder stripe_account_id
return $account;
}
// GET /api/amicales/{id}/onboarding-link
public function getOnboardingLink($amicaleId) {
$accountLink = \Stripe\AccountLink::create([
'account' => $amicale->stripe_account_id,
'refresh_url' => config('app.url') . '/stripe/refresh',
'return_url' => config('app.url') . '/stripe/success',
'type' => 'account_onboarding',
]);
return ['url' => $accountLink->url];
}
```
---
## 📅 MARDI 26/08 - Webhooks et Terminal (8h)
### 🌅 Matin (4h)
#### ✅ Webhooks handler
```php
// POST /api/webhooks/stripe
public function handleWebhook(Request $request) {
$payload = $request->getContent();
$sig_header = $request->header('Stripe-Signature');
try {
$event = \Stripe\Webhook::constructEvent(
$payload, $sig_header, config('stripe.webhook_secret')
);
} catch(\Exception $e) {
return response('Invalid signature', 400);
}
switch ($event->type) {
case 'account.updated':
$this->handleAccountUpdated($event->data->object);
break;
case 'account.application.authorized':
$this->handleAccountAuthorized($event->data->object);
break;
case 'payment_intent.succeeded':
$this->handlePaymentSucceeded($event->data->object);
break;
}
return response('Webhook handled', 200);
}
```
#### ✅ Terminal Connection Token
```php
// POST /api/terminal/connection-token
public function createConnectionToken(Request $request) {
$pompier = Auth::user();
$amicale = $pompier->amicale;
$connectionToken = \Stripe\Terminal\ConnectionToken::create([
'location' => $amicale->stripe_location_id,
], [
'stripe_account' => $amicale->stripe_account_id
]);
return ['secret' => $connectionToken->secret];
}
```
### 🌆 Après-midi (4h)
#### ✅ Gestion des Locations
```php
// POST /api/amicales/{id}/create-location
public function createLocation($amicaleId) {
$amicale = Amicale::find($amicaleId);
$location = \Stripe\Terminal\Location::create([
'display_name' => $amicale->name,
'address' => [
'line1' => $amicale->address,
'city' => $amicale->city,
'postal_code' => $amicale->postal_code,
'country' => 'FR',
],
], [
'stripe_account' => $amicale->stripe_account_id
]);
$amicale->update(['stripe_location_id' => $location->id]);
return $location;
}
```
---
## 📅 MERCREDI 27/08 - Paiements et fees (8h)
### 🌅 Matin (4h)
#### ✅ Création PaymentIntent avec commission
```php
// POST /api/payments/create-intent
public function createPaymentIntent(Request $request) {
$validated = $request->validate([
'amount' => 'required|integer|min:100', // en centimes
'amicale_id' => 'required|exists:amicales,id',
]);
$pompier = Auth::user();
$amicale = Amicale::find($validated['amicale_id']);
// Calculer la commission (2.5% ou 50 centimes minimum)
$applicationFee = max(
50, // 0.50€ minimum
round($validated['amount'] * 0.025) // 2.5%
);
$paymentIntent = \Stripe\PaymentIntent::create([
'amount' => $validated['amount'],
'currency' => 'eur',
'payment_method_types' => ['card_present'],
'capture_method' => 'automatic',
'application_fee_amount' => $applicationFee,
'transfer_data' => [
'destination' => $amicale->stripe_account_id,
],
'metadata' => [
'pompier_id' => $pompier->id,
'pompier_name' => $pompier->name,
'amicale_id' => $amicale->id,
'calendrier_annee' => date('Y'),
],
]);
// Sauvegarder en DB
PaymentIntent::create([
'stripe_payment_intent_id' => $paymentIntent->id,
'amicale_id' => $amicale->id,
'pompier_id' => $pompier->id,
'amount' => $validated['amount'],
'application_fee' => $applicationFee,
'status' => $paymentIntent->status,
]);
return [
'client_secret' => $paymentIntent->client_secret,
'payment_intent_id' => $paymentIntent->id,
];
}
```
### 🌆 Après-midi (4h)
#### ✅ Capture et confirmation
```php
// POST /api/payments/{id}/capture
public function capturePayment($paymentIntentId) {
$localPayment = PaymentIntent::where('stripe_payment_intent_id', $paymentIntentId)->first();
$paymentIntent = \Stripe\PaymentIntent::retrieve($paymentIntentId);
if ($paymentIntent->status === 'requires_capture') {
$paymentIntent->capture();
}
$localPayment->update(['status' => $paymentIntent->status]);
// Si succès, envoyer email reçu
if ($paymentIntent->status === 'succeeded') {
$this->sendReceipt($localPayment);
}
return $paymentIntent;
}
// GET /api/payments/{id}/status
public function getPaymentStatus($paymentIntentId) {
$payment = PaymentIntent::where('stripe_payment_intent_id', $paymentIntentId)->first();
return [
'status' => $payment->status,
'amount' => $payment->amount,
'created_at' => $payment->created_at,
];
}
```
---
## 📅 JEUDI 28/08 - Reporting et Android compatibility (8h)
### 🌅 Matin (4h)
#### ✅ Gestion appareils Android certifiés
```php
// POST /api/devices/check-tap-to-pay
public function checkTapToPayCapability(Request $request) {
$validated = $request->validate([
'platform' => 'required|in:ios,android',
'manufacturer' => 'required_if:platform,android',
'model' => 'required_if:platform,android',
'os_version' => 'required',
]);
if ($validated['platform'] === 'ios') {
// iPhone XS et ultérieurs avec iOS 15.4+
$supportedModels = ['iPhone11,', 'iPhone12,', 'iPhone13,', 'iPhone14,', 'iPhone15,', 'iPhone16,'];
$modelSupported = false;
foreach ($supportedModels as $prefix) {
if (str_starts_with($validated['model'], $prefix)) {
$modelSupported = true;
break;
}
}
$osVersion = explode('.', $validated['os_version']);
$osSupported = $osVersion[0] > 15 ||
($osVersion[0] == 15 && isset($osVersion[1]) && $osVersion[1] >= 4);
return [
'tap_to_pay_supported' => $modelSupported && $osSupported,
'message' => $modelSupported && $osSupported ?
'Tap to Pay disponible' :
'iPhone XS ou ultérieur avec iOS 15.4+ requis'
];
}
// Android - vérifier dans la base de données
$device = DB::table('android_certified_devices')
->where('manufacturer', $validated['manufacturer'])
->where('model', $validated['model'])
->where('tap_to_pay_certified', true)
->first();
return [
'tap_to_pay_supported' => $device !== null,
'message' => $device ?
'Tap to Pay disponible sur cet appareil' :
'Appareil non certifié pour Tap to Pay en France',
'alternative' => !$device ? 'Utilisez un iPhone compatible' : null
];
}
// GET /api/devices/certified-android
public function getCertifiedAndroidDevices() {
return DB::table('android_certified_devices')
->where('tap_to_pay_certified', true)
->where('country', 'FR')
->orderBy('manufacturer')
->orderBy('model')
->get();
}
```
#### ✅ Seeder pour appareils certifiés
```php
// database/seeders/AndroidCertifiedDevicesSeeder.php
public function run() {
$devices = [
// Samsung
['manufacturer' => 'Samsung', 'model' => 'Galaxy S21', 'model_identifier' => 'SM-G991B', 'min_android_version' => 11],
['manufacturer' => 'Samsung', 'model' => 'Galaxy S21+', 'model_identifier' => 'SM-G996B', 'min_android_version' => 11],
['manufacturer' => 'Samsung', 'model' => 'Galaxy S21 Ultra', 'model_identifier' => 'SM-G998B', 'min_android_version' => 11],
['manufacturer' => 'Samsung', 'model' => 'Galaxy S22', 'model_identifier' => 'SM-S901B', 'min_android_version' => 12],
['manufacturer' => 'Samsung', 'model' => 'Galaxy S23', 'model_identifier' => 'SM-S911B', 'min_android_version' => 13],
['manufacturer' => 'Samsung', 'model' => 'Galaxy S24', 'model_identifier' => 'SM-S921B', 'min_android_version' => 14],
// Google Pixel
['manufacturer' => 'Google', 'model' => 'Pixel 6', 'model_identifier' => 'oriole', 'min_android_version' => 12],
['manufacturer' => 'Google', 'model' => 'Pixel 6 Pro', 'model_identifier' => 'raven', 'min_android_version' => 12],
['manufacturer' => 'Google', 'model' => 'Pixel 7', 'model_identifier' => 'panther', 'min_android_version' => 13],
['manufacturer' => 'Google', 'model' => 'Pixel 8', 'model_identifier' => 'shiba', 'min_android_version' => 14],
];
foreach ($devices as $device) {
DB::table('android_certified_devices')->insert([
'manufacturer' => $device['manufacturer'],
'model' => $device['model'],
'model_identifier' => $device['model_identifier'],
'tap_to_pay_certified' => true,
'certification_date' => now(),
'min_android_version' => $device['min_android_version'],
'country' => 'FR',
]);
}
}
```
#### ✅ Endpoints statistiques
```php
// GET /api/amicales/{id}/stats
public function getAmicaleStats($amicaleId) {
$stats = DB::table('payment_intents')
->where('amicale_id', $amicaleId)
->where('status', 'succeeded')
->selectRaw('
COUNT(*) as total_ventes,
SUM(amount) as total_montant,
SUM(application_fee) as total_commissions,
DATE(created_at) as date
')
->groupBy('date')
->get();
return $stats;
}
// GET /api/pompiers/{id}/ventes
public function getPompierVentes($pompierId) {
return PaymentIntent::where('pompier_id', $pompierId)
->where('status', 'succeeded')
->orderBy('created_at', 'desc')
->paginate(20);
}
```
### 🌆 Après-midi (4h)
#### ✅ Gestion des remboursements
```php
// POST /api/payments/{id}/refund
public function refundPayment($paymentIntentId, Request $request) {
$validated = $request->validate([
'amount' => 'integer|min:100', // optionnel, remboursement partiel
'reason' => 'string|in:duplicate,fraudulent,requested_by_customer',
]);
$payment = PaymentIntent::where('stripe_payment_intent_id', $paymentIntentId)->first();
$refund = \Stripe\Refund::create([
'payment_intent' => $paymentIntentId,
'amount' => $validated['amount'] ?? null, // null = remboursement total
'reason' => $validated['reason'] ?? 'requested_by_customer',
'reverse_transfer' => true, // Important pour Connect
'refund_application_fee' => true, // Rembourser aussi la commission
]);
$payment->update(['status' => 'refunded']);
return $refund;
}
```
---
## 📅 VENDREDI 29/08 - Mode offline et sync (8h)
### 🌅 Matin (4h)
#### ✅ Queue de synchronisation
```php
// POST /api/payments/batch-sync
public function batchSync(Request $request) {
$validated = $request->validate([
'transactions' => 'required|array',
'transactions.*.local_id' => 'required|string',
'transactions.*.amount' => 'required|integer',
'transactions.*.created_at' => 'required|date',
'transactions.*.payment_method' => 'required|in:card,cash',
]);
$results = [];
foreach ($validated['transactions'] as $transaction) {
if ($transaction['payment_method'] === 'cash') {
// Enregistrer paiement cash uniquement en DB
$results[] = $this->recordCashPayment($transaction);
} else {
// Créer PaymentIntent a posteriori (si possible)
$results[] = $this->createOfflinePayment($transaction);
}
}
return ['synced' => $results];
}
```
### 🌆 Après-midi (4h)
#### ✅ Tests unitaires critiques
```php
class StripePaymentTest extends TestCase {
public function test_create_payment_intent_with_fees() {
// Test création PaymentIntent avec commission
}
public function test_webhook_signature_validation() {
// Test sécurité webhook
}
public function test_refund_reverses_transfer() {
// Test remboursement avec annulation virement
}
}
```
---
## 📅 LUNDI 01/09 - Sécurité et optimisations (8h)
### 🌅 Matin (4h)
#### ✅ Rate limiting et sécurité
```php
// Middleware RateLimiter pour endpoints sensibles
Route::middleware(['throttle:10,1'])->group(function () {
Route::post('/payments/create-intent', 'PaymentController@createIntent');
});
// Validation des montants
public function validateAmount($amount) {
if ($amount < 100 || $amount > 50000) { // 1€ - 500€
throw new ValidationException('Montant invalide');
}
}
```
### 🌆 Après-midi (4h)
#### ✅ Logs et monitoring
```php
// Logger tous les événements Stripe
Log::channel('stripe')->info('Payment created', [
'payment_intent_id' => $paymentIntent->id,
'amount' => $paymentIntent->amount,
'pompier_id' => $pompier->id,
]);
```
---
## 📅 MARDI 02/09 - Documentation API (4h)
#### ✅ Documentation OpenAPI/Swagger
```yaml
/api/payments/create-intent:
post:
summary: Créer une intention de paiement
parameters:
- name: amount
type: integer
required: true
description: Montant en centimes
responses:
200:
description: PaymentIntent créé
```
---
## 📅 MERCREDI 03/09 - Tests d'intégration (8h)
#### ✅ Tests end-to-end
- [ ] Parcours complet onboarding amicale
- [ ] Création paiement → capture → confirmation
- [ ] Test remboursement complet et partiel
- [ ] Test webhooks avec ngrok
---
## 📅 JEUDI 04/09 - Mise en production (8h)
---
## 📅 VENDREDI 05/09 - Support et livraison finale (8h)
### 🌅 Matin (4h)
#### ✅ Déploiement final
- [ ] Migration DB production
- [ ] Variables environnement LIVE
- [ ] Smoke tests production
- [ ] Vérification des webhooks en production
### 🌆 Après-midi (4h)
#### ✅ Support et monitoring
- [ ] Monitoring des premiers paiements réels
- [ ] Support hotline pour équipes terrain
- [ ] Documentation de passation
- [ ] Réunion de clôture et retour d'expérience
---
## 📊 RÉCAPITULATIF
- **Total heures** : 72h sur 10 jours
- **Endpoints créés** : 15
- **Tables DB** : 3
- **Tests** : 20+
## 🔧 DÉPENDANCES
```json
{
"require": {
"php": "^8.3",
"stripe/stripe-php": "^13.0",
"laravel/framework": "^10.0"
}
}
```
## ⚠️ CHECKLIST SÉCURITÉ
- [ ] ❌ JAMAIS logger les clés secrètes
- [ ] ✅ TOUJOURS valider signature webhooks
- [ ] ✅ TOUJOURS utiliser HTTPS
- [ ] ✅ Rate limiting sur endpoints paiement
- [ ] ✅ Logs détaillés pour audit
---
*Document créé le 24/08/2024 - À mettre à jour quotidiennement*

View File

@@ -78,7 +78,7 @@ $clientIp = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
if (IPBlocker::isBlocked($clientIp)) {
http_response_code(403);
Response::json([
'success' => false,
'status' => 'error',
'message' => 'Access denied. Your IP has been blocked.',
'error_code' => 'IP_BLOCKED'
], 403);
@@ -89,7 +89,7 @@ if (IPBlocker::isBlocked($clientIp)) {
if (!SecurityMonitor::checkRateLimit($clientIp)) {
http_response_code(429);
Response::json([
'success' => false,
'status' => 'error',
'message' => 'Too many requests. Please try again later.',
'error_code' => 'RATE_LIMIT_EXCEEDED'
], 429);
@@ -109,7 +109,7 @@ if (!SecurityMonitor::checkScanPattern($requestUri)) {
IPBlocker::block($clientIp, 3600, 'Suspicious scan pattern detected');
http_response_code(404);
Response::json([
'success' => false,
'status' => 'error',
'message' => 'Not found'
], 404);
exit;
@@ -122,7 +122,7 @@ if (!empty($allParams) && !SecurityMonitor::checkRequestParameters($allParams))
IPBlocker::blockPermanent($clientIp, 'SQL injection attempt');
http_response_code(400);
Response::json([
'success' => false,
'status' => 'error',
'message' => 'Bad request'
], 400);
exit;
@@ -182,7 +182,7 @@ set_exception_handler(function($exception) use ($requestUri, $requestMethod) {
// Retourner une erreur 500
http_response_code(500);
Response::json([
'success' => false,
'status' => 'error',
'message' => 'Internal server error'
], 500);
});

View File

@@ -79,10 +79,11 @@ try {
Database::init($dbConfig);
$db = Database::getInstance();
LogService::log('Démarrage du processeur de queue d\'emails', [
'level' => 'info',
'script' => 'process_email_queue.php'
]);
// Log uniquement si mode debug activé
// LogService::log('Démarrage du processeur de queue d\'emails', [
// 'level' => 'info',
// 'script' => 'process_email_queue.php'
// ]);
// Récupérer les emails en attente
$stmt = $db->prepare('
@@ -97,9 +98,10 @@ try {
$emails = $stmt->fetchAll(PDO::FETCH_ASSOC);
if (empty($emails)) {
LogService::log('Aucun email en attente dans la queue', [
'level' => 'debug'
]);
// Ne pas logger quand il n'y a rien à faire (toutes les 5 minutes)
// LogService::log('Aucun email en attente dans la queue', [
// 'level' => 'debug'
// ]);
exit(0);
}
@@ -262,12 +264,15 @@ try {
usleep(500000); // 0.5 seconde
}
LogService::log('Traitement de la queue terminé', [
'level' => 'info',
'success' => $successCount,
'failures' => $failureCount,
'total' => count($emails)
]);
// Logger uniquement s'il y avait des emails à traiter
if (count($emails) > 0) {
LogService::log('Traitement de la queue terminé', [
'level' => 'info',
'success' => $successCount,
'failures' => $failureCount,
'total' => count($emails)
]);
}
} catch (Exception $e) {
LogService::log('Erreur fatale dans le processeur de queue', [

File diff suppressed because it is too large Load Diff

View File

@@ -54,14 +54,17 @@ class Response {
]);
}
// Log de débogage
error_log('Envoi de la réponse JSON: ' . $jsonResponse);
// Log de débogage (désactivé car peut causer des problèmes avec de grandes réponses)
// error_log('Envoi de la réponse JSON: ' . $jsonResponse);
// Envoyer la réponse
echo $jsonResponse;
// S'assurer que tout est envoyé
flush();
// Terminer l'exécution pour éviter d'envoyer du contenu supplémentaire
exit;
}
/**

View File

@@ -103,8 +103,11 @@ class Router {
// Routes du module Chat
$this->get('chat/rooms', ['ChatController', 'getRooms']);
$this->post('chat/rooms', ['ChatController', 'createRoom']);
$this->put('chat/rooms/:id', ['ChatController', 'updateRoom']);
$this->delete('chat/rooms/:id', ['ChatController', 'deleteRoom']);
$this->get('chat/rooms/:id/messages', ['ChatController', 'getRoomMessages']);
$this->post('chat/rooms/:id/messages', ['ChatController', 'sendMessage']);
$this->put('chat/messages/:id', ['ChatController', 'updateMessage']);
$this->post('chat/rooms/:id/read', ['ChatController', 'markAsRead']);
$this->get('chat/recipients', ['ChatController', 'getRecipients']);

156
api/test_chat_temp_id.php Normal file
View File

@@ -0,0 +1,156 @@
<?php
/**
* Script de test pour vérifier le support des temp_id dans l'API chat
*
* Usage: php test_chat_temp_id.php
*/
// Configuration
$apiUrl = 'http://localhost/api';
$sessionId = 'test_session_id'; // À remplacer par un session_id valide
// Fonction pour faire une requête HTTP
function makeRequest($method, $endpoint, $data = null, $sessionId = null) {
global $apiUrl;
$ch = curl_init($apiUrl . $endpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
$headers = ['Content-Type: application/json'];
if ($sessionId) {
$headers[] = 'Authorization: Bearer ' . $sessionId;
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
if ($data !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return [
'code' => $httpCode,
'response' => json_decode($response, true)
];
}
// Tests
echo "=== Tests de support des temp_id dans l'API Chat ===\n\n";
// Test 1: Création d'une room avec temp_id
echo "Test 1: Création d'une room avec temp_id\n";
$roomData = [
'type' => 'private',
'participants' => [2], // ID d'un autre utilisateur
'title' => 'Test Room Offline',
'temp_id' => 'temp_room_' . uniqid()
];
echo "Requête POST /chat/rooms avec temp_id: {$roomData['temp_id']}\n";
$result = makeRequest('POST', '/chat/rooms', $roomData, $sessionId);
if ($result['code'] === 201 && isset($result['response']['room']['temp_id'])) {
echo "✅ Succès: Room créée avec temp_id retourné\n";
echo " - ID réel: " . $result['response']['room']['id'] . "\n";
echo " - temp_id: " . $result['response']['room']['temp_id'] . "\n";
$roomId = $result['response']['room']['id'];
} else {
echo "❌ Échec: temp_id non retourné dans la réponse\n";
echo " Response: " . json_encode($result['response']) . "\n";
$roomId = null;
}
echo "\n";
// Test 2: Envoi d'un message avec temp_id
if ($roomId) {
echo "Test 2: Envoi d'un message avec temp_id\n";
$messageData = [
'content' => 'Message créé hors ligne',
'temp_id' => 'temp_msg_' . uniqid()
];
echo "Requête POST /chat/rooms/{$roomId}/messages avec temp_id: {$messageData['temp_id']}\n";
$result = makeRequest('POST', "/chat/rooms/{$roomId}/messages", $messageData, $sessionId);
if ($result['code'] === 201 && isset($result['response']['message']['temp_id'])) {
echo "✅ Succès: Message créé avec temp_id retourné\n";
echo " - ID réel: " . $result['response']['message']['id'] . "\n";
echo " - temp_id: " . $result['response']['message']['temp_id'] . "\n";
$messageId = $result['response']['message']['id'];
} else {
echo "❌ Échec: temp_id non retourné dans la réponse\n";
echo " Response: " . json_encode($result['response']) . "\n";
$messageId = null;
}
echo "\n";
}
// Test 3: Mise à jour d'une room avec temp_id
if ($roomId) {
echo "Test 3: Mise à jour d'une room avec temp_id\n";
$updateData = [
'title' => 'Room mise à jour',
'temp_id' => 'temp_update_room_' . uniqid()
];
echo "Requête PUT /chat/rooms/{$roomId} avec temp_id: {$updateData['temp_id']}\n";
$result = makeRequest('PUT', "/chat/rooms/{$roomId}", $updateData, $sessionId);
if ($result['code'] === 200 && isset($result['response']['room']['temp_id'])) {
echo "✅ Succès: Room mise à jour avec temp_id retourné\n";
echo " - temp_id: " . $result['response']['room']['temp_id'] . "\n";
} else {
echo "❌ Échec: temp_id non retourné dans la réponse\n";
echo " Response: " . json_encode($result['response']) . "\n";
}
echo "\n";
}
// Test 4: Mise à jour d'un message avec temp_id
if ($messageId) {
echo "Test 4: Mise à jour d'un message avec temp_id\n";
$updateData = [
'content' => 'Message modifié',
'temp_id' => 'temp_update_msg_' . uniqid()
];
echo "Requête PUT /chat/messages/{$messageId} avec temp_id: {$updateData['temp_id']}\n";
$result = makeRequest('PUT', "/chat/messages/{$messageId}", $updateData, $sessionId);
if ($result['code'] === 200 && isset($result['response']['message']['temp_id'])) {
echo "✅ Succès: Message mis à jour avec temp_id retourné\n";
echo " - temp_id: " . $result['response']['message']['temp_id'] . "\n";
} else {
echo "❌ Échec: temp_id non retourné dans la réponse\n";
echo " Response: " . json_encode($result['response']) . "\n";
}
echo "\n";
}
// Test 5: Création sans temp_id (doit continuer à fonctionner)
echo "Test 5: Création d'une room SANS temp_id (compatibilité)\n";
$roomData = [
'type' => 'private',
'participants' => [2],
'title' => 'Test Room Normal'
];
echo "Requête POST /chat/rooms sans temp_id\n";
$result = makeRequest('POST', '/chat/rooms', $roomData, $sessionId);
if ($result['code'] === 201 && !isset($result['response']['room']['temp_id'])) {
echo "✅ Succès: Room créée normalement sans temp_id\n";
echo " - ID: " . $result['response']['room']['id'] . "\n";
} else if ($result['code'] === 201) {
echo "⚠️ Attention: Room créée mais temp_id présent alors qu'il ne devrait pas\n";
} else {
echo "❌ Échec: Erreur lors de la création\n";
echo " Response: " . json_encode($result['response']) . "\n";
}
echo "\n=== Fin des tests ===\n";
echo "\nNote: Assurez-vous d'avoir un session_id valide et que l'API est accessible.\n";
echo "Pour obtenir un session_id valide, connectez-vous d'abord via POST /api/login\n";