Files
backend/documentation/CDC_API_Backend.md
2026-07-04 22:47:55 +02:00

23 KiB

Cahier des Charges — API & Base de Données

Projet : Laverie Connectée — Backend

Version : 1.0
Date : 2026-06-27
Stack principale : Laravel 11, MySQL 8, Redis, Docker


1. Contexte & Objectifs

1.1 Contexte

Développement d'une API REST centrale servant :

  • Une application mobile multi-plateforme (Flutter : Android / iOS / Web)
  • Un back-office d'administration (Vue.js)
  • Des automates de laverie connectés via passerelle LLDP

L'API est le seul point d'entrée pour toute la logique métier. Aucun client ne communique directement avec la base de données.

1.2 Objectifs

  • Exposer des endpoints REST sécurisés (JWT) pour tous les clients
  • Gérer les utilisateurs, l'authentification, les porte-monnaie et les paiements
  • Orchestrer les communications avec les automates (commandes machines)
  • Automatiser les notifications (cron, push, événementiel)
  • Garantir la conformité RGPD sur toutes les données personnelles

2. Stack Technique

Composant Technologie Version Justification
Framework Laravel 11.x Robustesse, écosystème, ORM Eloquent
Base de données principale MySQL 8.0 Transactions ACID, maturité
Cache & Queues Redis 7.x Queues asynchrones, cache sessions
Auth Laravel Sanctum 4.x JWT stateless multi-guard
ORM Eloquent (Laravel) Relations, scopes, casting
Queues Laravel Horizon 5.x Monitoring des workers Redis
Scheduler Laravel Scheduler (Laravel) Cron jobs natifs
Notifications Push Firebase FCM HTTP v1 Android + Web
Notifications Push APNs (Apple) HTTP/2 iOS
Paiement Abstraction PaymentProvider Multi-partenaire
Tests PHPUnit + Pest TDD sur logique métier critique
Documentation API Scramble (L5-Swagger) OpenAPI 3.0 auto-générée
Conteneurisation Docker + Docker Compose Dev/staging/prod reproductibles
CI/CD GitHub Actions Tests + déploiement automatisé

3. Architecture

3.1 Structure du projet Laravel

app/
├── Http/
│   ├── Controllers/
│   │   ├── Auth/
│   │   │   ├── UserAuthController.php
│   │   │   └── SupervisorAuthController.php
│   │   ├── User/
│   │   │   ├── WalletController.php
│   │   │   ├── BookingController.php
│   │   │   └── WashController.php
│   │   ├── Supervisor/
│   │   │   ├── DashboardController.php
│   │   │   ├── MachineController.php
│   │   │   └── PricingController.php
│   │   └── Webhook/
│   │       └── PaymentWebhookController.php
│   ├── Middleware/
│   │   ├── EnsureEmailVerified.php
│   │   ├── CheckWalletBalance.php
│   │   └── RateLimitPayment.php
│   └── Requests/          # Form Requests (validation)
├── Models/
│   ├── User.php
│   ├── Supervisor.php
│   ├── Establishment.php
│   ├── Machine.php
│   ├── Booking.php
│   ├── Wash.php
│   ├── WalletTransaction.php
│   ├── PricingRule.php
│   ├── Promotion.php
│   ├── Notification.php
│   └── GdprConsent.php
├── Services/
│   ├── WalletService.php           # Logique porte-monnaie (atomique)
│   ├── BookingService.php          # Réservation + débit auto no-show
│   ├── MachineCommandService.php   # Abstraction commandes LLDP
│   ├── NotificationService.php     # FCM + APNs
│   ├── Payment/
│   │   ├── PaymentProviderInterface.php
│   │   ├── StripeProvider.php
│   │   └── LydiProvider.php        # Exemple partenaire alternatif
│   └── PricingService.php          # Calcul tarif dynamique
├── Jobs/
│   ├── SendPushNotificationJob.php
│   ├── DebitNoShowBookingJob.php
│   └── SyncMachineStatusJob.php
├── Events/ & Listeners/
├── Policies/                       # Autorisation par modèle
└── Console/Commands/               # Artisan custom

3.2 Guards d'authentification

sanctum guards :
├── user      → token scope [user]    → app mobile
├── supervisor → token scope [supervisor] → back-office
└── machine   → token scope [machine] → automates (optionnel)

3.3 Flux de données principal

App Flutter → HTTPS → API Laravel → MySQL
                              ↓
                        Redis Queue → Job Workers
                              ↓
                    FCM / APNs / LLDP Gateway

4. Base de Données — Schéma Complet

4.1 Table users

CREATE TABLE users (
    id              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    uuid            CHAR(36) UNIQUE NOT NULL,              -- Identifiant public exposé dans l'API
    first_name      VARCHAR(100) NOT NULL,
    last_name       VARCHAR(100) NOT NULL,
    email           VARCHAR(255) UNIQUE NOT NULL,
    phone           VARCHAR(20) UNIQUE,
    birthdate       DATE,
    email_verified_at TIMESTAMP NULL,
    password        VARCHAR(255) NOT NULL,
    wallet_balance  DECIMAL(10,2) NOT NULL DEFAULT 0.00,
    referral_code   VARCHAR(20) UNIQUE,
    referred_by     BIGINT UNSIGNED NULL REFERENCES users(id),
    loyalty_points  INT UNSIGNED NOT NULL DEFAULT 0,
    fcm_token       VARCHAR(255) NULL,                     -- Token push Android/Web
    apns_token      VARCHAR(255) NULL,                     -- Token push iOS
    locale          VARCHAR(10) DEFAULT 'fr',
    is_active       BOOLEAN DEFAULT TRUE,
    anonymized_at   TIMESTAMP NULL,                        -- RGPD : suppression logique
    created_at      TIMESTAMP,
    updated_at      TIMESTAMP,
    deleted_at      TIMESTAMP NULL                         -- Soft delete
);

4.2 Table gdpr_consents

CREATE TABLE gdpr_consents (
    id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id     BIGINT UNSIGNED NOT NULL REFERENCES users(id),
    type        ENUM('data_processing','marketing','analytics') NOT NULL,
    accepted    BOOLEAN NOT NULL,
    ip_address  VARCHAR(45),
    user_agent  TEXT,
    accepted_at TIMESTAMP NOT NULL,
    revoked_at  TIMESTAMP NULL
);

4.3 Table supervisors

CREATE TABLE supervisors (
    id                BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    establishment_id  BIGINT UNSIGNED NOT NULL REFERENCES establishments(id),
    name              VARCHAR(200) NOT NULL,
    email             VARCHAR(255) UNIQUE NOT NULL,
    password          VARCHAR(255) NOT NULL,
    role              ENUM('admin','manager','viewer') DEFAULT 'manager',
    is_active         BOOLEAN DEFAULT TRUE,
    created_at        TIMESTAMP,
    updated_at        TIMESTAMP
);

4.4 Table establishments

CREATE TABLE establishments (
    id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    uuid        CHAR(36) UNIQUE NOT NULL,
    name        VARCHAR(200) NOT NULL,
    address     TEXT NOT NULL,
    city        VARCHAR(100),
    zip_code    VARCHAR(10),
    latitude    DECIMAL(10,8),
    longitude   DECIMAL(11,8),
    timezone    VARCHAR(50) DEFAULT 'Europe/Paris',
    is_active   BOOLEAN DEFAULT TRUE,
    created_at  TIMESTAMP,
    updated_at  TIMESTAMP
);

4.5 Table machines

CREATE TABLE machines (
    id                BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    uuid              CHAR(36) UNIQUE NOT NULL,
    establishment_id  BIGINT UNSIGNED NOT NULL REFERENCES establishments(id),
    name              VARCHAR(100) NOT NULL,
    type              ENUM('washer_small','washer_large','dryer_small','dryer_large') NOT NULL,
    qr_code           VARCHAR(255) UNIQUE NOT NULL,
    lldp_device_id    VARCHAR(100),                        -- Identifiant côté passerelle LLDP
    status            ENUM('available','running','reserved','maintenance','offline') DEFAULT 'available',
    current_user_id   BIGINT UNSIGNED NULL REFERENCES users(id),
    cycle_started_at  TIMESTAMP NULL,
    cycle_ends_at     TIMESTAMP NULL,
    last_heartbeat    TIMESTAMP NULL,                      -- Dernier ping de l'automate
    created_at        TIMESTAMP,
    updated_at        TIMESTAMP
);

4.6 Table pricing_rules

CREATE TABLE pricing_rules (
    id            BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    machine_id    BIGINT UNSIGNED NOT NULL REFERENCES machines(id),
    day_type      ENUM('weekday','weekend','holiday','all') DEFAULT 'all',
    slot_start    TIME NOT NULL,
    slot_end      TIME NOT NULL,
    price         DECIMAL(6,2) NOT NULL,
    label         VARCHAR(100),                            -- Ex: "Heure creuse", "Heure pleine"
    requires_app  BOOLEAN DEFAULT FALSE,                   -- Tarif exclusif app
    created_at    TIMESTAMP,
    updated_at    TIMESTAMP
);

4.7 Table promotions

CREATE TABLE promotions (
    id                BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    establishment_id  BIGINT UNSIGNED NOT NULL REFERENCES establishments(id),
    machine_type      ENUM('washer_small','washer_large','dryer_small','dryer_large','all') DEFAULT 'all',
    discount_type     ENUM('percent','fixed') NOT NULL,
    discount_value    DECIMAL(6,2) NOT NULL,
    starts_at         TIMESTAMP NOT NULL,
    ends_at           TIMESTAMP NOT NULL,
    description       TEXT,
    is_active         BOOLEAN DEFAULT TRUE,
    created_at        TIMESTAMP,
    updated_at        TIMESTAMP
);

4.8 Table bookings

CREATE TABLE bookings (
    id              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    uuid            CHAR(36) UNIQUE NOT NULL,
    user_id         BIGINT UNSIGNED NOT NULL REFERENCES users(id),
    machine_id      BIGINT UNSIGNED NOT NULL REFERENCES machines(id),
    slot_start      TIMESTAMP NOT NULL,
    slot_end        TIMESTAMP NOT NULL,
    amount_reserved DECIMAL(6,2) NOT NULL,                 -- Montant débité au moment de la réservation
    amount_penalty  DECIMAL(6,2) DEFAULT 0.00,             -- Pénalité no-show
    status          ENUM('pending','confirmed','active','completed','cancelled','no_show') DEFAULT 'pending',
    cancelled_at    TIMESTAMP NULL,
    penalty_applied_at TIMESTAMP NULL,
    created_at      TIMESTAMP,
    updated_at      TIMESTAMP
);

4.9 Table washes

CREATE TABLE washes (
    id              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    uuid            CHAR(36) UNIQUE NOT NULL,
    user_id         BIGINT UNSIGNED NOT NULL REFERENCES users(id),
    machine_id      BIGINT UNSIGNED NOT NULL REFERENCES machines(id),
    booking_id      BIGINT UNSIGNED NULL REFERENCES bookings(id),
    trigger_method  ENUM('qr_code','booking','terminal') NOT NULL,
    started_at      TIMESTAMP NOT NULL,
    ended_at        TIMESTAMP NULL,
    duration_minutes INT UNSIGNED,
    cost            DECIMAL(6,2) NOT NULL,
    pricing_rule_id BIGINT UNSIGNED NULL REFERENCES pricing_rules(id),
    created_at      TIMESTAMP,
    updated_at      TIMESTAMP
);

4.10 Table wallet_transactions

CREATE TABLE wallet_transactions (
    id              BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    uuid            CHAR(36) UNIQUE NOT NULL,
    user_id         BIGINT UNSIGNED NOT NULL REFERENCES users(id),
    type            ENUM('credit','debit') NOT NULL,
    amount          DECIMAL(10,2) NOT NULL,
    balance_after   DECIMAL(10,2) NOT NULL,
    source          ENUM('top_up','wash','booking','penalty','refund','referral_bonus') NOT NULL,
    reference_id    BIGINT UNSIGNED NULL,                  -- ID du wash ou booking associé
    reference_type  VARCHAR(100) NULL,                     -- Morph polymorphique
    payment_provider VARCHAR(50) NULL,
    payment_ref     VARCHAR(255) NULL,                     -- Référence transaction externe
    created_at      TIMESTAMP
);

4.11 Table push_notifications

CREATE TABLE push_notifications (
    id          BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_id     BIGINT UNSIGNED NULL REFERENCES users(id),   -- NULL = broadcast
    type        VARCHAR(100) NOT NULL,
    title       VARCHAR(255) NOT NULL,
    body        TEXT NOT NULL,
    data        JSON NULL,
    channel     ENUM('fcm','apns','both') DEFAULT 'both',
    status      ENUM('pending','sent','failed') DEFAULT 'pending',
    sent_at     TIMESTAMP NULL,
    created_at  TIMESTAMP
);

5. Endpoints API — Spécification Complète

5.1 Authentification utilisateur

Méthode Endpoint Description Auth
POST /api/v1/auth/register Inscription + collecte consentements RGPD Public
POST /api/v1/auth/login Connexion, retourne access_token + refresh_token Public
POST /api/v1/auth/refresh Renouvelle le token Public
POST /api/v1/auth/logout Révoque le token User
POST /api/v1/auth/forgot-password Envoi email reset Public
POST /api/v1/auth/reset-password Réinitialisation Public
GET /api/v1/auth/verify-email/{token} Vérification email Public

Payload register :

{
  "first_name": "Jean",
  "last_name": "Dupont",
  "email": "jean@example.com",
  "phone": "+33612345678",
  "password": "••••••••",
  "birthdate": "1990-01-15",
  "referral_code": "ABC123",
  "consents": {
    "data_processing": true,
    "marketing": false,
    "analytics": true
  }
}

Réponse login :

{
  "access_token": "eyJ...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "def50200...",
  "user": {
    "uuid": "550e8400-e29b-41d4-a716-446655440000",
    "first_name": "Jean",
    "wallet_balance": "12.50"
  }
}

5.2 Porte-monnaie

Méthode Endpoint Description Auth
GET /api/v1/wallet Solde actuel User
GET /api/v1/wallet/transactions Historique paginé User
POST /api/v1/wallet/top-up/initiate Initier rechargement User
POST /api/v1/wallet/top-up/confirm Confirmer après paiement User
GET /api/v1/wallet/top-up/providers Liste partenaires paiement disponibles User

Payload top-up initiate :

{
  "amount": 20.00,
  "provider": "stripe",
  "return_url": "laverie://wallet/topup-result"
}

Réponse :

{
  "payment_intent_id": "pi_xxx",
  "redirect_url": "https://checkout.stripe.com/...",
  "expires_at": "2026-06-27T15:30:00Z"
}

5.3 Établissements & Machines

Méthode Endpoint Description Auth
GET /api/v1/establishments Liste avec coordonnées GPS User
GET /api/v1/establishments/{uuid} Détail + machines + disponibilités User
GET /api/v1/machines/{uuid} Détail machine + tarif actuel User
GET /api/v1/machines/{uuid}/availability Créneaux disponibles sur N jours User
GET /api/v1/machines/{uuid}/pricing Grille tarifaire active User
POST /api/v1/machines/{uuid}/heartbeat Ping de l'automate (machine scope) Machine

5.4 Réservations

Méthode Endpoint Description Auth
POST /api/v1/bookings Créer une réservation User
GET /api/v1/bookings Mes réservations User
GET /api/v1/bookings/{uuid} Détail réservation User
PATCH /api/v1/bookings/{uuid}/cancel Annuler User
PATCH /api/v1/bookings/{uuid}/move Déplacer le créneau User

Payload POST /bookings :

{
  "machine_uuid": "550e8400-...",
  "slot_start": "2026-06-28T09:00:00Z",
  "slot_end": "2026-06-28T10:00:00Z"
}

Règles métier :

  • Vérification atomique du solde (mutex Redis sur wallet:user:{id})
  • Débit immédiat du montant + supplément réservation (1€)
  • Si annulation > 2h avant : remboursement intégral
  • Si annulation < 2h ou no-show : pénalité définie par machine.no_show_penalty
  • Cron toutes les 5 minutes : détection no-show → DebitNoShowBookingJob

5.5 Lavages

Méthode Endpoint Description Auth
POST /api/v1/washes/start Démarrer via QR code User
GET /api/v1/washes/{uuid} Statut en cours User
GET /api/v1/washes Historique User

Payload POST /washes/start :

{
  "qr_code": "MACHINE_QR_ABC123",
  "program": "60C_standard"
}

Flux :

  1. Valider le QR → identifier la machine
  2. Vérifier statut machine (available)
  3. Vérifier solde suffisant
  4. Calculer prix (tarif actuel + promo éventuelle)
  5. Débit atomique du porte-monnaie
  6. Envoyer commande à la passerelle LLDP via MachineCommandService
  7. Mettre à jour le statut machine (running)
  8. Retourner confirmation + durée estimée

5.6 Authentification superviseur

Méthode Endpoint Description Auth
POST /api/v1/supervisor/auth/login Connexion superviseur Public
POST /api/v1/supervisor/auth/logout Déconnexion Supervisor
GET /api/v1/supervisor/dashboard Statistiques + alertes Supervisor
GET /api/v1/supervisor/machines Parc machines de l'établissement Supervisor
PATCH /api/v1/supervisor/machines/{uuid} Mise à jour machine Supervisor
GET /api/v1/supervisor/pricing Grille tarifaire Supervisor
POST /api/v1/supervisor/pricing Créer règle tarifaire Supervisor
PUT /api/v1/supervisor/pricing/{id} Modifier règle Supervisor
DELETE /api/v1/supervisor/pricing/{id} Supprimer règle Supervisor
GET /api/v1/supervisor/promotions Promotions actives Supervisor
POST /api/v1/supervisor/promotions Créer promotion Supervisor

6. Services Métier Critiques

6.1 WalletService — Opérations atomiques

Toute opération sur le porte-monnaie passe par ce service. Les débits/crédits sont encapsulés dans des transactions MySQL + verrou Redis pour éviter les race conditions.

WalletService::credit(user, amount, source, referenceId)
WalletService::debit(user, amount, source, referenceId)  ← lance WalletInsufficientFundsException si insuffisant
WalletService::reserve(user, amount, bookingId)          ← débit préventif réservation
WalletService::refund(user, walletTransactionId)

Règle absolue : Aucun controller ne doit modifier users.wallet_balance directement. Tout passe par WalletService.

6.2 PricingService — Tarif dynamique

PricingService::getCurrentPrice(machine, datetime)
  → Cherche la PricingRule active pour (machine_id, day_type, heure)
  → Applique Promotion active si existante
  → Retourne { base_price, discount, final_price, label, requires_app }

Priorité des règles : Règle spécifique machine > Règle par type machine > Tarif défaut établissement.

6.3 MachineCommandService — Abstraction LLDP

MachineCommandService::startWash(machine, program)
MachineCommandService::stopWash(machine)
MachineCommandService::getStatus(machine)

L'implémentation concrète (LldpGatewayAdapter) communique avec la passerelle LLDP via HTTP interne. En cas d'indisponibilité de la passerelle, la commande est mise en queue Redis et retentée 3 fois (backoff exponentiel).

6.4 PaymentProviderInterface

interface PaymentProviderInterface {
    public function initiateTopUp(User $user, float $amount, string $returnUrl): TopUpIntent;
    public function confirmTopUp(string $paymentRef): TopUpConfirmation;
    public function handleWebhook(Request $request): void;
}

Implémentations : StripeProvider, LydiProvider, SumUpProvider (extensible).


7. Tâches Planifiées (Cron)

Fréquence Job Description
Toutes les 5 min CheckNoShowBookings Détecter et débiter les no-shows
Toutes les 5 min SyncMachineStatuses Interroger la passerelle LLDP
Toutes les heures SendScheduledNotifications Notifications planifiées en attente
Chaque jour 02:00 GenerateDailyStats Agrégation statistiques pour dashboard
Chaque jour 03:00 CleanExpiredTokens Purge tokens révoqués
Chaque semaine AnonymizeInactiveUsers RGPD : users inactifs > 3 ans

8. Notifications — Architecture

8.1 Déclencheurs

Événement Canal Timing
Rechargement confirmé Push Immédiat
Réservation confirmée Push Immédiat
Rappel créneau Push 30 min avant
Lavage terminé Push Immédiat
Solde faible (< 5€) Push À chaque débit
Promotion disponible Push Planifié par superviseur
No-show détecté Push À la détection

8.2 Implémentation

  • Les notifications sont toujours créées en base (push_notifications) avant envoi
  • L'envoi passe par un Job Redis (SendPushNotificationJob) → résilience
  • FCM HTTP v1 pour Android/Web, APNs HTTP/2 pour iOS
  • En cas d'échec, 3 retries avec backoff (1min, 5min, 15min)

9. Sécurité

9.1 Authentification

  • Tokens JWT via Laravel Sanctum (stateless)
  • Refresh token rotation (invalidation à chaque renouvellement)
  • Rate limiting : 5 tentatives de login / 10 minutes / IP
  • Rate limiting paiements : 3 initiations / minute / user

9.2 Données

  • Mots de passe hashés : bcrypt (cost 12)
  • Données sensibles (IBAN, tokens paiement) : chiffrées AES-256-CBC en base
  • HTTPS obligatoire (TLS 1.3 minimum)
  • UUID exposés dans l'API (jamais les auto-increment IDs)
  • Soft deletes sur users — anonymisation RGPD séparée

9.3 API

  • Validation stricte de tous les inputs (FormRequest)
  • Politique CORS restrictive (origines whitelist)
  • Headers sécurité : Strict-Transport-Security, X-Frame-Options, Content-Security-Policy

10. Tests

Type Outil Couverture cible
Tests unitaires Pest WalletService, PricingService, BookingService
Tests d'intégration Pest + SQLite in-memory Tous les endpoints API
Tests de contrats Webhooks paiement
Tests de charge k6 Endpoints critiques (start wash, top-up)

Cas critiques à tester :

  • Race condition débit simultané du porte-monnaie
  • Timeout passerelle LLDP → comportement de la queue
  • Webhook paiement reçu en doublon (idempotence)
  • No-show avec annulation simultanée

11. Variables d'environnement requises

APP_ENV=production
APP_KEY=base64:...

DB_HOST=
DB_DATABASE=laverie
DB_USERNAME=
DB_PASSWORD=

REDIS_HOST=
REDIS_PASSWORD=

# Paiement
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=

# Notifications
FIREBASE_PROJECT_ID=
FIREBASE_PRIVATE_KEY=
FIREBASE_CLIENT_EMAIL=
APNS_KEY_ID=
APNS_TEAM_ID=
APNS_PRIVATE_KEY_PATH=

# LLDP Gateway
LLDP_GATEWAY_URL=
LLDP_GATEWAY_TOKEN=

# URLs
FRONTEND_URL=
APP_URL=

12. Livrables attendus

  • Code source Laravel complet (GitHub)
  • Migrations SQL versionnées
  • Seeders (données de démo)
  • Documentation OpenAPI 3.0 auto-générée (/api/documentation)
  • Collection Postman exportée
  • docker-compose.yml pour dev local
  • .env.example documenté
  • README avec instructions d'installation
  • Suite de tests (couverture > 80% sur les services critiques)