/* ============================================================ DONATE — widget + full page ============================================================ */ const PRESETS = [300, 500, 1000, 3000, 5000]; const IMPACT_BY_AMOUNT = { 300: 'печать рекламного буклета для привлечения волонтёров', 500: 'билет на электричку для куратора в маленький город, чтобы найти подростков, кому нужен значимый взрослый', 1000: 'консультация психолога для наставника в случае, если ему нужна помощь', 3000: 'проведение групповой супервизии для наставников командой проекта', 5000: 'участие пары наставник-подопечный в традиционном мероприятии фонда / фотосессии', }; const fmt = (n) => n.toLocaleString('ru-RU'); function reachDonationGoal(goal, reference, amount) { const key = `nl-goal:${goal}:${reference || 'no-reference'}`; try { if (reference && window.sessionStorage && window.sessionStorage.getItem(key)) return; } catch (error) {} const sent = window.nlReachGoal ? window.nlReachGoal(goal, { reference, amount }) : false; try { if (sent && reference && window.sessionStorage) window.sessionStorage.setItem(key, '1'); } catch (error) {} } function readDonationParams() { const params = new URLSearchParams(window.location.search); return { state: params.get('donation') || '', reference: params.get('reference') || '', mode: params.get('mode') || '', }; } function DonateWidget({ compact }) { const [mode, setMode] = useState('month'); // 'month' | 'once' const [amount, setAmount] = useState(1000); const [custom, setCustom] = useState(''); const [fields, setFields] = useState({ name: '', email: '', phone: '', consent: false }); const [errors, setErrors] = useState({}); const [submitError, setSubmitError] = useState(''); const [submitting, setSubmitting] = useState(false); const [feedback, setFeedback] = useState(() => ({ loading: false, state: '', text: '' })); const isCustom = amount === 'custom'; const value = isCustom ? (parseInt(custom, 10) || 0) : amount; const impact = !isCustom && IMPACT_BY_AMOUNT[amount]; useEffect(() => { const params = readDonationParams(); if (!params.state) return; let cancelled = false; if (params.state === 'success' && params.reference) { setFeedback({ loading: true, state: 'info', text: 'Проверяем статус платежа…' }); const checkStatus = (attempt = 0) => { fetch(`/api/donation-status?reference=${encodeURIComponent(params.reference)}&t=${Date.now()}`, { cache: 'no-store' }) .then((response) => { if (!response.ok) throw new Error('status'); return response.json(); }) .then((payload) => { if (cancelled) return; if (payload.paymentStatus === 'paid') { reachDonationGoal( params.mode === 'month' ? 'pay_repeat' : 'pay_once', params.reference, payload.amountRub ); setFeedback({ loading: false, state: 'success', text: params.mode === 'month' ? 'Спасибо. Ежемесячное пожертвование оформлено, первое списание прошло успешно.' : 'Спасибо. Платёж прошёл успешно.', }); return; } if (attempt < 14) { setTimeout(() => checkStatus(attempt + 1), attempt < 4 ? 2000 : 5000); return; } setFeedback({ loading: false, state: 'info', text: 'Платёжная форма завершена. Ожидаем подтверждение от платёжного сервиса.', }); }) .catch(() => { if (cancelled) return; if (attempt < 14) { setTimeout(() => checkStatus(attempt + 1), attempt < 4 ? 2000 : 5000); return; } setFeedback({ loading: false, state: 'info', text: 'Платёжная форма завершена. Если деньги списались, подтверждение появится через несколько секунд.', }); }); }; checkStatus(); return () => { cancelled = true; }; } if (params.state === 'success') { setFeedback({ loading: false, state: 'info', text: 'Платёжная форма завершена. Ожидаем подтверждение от платёжного сервиса.', }); return; } if (params.state === 'fail') { setFeedback({ loading: false, state: 'error', text: 'Платёж не был завершён. Проверьте данные карты или попробуйте ещё раз.', }); } }, []); const setField = (key) => (event) => { const nextValue = key === 'consent' ? event.target.checked : event.target.value; setFields((current) => ({ ...current, [key]: nextValue })); setErrors((current) => (current[key] ? { ...current, [key]: '' } : current)); }; const validate = () => { const nextErrors = {}; if (value < 50) nextErrors.amount = 'Минимальная сумма пожертвования — 50 ₽'; if (!fields.email.trim() || !fields.email.includes('@')) nextErrors.email = 'Укажите корректный e-mail'; if (!fields.consent) nextErrors.consent = 'Нужно согласие с условиями пожертвования'; setErrors(nextErrors); return Object.keys(nextErrors).length === 0; }; const handleDonate = async () => { if (submitting) return; if (!validate()) return; setSubmitting(true); setSubmitError(''); try { const response = await fetch('/api/create-donation', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode, amount: value, name: fields.name.trim(), email: fields.email.trim(), phone: fields.phone.trim(), consent: fields.consent, }), }); const payload = await response.json(); if (!response.ok) { throw new Error(payload.error || 'Не удалось создать платёж'); } window.location.href = payload.redirectUrl; } catch (err) { setSubmitError(err.message || 'Не удалось создать платёж'); setSubmitting(false); } }; return (
Ваше пожертвование — это не абстракция. Это конкретные встречи, поддержка психолога и ощущение ребёнка, что он кому-то по-настоящему нужен.
{w.text}
{a}
}