- Programme de parrainage (referrals): * Page /parrainage pour clients et staff * API /api/referrals (GET, POST) * Table referrals avec tracking complet * Email template avec design orange/gradient * Réductions: 30€ HT parrain, 20€ HT filleul - Bannières promotionnelles (promo_banners): * Page staff /staff/offres-promo pour gérer les bannières * API /api/promo-banners (CRUD complet) * Composant PromoBanner affiché en haut de l'espace * Compte à rebours optionnel * Customisation couleurs (gradient, texte, CTA) - Déduplication des webhooks DocuSeal: * Table webhook_events pour tracker les webhooks traités * Helper checkAndMarkWebhookProcessed() * Intégré dans docuseal-amendment et docuseal-amendment-completed * Prévient les doublons d'emails - Avenants signés: * API GET /api/contrats/[id]/avenants * Affichage des avenants signés dans DocumentsCard * Génération d'URLs presignées S3 - Brouillons d'emails groupés: * Table bulk_email_drafts pour sauvegarder les brouillons * Template HTML bulk-email-template.html - Améliorations ContractsGrid: * Ajout filtre par production (dépendant de la structure) * Tri par production - Templates emails: * referral-template.html (parrainage) * bulk-email-template.html (emails groupés staff)
90 lines
2.8 KiB
TypeScript
90 lines
2.8 KiB
TypeScript
import { createSbServiceRole } from "./supabaseServer";
|
|
|
|
/**
|
|
* Vérifie si un webhook a déjà été traité pour éviter les doublons
|
|
* Utilise une clé unique basée sur submission_id + event_type + timestamp
|
|
*
|
|
* @param submissionId - ID de la soumission DocuSeal
|
|
* @param eventType - Type d'événement (ex: "avenant_employer_signed")
|
|
* @param timestamp - Timestamp de l'événement (optionnel, sinon on utilise l'heure actuelle)
|
|
* @param payload - Données du webhook (optionnel, pour debug)
|
|
* @returns true si le webhook est nouveau, false si déjà traité
|
|
*/
|
|
export async function checkAndMarkWebhookProcessed(
|
|
submissionId: string,
|
|
eventType: string,
|
|
timestamp?: string,
|
|
payload?: any
|
|
): Promise<boolean> {
|
|
const supabase = createSbServiceRole();
|
|
|
|
// Créer une clé unique
|
|
// On utilise submission_id + event_type (pas de timestamp car DocuSeal peut renvoyer le même timestamp)
|
|
const webhookKey = `${submissionId}_${eventType}`;
|
|
|
|
console.log(`🔍 [WEBHOOK DEDUP] Vérification: ${webhookKey}`);
|
|
|
|
try {
|
|
// Essayer d'insérer l'événement
|
|
const { data, error } = await supabase
|
|
.from("webhook_events")
|
|
.insert({
|
|
webhook_key: webhookKey,
|
|
event_type: eventType,
|
|
submission_id: submissionId,
|
|
payload: payload || null,
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (error) {
|
|
// Si l'erreur est une violation de contrainte unique, c'est un doublon
|
|
if (error.code === "23505") {
|
|
console.log(`⚠️ [WEBHOOK DEDUP] Doublon détecté: ${webhookKey}`);
|
|
return false; // Webhook déjà traité
|
|
}
|
|
|
|
// Autre erreur
|
|
console.error(`❌ [WEBHOOK DEDUP] Erreur lors de l'insertion:`, error);
|
|
// En cas d'erreur technique, on laisse passer pour éviter de bloquer
|
|
return true;
|
|
}
|
|
|
|
console.log(`✅ [WEBHOOK DEDUP] Nouveau webhook: ${webhookKey}`);
|
|
return true; // Nouveau webhook, on peut traiter
|
|
} catch (error) {
|
|
console.error(`❌ [WEBHOOK DEDUP] Exception:`, error);
|
|
// En cas d'exception, on laisse passer pour éviter de bloquer
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Vérifie si un webhook a déjà été traité (lecture seule)
|
|
* Utile pour vérifier sans marquer comme traité
|
|
*/
|
|
export async function isWebhookProcessed(
|
|
submissionId: string,
|
|
eventType: string
|
|
): Promise<boolean> {
|
|
const supabase = createSbServiceRole();
|
|
const webhookKey = `${submissionId}_${eventType}`;
|
|
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from("webhook_events")
|
|
.select("id")
|
|
.eq("webhook_key", webhookKey)
|
|
.maybeSingle();
|
|
|
|
if (error) {
|
|
console.error(`❌ [WEBHOOK DEDUP] Erreur vérification:`, error);
|
|
return false;
|
|
}
|
|
|
|
return !!data; // true si trouvé, false sinon
|
|
} catch (error) {
|
|
console.error(`❌ [WEBHOOK DEDUP] Exception:`, error);
|
|
return false;
|
|
}
|
|
}
|