/** * Commission Payment Page * * 2-column layout: * - Left: payment method selector + action * - Right: booking summary */ 'use client'; import { useState, useEffect } from 'react'; import { useRouter, useParams } from 'next/navigation'; import { CreditCard, Building2, ArrowLeft, Loader2, AlertTriangle, CheckCircle, Copy, Clock, ShieldCheck, Send, } from 'lucide-react'; import { getCsvBooking, payBookingCommission, declareBankTransfer } from '@/lib/api/bookings'; import { getOrganization, requestSiretApproval } from '@/lib/api/organizations'; import { useAuth } from '@/lib/context/auth-context'; interface BookingData { id: string; bookingNumber?: string; carrierName: string; carrierEmail: string; origin: string; destination: string; volumeCBM: number; weightKG: number; palletCount: number; priceEUR: number; priceUSD: number; primaryCurrency: string; transitDays: number; containerType: string; status: string; commissionRate?: number; commissionAmountEur?: number; freightTotal?: number; freightCurrency?: string; fobTotal?: number; fobCurrency?: string; } interface OrgData { siren?: string | null; siret?: string | null; siretVerified?: boolean; name: string; } type PaymentMethod = 'card' | 'transfer' | null; const BANK_DETAILS = { beneficiary: 'XPEDITIS SAS', iban: 'FR76 XXXX XXXX XXXX XXXX XXXX XXX', bic: 'XXXXXXXX', }; export default function PayCommissionPage() { const router = useRouter(); const params = useParams(); const bookingId = params.id as string; const { user } = useAuth(); const [booking, setBooking] = useState(null); const [org, setOrg] = useState(null); const [loading, setLoading] = useState(true); const [paying, setPaying] = useState(false); const [declaring, setDeclaring] = useState(false); const [error, setError] = useState(null); const [selectedMethod, setSelectedMethod] = useState(null); const [copied, setCopied] = useState(null); const [requestingApproval, setRequestingApproval] = useState(false); const [approvalSent, setApprovalSent] = useState(false); const [approvalError, setApprovalError] = useState(null); useEffect(() => { async function fetchData() { try { const [bookingData, orgData] = await Promise.all([ getCsvBooking(bookingId), user?.organizationId ? getOrganization(user.organizationId) : Promise.resolve(null), ]); setBooking(bookingData as any); if (orgData) setOrg(orgData); if ((bookingData as any).status !== 'PENDING_PAYMENT') { router.replace('/dashboard/bookings'); } } catch (err) { setError('Impossible de charger les détails du booking'); } finally { setLoading(false); } } if (bookingId) fetchData(); }, [bookingId, router, user?.organizationId]); const handlePayByCard = async () => { setPaying(true); setError(null); try { const result = await payBookingCommission(bookingId); window.location.href = result.sessionUrl; } catch (err) { setError(err instanceof Error ? err.message : 'Erreur lors de la création du paiement'); setPaying(false); } }; const handleDeclareTransfer = async () => { setDeclaring(true); setError(null); try { await declareBankTransfer(bookingId); router.push('/dashboard/bookings?transfer=declared'); } catch (err) { setError(err instanceof Error ? err.message : 'Erreur lors de la déclaration du virement'); setDeclaring(false); } }; const handleRequestApproval = async () => { setRequestingApproval(true); setApprovalError(null); try { await requestSiretApproval(); setApprovalSent(true); } catch (err) { setApprovalError( err instanceof Error ? err.message : 'Erreur lors de l\'envoi de la demande' ); } finally { setRequestingApproval(false); } }; const copyToClipboard = (value: string, key: string) => { navigator.clipboard.writeText(value); setCopied(key); setTimeout(() => setCopied(null), 2000); }; const formatPrice = (price: number, currency: string) => new Intl.NumberFormat('fr-FR', { style: 'currency', currency }).format(price); const hasSiretOrSiren = org && (org.siret || org.siren); const siretNotVerified = hasSiretOrSiren && !org.siretVerified; const identifier = org?.siret ? `SIRET ${org.siret}` : org?.siren ? `SIREN ${org.siren}` : null; if (loading) { return (
Chargement...
); } if (error && !booking) { return (

{error}

); } if (!booking) return null; const bookingFeeAmount = booking.commissionAmountEur || 0; const reference = booking.bookingNumber || booking.id.slice(0, 8).toUpperCase(); // Transport cost breakdown (informational — paid to the carrier, not collected here). // Freight and FOB may be in different currencies, so they are shown separately. const freightTotal = booking.freightTotal ?? null; const fobTotal = booking.fobTotal ?? null; const freightCurrency = booking.freightCurrency || booking.primaryCurrency || 'USD'; const fobCurrency = booking.fobCurrency || 'EUR'; const hasBreakdown = freightTotal != null || fobTotal != null; // Single transport total, always shown in USD = freight + FOB summed. // New bookings store the freight/FOB breakdown; legacy ones only keep the // per-currency price sums. In both cases the charges are added together. const transportTotalUSD = hasBreakdown ? (freightTotal ?? 0) + (fobTotal ?? 0) : (booking.priceUSD || 0) + (booking.priceEUR || 0); return (
{/* Back button — returns to the previous context (list or booking detail) instead of always jumping to the global list. */}

Paiement du forfait booking

Finalisez votre booking en réglant le forfait par booking

{/* SIRET/SIREN non vérifié — bannière de demande */} {siretNotVerified && (

Votre {identifier} n'est pas encore validé

Un administrateur doit vérifier votre numéro d'identification avant de finaliser certaines opérations. Vous pouvez lui envoyer une demande de validation directement depuis cette page.

{approvalSent ? (
Demande envoyée aux administrateurs. Vous serez notifié dès que votre{' '} {identifier} sera validé.
) : (
{approvalError && (

{approvalError}

)}
)}
)} {error && (

{error}

)}
{/* LEFT — Payment method selector */}

Choisir le mode de paiement

{/* Card option */} {/* Transfer option */} {/* Card action */} {selectedMethod === 'card' && (

Vous serez redirigé vers Stripe pour finaliser votre paiement en toute sécurité.

)} {/* Transfer action */} {selectedMethod === 'transfer' && (

Effectuez le virement avec les coordonnées ci-dessous, puis cliquez sur “J'ai effectué le virement”.

{/* Bank details */}
{[ { label: 'Bénéficiaire', value: BANK_DETAILS.beneficiary, key: 'beneficiary' }, { label: 'IBAN', value: BANK_DETAILS.iban, key: 'iban', mono: true }, { label: 'BIC / SWIFT', value: BANK_DETAILS.bic, key: 'bic', mono: true }, { label: 'Montant', value: formatPrice(bookingFeeAmount, 'EUR'), key: 'amount', bold: true, }, { label: 'Référence', value: reference, key: 'ref', mono: true }, ].map(({ label, value, key, mono, bold }) => (
{label}
{value} {key !== 'amount' && ( )}
))}
Mentionnez impérativement la référence {reference} dans le libellé du virement.
)} {/* Placeholder when no method selected */} {selectedMethod === null && (
Sélectionnez un mode de paiement ci-dessus
)}
{/* RIGHT — Booking summary */}

Récapitulatif

{booking.bookingNumber && (
Numéro {booking.bookingNumber}
)}
Transporteur {booking.carrierName}
Trajet {booking.origin} → {booking.destination}
Volume / Poids {booking.volumeCBM} CBM · {booking.weightKG} kg
Transit {booking.transitDays} jours
{/* Transport cost breakdown — Fret + FOB (paid to the carrier) */} {hasBreakdown && ( <> {freightTotal != null && (
Fret {formatPrice(freightTotal, freightCurrency)}
)} {fobTotal != null && fobTotal > 0 && (
Frais FOB {formatPrice(fobTotal, fobCurrency)}
)} )} {/* Single transport total — always in USD (fret + FOB) */}
Total {formatPrice(transportTotalUSD, 'USD')}

Fret et frais FOB sont réglés directement au transporteur.

{/* Booking fee box */}

Forfait par booking — à régler maintenant

{formatPrice(bookingFeeAmount, 'EUR')}

Montant fixe par booking (hors fret et frais FOB)

{/* Organisation info */} {org && (

{org.name}

{identifier && (
{identifier} {org.siretVerified ? ( Vérifié ) : ( Non vérifié )}
)}
)}

Après validation du paiement, votre demande est envoyée au transporteur ( {booking.carrierEmail}). Vous serez notifié de sa réponse.

); }