673 lines
27 KiB
TypeScript
673 lines
27 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { useCart } from '@/contexts/CartContext';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
import { Textarea } from '@/components/ui/textarea';
|
|
import { Separator } from '@/components/ui/separator';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { ArrowLeft, CreditCard, Smartphone, Building, Truck, Shield, Check, X, Loader2 } from 'lucide-react';
|
|
import Header from '@/components/Header';
|
|
import Footer from '@/components/Footer';
|
|
import { useToast } from '@/hooks/use-toast';
|
|
import { useBooking } from '@/hooks/useBooking';
|
|
import { useStripe } from '@/hooks/useStripe';
|
|
import { paymentService } from '@/services/paymentService';
|
|
|
|
interface CustomerInfo {
|
|
firstName: string;
|
|
lastName: string;
|
|
email: string;
|
|
phone: string;
|
|
address: string;
|
|
city: string;
|
|
country: string;
|
|
zipCode: string;
|
|
}
|
|
|
|
interface PaymentInfo {
|
|
method: 'credit_card' | 'paypal' | 'bank_transfer' | 'cash';
|
|
cardNumber: string;
|
|
expiryDate: string;
|
|
cvv: string;
|
|
cardName: string;
|
|
}
|
|
|
|
const Checkout = () => {
|
|
const navigate = useNavigate();
|
|
const { items, getTotalPrice, clearCart } = useCart();
|
|
const { toast } = useToast();
|
|
const { createBooking } = useBooking();
|
|
const { stripe, credentials, loading: stripeLoading } = useStripe();
|
|
const [step, setStep] = useState(1);
|
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
|
|
const [customerInfo, setCustomerInfo] = useState<CustomerInfo>({
|
|
firstName: '',
|
|
lastName: '',
|
|
email: '',
|
|
phone: '',
|
|
address: '',
|
|
city: '',
|
|
country: 'Dominican Republic',
|
|
zipCode: ''
|
|
});
|
|
|
|
const [paymentInfo, setPaymentInfo] = useState<PaymentInfo>({
|
|
method: 'credit_card',
|
|
cardNumber: '',
|
|
expiryDate: '',
|
|
cvv: '',
|
|
cardName: ''
|
|
});
|
|
|
|
const [specialRequests, setSpecialRequests] = useState('');
|
|
|
|
if (items.length === 0) {
|
|
return (
|
|
<div className="min-h-screen bg-gray-50">
|
|
<Header />
|
|
<div className="container mx-auto px-4 pt-24 pb-12">
|
|
<div className="text-center py-12">
|
|
<h1 className="text-2xl font-bold mb-4">Tu carrito está vacío</h1>
|
|
<p className="text-gray-600 mb-6">Agrega algunas ofertas a tu carrito para continuar.</p>
|
|
<Button onClick={() => navigate('/explore')}>
|
|
Explorar Ofertas
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<Footer />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const totalPrice = getTotalPrice();
|
|
const taxes = totalPrice * 0.18; // 18% ITBIS (impuesto dominicano)
|
|
const serviceFee = totalPrice * 0.05; // 5% fee de servicio
|
|
const finalTotal = totalPrice + taxes + serviceFee;
|
|
|
|
const paymentMethods = [
|
|
{ id: 'credit_card', name: 'Tarjeta de Crédito/Débito', icon: CreditCard },
|
|
{ id: 'paypal', name: 'PayPal', icon: Smartphone },
|
|
{ id: 'bank_transfer', name: 'Transferencia Bancaria', icon: Building },
|
|
{ id: 'cash', name: 'Pago en Efectivo', icon: Truck }
|
|
];
|
|
|
|
const handleCustomerInfoChange = (field: keyof CustomerInfo, value: string) => {
|
|
setCustomerInfo(prev => ({ ...prev, [field]: value }));
|
|
};
|
|
|
|
const handlePaymentInfoChange = (field: keyof PaymentInfo, value: string) => {
|
|
setPaymentInfo(prev => ({ ...prev, [field]: value }));
|
|
};
|
|
|
|
const saveOrderToJSON = (orderData: any) => {
|
|
const orders = JSON.parse(localStorage.getItem('karibeo_orders') || '[]');
|
|
orders.push(orderData);
|
|
localStorage.setItem('karibeo_orders', JSON.stringify(orders));
|
|
};
|
|
|
|
const processPayment = async () => {
|
|
setIsProcessing(true);
|
|
|
|
try {
|
|
// Si el método de pago es tarjeta de crédito y Stripe está disponible
|
|
if (paymentInfo.method === 'credit_card' && stripe && credentials) {
|
|
// Crear PaymentIntent desde el backend
|
|
const paymentIntent = await paymentService.createPaymentIntent({
|
|
amount: Math.round(finalTotal * 100), // Convertir a centavos
|
|
currency: 'usd',
|
|
description: `Reserva de ${items.length} item(s)`,
|
|
metadata: {
|
|
customerName: `${customerInfo.firstName} ${customerInfo.lastName}`,
|
|
customerEmail: customerInfo.email,
|
|
},
|
|
});
|
|
|
|
// En un entorno real, aquí se confirmaría el pago con Stripe Elements
|
|
// Por ahora, simulamos que el pago fue exitoso
|
|
console.log('Payment Intent created:', paymentIntent);
|
|
}
|
|
|
|
// Create reservations for each cart item
|
|
const reservationPromises = items.map(async (item) => {
|
|
const checkInDate = item.selectedDate
|
|
? new Date(item.selectedDate)
|
|
: new Date();
|
|
|
|
// Add time slot if available
|
|
if (item.timeSlot) {
|
|
const [hours, minutes] = item.timeSlot.split(':');
|
|
checkInDate.setHours(parseInt(hours), parseInt(minutes), 0, 0);
|
|
}
|
|
|
|
const bookingData = {
|
|
listingId: item.id,
|
|
guestName: `${customerInfo.firstName} ${customerInfo.lastName}`,
|
|
guestEmail: customerInfo.email,
|
|
guestPhone: customerInfo.phone,
|
|
checkIn: checkInDate.toISOString(),
|
|
guests: item.guests || 1,
|
|
totalAmount: item.price * (item.guests || 1),
|
|
specialRequests: specialRequests,
|
|
};
|
|
|
|
return await createBooking(bookingData);
|
|
});
|
|
|
|
const results = await Promise.all(reservationPromises);
|
|
|
|
// Check if all reservations were created successfully
|
|
const allSuccess = results.every(result => result.success);
|
|
|
|
if (!allSuccess) {
|
|
throw new Error('Some reservations failed to create');
|
|
}
|
|
|
|
// Simulate payment processing
|
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
|
|
const orderData = {
|
|
id: Date.now().toString(),
|
|
date: new Date().toISOString(),
|
|
items: items,
|
|
customerInfo,
|
|
paymentInfo: {
|
|
method: paymentInfo.method,
|
|
cardLast4: paymentInfo.method === 'credit_card' ? paymentInfo.cardNumber.slice(-4) : undefined
|
|
},
|
|
specialRequests,
|
|
pricing: {
|
|
subtotal: totalPrice,
|
|
taxes,
|
|
serviceFee,
|
|
total: finalTotal
|
|
},
|
|
status: 'confirmed',
|
|
reservations: results.map(r => r.data),
|
|
stripeEnabled: !!credentials?.enabled,
|
|
};
|
|
|
|
saveOrderToJSON(orderData);
|
|
clearCart();
|
|
|
|
toast({
|
|
title: "¡Pago procesado exitosamente!",
|
|
description: "Tus reservas han sido confirmadas. Recibirás un email de confirmación.",
|
|
});
|
|
|
|
navigate('/order-confirmation', { state: { orderData } });
|
|
} catch (error) {
|
|
console.error('Payment error:', error);
|
|
toast({
|
|
title: "Error al procesar el pago",
|
|
description: "Hubo un problema al procesar tu reserva. Por favor intenta nuevamente.",
|
|
variant: "destructive",
|
|
});
|
|
} finally {
|
|
setIsProcessing(false);
|
|
}
|
|
};
|
|
|
|
const validateStep1 = () => {
|
|
return customerInfo.firstName && customerInfo.lastName && customerInfo.email && customerInfo.phone;
|
|
};
|
|
|
|
const validateStep2 = () => {
|
|
if (paymentInfo.method === 'credit_card') {
|
|
return paymentInfo.cardNumber && paymentInfo.expiryDate && paymentInfo.cvv && paymentInfo.cardName;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-50">
|
|
<Header />
|
|
|
|
<div className="container mx-auto px-4 pt-24 pb-12">
|
|
<div className="mb-6">
|
|
<Button variant="ghost" onClick={() => navigate(-1)} className="mb-4">
|
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
|
Volver
|
|
</Button>
|
|
<h1 className="text-3xl font-bold">Checkout</h1>
|
|
|
|
{/* Progress Steps */}
|
|
<div className="flex items-center mt-6 mb-8">
|
|
<div className={`flex items-center ${step >= 1 ? 'text-primary' : 'text-gray-400'}`}>
|
|
<div className={`w-8 h-8 rounded-full flex items-center justify-center border-2 ${
|
|
step >= 1 ? 'border-primary bg-primary text-white' : 'border-gray-300'
|
|
}`}>
|
|
{step > 1 ? <Check className="w-4 h-4" /> : '1'}
|
|
</div>
|
|
<span className="ml-2 font-medium">Información Personal</span>
|
|
</div>
|
|
<div className={`flex-1 h-px mx-4 ${step > 1 ? 'bg-primary' : 'bg-gray-300'}`} />
|
|
<div className={`flex items-center ${step >= 2 ? 'text-primary' : 'text-gray-400'}`}>
|
|
<div className={`w-8 h-8 rounded-full flex items-center justify-center border-2 ${
|
|
step >= 2 ? 'border-primary bg-primary text-white' : 'border-gray-300'
|
|
}`}>
|
|
{step > 2 ? <Check className="w-4 h-4" /> : '2'}
|
|
</div>
|
|
<span className="ml-2 font-medium">Pago</span>
|
|
</div>
|
|
<div className={`flex-1 h-px mx-4 ${step > 2 ? 'bg-primary' : 'bg-gray-300'}`} />
|
|
<div className={`flex items-center ${step >= 3 ? 'text-primary' : 'text-gray-400'}`}>
|
|
<div className={`w-8 h-8 rounded-full flex items-center justify-center border-2 ${
|
|
step >= 3 ? 'border-primary bg-primary text-white' : 'border-gray-300'
|
|
}`}>
|
|
3
|
|
</div>
|
|
<span className="ml-2 font-medium">Confirmación</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
{/* Main Content */}
|
|
<div className="lg:col-span-2">
|
|
{step === 1 && (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Información Personal</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<Label htmlFor="firstName">Nombre *</Label>
|
|
<Input
|
|
id="firstName"
|
|
value={customerInfo.firstName}
|
|
onChange={(e) => handleCustomerInfoChange('firstName', e.target.value)}
|
|
placeholder="Tu nombre"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="lastName">Apellido *</Label>
|
|
<Input
|
|
id="lastName"
|
|
value={customerInfo.lastName}
|
|
onChange={(e) => handleCustomerInfoChange('lastName', e.target.value)}
|
|
placeholder="Tu apellido"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<Label htmlFor="email">Email *</Label>
|
|
<Input
|
|
id="email"
|
|
type="email"
|
|
value={customerInfo.email}
|
|
onChange={(e) => handleCustomerInfoChange('email', e.target.value)}
|
|
placeholder="tu@email.com"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Label htmlFor="phone">Teléfono *</Label>
|
|
<Input
|
|
id="phone"
|
|
value={customerInfo.phone}
|
|
onChange={(e) => handleCustomerInfoChange('phone', e.target.value)}
|
|
placeholder="+1 (809) 123-4567"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Label htmlFor="address">Dirección</Label>
|
|
<Input
|
|
id="address"
|
|
value={customerInfo.address}
|
|
onChange={(e) => handleCustomerInfoChange('address', e.target.value)}
|
|
placeholder="Calle, número, sector"
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div>
|
|
<Label htmlFor="city">Ciudad</Label>
|
|
<Input
|
|
id="city"
|
|
value={customerInfo.city}
|
|
onChange={(e) => handleCustomerInfoChange('city', e.target.value)}
|
|
placeholder="Santo Domingo"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="country">País</Label>
|
|
<Select
|
|
value={customerInfo.country}
|
|
onValueChange={(value) => handleCustomerInfoChange('country', value)}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="Dominican Republic">República Dominicana</SelectItem>
|
|
<SelectItem value="United States">Estados Unidos</SelectItem>
|
|
<SelectItem value="Canada">Canadá</SelectItem>
|
|
<SelectItem value="Other">Otro</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="zipCode">Código Postal</Label>
|
|
<Input
|
|
id="zipCode"
|
|
value={customerInfo.zipCode}
|
|
onChange={(e) => handleCustomerInfoChange('zipCode', e.target.value)}
|
|
placeholder="10001"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<Label htmlFor="requests">Solicitudes Especiales</Label>
|
|
<Textarea
|
|
id="requests"
|
|
value={specialRequests}
|
|
onChange={(e) => setSpecialRequests(e.target.value)}
|
|
placeholder="Algún requerimiento especial para tu reserva..."
|
|
rows={3}
|
|
/>
|
|
</div>
|
|
|
|
<Button
|
|
className="w-full"
|
|
onClick={() => setStep(2)}
|
|
disabled={!validateStep1()}
|
|
>
|
|
Continuar al Pago
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{step === 2 && (
|
|
<Card>
|
|
<CardHeader>
|
|
<div className="flex items-center justify-between">
|
|
<CardTitle>Método de Pago</CardTitle>
|
|
{credentials?.enabled && (
|
|
<Badge variant="default" className="flex items-center gap-1">
|
|
<Shield className="w-3 h-3" />
|
|
Pagos Seguros con Stripe
|
|
</Badge>
|
|
)}
|
|
{stripeLoading && (
|
|
<Badge variant="outline" className="flex items-center gap-1">
|
|
<Loader2 className="w-3 h-3 animate-spin" />
|
|
Cargando...
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
{/* Payment Method Selection */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
{paymentMethods.map((method) => {
|
|
const IconComponent = method.icon;
|
|
return (
|
|
<button
|
|
key={method.id}
|
|
onClick={() => handlePaymentInfoChange('method', method.id as any)}
|
|
className={`p-4 border-2 rounded-lg flex items-center space-x-3 transition-colors ${
|
|
paymentInfo.method === method.id
|
|
? 'border-primary bg-primary/5'
|
|
: 'border-gray-200 hover:border-gray-300'
|
|
}`}
|
|
>
|
|
<IconComponent className="w-5 h-5" />
|
|
<span className="font-medium">{method.name}</span>
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{/* Credit Card Form */}
|
|
{paymentInfo.method === 'credit_card' && (
|
|
<div className="space-y-4 border-t pt-6">
|
|
{credentials?.enabled && (
|
|
<div className="bg-green-50 border border-green-200 rounded-lg p-4 mb-4">
|
|
<div className="flex items-start gap-3">
|
|
<Shield className="w-5 h-5 text-green-600 mt-0.5" />
|
|
<div>
|
|
<h4 className="font-medium text-green-900">Pago Seguro</h4>
|
|
<p className="text-sm text-green-700">
|
|
Tu pago está protegido por Stripe. Tus datos son encriptados y seguros.
|
|
{credentials.testMode && ' (Modo de prueba activo)'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div>
|
|
<Label htmlFor="cardName">Nombre en la Tarjeta *</Label>
|
|
<Input
|
|
id="cardName"
|
|
value={paymentInfo.cardName}
|
|
onChange={(e) => handlePaymentInfoChange('cardName', e.target.value)}
|
|
placeholder="Como aparece en la tarjeta"
|
|
/>
|
|
</div>
|
|
|
|
<div>
|
|
<Label htmlFor="cardNumber">Número de Tarjeta *</Label>
|
|
<Input
|
|
id="cardNumber"
|
|
value={paymentInfo.cardNumber}
|
|
onChange={(e) => handlePaymentInfoChange('cardNumber', e.target.value)}
|
|
placeholder="1234 5678 9012 3456"
|
|
maxLength={19}
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<Label htmlFor="expiryDate">Fecha de Vencimiento *</Label>
|
|
<Input
|
|
id="expiryDate"
|
|
value={paymentInfo.expiryDate}
|
|
onChange={(e) => handlePaymentInfoChange('expiryDate', e.target.value)}
|
|
placeholder="MM/YY"
|
|
maxLength={5}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Label htmlFor="cvv">CVV *</Label>
|
|
<Input
|
|
id="cvv"
|
|
value={paymentInfo.cvv}
|
|
onChange={(e) => handlePaymentInfoChange('cvv', e.target.value)}
|
|
placeholder="123"
|
|
maxLength={4}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Other Payment Methods Info */}
|
|
{paymentInfo.method === 'paypal' && (
|
|
<div className="bg-blue-50 p-4 rounded-lg border-t">
|
|
<p className="text-sm text-blue-700">
|
|
Serás redirigido a PayPal para completar tu pago de forma segura.
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{paymentInfo.method === 'bank_transfer' && (
|
|
<div className="bg-green-50 p-4 rounded-lg border-t">
|
|
<p className="text-sm text-green-700 mb-2">
|
|
Te enviaremos los detalles de la cuenta bancaria para realizar la transferencia.
|
|
</p>
|
|
<p className="text-xs text-green-600">
|
|
Tu reserva se confirmará una vez recibamos el comprobante de pago.
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{paymentInfo.method === 'cash' && (
|
|
<div className="bg-orange-50 p-4 rounded-lg border-t">
|
|
<p className="text-sm text-orange-700 mb-2">
|
|
Podrás pagar en efectivo al momento del check-in o al llegar al lugar.
|
|
</p>
|
|
<p className="text-xs text-orange-600">
|
|
Tu reserva quedará pendiente hasta el pago.
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex gap-3">
|
|
<Button variant="outline" onClick={() => setStep(1)}>
|
|
Volver
|
|
</Button>
|
|
<Button
|
|
className="flex-1"
|
|
onClick={() => setStep(3)}
|
|
disabled={!validateStep2()}
|
|
>
|
|
Revisar Orden
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{step === 3 && (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Confirmar Reserva</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
<div className="bg-gray-50 p-4 rounded-lg">
|
|
<h3 className="font-semibold mb-2">Información de Contacto</h3>
|
|
<p className="text-sm">{customerInfo.firstName} {customerInfo.lastName}</p>
|
|
<p className="text-sm">{customerInfo.email}</p>
|
|
<p className="text-sm">{customerInfo.phone}</p>
|
|
</div>
|
|
|
|
<div className="bg-gray-50 p-4 rounded-lg">
|
|
<h3 className="font-semibold mb-2">Método de Pago</h3>
|
|
<p className="text-sm">
|
|
{paymentMethods.find(m => m.id === paymentInfo.method)?.name}
|
|
{paymentInfo.method === 'credit_card' && paymentInfo.cardNumber &&
|
|
` terminada en ${paymentInfo.cardNumber.slice(-4)}`
|
|
}
|
|
</p>
|
|
</div>
|
|
|
|
{specialRequests && (
|
|
<div className="bg-gray-50 p-4 rounded-lg">
|
|
<h3 className="font-semibold mb-2">Solicitudes Especiales</h3>
|
|
<p className="text-sm">{specialRequests}</p>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex items-center p-4 bg-blue-50 rounded-lg">
|
|
<Shield className="w-5 h-5 text-blue-600 mr-3" />
|
|
<div>
|
|
<p className="text-sm font-medium text-blue-900">Pago Seguro</p>
|
|
<p className="text-xs text-blue-700">
|
|
Tu información está protegida con encriptación SSL
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex gap-3">
|
|
<Button variant="outline" onClick={() => setStep(2)}>
|
|
Volver
|
|
</Button>
|
|
<Button
|
|
className="flex-1"
|
|
onClick={processPayment}
|
|
disabled={isProcessing}
|
|
>
|
|
{isProcessing ? 'Procesando...' : `Confirmar y Pagar $${finalTotal.toFixed(2)}`}
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
|
|
{/* Order Summary Sidebar */}
|
|
<div className="lg:col-span-1">
|
|
<Card className="sticky top-24">
|
|
<CardHeader>
|
|
<CardTitle>Resumen de Orden</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
{/* Cart Items */}
|
|
<div className="space-y-3">
|
|
{items.map((item) => (
|
|
<div key={item.id} className="flex space-x-3">
|
|
<img
|
|
src={item.image}
|
|
alt={item.title}
|
|
className="w-16 h-16 object-cover rounded"
|
|
/>
|
|
<div className="flex-1 min-w-0">
|
|
<h4 className="font-medium text-sm truncate">{item.title}</h4>
|
|
<p className="text-xs text-gray-500">{item.location}</p>
|
|
{item.selectedDate && (
|
|
<p className="text-xs text-gray-600">Fecha: {item.selectedDate}</p>
|
|
)}
|
|
{item.guests && (
|
|
<p className="text-xs text-gray-600">Personas: {item.guests}</p>
|
|
)}
|
|
<div className="flex justify-between items-center mt-1">
|
|
<span className="text-xs text-gray-500">Qty: {item.quantity}</span>
|
|
<span className="text-sm font-semibold">${(item.price * item.quantity).toFixed(2)}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
{/* Pricing Breakdown */}
|
|
<div className="space-y-2">
|
|
<div className="flex justify-between text-sm">
|
|
<span>Subtotal</span>
|
|
<span>${totalPrice.toFixed(2)}</span>
|
|
</div>
|
|
<div className="flex justify-between text-sm">
|
|
<span>ITBIS (18%)</span>
|
|
<span>${taxes.toFixed(2)}</span>
|
|
</div>
|
|
<div className="flex justify-between text-sm">
|
|
<span>Fee de servicio (5%)</span>
|
|
<span>${serviceFee.toFixed(2)}</span>
|
|
</div>
|
|
<Separator />
|
|
<div className="flex justify-between font-semibold">
|
|
<span>Total</span>
|
|
<span>${finalTotal.toFixed(2)}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="text-xs text-gray-500 mt-4">
|
|
<p>• Cancelación gratuita hasta 24 horas antes</p>
|
|
<p>• Recibirás confirmación por email</p>
|
|
<p>• Soporte al cliente 24/7</p>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<Footer />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Checkout;
|