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

Благодійний фонд втрачає пожертви через збої при онлайн-оплаті, а донори не повертаються після невдалих спроб. Ми розробляємо портали для фондів із надійними рекурентними платежами та прозорою звітністю, реалізуючи проєкт під ключ. Наша команда забезпечує стабільну роботу та подальшу підтримку, щоб ваш фонд отримував регулярну допомогу без зриву термінів.

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

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

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

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

Фонд втрачає до 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 'USD',
    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 'USD',
    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': 'USD',
        },
        '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': 'USD'},
                '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': 'USD'},
                '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% — зв'яжіться, ми підготуємо пропозицію. Отримайте консультацію: заповніть форму на сайті — ми обговоримо ваш проект і підготуємо комерційну пропозицію.

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

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

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1394
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1369
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1142
  • Розробка веб-сайту для компанії ФІКСПЕР
    Розробка веб-сайту для компанії ФІКСПЕР
    1060