112 lines
3.3 KiB
TypeScript
112 lines
3.3 KiB
TypeScript
import React from 'react';
|
|
import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
|
|
import { useForm } from '@inertiajs/react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Loader2 } from 'lucide-react';
|
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
|
|
|
interface PaymentFormProps {
|
|
packageId: number;
|
|
onSuccess?: () => void;
|
|
}
|
|
|
|
export default function PaymentForm({ packageId, onSuccess }: PaymentFormProps) {
|
|
const stripe = useStripe();
|
|
const elements = useElements();
|
|
const { t } = useTranslation('marketing');
|
|
const { data, setData, post, processing, errors, setError } = useForm({
|
|
package_id: packageId,
|
|
payment_method_id: '',
|
|
});
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
|
|
if (!stripe || !elements) {
|
|
return;
|
|
}
|
|
|
|
const cardElement = elements.getElement(CardElement);
|
|
|
|
if (!cardElement) {
|
|
return;
|
|
}
|
|
|
|
const { error, paymentMethod } = await stripe.createPaymentMethod({
|
|
type: 'card',
|
|
card: cardElement,
|
|
});
|
|
|
|
if (error) {
|
|
setError('payment', error.message || 'Payment failed');
|
|
return;
|
|
}
|
|
|
|
setData('payment_method_id', paymentMethod.id);
|
|
|
|
const { error: confirmError } = await stripe.confirmCardPayment('/api/purchase/payment-intent', {
|
|
payment_method: paymentMethod.id,
|
|
});
|
|
|
|
if (confirmError) {
|
|
setError('payment', confirmError.message || 'Payment confirmation failed');
|
|
return;
|
|
}
|
|
|
|
post('/api/purchase/complete', {
|
|
package_id: packageId,
|
|
preserveScroll: true,
|
|
onSuccess: () => {
|
|
if (onSuccess) {
|
|
onSuccess();
|
|
}
|
|
},
|
|
onError: (err) => {
|
|
setError('payment', err.payment || 'Payment error');
|
|
},
|
|
});
|
|
};
|
|
|
|
if (!stripe || !elements) {
|
|
return <div>Loading Stripe...</div>;
|
|
}
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>{t('payment.title')}</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div className="space-y-2">
|
|
<label htmlFor="card-element" className="text-sm font-medium">
|
|
{t('payment.card_details')}
|
|
</label>
|
|
<div className="p-3 border border-gray-300 rounded-md">
|
|
<CardElement
|
|
options={{
|
|
style: {
|
|
base: {
|
|
fontSize: '16px',
|
|
color: '#424770',
|
|
'::placeholder': {
|
|
color: '#aab7c4',
|
|
},
|
|
},
|
|
},
|
|
}}
|
|
/>
|
|
</div>
|
|
{errors.payment && <Alert variant="destructive"><AlertDescription>{errors.payment}</AlertDescription></Alert>}
|
|
</div>
|
|
<Button type="submit" className="w-full" disabled={!stripe || processing}>
|
|
{processing ? <Loader2 className="h-4 w-4 animate-spin mr-2" /> : null}
|
|
{t('payment.submit', { price: 'XX €' })} {/* Replace with actual price */}
|
|
</Button>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
} |