diff --git a/components/checkout/PaymentForm.tsx b/components/checkout/PaymentForm.tsx index fdd0a92..83f3c07 100644 --- a/components/checkout/PaymentForm.tsx +++ b/components/checkout/PaymentForm.tsx @@ -3,9 +3,10 @@ import { useEffect, useRef, useState } from 'react' import { useRouter } from 'next/navigation' import Script from 'next/script' -import { Lock, ShieldCheck } from '@phosphor-icons/react' +import { motion, AnimatePresence } from 'framer-motion' +import { CreditCard, Lock, ShieldCheck, Bank } from '@phosphor-icons/react' import { initMollie, getMollie, MOLLIE_FIELD_STYLES, type MollieComponent } from '@/lib/mollie' -import { createEmbeddedCheckout } from '@/lib/api/billing' +import { createEmbeddedCheckout, createCheckoutSession } from '@/lib/api/billing' interface PaymentFormProps { plan: string @@ -15,12 +16,56 @@ interface PaymentFormProps { vatId: string } +const PAYMENT_METHODS: Array<{ id: string; label: string; description?: string; icon: string }> = [ + { id: 'card', label: 'Card', description: 'Visa, Mastercard, Amex', icon: 'card' }, + { id: 'bancontact', label: 'Bancontact', icon: 'bancontact' }, + { id: 'ideal', label: 'iDEAL', icon: 'ideal' }, + { id: 'applepay', label: 'Apple Pay', icon: 'applepay' }, + { id: 'directdebit', label: 'SEPA Direct Debit', icon: 'sepa' }, + { id: 'banktransfer', label: 'Bank Transfer', icon: 'banktransfer' }, +] + +function MethodIcon({ type, className }: { type: string; className?: string }) { + const cn = className || 'h-6 w-6' + switch (type) { + case 'card': + return + case 'bancontact': + return ( + + + + + + ) + case 'ideal': + return ( + + + + + ) + case 'applepay': + return ( + + + + ) + case 'sepa': + case 'banktransfer': + return + default: + return + } +} + const mollieFieldBase = 'w-full rounded-lg border border-neutral-700 bg-neutral-800/50 px-3 py-3 h-[48px] transition-all focus-within:ring-1 focus-within:ring-brand-orange focus-within:border-brand-orange' export default function PaymentForm({ plan, interval, limit, country, vatId }: PaymentFormProps) { const router = useRouter() + const [selectedMethod, setSelectedMethod] = useState('card') const [mollieReady, setMollieReady] = useState(false) const [mollieError, setMollieError] = useState(false) const [formError, setFormError] = useState(null) @@ -34,14 +79,14 @@ export default function PaymentForm({ plan, interval, limit, country, vatId }: P expiryDate: null, verificationCode: null, }) + const mollieInitialized = useRef(false) const [scriptLoaded, setScriptLoaded] = useState(false) - // Mount Mollie components AFTER both script loaded AND DOM elements exist + // Mount Mollie components AFTER script loaded useEffect(() => { - if (!scriptLoaded) return + if (!scriptLoaded || mollieInitialized.current) return - // Small delay to ensure DOM elements are painted const timer = setTimeout(() => { const mollie = initMollie() if (!mollie) { @@ -79,6 +124,7 @@ export default function PaymentForm({ plan, interval, limit, country, vatId }: P componentsRef.current[type] = component } + mollieInitialized.current = true setMollieReady(true) } catch (err) { console.error('Mollie mount error:', err) @@ -99,41 +145,56 @@ export default function PaymentForm({ plan, interval, limit, country, vatId }: P }, []) const handleSubmit = async (e: React.FormEvent) => { - setSubmitted(true) e.preventDefault() + setSubmitted(true) setFormError(null) + if (!country) { setFormError('Please select your country') return } - const mollie = getMollie() - if (!mollie) { - setFormError('Payment system not loaded. Please refresh.') - return - } - setSubmitting(true) + try { - const { token, error } = await mollie.createToken() - if (error || !token) { - setFormError(error?.message || 'Invalid card details.') - setSubmitting(false) - return + if (selectedMethod === 'card') { + const mollie = getMollie() + if (!mollie) { + setFormError('Payment system not loaded. Please refresh.') + setSubmitting(false) + return + } + + const { token, error } = await mollie.createToken() + if (error || !token) { + setFormError(error?.message || 'Invalid card details.') + setSubmitting(false) + return + } + + const result = await createEmbeddedCheckout({ + plan_id: plan, + interval, + limit, + country, + vat_id: vatId || undefined, + card_token: token, + }) + + if (result.status === 'success') router.push('/checkout?status=success') + else if (result.status === 'pending' && result.redirect_url) + window.location.href = result.redirect_url + } else { + const result = await createCheckoutSession({ + plan_id: plan, + interval, + limit, + country, + vat_id: vatId || undefined, + method: selectedMethod, + }) + window.location.href = result.url } - - const result = await createEmbeddedCheckout({ - plan_id: plan, - interval: interval, - limit, - country, - vat_id: vatId || undefined, - card_token: token, - }) - - if (result.status === 'success') router.push('/checkout?status=success') - else if (result.status === 'pending' && result.redirect_url) - window.location.href = result.redirect_url } catch (err) { setFormError((err as Error)?.message || 'Payment failed. Please try again.') } finally { @@ -141,6 +202,8 @@ export default function PaymentForm({ plan, interval, limit, country, vatId }: P } } + const isCard = selectedMethod === 'card' + return ( <>