Check-in & Payment V2

Nouveau flow de prise de rendez-vous sans carte pour Centre Progression

Flow 1 — Avec carte
Flow 2 — Sans carte
No-Show
Admin Override
Architecture technique
💳
Flow 1 — Client avec carte (inchange)

Cliquez sur chaque etape pour voir les details

1

Reservation

Le client reserve un rendez-vous avec sa carte enregistree.

Confirme immediatement
Ce qui se passe :
• La carte est validee via validateCustomerPaymentMethod
• Si RDV dans < 4 jours : pre-autorisation immediate via preauthorizePayment
• Si RDV dans > 4 jours : pre-autorisation differee par seven-days-preauthorization
confirmed_on est set immediatement — plus de confirmation differee
• Email de confirmation envoye au client et au therapeute
2

7 jours avant — Pre-autorisation

Le cron seven-days-preauthorization cree le PaymentIntent si pas encore fait.

Edge function : seven-days-preauthorization
• Verifie que le client n'est pas bloque
• Verifie qu'il a une carte valide
• Cree un PaymentIntent en mode capture_method: manual
• Email de confirmation pre-autorisation envoye
3

24h avant — Rappel

Email de rappel envoye automatiquement au client.

Edge function : appointments-background-tasks
• Envoie un rappel pour tous les RDV dans les 24 prochaines heures
• Meme email pour les deux flows (carte et sans carte)
• Plus aucune logique d'auto-confirmation
4

Jour J — Therapeute complete le RDV

Le therapeute clique "Completer" → paiement capture.

Paiement capture
Fonction : captureAppointmentPayment
• Capture le PaymentIntent via Stripe
• Set capture_on, amount_received, balance_tx_id
• Email de recu envoye au client
• Le webhook balance.available distribuera les fonds plus tard
5

Distribution des fonds

stripe-balance-transfert distribue : therapeute + centre + commission referent.

Edge function : stripe-balance-transfert
• Calcule le % therapeute selon son plan/tier
• Transfere la part therapeute via Stripe Connect
• Accumule la part Centre Progression
• Envoie la commission referent si applicable
• Email de recu au therapeute
👤
Flow 2 — Client sans carte (nouveau)

Cliquez sur chaque etape pour voir les details

1

Reservation sans carte

Le client reserve sans carte. Le RDV est confirme immediatement.

Pas de pre-autorisation
Ce qui se passe :
validateCustomerPaymentMethod detecte "no_card" → customerHasCard = false
• Aucune pre-autorisation, aucun PaymentIntent
confirmed_on set immediatement
• Email de confirmation standard envoye (meme que flow carte)
• Le lien de paiement n'est PAS envoye a la reservation
2

7 jours avant — Skip

seven-days-preauthorization ignore ce client (pas de carte).

• Le guard verifie booking_blocked_reason → skip si bloque
• Le guard verifie les payment methods Stripe → skip si aucune carte
• Aucun echec de paiement enregistre, aucune annulation
3

24h avant — Rappel

Email de rappel identique au flow carte.

Meme edge function, meme template. Pas de distinction carte/sans carte pour le rappel.
4

Jour J — Check-in par le therapeute

Le therapeute effectue le check-in. C'est le point d'entree obligatoire.

3 options de paiement
Fonction : checkInAppointment
Le therapeute choisit parmi 3 options. Chaque option a un ecran de confirmation.
▶ Option A : Payer sur place
• Cree une Checkout Session Stripe
• Envoie l'email avec le lien de paiement au client
• Affiche le QR code + lien copiable au therapeute
• Le client paie sur son telephone → webhook checkout.session.completed confirme automatiquement
• Statut : completed_pending_payment → puis NULL apres paiement
▶ Option B : Paiement dans 48h
• Set status = completed_pending_payment
• Set payment_deadline_at = now + 48h
• Envoie l'email avec lien de paiement + deadline
• Si paye dans les 48h → statut nettoye, distribution normale
• Si pas paye → payment-deadline-checker bloque le compte
▶ Option C : Autre moyen (cash, virement...)
• Set status = completed_pending_payment (sans deadline)
• Aucun lien de paiement envoye
• Le therapeute contacte l'admin
• L'admin fait un override manuel avec raison obligatoire
5

Si pas paye apres 48h

Le cron payment-deadline-checker bloque le client.

Compte bloque
Edge function : payment-deadline-checker (toutes les 30 min)
• Cherche les RDV avec status = completed_pending_payment et payment_deadline_at < now et capture_on IS NULL
• Set booking_blocked_reason = blocked_until_payment_completed
• Email de blocage envoye au client
• Le client ne peut plus reserver tant que la dette n'est pas reglee
6

Client paie via le lien

Le webhook Stripe confirme et nettoie le statut.

Automatique
Webhook : checkout.session.completed
• Lie le PaymentIntent a l'appointment
• Enregistre amount_received, capture_on, balance_tx_id
• Clear status = NULL
• Debloque le client si blocked_until_payment_completed
• Distribution des fonds via balance.available ensuite
No-Show — Absence du client

Client avec carte

  • Le therapeute clique "Absence non justifiee"
  • Le paiement est capture a 100% (montant total)
  • Email de recu no-show envoye au client
  • Distribution normale des fonds

Client sans carte

  • Le therapeute clique "Absence non justifiee"
  • Aucun paiement capture (pas de carte)
  • Incrementa no_show_count
  • Comportement selon le compteur ↓
1

1er no-show — Avertissement

Premier avertissement, pas de blocage.

Tolere
no_show_count passe a 1
appointments.status = no_show_unpaid_first_visit
• Aucun email envoye
• Le client peut toujours reserver
• Badge dans le dashboard : "Absence (1er avertissement)"
2

2e no-show — Compte bloque

Le compte est bloque jusqu'a l'ajout d'une carte.

Bloque
no_show_count passe a 2+
booking_blocked_reason = blocked_until_card_added
• Email sendAddCardToUnblockEmail envoye avec lien vers /customer/add-card
• Le client ne peut plus reserver
• Badge : "Absence — Compte bloque"
3

Client ajoute une carte

Le client visite /customer/add-card et ajoute sa carte.

Debloque
• Page /customer/add-card avec Stripe Elements
• Cree un SetupIntent (aucun montant preleve)
• Apres confirmation → booking_blocked_reason = NULL
booking_unblocked_at = now()
• Le client redevient un client standard avec carte
• Les regles de no-show classiques s'appliquent desormais
🔐
Admin Override — Paiement manuel
1

Quand utiliser ?

Le client a paye en dehors du systeme (cash, virement, etc.).

• Le therapeute choisit "Autre moyen" au check-in
• Le RDV passe en completed_pending_payment
• Le therapeute contacte l'admin pour valider
2

Admin ouvre le dashboard

Le bouton "Marquer comme payee manuellement" apparait dans les details du RDV.

Admin seulement
• Visible uniquement pour les RDV avec status = completed_pending_payment
• Jamais visible pour les therapeutes ou clients
• Bouton ambre dans le footer du modal de details
3

Raison obligatoire

L'admin doit fournir une raison avant de confirmer.

• Champ texte obligatoire — impossible de soumettre sans
• La raison est loggee dans appointment_manual_overrides
• Avec admin_id et overridden_at
4

Resultat

Le RDV est marque comme complete, le client est debloque si necessaire.

Termine
status = NULL (nettoye)
capture_on = now()
• Si le client etait bloque pour blocked_until_payment_completed → debloque
• Row inseree dans appointment_manual_overrides pour l'audit
🛠
Architecture technique

Nouvelles colonnes DB

  • appointments.status — completed_pending_payment, no_show_unpaid_first_visit
  • appointments.payment_deadline_at — deadline 48h
  • customers.booking_blocked_reason — blocked_until_card_added, blocked_until_payment_completed
  • customers.booking_unblocked_at — timestamp deblocage
  • customers.no_show_count — compteur no-shows

Nouvelle table

  • appointment_manual_overrides
  • id, appointment_id, admin_id, reason, overridden_at
  • Log d'audit pour chaque override admin

Edge Functions modifiees

  • appointments-background-tasks — Rappel uniquement, plus d'auto-confirm
  • seven-days-preauthorization — Guards bloques/sans carte
  • stripe-balance-transfert — Handler checkout.session.completed + guards distribution

Nouvelle Edge Function

  • payment-deadline-checker — Cron toutes les 30 min
  • Bloque les clients qui n'ont pas paye apres 48h
  • Envoie l'email de blocage

Emails Postmark

  • payment-link — Lien paiement post check-in
  • payment-pending-48h — Paiement differe avec deadline
  • account-blocked-payment — Compte bloque (paiement en retard)
  • add-card-to-unblock — Ajouter carte apres no-shows

Nouvelles pages

  • /customer/add-card — Ajout carte via Stripe Elements
  • /api/therapists/appointments/checkin — Check-in API
  • /api/therapists/appointments/noshow-cardless — No-show sans carte
  • /api/therapists/appointments/payment-link — Generer lien paiement
  • /api/admin/appointments/[id]/manual-override — Override admin
  • /api/customers/add-card/* — SetupIntent + confirm

Webhooks Stripe requis

  • payment_intent.succeeded — Capture paiement (existant)
  • balance.available — Distribution fonds (existant)
  • checkout.session.completed — Confirmation paiement Checkout (nouveau V2)