import React, { useEffect, useState } from 'react'; interface SquarePaymentProps { amount: number; onPaymentSuccess: (paymentResult: any) => void; onPaymentError: (error: string) => void; tierId: number; } const SquarePayment: React.FC = ({ amount, onPaymentSuccess, onPaymentError, tierId }) => { const [isLoading, setIsLoading] = useState(true); const [card, setCard] = useState(null); const [payments, setPayments] = useState(null); const [squareConfig, setSquareConfig] = useState(null); // Billing details state const [cardholderName, setCardholderName] = useState(''); const [addressLine1, setAddressLine1] = useState(''); const [addressLine2, setAddressLine2] = useState(''); const [city, setCity] = useState(''); const [postalCode, setPostalCode] = useState(''); const [isProcessing, setIsProcessing] = useState(false); useEffect(() => { loadSquareConfig(); }, []); useEffect(() => { if (squareConfig && !payments) { initializeSquare(); } }, [squareConfig]); const loadSquareSDK = (environment: string): Promise => { return new Promise((resolve, reject) => { // Check if Square SDK is already loaded if (window.Square) { resolve(); return; } const script = document.createElement('script'); script.type = 'text/javascript'; // Load the correct SDK based on environment if (environment?.toLowerCase() === 'sandbox') { script.src = 'https://sandbox.web.squarecdn.com/v1/square.js'; } else { script.src = 'https://web.squarecdn.com/v1/square.js'; } script.onload = () => resolve(); script.onerror = () => reject(new Error('Failed to load Square SDK')); document.head.appendChild(script); }); }; const loadSquareConfig = async () => { try { const response = await fetch('/api/v1/payments/config/square', { headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` } }); const config = await response.json(); console.log('Square config received:', config); // Load the appropriate Square SDK based on environment await loadSquareSDK(config.environment); console.log('Square SDK loaded for environment:', config.environment); setSquareConfig(config); } catch (error) { console.error('Failed to load Square config:', error); onPaymentError('Failed to load payment configuration'); setIsLoading(false); } }; const initializeSquare = async () => { if (!window.Square) { console.error('Square.js failed to load'); onPaymentError('Payment system failed to load. Please refresh the page.'); setIsLoading(false); return; } try { // Determine environment - default to production if not explicitly set to sandbox const environment = squareConfig.environment?.toLowerCase() === 'sandbox' ? 'sandbox' : 'production'; console.log('Initializing Square with environment:', environment); console.log('Application ID:', squareConfig.application_id); console.log('Location ID:', squareConfig.location_id); const paymentsInstance = window.Square.payments( squareConfig.application_id, squareConfig.location_id, { environment: environment } ); setPayments(paymentsInstance); // Initialize card without postal code (we collect it separately in billing form) const cardInstance = await paymentsInstance.card({ style: { '.input-container': { borderColor: '#E0E0E0', borderRadius: '4px' }, '.input-container.is-focus': { borderColor: '#4CAF50' }, '.message-text': { color: '#999' }, '.message-icon': { color: '#999' }, 'input': { fontSize: '14px' } } }); await cardInstance.attach('#card-container'); setCard(cardInstance); setIsLoading(false); } catch (error) { console.error('Failed to initialize Square:', error); onPaymentError('Failed to initialize payment form'); setIsLoading(false); } }; const handlePayment = async () => { if (!card || !payments) { onPaymentError('Payment form not initialized'); return; } // Validate billing details if (!cardholderName.trim()) { onPaymentError('Please enter cardholder name'); return; } if (!addressLine1.trim()) { onPaymentError('Please enter address line 1'); return; } if (!city.trim()) { onPaymentError('Please enter city'); return; } if (!postalCode.trim()) { onPaymentError('Please enter postal code'); return; } setIsLoading(true); setIsProcessing(true); try { // Tokenize the payment method with billing details const result = await card.tokenize(); if (result.status === 'OK') { // Send the token to your backend with billing details const response = await fetch('/api/v1/payments/square/process', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}` }, body: JSON.stringify({ source_id: result.token, amount: amount, tier_id: tierId, note: `Membership payment - £${amount.toFixed(2)}`, billing_details: { cardholder_name: cardholderName, address_line_1: addressLine1, address_line_2: addressLine2 || undefined, city: city, postal_code: postalCode, country: 'GB' } }) }); const paymentResult = await response.json(); if (response.ok && paymentResult.success) { onPaymentSuccess(paymentResult); } else { // Handle error response gracefully let errorMessage = 'Payment failed. Please try again.'; if (paymentResult.errors && Array.isArray(paymentResult.errors) && paymentResult.errors.length > 0) { // Show the first error message (they're already user-friendly from backend) errorMessage = paymentResult.errors[0]; } else if (paymentResult.detail) { errorMessage = paymentResult.detail; } onPaymentError(errorMessage); } } else { const errors = result.errors?.map((e: any) => e.message).join(', ') || 'Card tokenization failed'; onPaymentError(errors); } } catch (error: any) { console.error('Payment error:', error); onPaymentError(error.message || 'Payment processing failed'); } finally { setIsLoading(false); setIsProcessing(false); } }; return (

Card Payment

Amount: £{amount.toFixed(2)}

{/* Cardholder Name */}
setCardholderName(e.target.value)} placeholder="Name as it appears on card" style={{ width: '100%', padding: '10px 12px', fontSize: '14px', border: '1px solid #E0E0E0', borderRadius: '4px', boxSizing: 'border-box' }} disabled={isLoading || !card} />
{/* Address Line 1 */}
setAddressLine1(e.target.value)} placeholder="Street address" style={{ width: '100%', padding: '10px 12px', fontSize: '14px', border: '1px solid #E0E0E0', borderRadius: '4px', boxSizing: 'border-box' }} disabled={isLoading || !card} />
{/* Address Line 2 */}
setAddressLine2(e.target.value)} placeholder="Apartment, suite, etc. (optional)" style={{ width: '100%', padding: '10px 12px', fontSize: '14px', border: '1px solid #E0E0E0', borderRadius: '4px', boxSizing: 'border-box' }} disabled={isLoading || !card} />
{/* City and Postal Code */}
setCity(e.target.value)} placeholder="City" style={{ width: '100%', padding: '10px 12px', fontSize: '14px', border: '1px solid #E0E0E0', borderRadius: '4px', boxSizing: 'border-box' }} disabled={isLoading || !card} />
setPostalCode(e.target.value.toUpperCase())} placeholder="SW1A 1AA" style={{ width: '100%', padding: '10px 12px', fontSize: '14px', border: '1px solid #E0E0E0', borderRadius: '4px', boxSizing: 'border-box' }} disabled={isLoading || !card} />
{/* Card Details */}

Secure payment powered by Square

{squareConfig?.environment === 'sandbox' && (

Test Mode: Use card 4111 1111 1111 1111 with any future expiry

)}
); }; // Extend Window interface for TypeScript declare global { interface Window { Square: any; } } export default SquarePayment;