'use client'
import { useEffect, useRef, useState } from 'react'
import { useRouter } from 'next/navigation'
import Script from 'next/script'
import { motion, AnimatePresence } from 'framer-motion'
import { Lock, ShieldCheck } from '@phosphor-icons/react'
import { initMollie, getMollie, MOLLIE_FIELD_STYLES, type MollieComponent } from '@/lib/mollie'
import { createEmbeddedCheckout, createCheckoutSession } from '@/lib/api/billing'
interface PaymentFormProps {
plan: string
interval: string
limit: number
country: string
vatId: string
}
const PAYMENT_METHODS = [
{ id: 'card', label: 'Card' },
{ id: 'bancontact', label: 'Bancontact' },
{ id: 'ideal', label: 'iDEAL' },
{ id: 'applepay', label: 'Apple Pay' },
{ id: 'googlepay', label: 'Google Pay' },
{ id: 'directdebit', label: 'SEPA' },
]
function MethodLogo({ type }: { type: string }) {
switch (type) {
case 'card':
return (
)
case 'bancontact':
return (
)
case 'ideal':
return (
)
case 'applepay':
return (
)
case 'googlepay':
return (
)
case 'directdebit':
return (
)
default:
return null
}
}
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)
const [cardErrors, setCardErrors] = useState>({})
const [submitted, setSubmitted] = useState(false)
const [submitting, setSubmitting] = useState(false)
const componentsRef = useRef>({
cardHolder: null,
cardNumber: null,
expiryDate: null,
verificationCode: null,
})
const mollieInitialized = useRef(false)
const [scriptLoaded, setScriptLoaded] = useState(false)
// Mount Mollie components AFTER script loaded
useEffect(() => {
if (!scriptLoaded || mollieInitialized.current) return
const timer = setTimeout(() => {
const mollie = initMollie()
if (!mollie) {
setMollieError(true)
return
}
try {
const fields: Array<{ type: string; selector: string; placeholder?: string }> = [
{ type: 'cardHolder', selector: '#mollie-card-holder', placeholder: 'John Doe' },
{ type: 'cardNumber', selector: '#mollie-card-number', placeholder: '1234 5678 9012 3456' },
{ type: 'expiryDate', selector: '#mollie-card-expiry', placeholder: 'MM / YY' },
{ type: 'verificationCode', selector: '#mollie-card-cvc', placeholder: 'CVC' },
]
for (const { type, selector, placeholder } of fields) {
const el = document.querySelector(selector) as HTMLElement | null
if (!el) {
setMollieError(true)
return
}
const opts: Record = { styles: MOLLIE_FIELD_STYLES }
if (placeholder) opts.placeholder = placeholder
const component = mollie.createComponent(type, opts)
component.mount(el)
component.addEventListener('change', (event: unknown) => {
const e = event as { error?: string }
setCardErrors((prev) => {
const next = { ...prev }
if (e.error) next[type] = e.error
else delete next[type]
return next
})
})
componentsRef.current[type] = component
}
mollieInitialized.current = true
setMollieReady(true)
} catch (err) {
console.error('Mollie mount error:', err)
setMollieError(true)
}
}, 100)
return () => clearTimeout(timer)
}, [scriptLoaded])
// Cleanup Mollie components on unmount
useEffect(() => {
return () => {
Object.values(componentsRef.current).forEach((c) => {
try { c?.unmount() } catch { /* DOM already removed */ }
})
}
}, [])
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setSubmitted(true)
setFormError(null)
if (!country) {
setFormError('Please select your country')
return
}
setSubmitting(true)
try {
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
}
} catch (err) {
setFormError((err as Error)?.message || 'Payment failed. Please try again.')
} finally {
setSubmitting(false)
}
}
const isCard = selectedMethod === 'card'
return (
<>