Implement Stripe Phase 2
This commit is contained in:
@@ -16,6 +16,7 @@ import Explore from "./pages/Explore";
|
||||
import { ListingDetails } from "./pages/ListingDetails";
|
||||
import OfferDetails from "./pages/OfferDetails";
|
||||
import Checkout from "./pages/Checkout";
|
||||
import PaymentError from "./pages/PaymentError";
|
||||
import OrderConfirmation from "./pages/OrderConfirmation";
|
||||
import DashboardLayout from "./components/DashboardLayout";
|
||||
import Dashboard from "./pages/dashboard/Dashboard";
|
||||
@@ -124,6 +125,11 @@ const AppRouter = () => (
|
||||
<Checkout />
|
||||
</FrontendLayout>
|
||||
} />
|
||||
<Route path="/payment-error" element={
|
||||
<FrontendLayout>
|
||||
<PaymentError />
|
||||
</FrontendLayout>
|
||||
} />
|
||||
<Route path="/order-confirmation" element={
|
||||
<FrontendLayout>
|
||||
<OrderConfirmation />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Elements, CardElement, useElements, useStripe as useStripeHook } from '@stripe/react-stripe-js';
|
||||
import { useCart } from '@/contexts/CartContext';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
@@ -9,13 +10,14 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@
|
||||
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 { ArrowLeft, CreditCard, Smartphone, Building, Truck, Shield, Check, X, Loader2, AlertCircle } 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';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
|
||||
interface CustomerInfo {
|
||||
firstName: string;
|
||||
@@ -30,12 +32,175 @@ interface CustomerInfo {
|
||||
|
||||
interface PaymentInfo {
|
||||
method: 'credit_card' | 'paypal' | 'bank_transfer' | 'cash';
|
||||
cardNumber: string;
|
||||
expiryDate: string;
|
||||
cvv: string;
|
||||
cardName: string;
|
||||
}
|
||||
|
||||
// Card Element Styles
|
||||
const CARD_ELEMENT_OPTIONS = {
|
||||
style: {
|
||||
base: {
|
||||
color: '#32325d',
|
||||
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
|
||||
fontSmoothing: 'antialiased',
|
||||
fontSize: '16px',
|
||||
'::placeholder': {
|
||||
color: '#aab7c4',
|
||||
},
|
||||
},
|
||||
invalid: {
|
||||
color: '#fa755a',
|
||||
iconColor: '#fa755a',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// CheckoutForm Component
|
||||
const CheckoutForm = ({
|
||||
customerInfo,
|
||||
paymentInfo,
|
||||
specialRequests,
|
||||
items,
|
||||
finalTotal,
|
||||
onSuccess,
|
||||
onError
|
||||
}: any) => {
|
||||
const stripeHook = useStripeHook();
|
||||
const elements = useElements();
|
||||
const { createBooking } = useBooking();
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [paymentError, setPaymentError] = useState<string | null>(null);
|
||||
|
||||
const processStripePayment = async () => {
|
||||
if (!stripeHook || !elements) {
|
||||
setPaymentError('Stripe no está disponible. Por favor recarga la página.');
|
||||
return;
|
||||
}
|
||||
|
||||
setIsProcessing(true);
|
||||
setPaymentError(null);
|
||||
|
||||
try {
|
||||
const cardElement = elements.getElement(CardElement);
|
||||
if (!cardElement) {
|
||||
throw new Error('Elemento de tarjeta no encontrado');
|
||||
}
|
||||
|
||||
// Crear PaymentIntent desde el backend
|
||||
const paymentIntent = await paymentService.createPaymentIntent({
|
||||
amount: Math.round(finalTotal * 100),
|
||||
currency: 'usd',
|
||||
description: `Reserva de ${items.length} item(s)`,
|
||||
metadata: {
|
||||
customerName: `${customerInfo.firstName} ${customerInfo.lastName}`,
|
||||
customerEmail: customerInfo.email,
|
||||
},
|
||||
});
|
||||
|
||||
// Confirmar el pago con Stripe Elements
|
||||
const { error, paymentIntent: confirmedPaymentIntent } = await stripeHook.confirmCardPayment(
|
||||
paymentIntent.clientSecret,
|
||||
{
|
||||
payment_method: {
|
||||
card: cardElement,
|
||||
billing_details: {
|
||||
name: `${customerInfo.firstName} ${customerInfo.lastName}`,
|
||||
email: customerInfo.email,
|
||||
phone: customerInfo.phone,
|
||||
address: {
|
||||
line1: customerInfo.address,
|
||||
city: customerInfo.city,
|
||||
country: customerInfo.country === 'Dominican Republic' ? 'DO' : 'US',
|
||||
postal_code: customerInfo.zipCode,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
|
||||
if (confirmedPaymentIntent?.status === 'succeeded') {
|
||||
// Crear reservas después de confirmar el pago
|
||||
const reservationPromises = items.map(async (item: any) => {
|
||||
const checkInDate = item.selectedDate
|
||||
? new Date(item.selectedDate)
|
||||
: new Date();
|
||||
|
||||
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);
|
||||
const allSuccess = results.every(result => result.success);
|
||||
|
||||
if (!allSuccess) {
|
||||
throw new Error('Algunas reservas fallaron al crearse');
|
||||
}
|
||||
|
||||
onSuccess({
|
||||
paymentIntentId: confirmedPaymentIntent.id,
|
||||
reservations: results.map(r => r.data),
|
||||
});
|
||||
} else {
|
||||
throw new Error('El pago no se pudo completar');
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Payment error:', error);
|
||||
setPaymentError(error.message || 'Error al procesar el pago');
|
||||
onError(error.message);
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{paymentError && (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{paymentError}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<div className="p-4 border rounded-lg">
|
||||
<Label className="mb-2 block">Información de Tarjeta</Label>
|
||||
<CardElement options={CARD_ELEMENT_OPTIONS} />
|
||||
</div>
|
||||
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={processStripePayment}
|
||||
disabled={isProcessing || !stripeHook}
|
||||
>
|
||||
{isProcessing ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
Procesando pago...
|
||||
</>
|
||||
) : (
|
||||
`Pagar $${finalTotal.toFixed(2)}`
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Checkout = () => {
|
||||
const navigate = useNavigate();
|
||||
const { items, getTotalPrice, clearCart } = useCart();
|
||||
@@ -58,10 +223,6 @@ const Checkout = () => {
|
||||
|
||||
const [paymentInfo, setPaymentInfo] = useState<PaymentInfo>({
|
||||
method: 'credit_card',
|
||||
cardNumber: '',
|
||||
expiryDate: '',
|
||||
cvv: '',
|
||||
cardName: ''
|
||||
});
|
||||
|
||||
const [specialRequests, setSpecialRequests] = useState('');
|
||||
@@ -101,7 +262,7 @@ const Checkout = () => {
|
||||
};
|
||||
|
||||
const handlePaymentInfoChange = (field: keyof PaymentInfo, value: string) => {
|
||||
setPaymentInfo(prev => ({ ...prev, [field]: value }));
|
||||
setPaymentInfo(prev => ({ ...prev, [field]: value as any }));
|
||||
};
|
||||
|
||||
const saveOrderToJSON = (orderData: any) => {
|
||||
@@ -110,35 +271,57 @@ const Checkout = () => {
|
||||
localStorage.setItem('karibeo_orders', JSON.stringify(orders));
|
||||
};
|
||||
|
||||
const processPayment = async () => {
|
||||
const handlePaymentSuccess = (paymentData: any) => {
|
||||
const orderData = {
|
||||
id: Date.now().toString(),
|
||||
date: new Date().toISOString(),
|
||||
items: items,
|
||||
customerInfo,
|
||||
paymentInfo: {
|
||||
method: paymentInfo.method,
|
||||
paymentIntentId: paymentData.paymentIntentId,
|
||||
},
|
||||
specialRequests,
|
||||
pricing: {
|
||||
subtotal: totalPrice,
|
||||
taxes,
|
||||
serviceFee,
|
||||
total: finalTotal
|
||||
},
|
||||
status: 'confirmed',
|
||||
reservations: paymentData.reservations,
|
||||
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 } });
|
||||
};
|
||||
|
||||
const handlePaymentError = (errorMessage: string) => {
|
||||
toast({
|
||||
title: "Error al procesar el pago",
|
||||
description: errorMessage || "Hubo un problema al procesar tu reserva. Por favor intenta nuevamente.",
|
||||
variant: "destructive",
|
||||
});
|
||||
};
|
||||
|
||||
const processOtherPayment = 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
|
||||
// Create reservations for non-card payments
|
||||
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);
|
||||
@@ -159,54 +342,22 @@ const Checkout = () => {
|
||||
});
|
||||
|
||||
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');
|
||||
throw new Error('Algunas reservas fallaron al crearse');
|
||||
}
|
||||
|
||||
// Simulate payment processing
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
// Simulate processing
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
|
||||
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',
|
||||
handlePaymentSuccess({
|
||||
paymentIntentId: null,
|
||||
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) {
|
||||
} catch (error: any) {
|
||||
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",
|
||||
});
|
||||
handlePaymentError(error.message);
|
||||
} finally {
|
||||
setIsProcessing(false);
|
||||
}
|
||||
@@ -217,10 +368,7 @@ const Checkout = () => {
|
||||
};
|
||||
|
||||
const validateStep2 = () => {
|
||||
if (paymentInfo.method === 'credit_card') {
|
||||
return paymentInfo.cardNumber && paymentInfo.expiryDate && paymentInfo.cvv && paymentInfo.cardName;
|
||||
}
|
||||
return true;
|
||||
return true; // Stripe Elements handles validation
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -428,67 +576,44 @@ const Checkout = () => {
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Credit Card Form */}
|
||||
{paymentInfo.method === 'credit_card' && (
|
||||
{/* Credit Card Form with Stripe Elements */}
|
||||
{paymentInfo.method === 'credit_card' && stripe && credentials?.enabled && (
|
||||
<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 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 nunca se almacenan en nuestros servidores.
|
||||
{credentials.testMode && ' (Modo de prueba activo)'}
|
||||
</p>
|
||||
</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>
|
||||
|
||||
<Elements stripe={stripe}>
|
||||
<CheckoutForm
|
||||
customerInfo={customerInfo}
|
||||
paymentInfo={paymentInfo}
|
||||
specialRequests={specialRequests}
|
||||
items={items}
|
||||
finalTotal={finalTotal}
|
||||
onSuccess={handlePaymentSuccess}
|
||||
onError={handlePaymentError}
|
||||
/>
|
||||
</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>
|
||||
</Elements>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{paymentInfo.method === 'credit_card' && (!stripe || !credentials?.enabled) && (
|
||||
<div className="space-y-4 border-t pt-6">
|
||||
<Alert>
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
Los pagos con tarjeta no están disponibles en este momento. Por favor selecciona otro método de pago o contacta al administrador.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -523,18 +648,26 @@ const Checkout = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Button variant="outline" onClick={() => setStep(1)}>
|
||||
{paymentInfo.method !== 'credit_card' && (
|
||||
<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>
|
||||
)}
|
||||
|
||||
{paymentInfo.method === 'credit_card' && (
|
||||
<Button variant="outline" onClick={() => setStep(1)} className="w-full">
|
||||
Volver
|
||||
</Button>
|
||||
<Button
|
||||
className="flex-1"
|
||||
onClick={() => setStep(3)}
|
||||
disabled={!validateStep2()}
|
||||
>
|
||||
Revisar Orden
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
@@ -556,9 +689,6 @@ const Checkout = () => {
|
||||
<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>
|
||||
|
||||
@@ -585,10 +715,17 @@ const Checkout = () => {
|
||||
</Button>
|
||||
<Button
|
||||
className="flex-1"
|
||||
onClick={processPayment}
|
||||
onClick={processOtherPayment}
|
||||
disabled={isProcessing}
|
||||
>
|
||||
{isProcessing ? 'Procesando...' : `Confirmar y Pagar $${finalTotal.toFixed(2)}`}
|
||||
{isProcessing ? (
|
||||
<>
|
||||
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
||||
Procesando...
|
||||
</>
|
||||
) : (
|
||||
`Confirmar y Pagar $${finalTotal.toFixed(2)}`
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
@@ -201,12 +201,17 @@ const OrderConfirmation = () => {
|
||||
<div className="flex items-center text-blue-800">
|
||||
<CheckCircle className="w-5 h-5 mr-2" />
|
||||
<span className="font-medium">
|
||||
Pago procesado via {orderData.paymentInfo.method === 'credit_card' ? 'Tarjeta de Crédito' : orderData.paymentInfo.method}
|
||||
Pago procesado via {
|
||||
orderData.paymentInfo.method === 'credit_card' ? 'Tarjeta de Crédito' :
|
||||
orderData.paymentInfo.method === 'paypal' ? 'PayPal' :
|
||||
orderData.paymentInfo.method === 'bank_transfer' ? 'Transferencia Bancaria' :
|
||||
'Efectivo'
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
{orderData.paymentInfo.cardLast4 && (
|
||||
<p className="text-sm text-blue-700 mt-1">
|
||||
Tarjeta terminada en {orderData.paymentInfo.cardLast4}
|
||||
{orderData.paymentInfo.paymentIntentId && (
|
||||
<p className="text-xs text-blue-700 mt-2 font-mono">
|
||||
ID de Transacción: {orderData.paymentInfo.paymentIntentId}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
101
src/pages/PaymentError.tsx
Normal file
101
src/pages/PaymentError.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import React from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { XCircle, ArrowLeft, RefreshCw } from 'lucide-react';
|
||||
import Header from '@/components/Header';
|
||||
import Footer from '@/components/Footer';
|
||||
|
||||
const PaymentError = () => {
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
const errorMessage = searchParams.get('message') || 'Hubo un problema al procesar tu pago';
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<Header />
|
||||
|
||||
<div className="container mx-auto px-4 pt-24 pb-12">
|
||||
<div className="max-w-2xl mx-auto">
|
||||
{/* Error Header */}
|
||||
<div className="text-center mb-8">
|
||||
<div className="w-20 h-20 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
||||
<XCircle className="w-12 h-12 text-red-600" />
|
||||
</div>
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-2">
|
||||
Error en el Pago
|
||||
</h1>
|
||||
<p className="text-gray-600">
|
||||
No pudimos procesar tu pago. No te preocupes, no se ha realizado ningún cargo.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Error Details */}
|
||||
<Card className="mb-6">
|
||||
<CardHeader>
|
||||
<CardTitle>Detalles del Error</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
|
||||
<p className="text-red-800">{errorMessage}</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 space-y-3">
|
||||
<h3 className="font-semibold text-gray-900">Razones comunes:</h3>
|
||||
<ul className="text-sm text-gray-700 space-y-2">
|
||||
<li>• Fondos insuficientes en tu tarjeta</li>
|
||||
<li>• Información de la tarjeta incorrecta</li>
|
||||
<li>• Tu banco bloqueó la transacción por seguridad</li>
|
||||
<li>• La tarjeta ha expirado o fue rechazada</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 space-y-3">
|
||||
<h3 className="font-semibold text-gray-900">¿Qué puedes hacer?</h3>
|
||||
<ul className="text-sm text-gray-700 space-y-2">
|
||||
<li>• Verifica los datos de tu tarjeta e intenta nuevamente</li>
|
||||
<li>• Intenta con otra tarjeta o método de pago</li>
|
||||
<li>• Contacta a tu banco para autorizar la transacción</li>
|
||||
<li>• Contacta nuestro soporte si el problema persiste</li>
|
||||
</ul>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<Button onClick={() => navigate('/checkout')} variant="default">
|
||||
<RefreshCw className="w-4 h-4 mr-2" />
|
||||
Intentar Nuevamente
|
||||
</Button>
|
||||
<Button onClick={() => navigate('/explore')} variant="outline">
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
Volver a Explorar
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Support Info */}
|
||||
<div className="mt-8 p-4 bg-blue-50 border border-blue-200 rounded-lg text-center">
|
||||
<h3 className="font-semibold text-blue-900 mb-2">¿Necesitas Ayuda?</h3>
|
||||
<p className="text-sm text-blue-700 mb-3">
|
||||
Nuestro equipo de soporte está disponible 24/7 para ayudarte
|
||||
</p>
|
||||
<div className="flex flex-col sm:flex-row gap-2 justify-center text-sm">
|
||||
<a href="tel:+18091234567" className="text-blue-600 hover:text-blue-800 font-medium">
|
||||
📞 +1 (809) 123-4567
|
||||
</a>
|
||||
<span className="hidden sm:inline text-blue-400">|</span>
|
||||
<a href="mailto:soporte@karibeo.com" className="text-blue-600 hover:text-blue-800 font-medium">
|
||||
✉️ soporte@karibeo.com
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PaymentError;
|
||||
Reference in New Issue
Block a user