import { CardCvcElement, CardExpiryElement, CardNumberElement, Elements, useElements, useStripe, } from '@stripe/react-stripe-js'; import { loadStripe, PaymentIntentResult, StripeCardNumberElementOptions, StripeElementStyle, } from '@stripe/stripe-js'; import { Button } from '@workspace/ui/components/button'; import { Input } from '@workspace/ui/components/input'; import { Label } from '@workspace/ui/components/label'; import { CheckCircle } from 'lucide-react'; import { useTranslations } from 'next-intl'; import { useTheme } from 'next-themes'; import { QRCodeCanvas } from 'qrcode.react'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; interface StripePaymentProps { method: string; client_secret: string; publishable_key: string; } interface CardPaymentFormProps { clientSecret: string; onError: (message: string) => void; } const CardPaymentForm: React.FC = ({ clientSecret, onError }) => { const stripe = useStripe(); const { theme, systemTheme } = useTheme(); const elements = useElements(); const [processing, setProcessing] = useState(false); const [succeeded, setSucceeded] = useState(false); const [errors, setErrors] = useState<{ cardNumber?: string; cardExpiry?: string; cardCvc?: string; name?: string; }>({}); const [cardholderName, setCardholderName] = useState(''); const t = useTranslations('payment.stripe.card'); const currentTheme = theme === 'system' ? systemTheme : theme; const elementStyle: StripeElementStyle = { base: { 'fontSize': '16px', 'color': currentTheme === 'dark' ? '#fff' : '#000', '::placeholder': { color: '#aab7c4', }, }, invalid: { color: '#EF4444', iconColor: '#EF4444', }, }; const elementOptions: StripeCardNumberElementOptions = { style: elementStyle, showIcon: true, }; const handleChange = (event: any, field: keyof typeof errors) => { if (event.error) { setErrors((prev) => ({ ...prev, [field]: event.error.message })); } else { setErrors((prev) => ({ ...prev, [field]: undefined })); } }; const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); if (!stripe || !elements) { onError(t('loading')); return; } if (!cardholderName.trim()) { setErrors((prev) => ({ ...prev, name: t('name_required') })); return; } setProcessing(true); const cardNumber = elements.getElement(CardNumberElement); const cardExpiry = elements.getElement(CardExpiryElement); const cardCvc = elements.getElement(CardCvcElement); if (!cardNumber || !cardExpiry || !cardCvc) { onError(t('element_error')); setProcessing(false); return; } const { error, paymentIntent } = await stripe.confirmCardPayment(clientSecret, { payment_method: { card: cardNumber, billing_details: { name: cardholderName, }, }, }); if (error) { onError(error.message || t('payment_failed')); setProcessing(false); } else if (paymentIntent && paymentIntent.status === 'succeeded') { setSucceeded(true); setProcessing(false); } else { onError(t('processing')); setProcessing(false); } }; return (
{succeeded ? (

{t('success_title')}

{t('success_message')}

) : ( <>
{/* Cardholder Name */}
setCardholderName(e.target.value)} placeholder={t('name_placeholder')} className={errors.name ? 'border-destructive' : ''} /> {errors.name &&

{errors.name}

}
{/* Card Number */}
handleChange(e, 'cardNumber')} />
{errors.cardNumber &&

{errors.cardNumber}

}
{/* Expiry Date */}
handleChange(e, 'cardExpiry')} />
{errors.cardExpiry && (

{errors.cardExpiry}

)}
{/* Security Code */}
handleChange(e, 'cardCvc')} />
{errors.cardCvc &&

{errors.cardCvc}

}

{t('secure_notice')}

)}
); }; const StripePayment: React.FC = ({ method, client_secret, publishable_key, }) => { const stripePromise = useMemo(() => loadStripe(publishable_key), [publishable_key]); return ( ); }; const CheckoutForm: React.FC> = ({ client_secret, method, }) => { const stripe = useStripe(); const [errorMessage, setErrorMessage] = useState(null); const [qrCodeUrl, setQrCodeUrl] = useState(null); const [isSubmitted, setIsSubmitted] = useState(false); const t = useTranslations('payment.stripe'); const handleError = useCallback((message: string) => { setErrorMessage(message); setIsSubmitted(false); }, []); const confirmPayment = useCallback(async (): Promise => { if (!stripe) { handleError(t('card.loading')); return null; } if (method === 'alipay') { return await stripe.confirmAlipayPayment( client_secret, { return_url: window.location.href }, { handleActions: false }, ); } if (method === 'wechat_pay') { return await stripe.confirmWechatPayPayment( client_secret, { payment_method_options: { wechat_pay: { client: 'web' } }, }, { handleActions: false }, ); } return null; }, [client_secret, method, stripe, handleError, t]); const autoSubmit = useCallback(async () => { if (isSubmitted || method === 'card') return; setIsSubmitted(true); try { const result = await confirmPayment(); if (!result) return; const { error, paymentIntent } = result; if (error) return handleError(error.message!); if (paymentIntent?.status === 'requires_action') { const nextAction = paymentIntent.next_action as any; const qrUrl = method === 'alipay' ? nextAction?.alipay_handle_redirect?.url : nextAction?.wechat_pay_display_qr_code?.image_url_svg; setQrCodeUrl(qrUrl || null); } } catch (error) { handleError(t('error')); } }, [confirmPayment, isSubmitted, handleError, method, t]); useEffect(() => { autoSubmit(); }, [autoSubmit]); return method === 'card' ? (
) : qrCodeUrl ? ( <>

{t(`qrcode.${method}`)}

) : ( errorMessage ); }; export default StripePayment;