import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useStripe, useElements, PaymentElement, Elements } from '@stripe/react-stripe-js'; import { loadStripe } from '@stripe/stripe-js'; import { PayPalButtons, PayPalScriptProvider } from '@paypal/react-paypal-js'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Button } from '@/components/ui/button'; import { LoaderCircle } from 'lucide-react'; import { useCheckoutWizard } from '../WizardContext'; interface PaymentStepProps { stripePublishableKey: string; paypalClientId: string; } type Provider = 'stripe' | 'paypal'; type PaymentStatus = 'idle' | 'loading' | 'ready' | 'processing' | 'error' | 'success'; interface StripePaymentFormProps { onProcessing: () => void; onSuccess: () => void; onError: (message: string) => void; selectedPackage: any; t: (key: string, options?: Record) => string; } const StripePaymentForm: React.FC = ({ onProcessing, onSuccess, onError, selectedPackage, t }) => { const stripe = useStripe(); const elements = useElements(); const [isProcessing, setIsProcessing] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); if (!stripe || !elements) { const message = t('checkout.payment_step.stripe_not_loaded'); onError(message); return; } onProcessing(); setIsProcessing(true); setErrorMessage(''); try { const { error: stripeError, paymentIntent } = await stripe.confirmPayment({ elements, confirmParams: { return_url: `${window.location.origin}/checkout/success`, }, redirect: 'if_required', }); if (stripeError) { let message = t('checkout.payment_step.payment_failed'); switch (stripeError.type) { case 'card_error': message += stripeError.message || t('checkout.payment_step.error_card'); break; case 'validation_error': message += t('checkout.payment_step.error_validation'); break; case 'api_connection_error': message += t('checkout.payment_step.error_connection'); break; case 'api_error': message += t('checkout.payment_step.error_server'); break; case 'authentication_error': message += t('checkout.payment_step.error_auth'); break; default: message += stripeError.message || t('checkout.payment_step.error_unknown'); } setErrorMessage(message); onError(message); return; } if (paymentIntent && paymentIntent.status === 'succeeded') { onSuccess(); return; } onError(t('checkout.payment_step.unexpected_status', { status: paymentIntent?.status })); } catch (error) { console.error('Stripe payment failed', error); onError(t('checkout.payment_step.error_unknown')); } finally { setIsProcessing(false); } }; return (
{errorMessage && ( {errorMessage} )}

{t('checkout.payment_step.secure_payment_desc')}

); }; interface PayPalPaymentFormProps { onProcessing: () => void; onSuccess: () => void; onError: (message: string) => void; selectedPackage: any; isReseller: boolean; paypalPlanId?: string | null; t: (key: string, options?: Record) => string; } const PayPalPaymentForm: React.FC = ({ onProcessing, onSuccess, onError, selectedPackage, isReseller, paypalPlanId, t }) => { const createOrder = async () => { if (!selectedPackage?.id) { const message = t('checkout.payment_step.paypal_order_error'); onError(message); throw new Error(message); } try { onProcessing(); const endpoint = isReseller ? '/paypal/create-subscription' : '/paypal/create-order'; const payload: Record = { package_id: selectedPackage.id, }; if (isReseller) { if (!paypalPlanId) { const message = t('checkout.payment_step.paypal_missing_plan'); onError(message); throw new Error(message); } payload.plan_id = paypalPlanId; } const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '', }, body: JSON.stringify(payload), }); const data = await response.json(); if (response.ok) { const orderId = isReseller ? data.order_id : data.id; if (typeof orderId === 'string' && orderId.length > 0) { return orderId; } } else { onError(data.error || t('checkout.payment_step.paypal_order_error')); } throw new Error('Failed to create PayPal order'); } catch (error) { console.error('PayPal create order failed', error); onError(t('checkout.payment_step.network_error')); throw error; } }; const onApprove = async (data: any) => { try { const response = await fetch('/paypal/capture-order', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '', }, body: JSON.stringify({ order_id: data.orderID }), }); const result = await response.json(); if (response.ok && result.status === 'captured') { onSuccess(); } else { onError(result.error || t('checkout.payment_step.paypal_capture_error')); } } catch (error) { console.error('PayPal capture failed', error); onError(t('checkout.payment_step.network_error')); } }; const handleError = (error: unknown) => { console.error('PayPal error', error); onError(t('checkout.payment_step.paypal_error')); }; const handleCancel = () => { onError(t('checkout.payment_step.paypal_cancelled')); }; return (

{t('checkout.payment_step.secure_paypal_desc') || 'Bezahlen Sie sicher mit PayPal.'}

createOrder()} onApprove={onApprove} onError={handleError} onCancel={handleCancel} />
); }; const statusVariantMap: Record = { idle: 'secondary', loading: 'secondary', ready: 'secondary', processing: 'secondary', error: 'destructive', success: 'success', }; export const PaymentStep: React.FC = ({ stripePublishableKey, paypalClientId }) => { const { t } = useTranslation('marketing'); const { selectedPackage, authUser, nextStep, resetPaymentState } = useCheckoutWizard(); const [paymentMethod, setPaymentMethod] = useState('stripe'); const [clientSecret, setClientSecret] = useState(''); const [status, setStatus] = useState('idle'); const [statusDetail, setStatusDetail] = useState(''); const [intentRefreshKey, setIntentRefreshKey] = useState(0); const [processingProvider, setProcessingProvider] = useState(null); const stripePromise = useMemo(() => loadStripe(stripePublishableKey), [stripePublishableKey]); const isFree = useMemo(() => (selectedPackage ? selectedPackage.price <= 0 : false), [selectedPackage]); const isReseller = selectedPackage?.type === 'reseller'; const paypalPlanId = useMemo(() => { if (!selectedPackage) { return null; } if (typeof selectedPackage.paypal_plan_id === 'string' && selectedPackage.paypal_plan_id.trim().length > 0) { return selectedPackage.paypal_plan_id; } const metadata = (selectedPackage as Record)?.metadata; if (metadata && typeof metadata === 'object') { const value = (metadata as Record).paypal_plan_id; if (typeof value === 'string' && value.trim().length > 0) { return value; } } return null; }, [selectedPackage]); const paypalDisabled = isReseller && !paypalPlanId; useEffect(() => { setStatus('idle'); setStatusDetail(''); setClientSecret(''); setProcessingProvider(null); }, [selectedPackage?.id]); useEffect(() => { if (isFree) { resetPaymentState(); setStatus('ready'); setStatusDetail(''); return; } if (!selectedPackage) { return; } if (paymentMethod === 'paypal') { if (paypalDisabled) { setStatus('error'); setStatusDetail(t('checkout.payment_step.paypal_missing_plan')); } else { setStatus('ready'); setStatusDetail(''); } return; } if (!authUser) { setStatus('error'); setStatusDetail(t('checkout.payment_step.auth_required')); return; } let cancelled = false; setStatus('loading'); setStatusDetail(t('checkout.payment_step.status_loading')); setClientSecret(''); const loadIntent = async () => { try { const response = await fetch('/stripe/create-payment-intent', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || '', }, body: JSON.stringify({ package_id: selectedPackage.id }), }); const data = await response.json(); if (!response.ok || !data.client_secret) { const message = data.error || t('checkout.payment_step.payment_intent_error'); if (!cancelled) { setStatus('error'); setStatusDetail(message); } return; } if (!cancelled) { setClientSecret(data.client_secret); setStatus('ready'); setStatusDetail(t('checkout.payment_step.status_ready')); } } catch (error) { if (!cancelled) { console.error('Failed to load payment intent', error); setStatus('error'); setStatusDetail(t('checkout.payment_step.network_error')); } } }; loadIntent(); return () => { cancelled = true; }; }, [authUser, intentRefreshKey, isFree, paymentMethod, paypalDisabled, resetPaymentState, selectedPackage, t]); const providerLabel = useCallback((provider: Provider) => { switch (provider) { case 'paypal': return 'PayPal'; default: return 'Stripe'; } }, []); const handleProcessing = useCallback((provider: Provider) => { setProcessingProvider(provider); setStatus('processing'); setStatusDetail(t('checkout.payment_step.status_processing', { provider: providerLabel(provider) })); }, [providerLabel, t]); const handleSuccess = useCallback((provider: Provider) => { setProcessingProvider(provider); setStatus('success'); setStatusDetail(t('checkout.payment_step.status_success')); setTimeout(() => nextStep(), 600); }, [nextStep, t]); const handleError = useCallback((provider: Provider, message: string) => { setProcessingProvider(provider); setStatus('error'); setStatusDetail(message); }, []); const handleRetry = () => { if (paymentMethod === 'stripe') { setIntentRefreshKey((key) => key + 1); } setStatus('idle'); setStatusDetail(''); setProcessingProvider(null); }; if (isFree) { return (
{t('checkout.payment_step.free_package_title')} {t('checkout.payment_step.free_package_desc')}
); } const renderStatusAlert = () => { if (status === 'idle') { return null; } const variant = statusVariantMap[status]; return ( {status === 'error' ? t('checkout.payment_step.status_error_title') : status === 'success' ? t('checkout.payment_step.status_success_title') : t('checkout.payment_step.status_info_title')} {statusDetail} {status === 'processing' && } {status === 'error' && ( )} ); }; return (
{renderStatusAlert()} {paymentMethod === 'stripe' && clientSecret && ( handleProcessing('stripe')} onSuccess={() => handleSuccess('stripe')} onError={(message) => handleError('stripe', message)} t={t} /> )} {paymentMethod === 'stripe' && !clientSecret && status === 'loading' && (
{t('checkout.payment_step.status_loading')}
)} {paymentMethod === 'paypal' && !paypalDisabled && ( handleProcessing('paypal')} onSuccess={() => handleSuccess('paypal')} onError={(message) => handleError('paypal', message)} paypalPlanId={paypalPlanId} selectedPackage={selectedPackage} t={t} /> )} {paymentMethod === 'paypal' && paypalDisabled && ( {t('checkout.payment_step.paypal_missing_plan')} )}
); };