chore: RGPD compliance audit & cleanup + UX signature salarie
- Audit RGPD complet: création RGPD_AUDIT_LOCALISATION_DONNEES.md - Suppression complète intégration n8n (API route, hooks, env vars) - Suppression variables Airtable (env vars) - Confirmation GoCardless: serveurs EEE + SCC - 8/9 services confirmés UE (89% compliance) - Ajout message info dans modale signature salarie (scroll down)
This commit is contained in:
parent
d992e339d7
commit
8cb366ee53
8 changed files with 246 additions and 1203 deletions
|
|
@ -20,12 +20,8 @@ AWS_REGION=eu-west-3
|
||||||
AWS_ACCESS_KEY_ID=your-access-key
|
AWS_ACCESS_KEY_ID=your-access-key
|
||||||
AWS_SECRET_ACCESS_KEY=your-secret-key
|
AWS_SECRET_ACCESS_KEY=your-secret-key
|
||||||
STRUCTURE_API_TOKEN=your-api-token
|
STRUCTURE_API_TOKEN=your-api-token
|
||||||
AIRTABLE_BASE_ID=your-base-id
|
|
||||||
AIRTABLE_API_KEY=your-airtable-pat
|
|
||||||
AIRTABLE_TABLE_CONTRATS=Contrats de travail
|
|
||||||
UPSTREAM_API_BASE=https://your-api-gateway.amazonaws.com/default
|
UPSTREAM_API_BASE=https://your-api-gateway.amazonaws.com/default
|
||||||
UPSTREAM_API_PREFIX=
|
UPSTREAM_API_PREFIX=
|
||||||
N8N_NOTES_WEBHOOK_URL=https://your-n8n.com/webhook/...
|
|
||||||
|
|
||||||
# DocuSeal direct API (recommended)
|
# DocuSeal direct API (recommended)
|
||||||
DOCUSEAL_API_BASE=https://api.docuseal.com
|
DOCUSEAL_API_BASE=https://api.docuseal.com
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,5 @@
|
||||||
NEXT_PUBLIC_API_BASE=https://api-dev.odentas.fr
|
NEXT_PUBLIC_API_BASE=https://api-dev.odentas.fr
|
||||||
AIRTABLE_BASE_ID=
|
|
||||||
AIRTABLE_API_KEY=
|
|
||||||
AIRTABLE_TABLE_CONTRATS=Contrats de travail
|
|
||||||
DOCUSEAL_API_BASE=
|
DOCUSEAL_API_BASE=
|
||||||
DOCUSEAL_TOKEN=
|
DOCUSEAL_TOKEN=
|
||||||
# Optional fallback
|
# Optional fallback
|
||||||
# DOCUSEAL_PROXY_BASE=
|
# DOCUSEAL_PROXY_BASE=
|
||||||
|
|
||||||
# Webhook n8n (virement effectué → mise à jour Airtable)
|
|
||||||
# Exemple: https://n8n.example.com/webhook/virement-effectue
|
|
||||||
N8N_VIREMENT_WEBHOOK_URL=
|
|
||||||
|
|
|
||||||
235
RGPD_AUDIT_LOCALISATION_DONNEES.md
Normal file
235
RGPD_AUDIT_LOCALISATION_DONNEES.md
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
# 🇪🇺 Audit RGPD - Localisation des données clients
|
||||||
|
|
||||||
|
> **Date de l'audit** : 23 octobre 2025
|
||||||
|
> **Projet** : Nouvel Espace Paie Odentas
|
||||||
|
> **Objectif** : Vérifier que toutes les données clients restent dans l'Union Européenne
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Tableau de conformité RGPD
|
||||||
|
|
||||||
|
| Service | Localisation | Infrastructure | Statut RGPD | Données transférées | Action requise |
|
||||||
|
|---------|--------------|----------------|-------------|---------------------|----------------|
|
||||||
|
| **AWS S3** | 🇪🇺 eu-west-3 (Paris) | AWS | ✅ Conforme | Documents, logs emails | - |
|
||||||
|
| **AWS SES** | 🇪🇺 eu-west-3 (Paris) | AWS | ✅ Conforme | Envoi emails | - |
|
||||||
|
| **AWS Lambda** | 🇪🇺 eu-west-3 (Paris) | AWS API Gateway | ✅ Conforme | Processing données | - |
|
||||||
|
| **Supabase** | 🇪🇺 EU | Cloudflare + serveurs UE | ✅ Conforme | Base de données complète | - |
|
||||||
|
| **Vercel Functions** | 🇪🇺 cdg1 (Paris) | Vercel Edge | ✅ Conforme | API routes, exécution | - |
|
||||||
|
| **Docuseal** | 🇪🇺 api.docuseal.eu | Version EU | ✅ Conforme | Signatures électroniques | - |
|
||||||
|
| **PostHog** | 🇪🇺 eu.i.posthog.com | Instance EU | ✅ Conforme | Analytics utilisateurs | - |
|
||||||
|
| **GoCardless** | 🇪🇺 EEE | Serveurs EEE + SCC UE | ✅ Conforme | Mandats SEPA, paiements | ✅ **Confirmé par support** |
|
||||||
|
| **PDFMonkey** | ⚠️ **En attente** | Heroku + AWS (région ?) | ⚠️ **À confirmer** | Contrats CDDU (données salariés) | **Mail envoyé - en attente réponse** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 Détails par service
|
||||||
|
|
||||||
|
### ✅ Services conformes RGPD
|
||||||
|
|
||||||
|
#### AWS (S3, SES, Lambda)
|
||||||
|
- **Région** : `eu-west-3` (Paris, France)
|
||||||
|
- **Configuration** : `AWS_REGION=eu-west-3`
|
||||||
|
- **Données stockées** :
|
||||||
|
- S3 bucket `odentas-docs` : Documents PDF, contrats, fiches de paie
|
||||||
|
- S3 bucket `stockage-logs-emails` : Logs d'envoi d'emails
|
||||||
|
- Lambda : Processing des données via API Gateway
|
||||||
|
- **Statut** : ✅ **100% conforme**
|
||||||
|
|
||||||
|
#### Supabase
|
||||||
|
- **Instance** : `fusqtpjififcmgbhmosq.supabase.co`
|
||||||
|
- **Infrastructure** : Serveurs EU via Cloudflare
|
||||||
|
- **Données stockées** : Base de données complète (profils, organisations, salariés, contrats, etc.)
|
||||||
|
- **Statut** : ✅ **100% conforme**
|
||||||
|
|
||||||
|
#### Vercel
|
||||||
|
- **Région Functions** : `cdg1` (Paris, France)
|
||||||
|
- **Configuration** : `vercel.json` → `"regions": ["cdg1"]`
|
||||||
|
- **Important** : Les builds se font aux USA mais **aucune donnée client** n'y transite
|
||||||
|
- Le code source est compilé aux USA
|
||||||
|
- Le code compilé est déployé sur cdg1
|
||||||
|
- Les données clients ne quittent jamais cdg1
|
||||||
|
- **Statut** : ✅ **100% conforme**
|
||||||
|
|
||||||
|
#### Docuseal
|
||||||
|
- **API** : `https://api.docuseal.eu`
|
||||||
|
- **Version** : Européenne (`.eu`)
|
||||||
|
- **Données** : Signatures électroniques (contrats, avenants)
|
||||||
|
- **Statut** : ✅ **100% conforme**
|
||||||
|
|
||||||
|
#### PostHog
|
||||||
|
- **Instance** : `https://eu.i.posthog.com`
|
||||||
|
- **Configuration** :
|
||||||
|
```env
|
||||||
|
NEXT_PUBLIC_POSTHOG_HOST=https://eu.i.posthog.com
|
||||||
|
```
|
||||||
|
- **Données** : Analytics utilisateurs (événements, pages vues)
|
||||||
|
- **Statut** : ✅ **100% conforme**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚠️ Services à vérifier
|
||||||
|
|
||||||
|
#### PDFMonkey
|
||||||
|
- **API** : `https://api.pdfmonkey.io`
|
||||||
|
- **Entreprise** : Française (Paris - 51 rue de Ponthieu, 75008)
|
||||||
|
- **Infrastructure déclarée** : Heroku + AWS (région non spécifiée)
|
||||||
|
- **Données envoyées** :
|
||||||
|
- Informations contrats CDDU (nom, prénom, dates, salaires)
|
||||||
|
- Informations structure employeur
|
||||||
|
- Informations production
|
||||||
|
- **Problème** : Heroku héberge par défaut aux USA (`us-east-1`)
|
||||||
|
- **Action** :
|
||||||
|
- ✅ Mail envoyé à `tinymonkey@pdfmonkey.io` le 23/10/2025
|
||||||
|
- ⏳ En attente de confirmation de la région de traitement
|
||||||
|
- **Statut** : ⚠️ **En attente de réponse**
|
||||||
|
|
||||||
|
#### GoCardless
|
||||||
|
- **Environment** : `live`
|
||||||
|
- **API** : GoCardless UK/EU
|
||||||
|
- **Infrastructure** : Serveurs dans l'Espace Économique Européen (EEE)
|
||||||
|
- **Données** : Mandats SEPA, paiements, coordonnées bancaires
|
||||||
|
- **Mécanismes de protection** :
|
||||||
|
- Opérations principales de traitement des paiements : ✅ Serveurs EEE
|
||||||
|
- Prestataires tiers (hors EEE) : ✅ Clauses Contractuelles Types (SCC) de l'UE
|
||||||
|
- Contrôle préalable des fournisseurs avec mécanismes RGPD approuvés
|
||||||
|
- **Documentation** :
|
||||||
|
- https://gocardless.com/legal/data-protection/
|
||||||
|
- https://gocardless.com/fr-fr/privacy/fr-gdpr/
|
||||||
|
- **Confirmation** : ✅ Support GoCardless - 23 octobre 2025
|
||||||
|
- **Statut** : ✅ **Conforme RGPD**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Notes sur les services retirés
|
||||||
|
|
||||||
|
### 🚨 Priorité CRITIQUE
|
||||||
|
|
||||||
|
1. **PDFMonkey - Attendre réponse**
|
||||||
|
- ✅ Mail envoyé le 23/10/2025
|
||||||
|
- ⏳ Attendre confirmation de la localisation des serveurs
|
||||||
|
- Si réponse négative (serveurs hors EU) → **Migrer vers alternative** :
|
||||||
|
- DocRaptor (option EU disponible)
|
||||||
|
- PDF.co (serveurs EU)
|
||||||
|
- Solution auto-hébergée (Puppeteer + AWS Lambda eu-west-3)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Notes sur les services retirés
|
||||||
|
|
||||||
|
### Airtable et n8n
|
||||||
|
Ces services ont été **complètement retirés** du projet le 23 octobre 2025 :
|
||||||
|
|
||||||
|
#### Actions effectuées :
|
||||||
|
- ✅ Suppression de l'API route `/api/contrats/[id]/virement`
|
||||||
|
- ✅ Suppression du hook `useToggleVirement` dans les pages contrats
|
||||||
|
- ✅ Suppression des variables d'environnement n8n de `.env.local`
|
||||||
|
- ✅ Suppression des variables d'environnement Airtable de `.env.local`
|
||||||
|
- ✅ Nettoyage des fichiers `.env.example` et `.env.local.example`
|
||||||
|
|
||||||
|
#### Résidus restants (inactifs) :
|
||||||
|
- Fichiers de backup : `.env.local.bak`, `.env.local.bak2`
|
||||||
|
- Fichier HTML standalone : `hub_signature_batch.html`
|
||||||
|
- Fichiers de documentation : `VIREMENTS_SALAIRES_STAFF_*.md`
|
||||||
|
- Commentaire dans `app/api/access/route.ts` (ligne 412)
|
||||||
|
- Lambda AWS ancienne : `/tmp/aws-toolkit-vscode/lambda/.../postDocuSealAvenantPDF/index.js`
|
||||||
|
|
||||||
|
**Impact RGPD** : ✅ **Aucun** - Aucune donnée n'est envoyée à ces services
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Plan d'action
|
||||||
|
|
||||||
|
### 🚨 Priorité CRITIQUE
|
||||||
|
|
||||||
|
1. **PDFMonkey - Attendre réponse**
|
||||||
|
- ✅ Mail envoyé le 23/10/2025
|
||||||
|
- ⏳ Attendre confirmation de la localisation des serveurs
|
||||||
|
- Si réponse négative (serveurs hors EU) → **Migrer vers alternative** :
|
||||||
|
- DocRaptor (option EU disponible)
|
||||||
|
- PDF.co (serveurs EU)
|
||||||
|
- Solution auto-hébergée (Puppeteer + AWS Lambda eu-west-3)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Emails et confirmations
|
||||||
|
|
||||||
|
### Email envoyé à PDFMonkey
|
||||||
|
|
||||||
|
**Date** : 23 octobre 2025
|
||||||
|
**Destinataire** : `tinymonkey@pdfmonkey.io`
|
||||||
|
**Objet** : Question sur la localisation des données (RGPD)
|
||||||
|
|
||||||
|
```
|
||||||
|
Bonjour,
|
||||||
|
|
||||||
|
Nous utilisons PDFMonkey pour générer des PDF contenant des données personnelles
|
||||||
|
de salariés (contrats de travail CDDU) et devons nous assurer de la conformité RGPD.
|
||||||
|
|
||||||
|
Pouvez-vous nous confirmer dans quelle région AWS/Heroku sont hébergées et
|
||||||
|
traitées les données que nous vous envoyons via l'API ?
|
||||||
|
|
||||||
|
Spécifiquement :
|
||||||
|
- Les données restent-elles dans l'Union Européenne ?
|
||||||
|
- Quelle est la région de vos serveurs de production (Heroku et AWS) ?
|
||||||
|
|
||||||
|
Merci d'avance pour votre retour rapide.
|
||||||
|
```
|
||||||
|
|
||||||
|
### Réponse de GoCardless
|
||||||
|
|
||||||
|
**Date** : 23 octobre 2025
|
||||||
|
**Source** : Support GoCardless
|
||||||
|
**Statut** : ✅ **Conforme RGPD**
|
||||||
|
|
||||||
|
**Résumé de la réponse** :
|
||||||
|
- ✅ **Opérations principales** : Serveurs dans l'Espace Économique Européen (EEE)
|
||||||
|
- ✅ **Prestataires tiers** : Clauses Contractuelles Types (SCC) de l'Union européenne
|
||||||
|
- ✅ **Contrôle préalable** : Mécanismes approuvés par le RGPD (constat d'adéquation, règles d'entreprise contraignantes)
|
||||||
|
- ✅ **Protection des données** : Normes de l'Union européenne respectées
|
||||||
|
|
||||||
|
**Documentation** :
|
||||||
|
- Politique RGPD : https://gocardless.com/fr-fr/privacy/fr-gdpr/
|
||||||
|
- Protection des données : https://gocardless.com/legal/data-protection/
|
||||||
|
|
||||||
|
**Citation** :
|
||||||
|
> "L'ensemble de nos principales opérations de traitement des paiements européens sont exécutées sur des serveurs situés dans l'Espace économique européen (EEE). [...] Dès lors que des données sont stockées dans ces services, nous veillons à ce qu'elles soient protégées selon les normes de l'Union européenne, en utilisant un mécanisme de transfert approuvé par le RGPD."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Notes importantes
|
||||||
|
|
||||||
|
### Concernant Vercel
|
||||||
|
Les builds Vercel aux USA **n'ont AUCUN impact** sur les données clients :
|
||||||
|
- Le **code source** est compilé aux USA
|
||||||
|
- Le **code compilé** est déployé sur `cdg1` (Paris)
|
||||||
|
- Les **API Functions** s'exécutent sur `cdg1`
|
||||||
|
- Les **données clients** ne transitent **JAMAIS** par les serveurs de build
|
||||||
|
- Vercel ne stocke pas de données clients, uniquement des logs techniques
|
||||||
|
|
||||||
|
✅ **Aucun risque RGPD** de ce côté.
|
||||||
|
|
||||||
|
### CDNs externes
|
||||||
|
Plusieurs CDNs sont utilisés pour charger des bibliothèques JavaScript :
|
||||||
|
- `cdn.docuseal.com` (formulaires de signature)
|
||||||
|
- `cdn.jsdelivr.net` (Bootstrap, Flatpickr)
|
||||||
|
- `cdnjs.cloudflare.com` (Font Awesome)
|
||||||
|
|
||||||
|
Ces CDNs peuvent servir depuis des serveurs hors UE, mais ils ne contiennent **aucune donnée client**, uniquement des fichiers statiques (CSS, JS, fonts).
|
||||||
|
|
||||||
|
✅ **Pas de problème RGPD** : Seuls des assets publics transitent, pas de données personnelles.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Résumé
|
||||||
|
|
||||||
|
| Statut | Nombre de services |
|
||||||
|
|--------|-------------------|
|
||||||
|
| ✅ Conforme RGPD | 8 services |
|
||||||
|
| ⚠️ À vérifier | 1 service |
|
||||||
|
| ❌ Non conforme | 0 service |
|
||||||
|
| ✅ Retiré | 2 services (Airtable, n8n) |
|
||||||
|
|
||||||
|
### Taux de conformité actuel
|
||||||
|
**8 / 9 confirmés = 89%**
|
||||||
|
|
||||||
|
Avec la vérification en cours (PDFMonkey), le taux devrait atteindre **100%**.
|
||||||
|
|
@ -406,30 +406,6 @@ function useContratDetail(id: string) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exemple mutation (toggle virement). Adapte au vrai endpoint si différent.
|
|
||||||
function useToggleVirement(id: string) {
|
|
||||||
const qc = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: async (params: { value: boolean; contractRef?: string }) => {
|
|
||||||
const { value, contractRef } = params;
|
|
||||||
// Normaliser en "Oui" / "Non" pour l'écosystème (Airtable via n8n)
|
|
||||||
const status = value ? "Oui" : "Non";
|
|
||||||
const res = await fetch(`/api/contrats/${id}/virement`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
||||||
credentials: "include",
|
|
||||||
body: JSON.stringify({ virement_effectue: status, contract_ref: contractRef || undefined }),
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
const t = await res.text().catch(() => "");
|
|
||||||
throw new Error(t || `HTTP ${res.status}`);
|
|
||||||
}
|
|
||||||
return (await res.json()) as { ok: boolean };
|
|
||||||
},
|
|
||||||
onSuccess: () => qc.invalidateQueries({ queryKey: ["contrat", id] }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- UI helpers ----------
|
// ---------- UI helpers ----------
|
||||||
function Section({ title, icon: Icon, children }: { title: string; icon?: React.ElementType; children: React.ReactNode }) {
|
function Section({ title, icon: Icon, children }: { title: string; icon?: React.ElementType; children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -862,8 +838,6 @@ export default function ContratPage() {
|
||||||
}
|
}
|
||||||
fetchSignedUrls();
|
fetchSignedUrls();
|
||||||
}, [payslipsQuery.data]);
|
}, [payslipsQuery.data]);
|
||||||
const toggleMut = useToggleVirement(id);
|
|
||||||
|
|
||||||
|
|
||||||
// Redirection de sécurité côté client
|
// Redirection de sécurité côté client
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -305,30 +305,6 @@ function useContratDetail(id: string) {
|
||||||
// Pas de mode normal : cette page est TOUJOURS en mode démo
|
// Pas de mode normal : cette page est TOUJOURS en mode démo
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exemple mutation (toggle virement). Adapte au vrai endpoint si différent.
|
|
||||||
function useToggleVirement(id: string) {
|
|
||||||
const qc = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: async (params: { value: boolean; contractRef?: string }) => {
|
|
||||||
const { value, contractRef } = params;
|
|
||||||
// Normaliser en "Oui" / "Non" pour l'écosystème (Airtable via n8n)
|
|
||||||
const status = value ? "Oui" : "Non";
|
|
||||||
const res = await fetch(`/api/contrats/${id}/virement`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
||||||
credentials: "include",
|
|
||||||
body: JSON.stringify({ virement_effectue: status, contract_ref: contractRef || undefined }),
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
const t = await res.text().catch(() => "");
|
|
||||||
throw new Error(t || `HTTP ${res.status}`);
|
|
||||||
}
|
|
||||||
return (await res.json()) as { ok: boolean };
|
|
||||||
},
|
|
||||||
onSuccess: () => qc.invalidateQueries({ queryKey: ["contrat", id] }),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- UI helpers ----------
|
// ---------- UI helpers ----------
|
||||||
function Section({ title, icon: Icon, children }: { title: string; icon?: React.ElementType; children: React.ReactNode }) {
|
function Section({ title, icon: Icon, children }: { title: string; icon?: React.ElementType; children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
|
|
@ -780,9 +756,6 @@ export default function ContratDemoPage() {
|
||||||
// }
|
// }
|
||||||
// fetchSignedUrls();
|
// fetchSignedUrls();
|
||||||
// }, [payslipsQuery.data]);
|
// }, [payslipsQuery.data]);
|
||||||
|
|
||||||
const toggleMut = useToggleVirement(id);
|
|
||||||
|
|
||||||
|
|
||||||
// Redirection de sécurité côté client
|
// Redirection de sécurité côté client
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,73 +0,0 @@
|
||||||
// app/api/contrats/[id]/virement/route.ts
|
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
|
||||||
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
|
|
||||||
import { cookies } from "next/headers";
|
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
|
||||||
export const revalidate = 0;
|
|
||||||
export const runtime = "nodejs";
|
|
||||||
|
|
||||||
export async function POST(req: NextRequest, { params }: { params: { id: string } }) {
|
|
||||||
const contractId = params?.id;
|
|
||||||
if (!contractId) return NextResponse.json({ error: "missing_id" }, { status: 400 });
|
|
||||||
|
|
||||||
const supabase = createRouteHandlerClient({ cookies });
|
|
||||||
const { data: { session } } = await supabase.auth.getSession();
|
|
||||||
if (!session) return NextResponse.json({ error: "unauthorized" }, { status: 401 });
|
|
||||||
|
|
||||||
let body: any = {};
|
|
||||||
try {
|
|
||||||
body = await req.json();
|
|
||||||
} catch {
|
|
||||||
return NextResponse.json({ error: "invalid_json" }, { status: 400 });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normaliser en "Oui" / "Non"
|
|
||||||
const raw = body?.virement_effectue;
|
|
||||||
const normalized = ((): "Oui" | "Non" => {
|
|
||||||
if (typeof raw === "string") {
|
|
||||||
const s = raw.trim().toLowerCase();
|
|
||||||
return s === "oui" || s === "true" || s === "1" ? "Oui" : "Non";
|
|
||||||
}
|
|
||||||
return raw ? "Oui" : "Non";
|
|
||||||
})();
|
|
||||||
|
|
||||||
const contract_ref = String(body?.contract_ref || "");
|
|
||||||
|
|
||||||
const webhookUrl = process.env.N8N_VIREMENT_WEBHOOK_URL || process.env.N8N_WEBHOOK_VIREMENT_URL;
|
|
||||||
if (!webhookUrl) {
|
|
||||||
// Pas de webhook configuré, mais on répond OK pour ne pas bloquer l'UI
|
|
||||||
console.warn("ℹ️ N8N webhook URL not configured (N8N_VIREMENT_WEBHOOK_URL)");
|
|
||||||
return NextResponse.json({ ok: true, skipped: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
contract_id: contractId,
|
|
||||||
contract_ref: contract_ref || null,
|
|
||||||
virement_effectue: normalized, // "Oui" / "Non"
|
|
||||||
// Contexte utilisateur minimal
|
|
||||||
user_id: session.user.id,
|
|
||||||
user_email: session.user.email,
|
|
||||||
triggered_at: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await fetch(webhookUrl, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
cache: "no-store",
|
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
const text = await res.text().catch(() => "");
|
|
||||||
console.error("❌ N8N webhook error:", res.status, text);
|
|
||||||
return NextResponse.json({ ok: false, error: "webhook_failed", status: res.status }, { status: 502 });
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error("💥 N8N webhook call threw:", e?.message || e);
|
|
||||||
return NextResponse.json({ ok: false, error: "webhook_exception" }, { status: 502 });
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.json({ ok: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -149,9 +149,17 @@ export default function DocuSealSignatureModal({
|
||||||
<div className="bg-white rounded-2xl h-full flex flex-col max-h-[90vh]">
|
<div className="bg-white rounded-2xl h-full flex flex-col max-h-[90vh]">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between px-6 py-4 border-b bg-gradient-to-r from-blue-50 to-indigo-50 flex-shrink-0">
|
<div className="flex items-center justify-between px-6 py-4 border-b bg-gradient-to-r from-blue-50 to-indigo-50 flex-shrink-0">
|
||||||
<h2 className="text-xl font-semibold text-slate-900">
|
<div className="flex-1">
|
||||||
Signature électronique du contrat
|
<h2 className="text-xl font-semibold text-slate-900">
|
||||||
</h2>
|
Signature électronique du contrat
|
||||||
|
</h2>
|
||||||
|
<p className="text-xs text-slate-600 mt-1 flex items-center gap-1">
|
||||||
|
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
Descendez tout en bas du contrat pour accéder au champs de signature.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!showCompletedModal) {
|
if (!showCompletedModal) {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue