- Remplacement de DocuSeal par solution souveraine Odentas Sign - Système d'authentification OTP pour signataires (bcryptjs + JWT) - 8 routes API: send-otp, verify-otp, sign, pdf-url, positions, status, webhook, signers - Interface moderne avec canvas de signature et animations (framer-motion, confetti) - Système de templates pour auto-détection des positions de signature (CDDU, RG, avenants) - PDF viewer avec @react-pdf-viewer (compatible Next.js) - Stockage S3: source/, signatures/, evidence/, signed/, certs/ - Tables Supabase: sign_requests, signers, sign_positions, sign_events, sign_assets - Evidence bundle automatique (JSON metadata + timestamps) - Templates emails: OTP et completion - Scripts Lambda prêts: pades-sign (KMS seal) et tsaStamp (RFC3161) - Mode test détecté automatiquement (emails whitelist) - Tests complets avec PDF CDDU réel (2 signataires)
92 lines
2.5 KiB
TypeScript
92 lines
2.5 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { supabaseAdmin, logSignEvent } from '@/lib/odentas-sign/supabase';
|
|
|
|
/**
|
|
* POST /api/odentas-sign/requests/[id]/cancel
|
|
*
|
|
* Annule une demande de signature
|
|
*/
|
|
export async function POST(
|
|
request: NextRequest,
|
|
{ params }: { params: { id: string } }
|
|
) {
|
|
try {
|
|
const requestId = params.id;
|
|
const body = await request.json();
|
|
const { reason } = body;
|
|
|
|
// Récupérer la demande
|
|
const { data: signRequest, error: requestError } = await supabaseAdmin
|
|
.from('sign_requests')
|
|
.select('*')
|
|
.eq('id', requestId)
|
|
.single();
|
|
|
|
if (requestError || !signRequest) {
|
|
return NextResponse.json(
|
|
{ error: 'Demande introuvable' },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
// Vérifier qu'elle n'est pas déjà complétée
|
|
if (signRequest.status === 'completed') {
|
|
return NextResponse.json(
|
|
{ error: 'Impossible d\'annuler une demande déjà complétée' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Vérifier qu'elle n'est pas déjà annulée
|
|
if (signRequest.status === 'cancelled') {
|
|
return NextResponse.json(
|
|
{ error: 'Cette demande est déjà annulée' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Mettre à jour le statut
|
|
const { error: updateError } = await supabaseAdmin
|
|
.from('sign_requests')
|
|
.update({ status: 'cancelled' })
|
|
.eq('id', requestId);
|
|
|
|
if (updateError) {
|
|
console.error('[CANCEL] Erreur mise à jour:', updateError);
|
|
return NextResponse.json(
|
|
{ error: 'Erreur lors de l\'annulation' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
|
|
// Logger l'événement
|
|
await logSignEvent({
|
|
requestId: signRequest.id,
|
|
event: 'request_cancelled',
|
|
ip: request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || undefined,
|
|
userAgent: request.headers.get('user-agent') || undefined,
|
|
metadata: {
|
|
reason: reason || 'Non spécifié',
|
|
},
|
|
});
|
|
|
|
console.log(`[CANCEL] ✅ Demande annulée: ${signRequest.ref}`);
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: 'Demande annulée avec succès',
|
|
request: {
|
|
id: signRequest.id,
|
|
ref: signRequest.ref,
|
|
status: 'cancelled',
|
|
},
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('[CANCEL] Erreur:', error);
|
|
return NextResponse.json(
|
|
{ error: 'Erreur serveur', details: error instanceof Error ? error.message : String(error) },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|