ordinarthur 77fdb6af48
All checks were successful
Build & Deploy Web / build-and-deploy (push) Successful in 38s
Build & Deploy API / build-and-deploy (push) Successful in 1m43s
Build & Deploy Landing / build-and-deploy (push) Successful in 1m16s
feat: email de remerciement automatique après confirmation de paiement
Quand l'utilisateur confirme « Oui, payé » via check-in (lien email ou modale
in-app) ou marque une facture encaissée manuellement, on envoie automatiquement
un email de remerciement chaleureux au client final. Subject + body éditables
par plan (mêmes variables que les relances), avec fallback hardcodé si vide.
Gardé par la transition `* → paid` pour idempotence ; envoi async via BullMQ
avec retry exponentiel ; capture en mode démo.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 16:41:26 +02:00

44 lines
1.8 KiB
TypeScript

import vine from '@vinejs/vine'
const RELANCE_TONES = ['amical', 'courtois', 'ferme', 'mise_en_demeure'] as const
const planStep = vine.object({
// id optionnel : présent si on édite une étape existante, absent pour
// une création (le contrôleur le générera).
id: vine.string().optional(),
order: vine.number().min(0),
// Plage : -30 (rappel avant échéance) à 180 jours (gros retards).
offsetDays: vine.number().min(-30).max(180),
tone: vine.enum(RELANCE_TONES),
subject: vine.string().minLength(1).maxLength(200),
body: vine.string().minLength(1).maxLength(5000),
requiresManualValidation: vine.boolean(),
})
/**
* Validator pour PATCH /plans/:slug. Tous les champs optionnels — l'éditeur
* front peut envoyer juste `name` ou juste `steps` selon ce qu'il modifie.
*
* `thanksSubject` / `thanksBody` : nullable pour permettre à l'utilisateur
* d'effacer le template (retomber sur le fallback hardcodé).
*/
export const updatePlanValidator = vine.create({
name: vine.string().minLength(1).maxLength(80).optional(),
description: vine.string().maxLength(500).optional(),
steps: vine.array(planStep).minLength(1).maxLength(10).optional(),
thanksSubject: vine.string().maxLength(200).nullable().optional(),
thanksBody: vine.string().maxLength(5000).nullable().optional(),
})
/**
* Validator pour POST /plans — création d'un plan custom.
* Le slug est généré côté contrôleur depuis le name.
*/
export const createPlanValidator = vine.create({
name: vine.string().minLength(1).maxLength(80),
description: vine.string().maxLength(500).optional(),
steps: vine.array(planStep).minLength(1).maxLength(10),
thanksSubject: vine.string().maxLength(200).nullable().optional(),
thanksBody: vine.string().maxLength(5000).nullable().optional(),
})