Files
Hotel-Booking/Frontend/src/components/payments/BoricaPaymentModal.tsx
Iliyan Angelov 627959f52b updates
2025-11-23 18:59:18 +02:00

228 lines
9.0 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { createBoricaPayment } from '../../services/api/paymentService';
import { X, Loader2, AlertCircle, CheckCircle, CreditCard } from 'lucide-react';
import { toast } from 'react-toastify';
import { useFormatCurrency } from '../../hooks/useFormatCurrency';
interface BoricaPaymentModalProps {
isOpen: boolean;
bookingId: number;
amount: number;
currency?: string;
onSuccess: () => void;
onClose: () => void;
}
const BoricaPaymentModal: React.FC<BoricaPaymentModalProps> = ({
isOpen,
bookingId,
amount,
currency: propCurrency,
onSuccess,
onClose,
}) => {
const { currency: contextCurrency } = useFormatCurrency();
const currency = propCurrency || contextCurrency || 'BGN';
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [paymentRequest, setPaymentRequest] = useState<any>(null);
useEffect(() => {
if (!isOpen) return;
const initializeBorica = async () => {
try {
setLoading(true);
setError(null);
const currentUrl = window.location.origin;
const returnUrl = `${currentUrl}/payment/borica/return?bookingId=${bookingId}`;
const response = await createBoricaPayment(
bookingId,
amount,
currency,
returnUrl
);
if (response.success && response.data) {
const { payment_request } = response.data;
if (!payment_request) {
throw new Error('Payment request not received from server');
}
setPaymentRequest(payment_request);
} else {
throw new Error(response.message || 'Failed to initialize Borica payment');
}
} catch (err: any) {
console.error('Error initializing Borica:', err);
const errorMessage = err.response?.data?.message || err.message || 'Failed to initialize Borica payment';
setError(errorMessage);
toast.error(errorMessage);
} finally {
setLoading(false);
}
};
initializeBorica();
}, [isOpen, bookingId, amount, currency]);
const handleSubmitPayment = () => {
if (!paymentRequest) return;
// Create a form and submit it to Borica gateway
const form = document.createElement('form');
form.method = 'POST';
form.action = paymentRequest.gateway_url;
form.style.display = 'none';
// Add all form fields
const fields = {
TERMINAL: paymentRequest.terminal_id,
MERCHANT: paymentRequest.merchant_id,
ORDER: paymentRequest.order_id,
AMOUNT: paymentRequest.amount,
CURRENCY: paymentRequest.currency,
DESC: paymentRequest.description,
TIMESTAMP: paymentRequest.timestamp,
P_SIGN: paymentRequest.signature,
TRTYPE: paymentRequest.trtype,
BACKREF: paymentRequest.return_url,
};
Object.entries(fields).forEach(([key, value]) => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = key;
input.value = value as string;
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-[10000] flex items-center justify-center p-2 sm:p-4">
<div
className="fixed inset-0 bg-black/90 backdrop-blur-sm"
onClick={onClose}
/>
<div className="relative w-full max-w-md max-h-[95vh] bg-gradient-to-br from-[#0a0a0a] via-[#1a1a1a] to-[#0a0a0a] rounded-xl sm:rounded-2xl border border-[#d4af37]/30 shadow-2xl shadow-[#d4af37]/20 overflow-hidden flex flex-col">
{/* Header */}
<div className="px-3 sm:px-4 md:px-6 py-3 sm:py-4 border-b border-[#d4af37]/20 bg-gradient-to-r from-[#1a1a1a] to-[#0a0a0a] flex-shrink-0">
<div className="flex items-center justify-between gap-2">
<h2 className="text-base sm:text-lg md:text-xl font-serif font-bold text-white tracking-tight truncate flex-1">
Borica Payment
</h2>
<button
onClick={onClose}
className="p-1.5 sm:p-2 hover:bg-[#d4af37]/10 rounded-lg transition-colors text-gray-400 hover:text-white flex-shrink-0"
>
<X className="w-4 h-4 sm:w-5 sm:h-5" />
</button>
</div>
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto p-3 sm:p-4 md:p-6">
{loading ? (
<div className="flex flex-col items-center justify-center py-8 sm:py-12">
<Loader2 className="w-10 h-10 sm:w-12 sm:h-12 animate-spin text-[#d4af37] mb-3 sm:mb-4" />
<p className="text-xs sm:text-sm text-gray-400">Initializing Borica payment...</p>
</div>
) : error ? (
<div className="bg-red-500/10 border border-red-500/30 rounded-lg p-3 sm:p-4">
<div className="flex items-start gap-2 sm:gap-3">
<AlertCircle className="w-4 h-4 sm:w-5 sm:h-5 text-red-400 mt-0.5 flex-shrink-0" />
<div className="min-w-0 flex-1">
<h3 className="text-xs sm:text-sm font-semibold text-red-300 mb-1">
Payment Initialization Failed
</h3>
<p className="text-xs text-red-200/80 leading-relaxed">
{error || 'Unable to initialize Borica payment. Please try again.'}
</p>
</div>
</div>
</div>
) : paymentRequest ? (
<div className="space-y-4 sm:space-y-6">
<div className="text-center">
<div className="mb-3 sm:mb-4 flex justify-center">
<CreditCard className="w-12 h-12 sm:w-16 sm:h-16 text-[#d4af37]" />
</div>
<h3 className="text-base sm:text-lg font-serif font-semibold text-white mb-1.5 sm:mb-2">
Complete Payment with Borica
</h3>
<p className="text-xs sm:text-sm text-gray-400 mb-3 sm:mb-4 leading-relaxed px-2">
You will be redirected to Borica payment gateway to securely complete your payment of{' '}
<span className="font-semibold text-[#d4af37]">
{new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency,
}).format(amount)}
</span>
</p>
</div>
<div className="bg-[#1a1a1a]/50 border border-[#d4af37]/20 rounded-lg p-3 sm:p-4 space-y-2 sm:space-y-3">
<div className="flex justify-between items-center text-xs sm:text-sm">
<span className="text-gray-400">Order ID:</span>
<span className="text-white font-mono">{paymentRequest.order_id}</span>
</div>
<div className="flex justify-between items-center text-xs sm:text-sm">
<span className="text-gray-400">Amount:</span>
<span className="text-[#d4af37] font-semibold">
{new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency,
}).format(amount)}
</span>
</div>
</div>
<div className="bg-blue-500/10 border border-blue-500/30 rounded-lg p-3 sm:p-4">
<div className="flex items-start gap-2 sm:gap-3">
<AlertCircle className="w-4 h-4 sm:w-5 sm:h-5 text-blue-400 mt-0.5 flex-shrink-0" />
<div className="min-w-0 flex-1">
<p className="text-xs text-blue-200/80 leading-relaxed">
Your payment will be processed securely through Borica payment gateway.
You will be redirected to complete the transaction.
</p>
</div>
</div>
</div>
</div>
) : null}
</div>
{/* Footer */}
{!loading && !error && paymentRequest && (
<div className="px-3 sm:px-4 md:px-6 py-3 sm:py-4 border-t border-[#d4af37]/20 bg-gradient-to-r from-[#1a1a1a] to-[#0a0a0a] flex-shrink-0 flex gap-2 sm:gap-3">
<button
onClick={onClose}
className="flex-1 px-3 sm:px-4 py-2 sm:py-2.5 text-xs sm:text-sm font-medium text-gray-300 bg-[#1a1a1a] border border-gray-700 rounded-lg hover:bg-[#2a2a2a] hover:border-gray-600 transition-colors"
>
Cancel
</button>
<button
onClick={handleSubmitPayment}
className="flex-1 px-3 sm:px-4 py-2 sm:py-2.5 text-xs sm:text-sm font-medium text-white bg-gradient-to-r from-[#d4af37] to-[#b8941f] rounded-lg hover:from-[#e5c048] hover:to-[#c9a428] transition-all shadow-lg shadow-[#d4af37]/20 flex items-center justify-center gap-2"
>
<CreditCard className="w-4 h-4" />
Proceed to Payment
</button>
</div>
)}
</div>
</div>
);
};
export default BoricaPaymentModal;