Files
Hotel-Booking/Frontend/src/features/payments/components/PayPalPaymentWrapper.tsx
Iliyan Angelov 7acf05e186 big update
2025-12-12 01:48:04 +02:00

197 lines
8.3 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { createPayPalOrder } from '../services/paymentService';
import { Loader2, AlertCircle } from 'lucide-react';
import { useFormatCurrency } from '../hooks/useFormatCurrency';
interface PayPalPaymentWrapperProps {
bookingId: number;
amount: number;
currency?: string;
onError?: (error: string) => void;
}
const PayPalPaymentWrapper: React.FC<PayPalPaymentWrapperProps> = ({
bookingId,
amount,
currency: propCurrency,
onError,
}) => {
const { currency: contextCurrency } = useFormatCurrency();
const currency = propCurrency || contextCurrency || 'USD';
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [approvalUrl, setApprovalUrl] = useState<string | null>(null);
useEffect(() => {
const initializePayPal = async () => {
try {
setLoading(true);
setError(null);
const currentUrl = window.location.origin;
const returnUrl = `${currentUrl}/payment/paypal/return?bookingId=${bookingId}`;
const cancelUrl = `${currentUrl}/payment/paypal/cancel?bookingId=${bookingId}`;
const response = await createPayPalOrder(
bookingId,
amount,
currency,
returnUrl,
cancelUrl
);
if (response.success && response.data) {
const { approval_url } = response.data;
if (!approval_url) {
throw new Error('Approval URL not received from server');
}
setApprovalUrl(approval_url);
} else {
throw new Error(response.message || 'Failed to initialize PayPal payment');
}
} catch (err: unknown) {
// SECURITY: Don't log payment errors with sensitive data in production
if (import.meta.env.DEV) {
console.error('Error initializing PayPal:', err);
}
// SECURITY: Sanitize error message to prevent information disclosure
const errorMessage = 'Failed to initialize PayPal payment. Please try again.';
setError(errorMessage);
if (onError) {
onError(errorMessage);
}
} finally {
setLoading(false);
}
};
initializePayPal();
}, [bookingId, amount, currency, onError]);
const handlePayPalClick = () => {
if (approvalUrl) {
window.location.href = approvalUrl;
}
};
if (loading) {
return (
<div className="flex items-center justify-center p-8">
<div className="w-16 h-16 bg-gradient-to-br from-[var(--luxury-gold)]/20 to-[var(--luxury-gold-light)]/20
rounded-full flex items-center justify-center
border border-[var(--luxury-gold)]/30 shadow-lg shadow-[var(--luxury-gold)]/20">
<Loader2 className="w-8 h-8 animate-spin text-[var(--luxury-gold)]" />
</div>
<span className="ml-4 text-gray-300 font-light tracking-wide">
Initializing PayPal payment...
</span>
</div>
);
}
if (error) {
return (
<div className="bg-gradient-to-br from-red-900/20 to-red-800/10
border border-red-500/30 rounded-xl p-6 backdrop-blur-sm">
<div className="flex items-start gap-4">
<AlertCircle className="w-6 h-6 text-red-400 mt-0.5 flex-shrink-0" />
<div>
<h3 className="text-lg font-serif font-semibold text-red-300 mb-2 tracking-wide">
Payment Initialization Failed
</h3>
<p className="text-sm text-red-200/80 font-light tracking-wide">
{error || 'Unable to initialize PayPal payment. Please try again.'}
</p>
</div>
</div>
</div>
);
}
if (!approvalUrl) {
return (
<div className="flex items-center justify-center p-8">
<div className="w-16 h-16 bg-gradient-to-br from-[var(--luxury-gold)]/20 to-[var(--luxury-gold-light)]/20
rounded-full flex items-center justify-center
border border-[var(--luxury-gold)]/30 shadow-lg shadow-[var(--luxury-gold)]/20">
<Loader2 className="w-8 h-8 animate-spin text-[var(--luxury-gold)]" />
</div>
<span className="ml-4 text-gray-300 font-light tracking-wide">
Loading PayPal...
</span>
</div>
);
}
return (
<div className="text-center">
<div className="mb-6">
<svg
className="mx-auto h-14 w-auto"
viewBox="0 0 283 64"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.7-9.2 12.2-9.2z"
fill="#003087"
/>
</svg>
</div>
<h3 className="text-xl font-serif font-semibold text-[var(--luxury-gold)] mb-3 tracking-wide">
Complete Payment with PayPal
</h3>
<p className="text-gray-300/80 font-light text-lg mb-8 tracking-wide">
You will be redirected to PayPal to securely complete your payment of{' '}
<span className="font-semibold text-[var(--luxury-gold)]">
{new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency,
}).format(amount)}
</span>
</p>
<button
onClick={handlePayPalClick}
className="w-full bg-gradient-to-r from-[#0070ba] to-[#005ea6]
hover:from-[#0080cc] hover:to-[#0070ba] text-white
font-semibold py-4 px-8 rounded-sm transition-all duration-300
flex items-center justify-center gap-3 shadow-lg shadow-blue-500/30
hover:shadow-xl hover:shadow-blue-500/40 tracking-wide"
>
<svg
className="w-6 h-6"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M7.076 21.337H2.47a.641.641 0 0 1-.633-.74L4.944.901C5.026.382 5.474 0 5.998 0h7.46c2.57 0 4.578.543 5.69 1.81 1.01 1.15 1.304 2.42 1.012 4.287-.023.143-.047.288-.077.437-.983 5.05-4.349 6.797-8.647 6.797h-2.19c-.524 0-.968.382-1.05.9l-1.12 7.203zm14.146-14.42a.477.477 0 0 0-.414-.24h-3.84c-.48 0-.856.355-.932.826-.075.47-.232 1.21-.232 1.21s-.156-.74-.232-1.21a.957.957 0 0 0-.932-.826H5.342a.957.957 0 0 0-.932.826c-.076.47-.232 1.21-.232 1.21s-.156-.74-.232-1.21a.957.957 0 0 0-.932-.826H.477a.477.477 0 0 0-.414.24c-.11.19-.14.426-.08.643.06.217.2.4.388.51l.04.02c.19.11.426.14.643.08.217-.06.4-.2.51-.388l.01-.02c.11-.19.14-.426.08-.643a.955.955 0 0 0-.388-.51l-.01-.01a.955.955 0 0 0-.51-.388.955.955 0 0 0-.643.08l-.01.01a.955.955 0 0 0-.388.51c-.06.217-.03.453.08.643l.01.02c.11.188.293.328.51.388.217.06.453.03.643-.08l.01-.02c.188-.11.328-.293.388-.51.06-.217.03-.453-.08-.643l-.01-.01z"/>
</svg>
Pay with PayPal
</button>
<div className="mt-6 space-y-2">
<p className="text-xs text-gray-400/70 font-light tracking-wide text-center">
Secure payment powered by PayPal
</p>
<div className="flex items-center justify-center gap-3 text-xs text-gray-500">
<span className="flex items-center gap-1">
<svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clipRule="evenodd" />
</svg>
PCI DSS Compliant
</span>
<span className="text-gray-600"></span>
<span>SSL Encrypted</span>
</div>
</div>
</div>
);
};
export default PayPalPaymentWrapper;