Aller au contenu
KodrudderConnexionCommencer gratuitement

Dossier d'exemple, non retouché

Réservation pour coachs sportifs

Ce dossier a été écrit par Kodrudder. Nous l'affichons tel quel, avec ses hypothèses et les écarts que la relecture a trouvés.

Relevé de cette génération : 174 s, coût estimé 0,56 $.

  • 15 règles métier
  • 32 critères d'acceptation testables
  • 8 tables SQL
  • 20 routes d'API
  • 25 tâches de développement
  • 28 sources web
  • 8 écarts trouvés par la relecture, corrigés dans le plan

À lire d'abord

Produit. Application web de réservation pour coachs sportifs indépendants : le coach publie disponibilités et tarifs, le client réserve et paie en ligne sans compte, puis reçoit un SMS de rappel J-1. Objectif : moins de no-shows et moins de relances manuelles.

Ce qui est décidé

  • Marché France, EUR, interface en français, web responsive Europe/Paris (D-01, D-09).
  • Paiement intégral via Stripe Checkout, versement direct au coach via Connect Express (D-02, D-03).
  • Pas de compte client : email et mobile (06/07), lien de gestion par email (D-08).
  • Annulation remboursée jusqu'à 24 h avant la séance, aucune commission au MVP (D-07, D-10).
  • Rappel SMS Twilio J-1, strictement transactionnel, sans offre promotionnelle (D-05, D-06).

Ce qui reste une hypothèse à vérifier avant de coder

  • Frais Stripe Connect et modèle de charge : qui absorbe les frais de remboursement (D-03, RM-09).
  • Remboursement automatique impossible si le solde du coach est négatif : à tester sur compte test.
  • SMS de service sans consentement (D-06) : validation juridique RGPD/CNIL avant production.

Corrections de la relecture

ÉcartCe qui prévaut sur le texte des sections
EC-01Webhook tardif sur EXPIREE : PAYE puis REMBOURSE dans une seule transaction (RM-04)
EC-02rappel_sms créé à la confirmation si début > 24 h ; passé à annule à toute annulation
EC-03D-05 : 1 rappel + 1 SMS d'annulation coach éventuel, contenu transactionnel
EC-04Jeton de gestion = 256 bits partout (RM-13 aligné)
EC-05Blocage de date refusé si réservation EN_ATTENTE_PAIEMENT ou CONFIRMEE ce jour
EC-06Mobile 06/07 uniquement (RM-11 aligné) ; validation API avant insertion, 400 et non 500
EC-07Ajouter GET /api/coach/seances, /disponibilites, /dates-bloquees
EC-08E-03 lit le statut via API-15 par jeton

Comment utiliser ce dossier

  1. Contexte : donner à l'outil cette page, puis Cadrage, modèle de données (§4) et règles métier (RM), en précisant que les corrections EC-xx prévalent.
  2. Plan : lui faire suivre §6 dans l'ordre des jalons, de J0 à J3 (tranche « réserver et payer » P-01), une tâche T-xx à la fois.
  3. Validation : pour chaque tâche, exiger les CA liés et la « preuve de fin » (tests Vitest/Playwright, services externes mockés) avant de passer à la suivante.

Cadrage

4 liens vers des sources14 hypothèses marquées

Résumé produit

  • Application web de réservation de créneaux pour coachs sportifs indépendants. Chaque coach publie ses disponibilités et ses tarifs.
  • Le client réserve et paie en ligne. Il reçoit un rappel SMS avant la séance.
  • Objectif : moins de no-shows et moins d'échanges manuels (FAIT : brief ; la cible chiffrée est en H dans les mesures).

Utilisateurs et résultat attendu

IDUtilisateurRésultat attendu
U-01Coach indépendantRemplir son agenda, être payé d'avance, ne plus relancer à la main
U-02Client du coach (particulier)Réserver et payer en moins de 2 min sans créer de compte H, puis être rappelé avant la séance

Décisions tranchées

IDQuestionDécision retenueFait / H
D-01MarchéFrance, EUR, interface en françaisH
D-02PaiementStripe Checkout, paiement intégral à la réservation. Les frais standard sont de 1,5 % + 0,25 € pour une carte EEE standard (stripe.com/en-fr/pricing).FAIT (frais) / H (intégral)
D-03Versement au coachStripe Connect Express, pour que chaque coach soit payé directement. Les frais Connect sont à vérifier.H
D-04Fournisseur SMSTwilio, environ 0,08 $ par SMS vers la France (twilio.com)FAIT (prix) / H (choix)
D-05Volume de SMS1 SMS de rappel 24 h avant la séance. La confirmation passe par email. Coût d'environ 0,08 $ par réservation.H
D-06Cadre légal du SMSUn rappel est un message de service, sans consentement spécifique (rendv.fr). Aucune offre promotionnelle dans le SMS, car un message mixte peut être requalifié en prospection (source).FAIT
D-07AnnulationRemboursement intégral jusqu'à 24 h avant la séance, aucun remboursement ensuiteH
D-08Compte clientPas de compte : email et mobile suffisent. Le lien de gestion de la réservation est envoyé par email.H
D-09PlateformeWeb responsive, fuseau Europe/Paris, sans application nativeH
D-10CommissionAucune commission plateforme au MVP, abonnement à tester plus tardH

Périmètre MVP

IDFonction
MVP-01Compte coach (email + mot de passe) et onboarding Stripe Connect
MVP-02Création de séances : durée, prix, lieu
MVP-03Disponibilités hebdomadaires récurrentes et blocage de dates
MVP-04Page publique du coach (lien partageable) avec choix du créneau
MVP-05Paiement en ligne et verrouillage du créneau (pas de double réservation)
MVP-06Email de confirmation et SMS de rappel J-1
MVP-07Annulation par le client (règle D-07) et par le coach (remboursement automatique)
MVP-08Tableau de bord coach : liste des réservations et paiements

Hors périmètre

IDExcluPreuve qui justifierait son ajout H
HP-01Synchronisation Google/Apple CalendarAu moins 3 coachs sur 5 la réclament
HP-02Forfaits, abonnements et séances collectivesDemandes récurrentes en pilote
HP-03SMS marketing et relancesConsentement et base légale conçus au préalable
HP-04Application native, multi-langue, multi-deviseUsage mobile saturé ou demande hors France
HP-05Facturation et comptabilité avancéesBesoin déclaré par au moins 3 coachs

Mesures de succès (H : à valider par le responsable produit)

IDMesureCible
MS-01Coachs pilotes actifs (≥ 1 réservation payée)≥ 5 en 8 semaines
MS-02Conversion page coach vers paiement≥ 20 %
MS-03Taux de no-show des séances rappelées par SMSInférieur à la base déclarée par les coachs pilotes
MS-04Rappels SMS délivrés≥ 95 %
MS-05Temps de réservation de bout en bout< 2 min

Règles métier et critères d'acceptation

4 hypothèses marquées

Statuts (liste unique)

Statut de réservationSens
EN_ATTENTE_PAIEMENTCréneau verrouillé temporairement, paiement non confirmé
CONFIRMEEPaiement reçu, séance planifiée
ANNULEE_CLIENTAnnulée par le client
ANNULEE_COACHAnnulée par le coach
EXPIREEPaiement non finalisé avant expiration du verrou
TERMINEEHeure de fin de séance dépassée
Statut de paiementSens
AUCUNAucun paiement initié
PAYECapturé
REMBOURSERemboursement intégral émis
ECHEC_REMBOURSEMENTRemboursement refusé ou en erreur, à traiter manuellement

Besoins

IDBesoinMVP
BE-01Le coach crée un compte et se connecte (email + mot de passe)MVP-01
BE-02Le coach active ses paiements via l'onboarding Stripe ConnectMVP-01
BE-03Le coach définit ses séances : durée, prix, lieuMVP-02
BE-04Le coach définit des disponibilités hebdomadaires et bloque des datesMVP-03
BE-05Le client consulte la page publique d'un coach et ses créneaux libresMVP-04
BE-06Le client réserve sans compte (nom, email, mobile)MVP-04
BE-07Un créneau ne peut être réservé que par un seul clientMVP-05
BE-08Le client paie en ligne en une foisMVP-05
BE-09Le client reçoit un email de confirmation avec lien de gestionMVP-06
BE-10Le client reçoit un SMS de rappel 24 h avant la séanceMVP-06
BE-11Le client annule via son lien, selon le délaiMVP-07
BE-12Le coach annule une séance et le client est remboursé automatiquementMVP-07
BE-13Le coach consulte réservations et paiementsMVP-08

Règles métier

Valeurs par défaut en H. Toutes les heures sont en Europe/Paris (D-09).

IDRègleDéfaut HCas limites
RM-01Un créneau = (coach, début, durée). Il est réservable s'il est dans une disponibilité, hors date bloquée et sans réservation EN_ATTENTE_PAIEMENT ou CONFIRMEE chevauchantePas de chevauchement, pas de pause tamponDeux séances de durées différentes qui se chevauchent : la seconde est refusée. Créneau passé : non réservable
RM-02Délai minimal de réservation avant le début2 hCréneau à moins de 2 h : non proposé
RM-03Horizon maximal de réservation60 joursAu-delà : non proposé
RM-04Verrou de paiement : la création passe la réservation en EN_ATTENTE_PAIEMENT et verrouille le créneau15 min. Expiration → EXPIREE, créneau libéréPaiement confirmé après expiration : remboursement automatique et information du client. Abandon sur Stripe : EXPIREE à l'échéance
RM-05Concurrence : le verrou est atomique (contrainte d'unicité en base), la première écriture gagnen/aDeux requêtes simultanées : une seule réussit, l'autre reçoit « créneau indisponible »
RM-06Paiement : montant = prix de la séance, EUR, intégral. Passage à CONFIRMEE uniquement sur webhook Stripe signé et idempotentAucune commission (D-10)Webhook en doublon : sans effet. Échec de carte : réservation reste EN_ATTENTE_PAIEMENT jusqu'à expiration. Montant reçu ≠ attendu : réservation non confirmée
RM-07Annulation client (D-07) via lien de gestionPlus de 24 h avant le début : ANNULEE_CLIENT + remboursement intégral. À 24 h pile ou moins : annulation refuséeSéance déjà TERMINEE ou annulée : refus. Lien utilisé deux fois : idempotent
RM-08Annulation coach : toujours possible avant le début, remboursement intégral automatiqueNotification email + SMS au clientAnnulation avec remboursement en échec : ANNULEE_COACH, paiement ECHEC_REMBOURSEMENT, alerte au coach
RM-09Frais de remboursement : le client reçoit le montant intégral payé. Le surcoût éventuel des frais Stripe sur remboursement est à vérifierAbsorbé par le coach HCoach sans solde Connect suffisant : remboursement à traiter manuellement
RM-10Rappel SMS : 1 SMS 24 h avant le début, uniquement pour une réservation CONFIRMEE (D-05, D-06). Contenu transactionnel sans offre promotionnelleEnvoi à début − 24 hRéservation faite à moins de 24 h du début : pas de rappel, la confirmation par email suffit. Annulée avant l'envoi : aucun SMS. Mobile invalide ou échec Twilio : 1 nouvel essai à +10 min, puis echec journalisé, sans blocage de la réservation. Jamais d'envoi en doublon pour une même réservation
RM-11Validation du mobile : format français (+33 / 0X) normalisé E.164 ; email valideMobile et email obligatoiresFormat invalide : réservation refusée avec message explicite
RM-12Modification de disponibilités : sans effet sur les réservations CONFIRMEE existantes. Un blocage de date sur une réservation existante est refusé tant que le coach ne l'annule pas (RM-08)n/aSuppression d'une séance du catalogue : les réservations existantes sont conservées
RM-13Droits : un coach ne voit et ne modifie que ses données. Le lien de gestion client contient un jeton aléatoire non devinable, limité à une réservationJeton ≥ 128 bitsJeton invalide ou inconnu : 404 sans fuite d'information
RM-14Paiement activé : une séance n'est réservable que si l'onboarding Connect du coach est completn/aCompte Connect restreint ou désactivé après coup : nouvelles réservations bloquées, existantes conservées
RM-15Passage à TERMINEE : automatique après l'heure de finn/aAucun remboursement possible après TERMINEE

Table de décision : annulation et remboursement (RM-07, RM-08, RM-15)

InitiateurStatut courantDélai avant le débutRésultatRemboursement
ClientCONFIRMEE> 24 hANNULEE_CLIENTIntégral
ClientCONFIRMEE≤ 24 hRefuséAucun
ClientEN_ATTENTE_PAIEMENTtoutEXPIREE (libération du créneau)Aucun paiement à rembourser
ClientTERMINEE, ANNULEE_*, EXPIREEtoutRefuséAucun
CoachCONFIRMEEtout (avant début)ANNULEE_COACHIntégral automatique
CoachEN_ATTENTE_PAIEMENTtoutEXPIREEAucun paiement à rembourser
CoachTERMINEE, ANNULEE_*, EXPIREEtoutRefuséAucun

Critères d'acceptation

IDÉtant donnéQuandAlorsBERM
CA-01Un coach a une disponibilité le lundi 10:00–12:00 et une séance de 60 minUn client ouvre sa page publiqueLes créneaux 10:00 et 11:00 sont proposésBE-05RM-01
CA-02Une date est bloquée par le coachUn client consulte cette dateAucun créneau n'est proposéBE-04RM-01
CA-03Un créneau dans 1 hUn client consulte la pageLe créneau n'est pas proposéBE-05RM-02
CA-04Un créneau dans 61 joursUn client consulte la pageLe créneau n'est pas proposéBE-05RM-03
CA-05Un créneau libreUn client soumet nom, email et mobile validesRéservation EN_ATTENTE_PAIEMENT, redirection vers Stripe Checkout, créneau verrouilléBE-06, BE-08RM-04
CA-06Un créneau libreDeux requêtes de réservation arrivent simultanémentUne seule réussit, l'autre reçoit « créneau indisponible »BE-07RM-05
CA-07Une réservation EN_ATTENTE_PAIEMENT depuis 16 minLe job d'expiration s'exécuteStatut EXPIREE, créneau à nouveau réservableBE-07RM-04
CA-08Une réservation EN_ATTENTE_PAIEMENTUn webhook Stripe signé de paiement réussi arriveStatut CONFIRMEE, paiement PAYE, email de confirmation envoyéBE-08, BE-09RM-06
CA-09Le même webhook déjà traitéIl est reçu à nouveauAucun changement d'état, aucun second emailBE-08RM-06
CA-10Un webhook avec signature invalideIl est reçuRejeté (400), aucun changement d'étatBE-08RM-06
CA-11Une réservation EXPIREEUn webhook de paiement réussi arrivePaiement REMBOURSE automatiquement, réservation reste EXPIREEBE-08RM-04
CA-12Un paiement dont le montant ≠ prix attenduLe webhook arriveLa réservation n'est pas confirmée et l'anomalie est journaliséeBE-08RM-06
CA-13Une réservation CONFIRMEE à plus de 24 hLe client annule via son lienANNULEE_CLIENT, paiement REMBOURSE intégral, créneau libéréBE-11RM-07
CA-14Une réservation CONFIRMEE à 24 h ou moinsLe client tente d'annulerRefus avec message explicite, statut inchangéBE-11RM-07
CA-15Une réservation ANNULEE_CLIENTLe client rouvre son lien d'annulationAucun second remboursement, message « déjà annulée »BE-11RM-07
CA-16Une réservation CONFIRMEE à 2 hLe coach l'annuleANNULEE_COACH, remboursement intégral, email et SMS au clientBE-12RM-08
CA-17Le remboursement Stripe échoueLe coach annuleANNULEE_COACH, paiement ECHEC_REMBOURSEMENT, alerte visible au coachBE-12RM-08
CA-18Une réservation CONFIRMEE début à TL'horloge atteint T − 24 hUn SMS de rappel est envoyé, une seule foisBE-10RM-10
CA-19Une réservation créée à moins de 24 h du débutElle passe en CONFIRMEEAucun SMS de rappel, email de confirmation envoyéBE-10RM-10
CA-20Une réservation annulée avant T − 24 hL'horloge atteint T − 24 hAucun SMSBE-10RM-10
CA-21Twilio renvoie une erreurLe rappel est envoyéUn nouvel essai à +10 min, puis échec journalisé, réservation inchangéeBE-10RM-10
CA-22Un SMS de rappel généréSon contenu est inspectéIl ne contient aucune offre promotionnelle (modèle fixe testé)BE-10RM-10
CA-23Un mobile « 12345 »Le client soumet le formulaireRefus avec message de format, aucune réservation crééeBE-06RM-11
CA-24Un mobile « 06 12 34 56 78 »Le client soumet le formulaireEnregistré en +33612345678BE-06RM-11
CA-25Une réservation CONFIRMEE sur une dateLe coach bloque cette dateRefus tant que la réservation n'est pas annuléeBE-04RM-12
CA-26Deux coachs A et BA demande une réservation de B403 ou 404, aucune donnée de B exposéeBE-13RM-13
CA-27Un jeton de gestion inconnuLe lien est ouvert404 sans information sur l'existence d'une réservationBE-11RM-13
CA-28Un coach sans onboarding Connect completUn client consulte sa pageAucune séance réservableBE-02RM-14
CA-29Une réservation CONFIRMEE dont l'heure de fin est dépasséeLe job de clôture s'exécuteStatut TERMINEE, annulation refuséeBE-13RM-15
CA-30Un coach connecté avec 3 réservationsIl ouvre son tableau de bordLes 3 réservations s'affichent avec statut de réservation et de paiementBE-13RM-13
CA-31Un email déjà utilisé par un coachUn second compte est créé avec cet emailRefus avec message d'erreurBE-01—
CA-32Un coach crée une séance de 60 min à 50 €Il l'enregistreElle apparaît sur sa page publique avec durée, prix et lieuBE-03RM-14

Questions ouvertes (hypothèses actuelles)

IDQuestionHypothèse H
Q-01Qui supporte les frais Stripe non restitués lors d'un remboursement ?Le coach (RM-09), à vérifier dans la tarification Stripe Connect
Q-02Faut-il un délai de grâce après l'heure de début pour une séance non honorée par le coach ?Non, hors MVP

Parcours et écrans

1 lien vers une source

Parcours principaux

P-01 — Client : réserver et payer (U-02, sans compte)

  1. Ouvre la page publique du coach (lien partagé) et choisit une séance.
  2. Choisit un jour puis un créneau libre (fuseau Europe/Paris affiché).
  3. Saisit nom, email et mobile ; validation en ligne du format (RM-11).
  4. Confirme : le créneau est verrouillé 15 min et le client est redirigé vers Stripe Checkout.
  5. Paie ; retour sur la page de confirmation (statut « en cours de validation » tant que le webhook n'est pas reçu).
  6. Reçoit l'email de confirmation avec le lien de gestion ; le SMS de rappel arrive 24 h avant la séance.

P-02 — Client : annuler (U-02)

  1. Ouvre le lien de gestion reçu par email.
  2. Consulte le détail de la réservation et la règle d'annulation (remboursement intégral si > 24 h).
  3. Clique « Annuler », confirme dans une boîte de dialogue.
  4. Voit le résultat : annulée et remboursée, ou refus motivé (≤ 24 h).

P-03 — Coach : démarrer (U-01)

  1. Crée son compte (email + mot de passe) et se connecte.
  2. Lance l'onboarding Stripe Connect et revient sur l'application.
  3. Crée une séance : durée, prix, lieu.
  4. Définit ses disponibilités hebdomadaires et bloque des dates.
  5. Copie son lien public et le partage.

P-04 — Coach : suivre et annuler (U-01)

  1. Ouvre le tableau de bord : liste des réservations avec statut de réservation et de paiement.
  2. Ouvre une réservation à venir et clique « Annuler la séance », avec confirmation.
  3. Vérifie le remboursement automatique, ou traite l'alerte ECHEC_REMBOURSEMENT.

Écrans

IDButÉléments clésÉtats à gérerCA
E-01Page publique du coachNom, séances (durée, prix, lieu), calendrier, liste de créneauxChargement ; aucun créneau ; coach sans Connect (aucune séance réservable) ; créneau devenu indisponible ; erreur réseauCA-01 à CA-04, CA-28, CA-32
E-02Coordonnées du clientRécapitulatif séance, champs nom/email/mobile, bouton « Payer », compte à rebours du verrouChamp invalide (message précis) ; envoi en cours (bouton désactivé) ; conflit « créneau indisponible » ; verrou expiréCA-05, CA-06, CA-23, CA-24
E-03Retour de paiementStatut, récapitulatif, lien vers la gestionEn attente du webhook ; confirmée ; échec ou abandon (relancer) ; expirée (refaire le choix)CA-07 à CA-12
E-04Gestion de la réservation (client)Détail, règle d'annulation, bouton « Annuler »Annulable ; refus ≤ 24 h ; déjà annulée ; terminée ; jeton inconnu (404) ; erreurCA-13 à CA-15, CA-27
E-05Inscription / connexion coachEmail, mot de passeEmail déjà utilisé ; identifiants erronés ; envoi en coursCA-31
E-06Onboarding paiementBouton Stripe Connect, statut du compteNon démarré ; en cours ; complet ; restreint ou désactivéCA-28
E-07Séances et disponibilitésListe des séances, formulaire séance, grille hebdomadaire, dates bloquées, lien public à copierAucune séance ; validation du formulaire ; blocage refusé (réservation confirmée existante) ; enregistrement réussiCA-02, CA-25, CA-32
E-08Tableau de bord coachListe des réservations (date, client, statut, paiement), détail, bouton « Annuler la séance », alerte remboursementListe vide ; chargement ; ECHEC_REMBOURSEMENT mis en évidence ; confirmation d'annulation ; erreurCA-16, CA-17, CA-26, CA-29, CA-30

Règles d'accessibilité et mobile

IDRègle
AM-01Conception mobile d'abord pour E-01 à E-04 ; cibles tactiles d'au moins 44 px ; aucun défilement horizontal.
AM-02Parcours complet au clavier avec focus visible ; la liste de créneaux est une suite de boutons, pas un calendrier piégeant le focus.
AM-03Chaque champ a une étiquette visible, un type adapté (email, tel) et une erreur en texte, liée au champ et annoncée aux lecteurs d'écran ; jamais la couleur seule.
AM-04Le statut (confirmée, annulée, échec remboursement) est exprimé en texte et pas seulement par une couleur.
AM-05Le compte à rebours du verrou est annoncé de façon non intrusive, sans mise à jour vocale à chaque seconde.
AM-06Les actions destructrices (annuler) exigent une confirmation avec l'issue financière indiquée (montant remboursé ou refus).

Limite : ces règles sont à vérifier sur le rendu réel selon les critères WCAG 2.2 retenus (quickref). Une maquette ne démontre pas l'accessibilité du code final.

Architecture et données

4 hypothèses marquées

Stack retenue

CoucheChoixRaisonAlternative écartée
Front + backNext.js (TypeScript), un seul dépôtPages publiques rapides, API et front dans un déploiement unique HSPA + API séparée : surcoût pour un MVP
BasePostgreSQLContraintes d'exclusion (RM-05), transactions, jobs sur SQLSQLite/NoSQL : pas d'exclusion de chevauchement fiable
PaiementStripe Checkout + Connect Express (D-02, D-03)Pas de données carte à gérerPaiement sur place : no-shows
SMSTwilio (D-04)Fournisseur retenu en section 1Brevo SMS : non évalué
EmailResend HAPI simple pour confirmationsSMTP maison
JobsTable job_planifie + worker cron (1 min) HPas de broker à exploiter, planification en SQLRedis/BullMQ : composant en plus
HébergementPaaS EU + Postgres managé, sauvegarde quotidienne HDonnées en UE, exploitation légèreKubernetes : disproportionné

Composants

  • Web/API : pages publiques, espace coach, endpoints de réservation et de gestion par jeton.
  • Webhook Stripe : vérifie la signature, déduplique (evenement_stripe), confirme ou rembourse (RM-06).
  • Worker : expire les verrous (RM-04), envoie les rappels (RM-10), clôture les séances (RM-15).
  • Adaptateurs : Stripe, Twilio, email, derrière des interfaces simulables.

Modèle de données (PostgreSQL)

CREATE EXTENSION IF NOT EXISTS btree_gist;  -- requis pour l'exclusion (RM-05)
CREATE EXTENSION IF NOT EXISTS pgcrypto;

CREATE TABLE coach (
  id                 uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  email              text NOT NULL,
  mot_de_passe_hash  text NOT NULL,
  nom_affiche        text NOT NULL,
  slug               text NOT NULL,                       -- lien public partageable (MVP-04)
  stripe_account_id  text,
  connect_complet    boolean NOT NULL DEFAULT false,      -- RM-14
  cree_le            timestamptz NOT NULL DEFAULT now(),
  CONSTRAINT coach_slug_format CHECK (slug ~ '^[a-z0-9-]{3,50}$')
);
-- CA-31 : un email = un compte (insensible à la casse)
CREATE UNIQUE INDEX coach_email_uniq ON coach (lower(email));
CREATE UNIQUE INDEX coach_slug_uniq ON coach (slug);

CREATE TABLE seance_type (
  id              uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  coach_id        uuid NOT NULL REFERENCES coach(id) ON DELETE CASCADE,
  titre           text NOT NULL,
  duree_minutes   integer NOT NULL CHECK (duree_minutes BETWEEN 15 AND 240),
  prix_cents      integer NOT NULL CHECK (prix_cents > 0),   -- RM-06 : EUR, intégral
  lieu            text NOT NULL,
  actif           boolean NOT NULL DEFAULT true,             -- RM-12 : désactivation sans suppression
  cree_le         timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX seance_type_coach_idx ON seance_type (coach_id) WHERE actif;

-- Disponibilités hebdomadaires récurrentes (heure locale Europe/Paris, D-09)
CREATE TABLE disponibilite (
  id           uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  coach_id     uuid NOT NULL REFERENCES coach(id) ON DELETE CASCADE,
  jour_semaine smallint NOT NULL CHECK (jour_semaine BETWEEN 1 AND 7),  -- 1 = lundi
  heure_debut  time NOT NULL,
  heure_fin    time NOT NULL,
  CONSTRAINT disponibilite_plage CHECK (heure_fin > heure_debut)
);
CREATE INDEX disponibilite_coach_idx ON disponibilite (coach_id, jour_semaine);

CREATE TABLE date_bloquee (
  id        uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  coach_id  uuid NOT NULL REFERENCES coach(id) ON DELETE CASCADE,
  jour      date NOT NULL,
  UNIQUE (coach_id, jour)
);
-- RM-12 (refus du blocage si réservation CONFIRMEE ce jour) : contrôle applicatif, CA-25.

CREATE TABLE reservation (
  id                       uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  coach_id                 uuid NOT NULL REFERENCES coach(id),
  seance_type_id           uuid NOT NULL REFERENCES seance_type(id),
  client_nom               text NOT NULL,
  client_email             text NOT NULL,
  client_mobile_e164       text NOT NULL,
  debut                    timestamptz NOT NULL,
  fin                      timestamptz NOT NULL,
  prix_cents               integer NOT NULL CHECK (prix_cents > 0),   -- copie figée du prix
  statut                   text NOT NULL DEFAULT 'EN_ATTENTE_PAIEMENT',
  statut_paiement          text NOT NULL DEFAULT 'AUCUN',
  verrou_expire_le         timestamptz,                                -- RM-04 : créée + 15 min
  jeton_gestion            text NOT NULL DEFAULT encode(gen_random_bytes(32), 'hex'), -- RM-13 : 256 bits
  stripe_session_id        text,
  stripe_payment_intent_id text,
  cree_le                  timestamptz NOT NULL DEFAULT now(),
  mis_a_jour_le            timestamptz NOT NULL DEFAULT now(),
  annulee_le               timestamptz,

  CONSTRAINT reservation_statut_valide CHECK (statut IN
    ('EN_ATTENTE_PAIEMENT','CONFIRMEE','ANNULEE_CLIENT','ANNULEE_COACH','EXPIREE','TERMINEE')),
  CONSTRAINT reservation_paiement_valide CHECK (statut_paiement IN
    ('AUCUN','PAYE','REMBOURSE','ECHEC_REMBOURSEMENT')),
  CONSTRAINT reservation_plage CHECK (fin > debut),
  -- RM-11 : mobile français normalisé E.164
  CONSTRAINT reservation_mobile_e164 CHECK (client_mobile_e164 ~ '^\+33[67][0-9]{8}$'),
  CONSTRAINT reservation_email_format CHECK (client_email ~ '^[^@\s]+@[^@\s]+\.[^@\s]+$'),
  -- RM-04 : un verrou est obligatoire tant que le paiement est en attente
  CONSTRAINT reservation_verrou_requis CHECK (statut <> 'EN_ATTENTE_PAIEMENT' OR verrou_expire_le IS NOT NULL),
  -- RM-06 : CONFIRMEE implique un paiement capturé
  CONSTRAINT reservation_confirmee_payee CHECK (statut <> 'CONFIRMEE' OR statut_paiement = 'PAYE'),

  -- RM-01 + RM-05 : pas de chevauchement pour un même coach tant que la réservation
  -- est EN_ATTENTE_PAIEMENT ou CONFIRMEE ; la première écriture gagne (CA-06).
  CONSTRAINT reservation_pas_de_chevauchement EXCLUDE USING gist (
    coach_id WITH =,
    tstzrange(debut, fin, '[)') WITH &&
  ) WHERE (statut IN ('EN_ATTENTE_PAIEMENT','CONFIRMEE'))
);
CREATE UNIQUE INDEX reservation_jeton_uniq ON reservation (jeton_gestion);       -- RM-13
CREATE UNIQUE INDEX reservation_session_uniq ON reservation (stripe_session_id)
  WHERE stripe_session_id IS NOT NULL;
CREATE INDEX reservation_coach_debut_idx ON reservation (coach_id, debut DESC);  -- tableau de bord (BE-13)
CREATE INDEX reservation_expiration_idx ON reservation (verrou_expire_le)
  WHERE statut = 'EN_ATTENTE_PAIEMENT';                                          -- job RM-04
CREATE INDEX reservation_cloture_idx ON reservation (fin)
  WHERE statut = 'CONFIRMEE';                                                    -- job RM-15

-- Idempotence des webhooks Stripe (RM-06, CA-09)
CREATE TABLE evenement_stripe (
  id_evenement  text PRIMARY KEY,           -- evt_... : doublon = INSERT en conflit, sans effet
  type          text NOT NULL,
  recu_le       timestamptz NOT NULL DEFAULT now(),
  traite_le     timestamptz
);

-- Rappels SMS : au plus un par réservation (RM-10, CA-18)
CREATE TABLE rappel_sms (
  id              uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  reservation_id  uuid NOT NULL UNIQUE REFERENCES reservation(id) ON DELETE CASCADE, -- jamais de doublon
  envoi_prevu_le  timestamptz NOT NULL,     -- début - 24 h
  statut          text NOT NULL DEFAULT 'a_envoyer'
                  CHECK (statut IN ('a_envoyer','envoye','echec','annule')),
  tentatives      smallint NOT NULL DEFAULT 0 CHECK (tentatives BETWEEN 0 AND 2),  -- 1 essai + 1 retry à +10 min
  twilio_sid      text,
  derniere_erreur text,
  envoye_le       timestamptz
);
CREATE INDEX rappel_sms_a_envoyer_idx ON rappel_sms (envoi_prevu_le) WHERE statut = 'a_envoyer';

-- Journal des anomalies (RM-06 montant ≠ attendu, ECHEC_REMBOURSEMENT, échec SMS)
CREATE TABLE journal_anomalie (
  id              uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  reservation_id  uuid REFERENCES reservation(id) ON DELETE SET NULL,
  type            text NOT NULL,
  detail          jsonb NOT NULL DEFAULT '{}',
  cree_le         timestamptz NOT NULL DEFAULT now()
);

Garanties portées par la base

RègleMécanisme
RM-01, RM-05EXCLUDE USING gist partiel sur EN_ATTENTE_PAIEMENT et CONFIRMEE
RM-04verrou_expire_le obligatoire, index dédié pour le job d'expiration
RM-06evenement_stripe (idempotence) et CONFIRMEE impossible sans PAYE
RM-10UNIQUE (reservation_id) sur rappel_sms
RM-11CHECK E.164 et email
RM-13Jeton 256 bits, index unique

Contrôles applicatifs (hors base)

RM-02, RM-03 (délai min. 2 h, horizon 60 j), RM-07 et RM-08 (table de décision d'annulation), RM-12 (blocage de date) et RM-14 (connect_complet). Le job d'expiration libère le créneau en passant le statut à EXPIREE, ce qui sort la ligne de la contrainte d'exclusion.

API, intégrations et sécurité

5 hypothèses marquées

Conventions : JSON sur HTTPS ; erreurs { "code", "message" } ; heures ISO 8601 en UTC, affichées en Europe/Paris (D-09). Session coach par cookie HttpOnly, Secure, SameSite=Lax H. Accès client par jeton de gestion (RM-13), sans compte.

API

IDMéthode et cheminRôle autoriséEntréeSortieErreursRM / CA
API-01POST /api/coach/inscriptionPublicemail, mot de passe, nom_affiche201 {id, slug}400 format ; 409 email déjà utiliséBE-01 ; CA-31
API-02POST /api/coach/connexionPublicemail, mot de passe200 + cookie de session401 identifiants erronés ; 429BE-01
API-03POST /api/coach/connect/onboardingCoach—200 {url} (lien Stripe Connect)401 ; 502 StripeBE-02 ; RM-14, CA-28
API-04GET /api/coach/connect/statutCoach—200 {connect_complet}401RM-14 ; CA-28
API-05POST /api/coach/seancesCoachtitre, duree_minutes, prix_cents, lieu201 séance400 validation ; 401BE-03 ; RM-06, CA-32
API-06PATCH /api/coach/seances/{id}Coach propriétairechamps modifiés ou actif=false200 séance400 ; 401 ; 404RM-12, RM-13
API-07PUT /api/coach/disponibilitesCoachliste {jour_semaine, heure_debut, heure_fin}200 liste400 plage invalide ; 401BE-04 ; RM-01, CA-01
API-08POST /api/coach/dates-bloqueesCoachjour201401 ; 409 réservation CONFIRMEE ce jourRM-12 ; CA-25, CA-02
API-09DELETE /api/coach/dates-bloquees/{id}Coach propriétaire—204401 ; 404RM-01
API-10GET /api/coach/reservationsCoachfiltres statut, du, au200 liste (statut, statut_paiement, alerte)401BE-13 ; RM-13, CA-30, CA-26
API-11POST /api/coach/reservations/{id}/annulerCoach propriétaire—200 {statut, statut_paiement}401 ; 404 ; 409 statut non annulable ou séance terminéeBE-12 ; RM-08, RM-15, CA-16, CA-17
API-12GET /api/public/coachs/{slug}Public—200 coach + séances actives (vide si connect_complet faux)404BE-05 ; RM-14, CA-28, CA-32
API-13GET /api/public/coachs/{slug}/creneauxPublicseance_type_id, du, au200 liste de débuts libres400 hors horizon ; 404BE-05 ; RM-01 à RM-03, CA-01 à CA-04
API-14POST /api/public/reservationsPublicseance_type_id, debut, nom, email, mobile201 {reservation_id, jeton_gestion, checkout_url}400 mobile/email invalide ; 409 créneau indisponible ; 422 coach sans Connect, créneau hors règlesBE-06, BE-07, BE-08 ; RM-04, RM-05, RM-11, RM-14, CA-05, CA-06, CA-23, CA-24
API-15GET /api/public/reservations/{jeton}Détenteur du jeton—200 détail, annulable (booléen), règle d'annulation404 jeton inconnu, sans fuiteBE-11 ; RM-13, CA-27, CA-15
API-16POST /api/public/reservations/{jeton}/annulerDétenteur du jeton—200 {statut, statut_paiement}404 ; 409 refus (≤ 24 h, terminée, déjà annulée)BE-11 ; RM-07, RM-15, CA-13, CA-14, CA-15
API-17POST /api/webhooks/stripeStripe (signature)événement signé200 ; doublon : 200 sans effet400 signature invalideBE-08, BE-09 ; RM-04, RM-06, CA-08 à CA-12
JOB-01Worker, chaque minuteInterne—Réservations EN_ATTENTE_PAIEMENT échues → EXPIREE—RM-04 ; CA-07
JOB-02Worker, chaque minuteInterne—Envoi des rappel_sms échus (réservation CONFIRMEE), retry à +10 min—BE-10 ; RM-10, CA-18 à CA-22
JOB-03Worker, chaque minuteInterne—CONFIRMEE passée de fin → TERMINEE—RM-15 ; CA-29

Comportement structurant :

  • API-14 : transaction unique. INSERT dans reservation (la contrainte reservation_pas_de_chevauchement tranche la concurrence, violation → 409), puis création de la session Checkout avec stripe_session_id. Si Stripe échoue, la ligne passe en EXPIREE.
  • API-17 : INSERT dans evenement_stripe ; conflit sur id_evenement → sans effet. Montant ≠ prix_cents → pas de confirmation, ligne dans journal_anomalie. Réservation déjà EXPIREE → remboursement automatique, statut_paiement = REMBOURSE.
  • API-11 / API-16 : mêmes transitions que la table de décision de la section 2 ; remboursement Stripe en échec → ECHEC_REMBOURSEMENT et anomalie journalisée.
  • API-03 : l'événement Stripe account.updated (reçu par API-17) met à jour connect_complet.

Intégrations externes

ServiceUsagePoint de défaillanceComportement de repli
Stripe CheckoutPaiement de la réservationIndisponibilité à la création de sessionRéservation EXPIREE, 502 au client, créneau libéré
Stripe webhooksConfirmation, account.updatedWebhook perdu ou en retardStripe rejoue ; si le verrou expire avant, paiement tardif remboursé (CA-11)
Stripe Connect / remboursementsOnboarding, remboursementsRefus ou solde insuffisant (RM-09)ECHEC_REMBOURSEMENT, alerte au coach, traitement manuel
TwilioSMS de rappel et d'annulation coachErreur API, mobile invalide1 retry à +10 min, puis echec journalisé ; réservation inchangée (CA-21)
Resend HEmail de confirmation et lien de gestionErreur APIRetry par le worker ; lien de gestion aussi affiché sur E-03
PostgreSQLDonnées, verrou, jobsIndisponibilité503 ; aucun état partiel grâce aux transactions

Sécurité et données personnelles

  1. Authentification coach : mot de passe haché argon2id ou bcrypt (mot_de_passe_hash), limitation des tentatives sur API-02, session expirante H.
  2. Autorisation : toute requête coach filtre par coach_id de la session ; accès hors périmètre → 404 (CA-26). Jeton de gestion à 256 bits, 404 uniforme si inconnu (CA-27).
  3. Paiement : aucune donnée de carte chez nous (Stripe Checkout). Webhook accepté uniquement avec signature valide ; clés Stripe, Twilio et Resend en variables d'environnement, jamais dans le dépôt.
  4. Transport et entrées : HTTPS obligatoire, validation serveur de tous les champs (E.164, email), requêtes SQL paramétrées, limitation de débit sur API-13, API-14 et API-16 contre l'abus de verrous H.
  5. Données personnelles (RGPD) : client = nom, email, mobile, minimisés à ce qui sert la séance. Hébergement UE, journaux sans mobile ni email en clair. Durée de conservation H : 3 ans après la séance, puis anonymisation. SMS strictement transactionnels (D-05, D-06).
  6. Droits des personnes : procédure manuelle d'accès, rectification et effacement sur demande (MVP). Information et base légale (exécution du contrat) à afficher en E-02. À valider par le porteur du projet ; ce document n'est pas un avis juridique.

Plan de développement

1 hypothèse marquée

Hypothèses de plan H : un seul dépôt Next.js + PostgreSQL ; Stripe, Twilio et Resend sont isolés derrière des interfaces simulables (mocks en test) ; tests : Vitest (unitaire et intégration sur Postgres de test) et Playwright (bout en bout). La tranche verticale prioritaire est P-01 (réserver et payer) : elle est livrée dès le jalon J3. Chaque tâche suppose les corrections EC-xx intégrées (marquées « corrige EC-xx »).

Décisions de plan issues de la relecture

ÉcartDécision appliquée dans le plan
EC-01Webhook tardif sur EXPIREE : PAYE puis REMBOURSE dans une même transaction (T-12)
EC-02Insertion de rappel_sms à la confirmation (T-11) ; passage à annule aux annulations (T-15, T-17)
EC-03SMS d'annulation coach conservé ; D-05 devient « 1 rappel + 1 SMS d'annulation coach éventuel », contenu transactionnel (T-17)
EC-04Jeton = 256 bits partout ; RM-13 aligné (T-02)
EC-05Blocage de date refusé si réservation EN_ATTENTE_PAIEMENT ou CONFIRMEE ce jour ; en plus, la confirmation Stripe d'une réservation sur date bloquée n'est pas empêchée car le créneau est verrouillé avant (T-08, T-12)
EC-06Mobile = 06/07 uniquement ; RM-11 aligné ; validation API avant insertion, 400 et non 500 (T-09)
EC-07Ajout des GET coach séances, disponibilités, dates bloquées (T-06, T-07, T-08)
EC-08E-03 utilise API-15 par jeton (T-13)

J0 — Socle (dépôt, base, outillage)

IDObjectifModules ou fichiersDépend deCAPreuve de finPiège à éviter
T-01Initialiser Next.js (TS), lint, Vitest, Playwright, variables d'environnement typées, CIpackage.json, src/lib/env.ts, .github/workflows/ci.yml——npm run lint && npm test verts en CINe jamais committer de clé ; échouer au démarrage si une variable manque
T-02Migrations : schéma §4 complet, avec jeton 256 bits (corrige EC-04) et CHECK mobile ^\+33[67][0-9]{8}$db/migrations/001_init.sqlT-01CA-06, CA-31Test d'intégration : insertion chevauchante rejetée par EXCLUDE ; email en double rejetéActiver btree_gist avant la contrainte ; tester la contrainte sur base réelle, pas en mémoire
T-03Adaptateurs simulables Stripe, Twilio, Resend, Horloge (interfaces et mocks)src/adapters/*.tsT-01—Tests unitaires avec mocks ; horloge injectableSans horloge injectable, les tests 24 h et 15 min sont impossibles

J1 — Compte coach et paiements activés

IDObjectifModules ou fichiersDépend deCAPreuve de finPiège à éviter
T-04API-01/02 : inscription, connexion, session cookie HttpOnly, hash argon2id, limitation des tentativessrc/app/api/coach/inscription, connexion, src/lib/auth.tsT-02CA-31Test : second compte avec le même email (casse différente) → 409Comparer l'email en lower() ; ne pas révéler si l'email existe à la connexion
T-05API-03/04 : onboarding Connect Express, statut ; account.updated met à jour connect_complet (traité dans T-12)src/app/api/coach/connect/*T-04, T-03CA-28Test mock Stripe : lien renvoyé ; statut reflète connect_completNe jamais déduire « complet » du retour navigateur, seulement du webhook
T-06Écrans E-05 et E-06 (inscription/connexion, onboarding)src/app/(coach)/connexion, onboardingT-04, T-05CA-31Playwright : création de compte puis affichage du statut ConnectMessages d'erreur liés aux champs (AM-03)

J2 — Séances et disponibilités

IDObjectifModules ou fichiersDépend deCAPreuve de finPiège à éviter
T-07API-05/06 + GET /api/coach/seances (corrige EC-07) : création, modification, désactivation (actif=false)src/app/api/coach/seancesT-04CA-32Test : séance 60 min à 5000 cents créée, relue par GET ; accès à la séance d'un autre coach → 404Filtrer toujours par coach_id de session ; prix en centimes entiers
T-08API-07/08/09 + GET /api/coach/disponibilites et GET /api/coach/dates-bloquees (corrige EC-07) ; blocage refusé si réservation EN_ATTENTE_PAIEMENT ou CONFIRMEE ce jour (corrige EC-05)src/app/api/coach/disponibilites, dates-bloqueesT-04CA-02, CA-25Test : blocage sur jour avec réservation CONFIRMEE → 409 ; idem avec EN_ATTENTE_PAIEMENTCalculer « le jour » en Europe/Paris, pas en UTC
T-09Module de calcul des créneaux : disponibilités, dates bloquées, durée, chevauchements, délai min 2 h, horizon 60 jsrc/domain/creneaux.tsT-02, T-03CA-01, CA-02, CA-03, CA-04Tests unitaires CA-01 à CA-04 avec horloge figéePassage heure d'été/hiver : tester un lundi de changement d'heure
T-10Écran E-07 (séances, disponibilités, dates bloquées, lien public copiable)src/app/(coach)/seancesT-07, T-08CA-02, CA-25, CA-32Playwright : création séance et disponibilité, blocage refusé affichéAfficher l'erreur 409 en texte clair (AM-04)

J3 — Tranche verticale : réserver et payer (P-01)

IDObjectifModules ou fichiersDépend deCAPreuve de finPiège à éviter
T-11API-12/13 : page publique et créneaux ; séances masquées si connect_complet fauxsrc/app/api/public/coachs/[slug]T-09, T-05CA-01 à CA-04, CA-28, CA-32Tests API : coach sans Connect → liste de séances videNe pas exposer d'email ni d'id interne coach
T-12API-14 : validation entrées (email, mobile 06/07 normalisé E.164, 400 avant insertion, corrige EC-06), INSERT réservation avec verrou 15 min, session Checkout ; violation d'exclusion → 409 ; échec Stripe → EXPIREEsrc/app/api/public/reservations, src/domain/reservation.ts, src/lib/telephone.tsT-11, T-03CA-05, CA-06, CA-23, CA-24Test concurrence : 2 requêtes parallèles → 1 × 201, 1 × 409 ; « 12345 » → 400 ; « 06 12 34 56 78 » → +33612345678Une transaction ; ne pas pré-vérifier la disponibilité puis insérer (course) : la contrainte tranche
T-13API-17 webhook Stripe : signature, idempotence evenement_stripe, CONFIRMEE si montant égal, anomalie sinon ; à la confirmation, insertion de rappel_sms si début − now > 24 h (corrige EC-02) ; webhook tardif sur EXPIREE : PAYE puis REMBOURSE dans la même transaction (corrige EC-01) ; account.updated → connect_completsrc/app/api/webhooks/stripe, src/domain/paiement.tsT-12CA-08 à CA-12, CA-19Tests : doublon sans effet, signature invalide → 400, montant ≠ → pas de confirmation et ligne journal_anomalie, paiement tardif → EXPIREE + REMBOURSELire le corps brut pour la signature ; INSERT dans evenement_stripe et traitement dans la même transaction
T-14JOB-01 : worker d'expiration des verrous (cron 1 min)src/worker/expiration.tsT-12CA-07Test avec horloge : réservation à 16 min → EXPIREE, créneau à nouveau réservableNe toucher que EN_ATTENTE_PAIEMENT ; requête idempotente
T-15Écrans E-01, E-02, E-03 (E-03 lit API-15 par jeton, corrige EC-08), compte à rebours, états d'erreursrc/app/c/[slug], reserver, retourT-11, T-12, T-13CA-05, CA-06, CA-23, CA-24, CA-07Playwright : parcours P-01 complet avec Stripe simulé, du créneau à « confirmée »Page de retour : statut « en cours de validation » tant que le webhook n'est pas reçu

J4 — Notifications et gestion client

IDObjectifModules ou fichiersDépend deCAPreuve de finPiège à éviter
T-16Email de confirmation avec lien de gestion (envoi après confirmation, retry par le worker)src/adapters/email.ts, src/domain/notifications.tsT-13CA-08, CA-09Test : 1 email à la confirmation, 0 sur webhook doublonEnvoyer après commit de la transaction, pas dedans
T-17JOB-02 : envoi des rappels SMS, 1 retry à +10 min puis echec ; modèle de message fixe sans promotion ; jamais de doublonsrc/worker/rappels.ts, src/domain/sms.tsT-13, T-03CA-18 à CA-22Tests : 1 SMS à T − 24 h ; erreur Twilio → retry puis echec, réservation inchangée ; contenu = modèle fixeUNIQUE(reservation_id) + mise à jour atomique du statut avant envoi pour éviter le double envoi
T-18API-15/16 : détail et annulation client par jeton, table de décision ; remboursement intégral ; rappel_sms.statut='annule' à l'annulation (corrige EC-02) ; 404 uniforme si jeton inconnusrc/app/api/public/reservations/[jeton]T-13CA-13 à CA-15, CA-27Tests : > 24 h → ANNULEE_CLIENT + REMBOURSE ; ≤ 24 h → 409 ; 2ᵉ appel idempotent sans second remboursement ; jeton inconnu → 404Clé d'idempotence Stripe sur le remboursement ; frontière exacte à 24 h pile = refus
T-19Écran E-04 (gestion client, confirmation d'annulation avec issue financière, AM-06)src/app/g/[jeton]T-18CA-13 à CA-15, CA-27Playwright : annulation depuis le lien, puis réouverture → « déjà annulée »Boîte de dialogue accessible au clavier

J5 — Espace coach : suivi et annulation

IDObjectifModules ou fichiersDépend deCAPreuve de finPiège à éviter
T-20API-10/11 : liste des réservations et annulation coach, remboursement automatique ; ECHEC_REMBOURSEMENT + anomalie + alerte ; rappel_sms → annule ; email + SMS d'annulation coach au client, contenu transactionnel, SMS documenté dans D-05 (corrige EC-03)src/app/api/coach/reservationsT-18, T-17CA-16, CA-17, CA-26, CA-30Tests : annulation à 2 h → ANNULEE_COACH + REMBOURSE + email et SMS ; échec Stripe → ECHEC_REMBOURSEMENT ; réservation d'un autre coach → 404Ne pas bloquer l'annulation si le remboursement échoue ; ne pas rembourser une réservation EN_ATTENTE_PAIEMENT (→ EXPIREE)
T-21JOB-03 : clôture CONFIRMEE → TERMINEE ; refus d'annulation ensuitesrc/worker/cloture.tsT-18, T-20CA-29Test horloge : après fin → TERMINEE, annulation → 409Comparer sur fin, pas sur debut
T-22Écran E-08 (tableau de bord, alerte ECHEC_REMBOURSEMENT, annulation avec confirmation)src/app/(coach)/tableau-de-bordT-20, T-21CA-16, CA-17, CA-26, CA-29, CA-30Playwright : 3 réservations affichées avec statuts en texte ; annulation et alerte visiblesStatuts en texte, pas seulement en couleur (AM-04)

J6 — Durcissement et livraison

IDObjectifModules ou fichiersDépend deCAPreuve de finPiège à éviter
T-23Limitation de débit (API-02, 13, 14, 16), en-têtes de sécurité, journaux sans mobile ni email en clairsrc/middleware.ts, src/lib/log.tsT-12, T-18CA-26, CA-27Test : rafale sur API-14 → 429 ; inspection des logs sans donnée personnelleLimiter aussi par IP sur les routes publiques
T-24Tests de bout en bout sur les parcours P-01 à P-04 et mention RGPD en E-02e2e/*.spec.ts, E-02T-15, T-19, T-22CA-05 à CA-30npx playwright test vert en CIMocks Stripe/Twilio : ne jamais appeler les services réels en CI
T-25README : installation, variables d'environnement, lancement des workers, limites connues et hypothèses à vérifier (frais de remboursement Connect, solde insuffisant, qualification juridique des SMS)README.mdT-24—Installation à froid réussie en suivant le READMELister les limites telles quelles, sans les masquer

Couverture des critères d'acceptation

CATâche(s)
CA-01T-09, T-11
CA-02T-08, T-09, T-10
CA-03T-09, T-11
CA-04T-09, T-11
CA-05T-12, T-15, T-24
CA-06T-02, T-12, T-15
CA-07T-14, T-15
CA-08T-13, T-16
CA-09T-13, T-16
CA-10T-13
CA-11T-13
CA-12T-13
CA-13T-18, T-19
CA-14T-18, T-19
CA-15T-18, T-19
CA-16T-20, T-22
CA-17T-20, T-22
CA-18T-17
CA-19T-13, T-17
CA-20T-17, T-18
CA-21T-17
CA-22T-17
CA-23T-12, T-15
CA-24T-12, T-15
CA-25T-08, T-10
CA-26T-20, T-22, T-23
CA-27T-18, T-19, T-23
CA-28T-05, T-11
CA-29T-21, T-22
CA-30T-20, T-22
CA-31T-02, T-04, T-06
CA-32T-07, T-10, T-11

Les 32 CA sont couverts. Les T-13 et T-17 s'appuient sur rappel_sms créé à la confirmation (EC-02) ; l'ordre T-13 → T-17 → T-18 → T-20 respecte les dépendances.

Relecture indépendante

Écarts

IDGravitéOùProblèmeCorrection
EC-01Haute§4 reservation_confirmee_payee vs RM-06 / CA-08Contrainte CONFIRMEE ⇒ PAYE. Mais RM-07/RM-08 passent en ANNULEE_* avec REMBOURSE : cohérent. En revanche TERMINEE n'a pas de contrainte, et surtout un remboursement automatique sur EXPIREE (RM-04/CA-11) est incohérent avec RM-06 « réservation expirée = créneau libéré » : le client payé tardivement ne voit pas son créneau garanti. Ce cas est traité, mais le paiement PAYE transitoire avant REMBOURSE n'est pas défini.Préciser en RM-04 : webhook tardif → paiement PAYE puis REMBOURSE dans la même transaction.
EC-02HauteRM-10 / §4 rappel_sms / JOB-02rappel_sms n'est jamais créé ni annulé dans les parcours : aucune règle ni API ne dit qui insère la ligne (à la confirmation, API-17) ni qui passe annule (annulations RM-07/RM-08). CA-19 (< 24 h : pas de rappel) suppose de ne pas insérer.Ajouter à API-17 : à la confirmation, insérer rappel_sms si début − now() > 24 h. Ajouter à API-11/API-16 : passer rappel_sms.statut = 'annule'.
EC-03MoyenneRM-08 / §5 intégrationsRM-08 prévoit « email + SMS au client » à l'annulation coach, mais aucun SMS d'annulation n'est dans D-05 (1 SMS par réservation), RM-10, ni les CA (CA-16 le cite). Coût et cadre D-06 non traités.Mettre à jour D-05 : « 1 rappel + 1 SMS d'annulation coach éventuel » et garder son contenu transactionnel, ou retirer le SMS de RM-08/CA-16.
EC-04MoyenneRM-13 vs §4RM-13 dit « jeton ≥ 128 bits », §4/§5 en fournissent 256 bits. Écart mineur de formulation.Aligner RM-13 sur « 256 bits » (ou « ≥ 128 » partout).
EC-05MoyenneRM-12 / CA-25 vs API-08RM-12 : blocage refusé si réservation CONFIRMEE ; la réservation EN_ATTENTE_PAIEMENT ce jour n'est pas couverte et peut devenir CONFIRMEE sur une date bloquée.Étendre à EN_ATTENTE_PAIEMENT ou filtrer les créneaux/confirmations sur dates bloquées.
EC-06Moyenne§4 CHECK reservation_mobile_e164 vs RM-11Regex \+33[67]… n'accepte que les mobiles 06/07 ; RM-11 dit « +33 / 0X » (tout format français). CA-24 passe, mais un 01–05, 08–09 est accepté par RM-11 et rejeté en base (500 au lieu de 400).Aligner RM-11 sur « mobile français 06/07 » et valider côté API avant insertion.
EC-07Faible§3 E-01/E-07 vs APIAucun endpoint de lecture pour E-07 (liste séances, disponibilités, dates bloquées du coach) ni de suppression de disponibilité/séance ; GET coach manquants.Ajouter GET /api/coach/seances, GET /api/coach/disponibilites, GET /api/coach/dates-bloquees.
EC-08Faible§3 E-03 vs APIE-03 affiche le statut « en attente du webhook » mais API-15 est la seule lecture et est présentée pour E-04.Indiquer que E-03 utilise API-15 par jeton.

Hypothèses à vérifier en premier

  • Frais de remboursement Stripe Connect (D-03, RM-09) : le coach les absorbe, avec risque de solde insuffisant et de remboursements bloqués ; vérifier la tarification Connect et le modèle de charge (destination vs direct) avant de coder.
  • Remboursement automatique Stripe Connect sans solde coach : si le compte du coach est négatif, le remboursement peut échouer systématiquement ; tester sur compte test.
  • SMS de service sans consentement (D-06) : la qualification « message de service » repose sur des sources de blogs ; faire valider par un juriste (RGPD/CNIL) avant la mise en production.

Sources web consultées

28 liens vers des sources