rubis/apps/api/app/services/demo/capture.ts
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

49 lines
1.5 KiB
TypeScript

import DemoCapturedEmail from '#models/demo_captured_email'
import * as clock from '#services/clock'
import Organization from '#models/organization'
/**
* Service de capture d'emails en mode démo.
*
* Tout le code "démo" vit dans `services/demo/*` — la prod ne référence
* qu'une seule fonction (`captureEmailIfDemo`) depuis `mail_dispatcher`,
* pour minimiser le couplage.
*
* Si l'org est en mode démo : crée une `DemoCapturedEmail` avec le
* timestamp virtualNow et retourne `true` → caller doit ne PAS envoyer
* via Resend.
*
* Sinon : retourne `false` → comportement prod inchangé.
*/
export type CaptureInput = {
organizationId: string
kind: 'relance' | 'checkin' | 'thanks'
to: { email: string; name?: string | null }
from: { email: string; name?: string | null }
replyTo?: string | null
subject: string
body: string
meta?: Record<string, unknown>
}
export async function captureEmailIfDemo(input: CaptureInput): Promise<boolean> {
const org = await Organization.find(input.organizationId)
if (!org || !org.demoMode) return false
const sentAt = await clock.now(input.organizationId)
await DemoCapturedEmail.create({
organizationId: input.organizationId,
kind: input.kind,
toEmail: input.to.email,
toName: input.to.name ?? null,
fromEmail: input.from.email,
fromName: input.from.name ?? null,
replyTo: input.replyTo ?? null,
subject: input.subject,
body: input.body,
meta: input.meta ?? {},
sentAt,
})
return true
}