"use client" import { useState, useRef } from "react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { CreditCard, Lock } from "lucide-react" interface Props { amount: number eventName: string eventId: string qrSourceId: string | null onComplete: (identity: { donorName: string donorEmail: string donorPhone: string giftAid: boolean }) => void } function formatCardNumber(value: string): string { const digits = value.replace(/\D/g, "").slice(0, 16) return digits.replace(/(\d{4})(?=\d)/g, "$1 ") } function formatExpiry(value: string): string { const digits = value.replace(/\D/g, "").slice(0, 4) if (digits.length >= 3) return digits.slice(0, 2) + "/" + digits.slice(2) return digits } function luhnCheck(num: string): boolean { const digits = num.replace(/\D/g, "") if (digits.length < 13) return false let sum = 0 let alt = false for (let i = digits.length - 1; i >= 0; i--) { let n = parseInt(digits[i], 10) if (alt) { n *= 2 if (n > 9) n -= 9 } sum += n alt = !alt } return sum % 10 === 0 } function getCardBrand(num: string): string { const d = num.replace(/\D/g, "") if (/^4/.test(d)) return "Visa" if (/^5[1-5]/.test(d) || /^2[2-7]/.test(d)) return "Mastercard" if (/^3[47]/.test(d)) return "Amex" if (/^6(?:011|5)/.test(d)) return "Discover" return "" } export function CardPaymentStep({ amount, eventName, eventId, qrSourceId, onComplete }: Props) { const [cardNumber, setCardNumber] = useState("") const [expiry, setExpiry] = useState("") const [cvc, setCvc] = useState("") const [name, setName] = useState("") const [email, setEmail] = useState("") const [giftAid, setGiftAid] = useState(false) const [processing, setProcessing] = useState(false) const [errors, setErrors] = useState>({}) const expiryRef = useRef(null) const cvcRef = useRef(null) const pounds = (amount / 100).toFixed(2) const brand = getCardBrand(cardNumber) const validate = (): boolean => { const errs: Record = {} const digits = cardNumber.replace(/\D/g, "") if (!luhnCheck(digits)) errs.card = "Invalid card number" if (digits.length < 13) errs.card = "Card number too short" const expiryDigits = expiry.replace(/\D/g, "") if (expiryDigits.length < 4) { errs.expiry = "Invalid expiry" } else { const month = parseInt(expiryDigits.slice(0, 2), 10) const year = parseInt("20" + expiryDigits.slice(2, 4), 10) const now = new Date() if (month < 1 || month > 12) errs.expiry = "Invalid month" else if (year < now.getFullYear() || (year === now.getFullYear() && month < now.getMonth() + 1)) errs.expiry = "Card expired" } if (cvc.length < 3) errs.cvc = "Invalid CVC" if (!name.trim()) errs.name = "Name required" if (!email.includes("@")) errs.email = "Valid email required" setErrors(errs) return Object.keys(errs).length === 0 } const handleSubmit = async () => { if (!validate()) return setProcessing(true) // Try real Stripe Checkout first try { const res = await fetch("/api/stripe/checkout", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ amountPence: amount, donorName: name, donorEmail: email, donorPhone: "", giftAid, eventId, qrSourceId, }), }) const data = await res.json() if (data.mode === "live" && data.checkoutUrl) { // Redirect to Stripe Checkout window.location.href = data.checkoutUrl return } // Simulated mode — fall through to onComplete } catch { // Fall through to simulated } // Simulated fallback await new Promise((r) => setTimeout(r, 1500)) onComplete({ donorName: name, donorEmail: email, donorPhone: "", giftAid, }) } const handleCardNumberChange = (value: string) => { const formatted = formatCardNumber(value) setCardNumber(formatted) // Auto-advance to expiry when complete if (formatted.replace(/\s/g, "").length === 16) { expiryRef.current?.focus() } } const handleExpiryChange = (value: string) => { const formatted = formatExpiry(value) setExpiry(formatted) if (formatted.length === 5) { cvcRef.current?.focus() } } const isReady = cardNumber.replace(/\D/g, "").length >= 13 && expiry.length === 5 && cvc.length >= 3 && name.trim() && email.includes("@") return (

Pay by Card

Pledge: £{pounds}{" "} for {eventName}

{/* Card form */}
{/* Card number */}
handleCardNumberChange(e.target.value)} inputMode="numeric" autoComplete="cc-number" className={`pl-11 font-mono text-base ${errors.card ? "border-red-500" : ""}`} /> {brand && ( {brand} )}
{errors.card &&

{errors.card}

}
{/* Expiry + CVC row */}
handleExpiryChange(e.target.value)} inputMode="numeric" autoComplete="cc-exp" maxLength={5} className={`font-mono text-base ${errors.expiry ? "border-red-500" : ""}`} /> {errors.expiry &&

{errors.expiry}

}
setCvc(e.target.value.replace(/\D/g, "").slice(0, 4))} inputMode="numeric" autoComplete="cc-csc" maxLength={4} className={`font-mono text-base ${errors.cvc ? "border-red-500" : ""}`} /> {errors.cvc &&

{errors.cvc}

}
{/* Cardholder name */}
setName(e.target.value)} autoComplete="cc-name" className={errors.name ? "border-red-500" : ""} /> {errors.name &&

{errors.name}

}
{/* Email */}
setEmail(e.target.value)} autoComplete="email" inputMode="email" className={errors.email ? "border-red-500" : ""} /> {errors.email &&

{errors.email}

}
{/* Gift Aid */} {/* Pay button */}
Secured with 256-bit encryption
) }