/* ============================================================ 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 (
{mode === 'month' && (
Регулярная помощь — самая ценная: она даёт фонду уверенность в завтрашнем дне.
)}
{PRESETS.map((p) => ( ))}
{isCustom && (
setCustom(e.target.value)} autoFocus />
)} {impact && (
{fmt(amount)} ₽ — это {impact}
)}
{errors.email &&
{errors.email}
}
{errors.consent &&
{errors.consent}
} {errors.amount &&
{errors.amount}
} {feedback.text && (
{feedback.text}
)} {submitError && (
{submitError}
)}
Безопасная оплата картой
МИР VISA MC
); } /* ---------- full donate page ---------- */ const DONATE_WAYS = [ { icon: 'repeat', tone: 'green', title: 'Регулярно', text: 'Оформите ежемесячное пожертвование — это самая нужная фонду помощь.' }, { icon: 'heart', tone: 'orange', title: 'Разово', text: 'Любая сумма в любой момент. Каждое пожертвование идёт в дело.' }, { icon: 'hands', tone: 'blue', title: 'Бизнесом', text: 'Компании помогают фонду финансово и делами — расскажем как.' }, ]; const DONATE_FAQ = [ { q: 'Куда идут мои деньги?', a: 'На подбор и обучение наставников, работу психолога, встречи и поездки для подростков и развитие проекта «Наставничество». Мы публикуем годовые отчёты в открытом доступе.' }, { q: 'Это безопасно?', a: 'Да. Оплата проходит через защищённый платёжный сервис Best2Pay от СовкомБанка. Данные вашей карты не попадают в фонд и передаются в зашифрованном виде.' }, { q: 'Можно ли отменить ежемесячное пожертвование?', a: 'Конечно. Вы в любой момент можете изменить сумму или отменить регулярный платёж — напишите нам на info@fond-nl.ru.' }, { q: 'Дадите ли вы документы?', a: 'Да, мы предоставляем все необходимые документы о пожертвовании. Свяжитесь с нами, и мы всё оформим.' }, ]; function DonatePage() { return (
Помочь фонду

Подарите подростку надёжного взрослого

Ваше пожертвование — это не абстракция. Это конкретные встречи, поддержка психолога и ощущение ребёнка, что он кому-то по-настоящему нужен.

{DONATE_WAYS.map((w, i) => { const map = { green: 'var(--green)', orange: 'var(--orange-600)', blue: 'var(--blue-700)' }[w.tone]; const bg = { green: 'var(--green-50)', orange: 'var(--orange-50)', blue: 'var(--blue-50)' }[w.tone]; return (

{w.title}

{w.text}

); })}

Частые вопросы

{DONATE_FAQ.map((f, i) => )}

На что пойдёт ваша помощь

{IMPACT.map((it, i) => (
{it.title} {it.text}
))}
); } function DonateNotice() { const { go } = useNav(); return (

Перед внесением пожертвования ознакомьтесь с{' '} { e.preventDefault(); go('politika'); }}>политикой безопасности платежей{' '} и с условиями договора-оферты.

); } function FaqItem({ q, a }) { const [open, setOpen] = useState(false); return (
{open &&

{a}

}
); } Object.assign(window, { DonateWidget, DonatePage, FaqItem, DonateNotice });