![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/lavocat.ca/public_html/src/components/payments/ |
'use client';
import React, { useState, useEffect } from 'react';
import { loadStripe } from '@stripe/stripe-js';
import {
Elements,
CardElement,
useStripe,
useElements
} from '@stripe/react-stripe-js';
import { useSession } from 'next-auth/react';
// Initialize Stripe with proper error handling
const getStripePromise = () => {
const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
// Check if the key is properly configured (not a placeholder)
if (!publishableKey ||
publishableKey === 'pk_test_your_stripe_publishable_key_here' ||
publishableKey === 'pk_live_your_stripe_publishable_key_here') {
console.warn('Stripe publishable key not properly configured. Payment functionality will be disabled.');
return null;
}
return loadStripe(publishableKey);
};
const stripePromise = getStripePromise();
interface PaymentFormProps {
caseId: string;
lawyerId: string;
amount: number;
description?: string;
onSuccess?: (result: any) => void;
onError?: (error: string) => void;
}
// Main Payment Form Component
export default function PaymentForm({
caseId,
lawyerId,
amount,
description,
onSuccess,
onError
}: PaymentFormProps) {
// If Stripe is not properly configured, show a message
if (!stripePromise) {
return (
<div className="max-w-md mx-auto bg-white rounded-lg shadow-lg p-6">
<div className="text-center">
<div className="text-red-500 mb-4">
<svg className="w-12 h-12 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
</svg>
</div>
<h3 className="text-lg font-semibold text-gray-900 mb-2">
Payment System Unavailable
</h3>
<p className="text-sm text-gray-600 mb-4">
Stripe payment processing is not properly configured. Please contact support.
</p>
<div className="text-xs text-gray-500">
Amount: ${amount.toFixed(2)} CAD
</div>
</div>
</div>
);
}
return (
<Elements stripe={stripePromise}>
<PaymentFormContent
caseId={caseId}
lawyerId={lawyerId}
amount={amount}
description={description}
onSuccess={onSuccess}
onError={onError}
/>
</Elements>
);
}
// Internal form component with Stripe hooks
function PaymentFormContent({
caseId,
lawyerId,
amount,
description,
onSuccess,
onError
}: PaymentFormProps) {
const stripe = useStripe();
const elements = useElements();
const { data: session } = useSession();
const [isLoading, setIsLoading] = useState(false);
const [paymentIntent, setPaymentIntent] = useState<any>(null);
const [societyDiscount, setSocietyDiscount] = useState(0);
const [platformFee, setPlatformFee] = useState(0);
const [lawyerPayout, setLawyerPayout] = useState(0);
const [xpReward, setXpReward] = useState(0);
const [clientSecret, setClientSecret] = useState('');
// Create payment intent when component mounts
useEffect(() => {
if (caseId && lawyerId && amount > 0) {
createPaymentIntent();
}
}, [caseId, lawyerId, amount]);
const createPaymentIntent = async () => {
try {
setIsLoading(true);
const response = await fetch('/api/payments/create-payment-intent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount,
caseId,
lawyerId,
description
})
});
if (!response.ok) {
throw new Error('Failed to create payment intent');
}
const data = await response.json();
setClientSecret(data.clientSecret);
setSocietyDiscount(data.societyDiscount);
setPlatformFee(data.platformFeeAmount);
setLawyerPayout(data.lawyerPayoutAmount);
setXpReward(data.xpEarned);
setPaymentIntent(data);
} catch (error) {
console.error('Error creating payment intent:', error);
onError?.('Failed to initialize payment. Please try again.');
} finally {
setIsLoading(false);
}
};
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
if (!stripe || !elements || !clientSecret) {
onError?.('Payment system not ready. Please wait and try again.');
return;
}
setIsLoading(true);
const cardElement = elements.getElement(CardElement);
if (!cardElement) {
onError?.('Card element not found');
setIsLoading(false);
return;
}
try {
// Confirm payment with Stripe
const { error, paymentIntent: confirmedPayment } = await stripe.confirmCardPayment(
clientSecret,
{
payment_method: {
card: cardElement,
billing_details: {
name: session?.user?.name || 'Anonymous',
email: session?.user?.email || undefined,
},
},
}
);
if (error) {
console.error('Payment confirmation error:', error);
onError?.(error.message || 'Payment failed. Please try again.');
} else if (confirmedPayment.status === 'succeeded') {
console.log('Payment succeeded:', confirmedPayment);
onSuccess?.({
paymentIntent: confirmedPayment,
...paymentIntent
});
} else {
onError?.('Payment was not completed. Please try again.');
}
} catch (error) {
console.error('Payment error:', error);
onError?.('An unexpected error occurred. Please try again.');
} finally {
setIsLoading(false);
}
};
const cardElementOptions = {
style: {
base: {
fontSize: '16px',
color: '#424770',
'::placeholder': {
color: '#aab7c4',
},
fontFamily: 'Inter, system-ui, sans-serif',
},
invalid: {
color: '#9e2146',
},
},
hidePostalCode: false,
};
if (isLoading && !clientSecret) {
return (
<div className="flex items-center justify-center p-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<span className="ml-3 text-gray-600">Initializing payment...</span>
</div>
);
}
return (
<div className="max-w-md mx-auto bg-white rounded-lg shadow-lg p-6">
<div className="mb-6">
<h3 className="text-lg font-semibold text-gray-900 mb-2">
Complete Payment
</h3>
<div className="text-sm text-gray-600 space-y-1">
<div className="flex justify-between">
<span>Case Fee:</span>
<span>${amount.toFixed(2)} CAD</span>
</div>
{societyDiscount > 0 && (
<div className="flex justify-between text-green-600">
<span>Society Discount:</span>
<span>-${(amount * societyDiscount).toFixed(2)}</span>
</div>
)}
<div className="flex justify-between text-gray-500">
<span>Platform Fee:</span>
<span>${platformFee.toFixed(2)}</span>
</div>
<div className="flex justify-between text-gray-500">
<span>Lawyer Receives:</span>
<span>${lawyerPayout.toFixed(2)}</span>
</div>
{xpReward > 0 && (
<div className="flex justify-between text-blue-600">
<span>XP Reward:</span>
<span>+{xpReward} XP</span>
</div>
)}
<hr className="my-2" />
<div className="flex justify-between font-semibold">
<span>Total:</span>
<span>${amount.toFixed(2)} CAD</span>
</div>
</div>
</div>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Card Information
</label>
<div className="p-3 border border-gray-300 rounded-md focus-within:ring-2 focus-within:ring-blue-500 focus-within:border-blue-500">
<CardElement options={cardElementOptions} />
</div>
</div>
{societyDiscount > 0 && (
<div className="bg-green-50 border border-green-200 rounded-md p-3">
<div className="flex items-center">
<svg className="h-5 w-5 text-green-400 mr-2" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
</svg>
<span className="text-sm text-green-800">
Society Member Discount Applied!
<br />
<span className="text-xs">
You saved ${(amount * societyDiscount).toFixed(2)} with your membership.
</span>
</span>
</div>
</div>
)}
<div className="bg-blue-50 border border-blue-200 rounded-md p-3">
<div className="flex items-start">
<svg className="h-5 w-5 text-blue-400 mr-2 mt-0.5" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
</svg>
<div className="text-sm text-blue-800">
<strong>Escrow Protection:</strong>
<br />
Your payment will be held in escrow until case milestones are met,
ensuring your money is protected.
</div>
</div>
</div>
<button
type="submit"
disabled={!stripe || isLoading}
className={`w-full py-3 px-4 rounded-md text-white font-medium ${
isLoading || !stripe
? 'bg-gray-400 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2'
} transition-colors`}
>
{isLoading ? (
<div className="flex items-center justify-center">
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
Processing...
</div>
) : (
`Pay $${amount.toFixed(2)} CAD`
)}
</button>
</form>
<div className="mt-4 text-xs text-gray-500 text-center">
<div className="flex items-center justify-center space-x-2">
<svg className="h-4 w-4" 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>
<span>Secured by Stripe • Your payment information is encrypted</span>
</div>
</div>
</div>
);
}