197 lines
5.6 KiB
TypeScript
197 lines
5.6 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { loadStripe, StripeElementsOptions } from '@stripe/stripe-js';
|
|
import { Elements } from '@stripe/react-stripe-js';
|
|
import StripePaymentForm from './StripePaymentForm';
|
|
import { createStripePaymentIntent, confirmStripePayment } from '../services/paymentService';
|
|
import { Loader2, AlertCircle } from 'lucide-react';
|
|
|
|
interface StripePaymentWrapperProps {
|
|
bookingId: number;
|
|
amount: number;
|
|
currency?: string;
|
|
onSuccess: () => void;
|
|
onError?: (error: string) => void;
|
|
}
|
|
|
|
const StripePaymentWrapper: React.FC<StripePaymentWrapperProps> = ({
|
|
bookingId,
|
|
amount,
|
|
currency = 'usd',
|
|
onSuccess,
|
|
onError,
|
|
}) => {
|
|
const [stripePromise, setStripePromise] = useState<Promise<any> | null>(null);
|
|
const [clientSecret, setClientSecret] = useState<string | null>(null);
|
|
const [, setPublishableKey] = useState<string | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [paymentCompleted, setPaymentCompleted] = useState(false);
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (paymentCompleted) {
|
|
return;
|
|
}
|
|
|
|
|
|
const initializeStripe = async () => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
const response = await createStripePaymentIntent(
|
|
bookingId,
|
|
amount,
|
|
currency
|
|
);
|
|
|
|
if (response.success && response.data) {
|
|
const { publishable_key, client_secret } = response.data;
|
|
|
|
console.log('Payment intent response:', { publishable_key: publishable_key ? 'present' : 'missing', client_secret: client_secret ? 'present' : 'missing' });
|
|
|
|
if (!client_secret) {
|
|
throw new Error('Client secret not received from server');
|
|
}
|
|
|
|
if (!publishable_key) {
|
|
throw new Error('Publishable key not configured. Please configure Stripe settings in Admin Panel.');
|
|
}
|
|
|
|
setPublishableKey(publishable_key);
|
|
setClientSecret(client_secret);
|
|
|
|
|
|
|
|
const stripePromise = loadStripe(publishable_key);
|
|
setStripePromise(stripePromise);
|
|
|
|
|
|
const stripe = await stripePromise;
|
|
if (!stripe) {
|
|
throw new Error('Failed to load Stripe');
|
|
}
|
|
} else {
|
|
throw new Error(response.message || 'Failed to initialize payment');
|
|
}
|
|
} catch (err: any) {
|
|
console.error('Error initializing Stripe:', err);
|
|
const errorMessage = err.response?.data?.message || err.message || 'Failed to initialize payment';
|
|
setError(errorMessage);
|
|
if (onError) {
|
|
onError(errorMessage);
|
|
}
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
initializeStripe();
|
|
}, [bookingId, amount, currency, onError, paymentCompleted]);
|
|
|
|
|
|
useEffect(() => {
|
|
if (clientSecret && stripePromise) {
|
|
console.log('Stripe initialized successfully', { hasClientSecret: !!clientSecret, hasStripePromise: !!stripePromise });
|
|
} else {
|
|
console.log('Stripe not ready', { hasClientSecret: !!clientSecret, hasStripePromise: !!stripePromise, error });
|
|
}
|
|
}, [clientSecret, stripePromise, error]);
|
|
|
|
const handlePaymentSuccess = async (paymentIntentId: string) => {
|
|
try {
|
|
|
|
setPaymentCompleted(true);
|
|
|
|
const response = await confirmStripePayment(paymentIntentId, bookingId);
|
|
|
|
if (response.success) {
|
|
onSuccess();
|
|
} else {
|
|
|
|
setPaymentCompleted(false);
|
|
throw new Error(response.message || 'Payment confirmation failed');
|
|
}
|
|
} catch (err: any) {
|
|
console.error('Error confirming payment:', err);
|
|
|
|
setPaymentCompleted(false);
|
|
const errorMessage = err.response?.data?.message || err.message || 'Payment confirmation failed';
|
|
setError(errorMessage);
|
|
if (onError) {
|
|
onError(errorMessage);
|
|
}
|
|
}
|
|
};
|
|
|
|
const handlePaymentError = (errorMessage: string) => {
|
|
setError(errorMessage);
|
|
if (onError) {
|
|
onError(errorMessage);
|
|
}
|
|
};
|
|
|
|
|
|
if (paymentCompleted) {
|
|
return null;
|
|
}
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex items-center justify-center p-8">
|
|
<Loader2 className="w-8 h-8 animate-spin text-indigo-600" />
|
|
<span className="ml-2 text-gray-600">Initializing payment...</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="bg-red-50 border border-red-200 rounded-lg p-6">
|
|
<div className="flex items-start gap-3">
|
|
<AlertCircle className="w-5 h-5 text-red-600 mt-0.5" />
|
|
<div>
|
|
<h3 className="text-lg font-semibold text-red-900 mb-1">
|
|
Payment Initialization Failed
|
|
</h3>
|
|
<p className="text-sm text-red-800">
|
|
{error || 'Unable to initialize payment. Please try again.'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!clientSecret || !stripePromise) {
|
|
return (
|
|
<div className="flex items-center justify-center p-8">
|
|
<Loader2 className="w-8 h-8 animate-spin text-indigo-600" />
|
|
<span className="ml-2 text-gray-600">Loading payment form...</span>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const options: StripeElementsOptions = {
|
|
clientSecret,
|
|
appearance: {
|
|
theme: 'stripe',
|
|
},
|
|
};
|
|
|
|
return (
|
|
<Elements stripe={stripePromise} options={options}>
|
|
<StripePaymentForm
|
|
clientSecret={clientSecret}
|
|
amount={amount}
|
|
onSuccess={handlePaymentSuccess}
|
|
onError={handlePaymentError}
|
|
/>
|
|
</Elements>
|
|
);
|
|
};
|
|
|
|
export default StripePaymentWrapper;
|
|
|