Files
motovaultpro/frontend/src/features/subscription/components/PaymentMethodForm.tsx
2026-01-18 16:37:10 -06:00

120 lines
2.8 KiB
TypeScript

import React, { useState } from 'react';
import { Box, Button, Typography, Alert, CircularProgress } from '@mui/material';
import { CardElement, useStripe, useElements } from '@stripe/react-stripe-js';
import type { StripeCardElementChangeEvent } from '@stripe/stripe-js';
interface PaymentMethodFormProps {
onSubmit: (paymentMethodId: string) => void;
isLoading?: boolean;
}
const CARD_ELEMENT_OPTIONS = {
style: {
base: {
fontSize: '16px',
color: '#424770',
'::placeholder': {
color: '#aab7c4',
},
},
invalid: {
color: '#9e2146',
},
},
};
export const PaymentMethodForm: React.FC<PaymentMethodFormProps> = ({
onSubmit,
isLoading = false,
}) => {
const stripe = useStripe();
const elements = useElements();
const [error, setError] = useState<string | null>(null);
const [processing, setProcessing] = useState(false);
const [cardComplete, setCardComplete] = useState(false);
const handleCardChange = (event: StripeCardElementChangeEvent) => {
setError(event.error?.message || null);
setCardComplete(event.complete);
};
const handleSubmit = async (event: React.FormEvent) => {
event.preventDefault();
if (!stripe || !elements) {
return;
}
const cardElement = elements.getElement(CardElement);
if (!cardElement) {
return;
}
setProcessing(true);
setError(null);
try {
const { error: createError, paymentMethod } = await stripe.createPaymentMethod({
type: 'card',
card: cardElement,
});
if (createError) {
setError(createError.message || 'Failed to create payment method');
setProcessing(false);
return;
}
if (paymentMethod) {
onSubmit(paymentMethod.id);
}
} catch {
setError('An unexpected error occurred');
setProcessing(false);
}
};
return (
<form onSubmit={handleSubmit}>
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" gutterBottom>
Card Details
</Typography>
<Box
sx={{
border: 1,
borderColor: 'divider',
borderRadius: 1,
p: 2,
'&:hover': {
borderColor: 'primary.main',
},
}}
>
<CardElement options={CARD_ELEMENT_OPTIONS} onChange={handleCardChange} />
</Box>
</Box>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
<Button
type="submit"
variant="contained"
color="primary"
fullWidth
disabled={!stripe || processing || isLoading || !cardComplete}
>
{processing || isLoading ? (
<CircularProgress size={24} />
) : (
'Update Payment Method'
)}
</Button>
</form>
);
};