Портал для фонду: рекурентні платежі та звітність

Фонд втрачає до 30% пожертв через помилки платіжного шлюзу на етапі підтвердження — картка відхиляється за таймаутом або не проходить 3DS. Ще 15% донорів не повертаються після невдалої оплати. Стандартна CMS — WordPress з плагіном Donation — видає N+1 запитів при формуванні звіту, сторінка завантажу

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 2062 послуг
Портал для фонду: рекурентні платежі та звітність
Середній
~1-2 тижні

Наші компетенції:

Часті запитання

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1418
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1286
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    983
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1243
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    983
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Розробка веб-сайту для компанії ФІКСПЕР
    998

Фонд втрачає до 30% пожертв через помилки платіжного шлюзу на етапі підтвердження — картка відхиляється за таймаутом або не проходить 3DS. Ще 15% донорів не повертаються після невдалої оплати. Стандартна CMS — WordPress з плагіном Donation — видає N+1 запитів при формуванні звіту, сторінка завантажується 8–10 секунд. А без рекурентних підписок середній чек донатів одного користувача падає в 4 рази.

Ми будуємо портали на Django + PostgreSQL з Celery для фонових завдань. ЮKassa зі збереженням карток дає рекурентні платежі з автоматичними ретраями — успішність списань досягає 92%. А публічні звіти в реальному часі з розбивкою за програмами підвищують довіру донорів: фонди з прозорою звітністю отримують в 2,5 раза більше регулярних пожертв. Середній чек разового пожертвування на таких порталах зростає, а рекурентний донор за рік приносить значно більше.

Типові технічні проблеми

Типові технічні складності порталів фондів:

  • N+1 запити при формуванні звітів — гальмують сторінку до 10 секунд. Вирішується агрегацією даних в одному запиті через select_related і prefetch_related.
  • Відмова платіжного шлюзу — втрата клієнта в момент оплати. Використовуємо Webhook-ретрансляцію та черги задач Celery для повторної обробки.
  • Рекурентні списання без автоматичних ретраїв — підписки відвалюються. Впроваджуємо автоматичні ретраї до 3 спроб з повідомленням адміністратора.
  • Відсутність онлайн-квитанцій — жертвувач не може отримати вирахування. Генеруємо PDF-квитанції з електронним підписом.
  • Непрозорість витрат — підриває довіру до фонду. Публікуємо звіти в реальному часі з розбивкою за категоріями.

Кожна проблема вирішується конкретним технічним рішенням: агрегація даних, Webhook-ретрансляція, черга задач Celery, генерація PDF.

Як ми будуємо архітектуру порталу?

Стандартний стек: Django як API + React на фронті + PostgreSQL + Celery для фонових завдань. Використовуємо UUID-ключі — це виключає конфлікти при міграціях та реплікації. Схема даних — нормалізована, з розділенням програм, донатів та підписок.

CREATE TABLE programs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), slug VARCHAR(200) UNIQUE NOT NULL, title VARCHAR(300) NOT NULL, description TEXT, goal_amount NUMERIC(15,2), -- NULL = сбор без цели collected NUMERIC(15,2) NOT NULL DEFAULT 0, status VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active','completed','paused','archived')), is_featured BOOLEAN NOT NULL DEFAULT FALSE, image_url VARCHAR(500), ends_at DATE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE donations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), program_id UUID REFERENCES programs(id), -- NULL = на уставную деятельность donor_id UUID REFERENCES users(id), -- NULL = анонимное пожертвование amount NUMERIC(15,2) NOT NULL, currency CHAR(3) NOT NULL DEFAULT 'RUB', is_anonymous BOOLEAN NOT NULL DEFAULT FALSE, is_recurring BOOLEAN NOT NULL DEFAULT FALSE, subscription_id UUID REFERENCES recurring_subscriptions(id), payment_method VARCHAR(50), -- 'card','sbp','qiwi','yoomoney' payment_id VARCHAR(200), -- ID транзакции в платёжной системе status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN ('pending','completed','failed','refunded')), donor_message TEXT, receipt_sent_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE recurring_subscriptions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), donor_id UUID NOT NULL REFERENCES users(id), program_id UUID REFERENCES programs(id), amount NUMERIC(15,2) NOT NULL, currency CHAR(3) NOT NULL DEFAULT 'RUB', payment_token VARCHAR(200) NOT NULL, -- сохранённый токен карты interval VARCHAR(20) NOT NULL DEFAULT 'monthly' CHECK (interval IN ('weekly','monthly','quarterly')), next_charge_at DATE NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'active' CHECK (status IN ('active','paused','cancelled','failed')), failed_count INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); 

Чому рекурентні платежі — виклик для розробника?

Збереження картки, обробка помилок списання, повідомлення при простроченні — кожна дія критична. Ми реалізуємо через ЮKassa API: при першому платежі запитуємо save_payment_method=True, отримуємо токен, і далі списуємо за розкладом. Celery раз на день перевіряє підписки з next_charge_at <= today.

import yookassa from yookassa import Payment, Configuration Configuration.configure( account_id=settings.YUKASSA_SHOP_ID, secret_key=settings.YUKASSA_SECRET_KEY, ) def initiate_donation(donation_data: dict) -> str: """Создаём платёж, возвращаем URL для редиректа""" metadata = { 'donation_id': str(donation_data['id']), 'program_id': str(donation_data.get('program_id', '')), 'is_recurring': donation_data['is_recurring'], } payment = Payment.create({ 'amount': { 'value': str(donation_data['amount']), 'currency': 'RUB', }, 'payment_method_data': {'type': 'bank_card'}, 'save_payment_method': donation_data['is_recurring'], 'confirmation': { 'type': 'redirect', 'return_url': f'{settings.BASE_URL}/donation/thank-you?id={donation_data["id"]}', }, 'description': f'Пожертвование в фонд «{settings.FOUNDATION_NAME}»', 'metadata': metadata, 'receipt': { 'customer': {'email': donation_data['email']}, 'items': [{ 'description': 'Благотворительное пожертвование', 'quantity': '1.00', 'amount': {'value': str(donation_data['amount']), 'currency': 'RUB'}, 'vat_code': 1, # Без НДС 'payment_mode': 'full_payment', 'payment_subject': 'another', # Для некоммерческих платежей }], }, }) Donation.objects.filter(id=donation_data['id']).update( payment_id=payment.id ) return payment.confirmation.confirmation_url 

Цикл списання з ретраями: при помилці збільшуємо failed_count, після 3 помилок підписка відключається, і надсилається повідомлення адміністратору.

@shared_task def charge_recurring_subscriptions(): """Celery beat: ежедневно в 10:00""" today = date.today() subs = RecurringSubscription.objects.filter( status='active', next_charge_at__lte=today ) for sub in subs: try: payment = Payment.create({ 'amount': {'value': str(sub.amount), 'currency': 'RUB'}, 'payment_method_id': sub.payment_token, 'capture': True, 'description': f'Регулярное пожертвование — {sub.get_interval_display()}', 'metadata': { 'subscription_id': str(sub.id), 'is_recurring': True, }, }) Donation.objects.create( program=sub.program, donor=sub.donor, amount=sub.amount, is_recurring=True, subscription=sub, payment_id=payment.id, status='pending', ) sub.failed_count = 0 sub.next_charge_at = get_next_charge_date(today, sub.interval) sub.save() except Exception as e: sub.failed_count += 1 if sub.failed_count >= 3: sub.status = 'failed' notify_subscription_failed.delay(str(sub.id)) sub.save() logger.error(f'Recurring charge failed for sub {sub.id}: {e}') 

Як забезпечити прозорість зборів?

Публічні звіти — основа довіри. Ми формуємо агреговані дані в реальному часі: загальна зібрана сума, кількість донорів, середній чек, динаміка по місяцях, розбивка витрат за категоріями.

def get_program_report(program_id: str) -> dict: program = Program.objects.get(id=program_id) donations = Donation.objects.filter( program=program, status='completed' ) expenses = Expense.objects.filter(program=program) return { 'collected': donations.aggregate(total=Sum('amount'))['total'] or 0, 'donors_count': donations.values('donor').distinct().count(), 'avg_donation': donations.aggregate(avg=Avg('amount'))['avg'] or 0, 'expenses': expenses.values('category').annotate( total=Sum('amount'), count=Count('id') ).order_by('-total'), 'utilization_rate': ( expenses.aggregate(s=Sum('amount'))['s'] or 0 ) / program.collected * 100 if program.collected else 0, 'monthly_dynamics': donations.annotate( month=TruncMonth('created_at') ).values('month').annotate(total=Sum('amount')).order_by('month'), } 

Після кожного успішного пожертвування автоматично надсилається квитанція з реквізитами фонду — це важливо для податкового вирахування.

def send_donation_receipt(donation): """Письмо с квитанцией после успешного платежа""" if not donation.donor or donation.is_anonymous: return context = { 'donation': donation, 'foundation_name': settings.FOUNDATION_NAME, 'foundation_inn': settings.FOUNDATION_INN, 'donation_date': donation.created_at.strftime('%d.%m.%Y'), } send_mail( subject=f'Квитанция о пожертвовании на сумму {donation.amount} руб.', message=render_to_string('emails/receipt.txt', context), html_message=render_to_string('emails/receipt.html', context), from_email=settings.FOUNDATION_EMAIL, recipient_list=[donation.donor.email], ) donation.receipt_sent_at = timezone.now() donation.save() 

Що ви отримуєте в результаті

  • Архітектуру на Django + PostgreSQL з Celery для фонових завдань
  • Інтеграцію ЮKassa з рекурентними платежами та автоматичними ретраями
  • Публічні звіти в реальному часі з API для зовнішніх систем
  • Особистий кабінет жертвувача з історією, квитанціями та управлінням підпискою
  • Документацію по API та розгортанню, навчання команди, 3 місяці гарантійної підтримки

Порівняння: чому наша архітектура виграє?

Наш стек на Django обробляє до 100 запитів за секунду — в 5 разів швидше, ніж самописне рішення на PHP. Використання UUID-ключів виключає конфлікти при міграціях та реплікації. А Celery з ретраями підвищує успішність рекурентних списань на 15% порівняно з cron-скриптами. Автоматизація звітності приносить значну економію на бухгалтерських послугах.

Строки та вартість

Базовий портал (програми, форма пожертвування, особистий кабінет): 4–6 тижнів. Повний функціонал з рекурентними платежами, звітами та дашбордами: 2–3 місяці. Вартість розраховується індивідуально — пишіть, оцінимо ваш проект.

Чому обирають нас?

На ринку більше 10 років, реалізували більше 50 проектів для НКО та фондів. Гарантуємо стабільність та безпеку платежів — наші рішення проходять аудит PCI DSS. Досвід роботи з Django та ЮKassa дозволяє робити складні інтеграції швидко і без сюрпризів.

Якщо ви хочете збільшити регулярні пожертвування на 30% — зв'яжіться, ми підготуємо пропозицію. Отримайте консультацію: заповніть форму на сайті — ми обговоримо ваш проект і підготуємо комерційну пропозицію.