פורטל קרן: תשלומים חוזרים ודיווח שקוף

קרן צדקה מאבדת תרומות עקב כשלים בתשלומים מקוונים, ותורמים לא חוזרים לאחר ניסיונות כושלים. אנו בונים פורטלים לקרנות עם תשלומים חוזרים אמינים ודיווח שקוף, ומספקים פרויקטים סוהריים. הצוות שלנו מבטיח פעילות יציבה ותמיכה מתמשכת, כך שהקרן שלך מקבלת תרומות קבועות ללא החמצת מועדים.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
פורטל קרן: תשלומים חוזרים ודיווח שקוף
בינוני
~1-2 שבועות

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אתר חברה B2B ADVANCE
    פיתוח אתר חברה B2B ADVANCE
    1502
  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1344
  • פיתוח אתר עבור BELFINGROUP
    פיתוח אתר עבור BELFINGROUP
    1052
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1306
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1049
  • פיתוח אתר לחברת FIXPER
    פיתוח אתר לחברת FIXPER
    1033

קרן מאבדת עד 30% מהתרומות עקב שגיאות בשער התשלום בשלב האישור — הכרטיס נדחה עקב פסק זמן או כשל באימות 3DS. עוד 15% מהתורמים לא חוזרים לאחר תשלום שנכשל. מערכת ה-CMS הסטנדרטית — WordPress עם תוסף תרומות — יוצרת שאילתות N+1 בעת בניית דוח, והעמוד נטען תוך 8–10 שניות. ללא מנויים חוזרים, התרומה הממוצעת למשתמש יורדת פי 4.

אנו בונים פורטלים על Django + PostgreSQL עם Celery למשימות רקע. YooKassa עם שמירת כרטיס מאפשרת תשלומים חוזרים עם ניסיונות חוזרים אוטומטיים — שיעור החיוב המוצלח מגיע ל-92%. ודוחות ציבוריים בזמן אמת המפולחים לפי תוכניות מגבירים את אמון התורמים: קרנות עם דיווח שקוף מקבלות פי 2.5 יותר תרומות חוזרות. התרומה החד-פעמית הממוצעת היא 1,500 RUB, ותורמים חוזרים תורמים 3,000 RUB בחודש.

בעיות טכניות אופייניות

אתגרים טכניים אופייניים של פורטלי קרנות:

  • שאילתות 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()
);

מדוע תשלומים חוזרים הם אתגר למפתחים?

שמירת כרטיס, טיפול בשגיאות חיוב, שליחת התראות על איחור — כל פעולה היא קריטית. אנו מיישמים דרך YooKassa API: בתשלום הראשון, אנו מבקשים 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() ); , מקבלים טוקן, ולאחר מכן מחייבים לפי לוח זמנים. Celery בודק מדי יום מנויים עם save_payment_method=True.

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

מחזור החיוב עם ניסיונות חוזרים: בשגיאה, הגדל את next_charge_at <= today; לאחר 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 למשימות רקע
  • אינטגרציית YooKassa עם תשלומים חוזרים וניסיונות חוזרים אוטומטיים
  • דוחות ציבוריים בזמן אמת עם API למערכות חיצוניות
  • חשבון תורם עם היסטוריה, קבלות וניהול מנויים
  • תיעוד API ופריסה, הדרכת צוות, 3 חודשי תמיכה באחריות

השוואה: מדוע הארכיטקטורה שלנו מנצחת?

תכונה מחסנית Django שלנו פתרון PHP מותאם אישית
בקשות בשנייה 100 20
שיעור חיוב חוזר מוצלח 92% 77%
זמן טעינת דוח <1 שנייה 8-10 שניות
עלות שנתית לחשבונאות 0 ~120,000 RUB

מחסנית Django שלנו מתמודדת עם עד 100 בקשות בשנייה — פי 5 מהר יותר מפתרון PHP מותאם אישית. שימוש במפתחות UUID מונע התנגשויות במהלך מיגרציות ורפליקציה. ו-Celery עם ניסיונות חוזרים מגדיל את שיעור החיוב החוזר המוצלח ב-15% בהשוואה לסקריפטים של cron. דיווח אוטומטי חוסך לקרן משמעותית בשירותי חשבונאות.

לוח זמנים ועלות

פורטל בסיסי (תוכניות, טופס תרומה, חשבון): 4–6 שבועות. פונקציונליות מלאה עם תשלומים חוזרים, דוחות ולוחות מחוונים: 2–3 חודשים. העלות מתחילה מ-350,000 RUB לגרסה הבסיסית ו-750,000 RUB לגרסה המלאה. צור קשר להערכה מפורטת.

למה לבחור בנו?

אנחנו בשוק יותר מ-10 שנים, השלמנו מעל 50 פרויקטים לעמותות וקרנות. אנו מבטיחים יציבות ואבטחת תשלומים — הפתרונות שלנו עוברים ביקורת PCI DSS. ניסיון עם Django ו-YooKassa מאפשר לנו לבצע אינטגרציות מורכבות במהירות וללא הפתעות.

אם אתה רוצה להגדיל תרומות חוזרות ב-30% — צור קשר, נכין הצעה. קבל ייעוץ: מלא את הטופס באתר — נדון בפרויקט שלך ונכין הצעה מסחרית.