287 lines
10 KiB
TypeScript
287 lines
10 KiB
TypeScript
/**
|
|
* Commission Payment Page
|
|
*
|
|
* Shows booking summary and commission amount, allows payment via Stripe or bank transfer
|
|
*/
|
|
|
|
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { useRouter, useParams } from 'next/navigation';
|
|
import { CreditCard, Building2, ArrowLeft, Loader2, AlertTriangle, CheckCircle } from 'lucide-react';
|
|
import { getCsvBooking, payBookingCommission } from '@/lib/api/bookings';
|
|
|
|
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;
|
|
}
|
|
|
|
export default function PayCommissionPage() {
|
|
const router = useRouter();
|
|
const params = useParams();
|
|
const bookingId = params.id as string;
|
|
|
|
const [booking, setBooking] = useState<BookingData | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [paying, setPaying] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
async function fetchBooking() {
|
|
try {
|
|
const data = await getCsvBooking(bookingId);
|
|
setBooking(data as any);
|
|
|
|
// If booking is not in PENDING_PAYMENT status, redirect
|
|
if (data.status !== 'PENDING_PAYMENT') {
|
|
router.replace('/dashboard/bookings');
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to fetch booking:', err);
|
|
setError('Impossible de charger les details du booking');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
if (bookingId) {
|
|
fetchBooking();
|
|
}
|
|
}, [bookingId, router]);
|
|
|
|
const handlePayByCard = async () => {
|
|
setPaying(true);
|
|
setError(null);
|
|
|
|
try {
|
|
const result = await payBookingCommission(bookingId);
|
|
// Redirect to Stripe Checkout
|
|
window.location.href = result.sessionUrl;
|
|
} catch (err) {
|
|
console.error('Payment error:', err);
|
|
setError(err instanceof Error ? err.message : 'Erreur lors de la creation du paiement');
|
|
setPaying(false);
|
|
}
|
|
};
|
|
|
|
const formatPrice = (price: number, currency: string) => {
|
|
return new Intl.NumberFormat('fr-FR', {
|
|
style: 'currency',
|
|
currency,
|
|
}).format(price);
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100">
|
|
<div className="flex items-center space-x-3">
|
|
<Loader2 className="h-6 w-6 animate-spin text-blue-600" />
|
|
<span className="text-gray-700">Chargement...</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error && !booking) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100">
|
|
<div className="bg-white rounded-lg shadow-md p-8 max-w-md">
|
|
<AlertTriangle className="h-12 w-12 text-red-500 mx-auto mb-4" />
|
|
<p className="text-center text-gray-700">{error}</p>
|
|
<button
|
|
onClick={() => router.push('/dashboard/bookings')}
|
|
className="mt-4 w-full px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700"
|
|
>
|
|
Retour aux bookings
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!booking) return null;
|
|
|
|
const commissionAmount = booking.commissionAmountEur || 0;
|
|
const commissionRate = booking.commissionRate || 0;
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 py-12 px-4">
|
|
<div className="max-w-2xl mx-auto">
|
|
{/* Header */}
|
|
<div className="mb-8">
|
|
<button
|
|
onClick={() => router.push('/dashboard/bookings')}
|
|
className="mb-4 flex items-center text-blue-600 hover:text-blue-800 font-medium"
|
|
>
|
|
<ArrowLeft className="h-4 w-4 mr-2" />
|
|
Retour aux bookings
|
|
</button>
|
|
|
|
<div className="bg-white rounded-lg shadow-md p-6">
|
|
<h1 className="text-2xl font-bold text-gray-900 mb-2">Paiement de la commission</h1>
|
|
<p className="text-gray-600">
|
|
Finalisez votre booking en payant la commission de service
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Error */}
|
|
{error && (
|
|
<div className="mb-6 bg-red-50 border-2 border-red-200 rounded-lg p-4">
|
|
<div className="flex items-start">
|
|
<AlertTriangle className="h-5 w-5 mr-3 text-red-500 flex-shrink-0" />
|
|
<p className="text-red-700">{error}</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Booking Summary */}
|
|
<div className="bg-white rounded-lg shadow-lg p-6 mb-6">
|
|
<h2 className="text-lg font-semibold text-gray-900 mb-4">Recapitulatif du booking</h2>
|
|
|
|
<div className="space-y-3 text-sm">
|
|
{booking.bookingNumber && (
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-600">Numero :</span>
|
|
<span className="font-semibold text-gray-900">{booking.bookingNumber}</span>
|
|
</div>
|
|
)}
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-600">Transporteur :</span>
|
|
<span className="font-semibold text-gray-900">{booking.carrierName}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-600">Trajet :</span>
|
|
<span className="font-semibold text-gray-900">
|
|
{booking.origin} → {booking.destination}
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-600">Volume / Poids :</span>
|
|
<span className="font-semibold text-gray-900">
|
|
{booking.volumeCBM} CBM / {booking.weightKG} kg
|
|
</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-600">Transit :</span>
|
|
<span className="font-semibold text-gray-900">{booking.transitDays} jours</span>
|
|
</div>
|
|
<div className="flex justify-between border-t pt-3">
|
|
<span className="text-gray-600">Prix transport :</span>
|
|
<span className="font-bold text-gray-900">
|
|
{formatPrice(booking.priceEUR, 'EUR')}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Commission Details */}
|
|
<div className="bg-white rounded-lg shadow-lg p-6 mb-6">
|
|
<h2 className="text-lg font-semibold text-gray-900 mb-4">Commission de service</h2>
|
|
|
|
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-4">
|
|
<div className="flex justify-between items-center">
|
|
<div>
|
|
<p className="text-sm text-gray-600">
|
|
Commission ({commissionRate}% du prix transport)
|
|
</p>
|
|
<p className="text-xs text-gray-500 mt-1">
|
|
{formatPrice(booking.priceEUR, 'EUR')} x {commissionRate}%
|
|
</p>
|
|
</div>
|
|
<p className="text-2xl font-bold text-blue-600">
|
|
{formatPrice(commissionAmount, 'EUR')}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-gray-50 rounded-lg p-3">
|
|
<div className="flex items-start space-x-2">
|
|
<CheckCircle className="h-4 w-4 text-green-500 mt-0.5 flex-shrink-0" />
|
|
<p className="text-xs text-gray-600">
|
|
Apres le paiement, votre demande sera envoyee par email au transporteur ({booking.carrierEmail}).
|
|
Vous recevrez une notification des que le transporteur repond.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Payment Methods */}
|
|
<div className="space-y-4">
|
|
{/* Pay by Card (Stripe) */}
|
|
<button
|
|
onClick={handlePayByCard}
|
|
disabled={paying}
|
|
className="w-full bg-blue-600 text-white rounded-lg p-4 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
|
|
>
|
|
<div className="flex items-center justify-center">
|
|
{paying ? (
|
|
<>
|
|
<Loader2 className="h-5 w-5 mr-3 animate-spin" />
|
|
Redirection vers Stripe...
|
|
</>
|
|
) : (
|
|
<>
|
|
<CreditCard className="h-5 w-5 mr-3" />
|
|
Payer {formatPrice(commissionAmount, 'EUR')} par carte
|
|
</>
|
|
)}
|
|
</div>
|
|
</button>
|
|
|
|
{/* Pay by Bank Transfer (informational) */}
|
|
<div className="bg-white rounded-lg shadow-lg p-6">
|
|
<div className="flex items-center mb-4">
|
|
<Building2 className="h-5 w-5 mr-3 text-gray-600" />
|
|
<h3 className="font-semibold text-gray-900">Payer par virement bancaire</h3>
|
|
</div>
|
|
|
|
<div className="bg-gray-50 rounded-lg p-4 space-y-2 text-sm">
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-600">Beneficiaire :</span>
|
|
<span className="font-medium text-gray-900">XPEDITIS SAS</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-600">IBAN :</span>
|
|
<span className="font-mono text-gray-900">FR76 XXXX XXXX XXXX XXXX XXXX XXX</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-600">BIC :</span>
|
|
<span className="font-mono text-gray-900">XXXXXXXX</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-600">Montant :</span>
|
|
<span className="font-bold text-gray-900">{formatPrice(commissionAmount, 'EUR')}</span>
|
|
</div>
|
|
<div className="flex justify-between">
|
|
<span className="text-gray-600">Reference :</span>
|
|
<span className="font-mono text-gray-900">{booking.bookingNumber || booking.id.slice(0, 8)}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<p className="mt-3 text-xs text-gray-500">
|
|
Le traitement du virement peut prendre 1 a 3 jours ouvrables.
|
|
Votre booking sera active une fois le paiement recu et verifie.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|