При разработке краудфандинговой платформы инженер сталкивается с двумя классами проблем: гарантия корректного списания средств по модели All-or-Nothing и обеспечение real-time обновления прогресс-бара при пиковых нагрузках. Мы разберем, как решаем эти задачи с помощью Stripe Payment Intents, фоновых задач Celery и правильно спроектированной схемы данных PostgreSQL. Также рассмотрим типичные ошибки: потеря авторизации при длинных кампаниях, N+1 запросы при подсчете собранной суммы и race conditions при одновременном завершении кампании и поступлении платежей. Пишите — оценим ваш проект за 2 рабочих дня.
За 5+ лет мы реализовали более 30 платформ разных моделей. Один из проектов — платформа для независимых художников с гибридной моделью и автоматическими выплатами через Stripe Connect. Это позволило клиенту сократить время на выплаты на 80%.
Какие модели финансирования выбрать?
Прежде чем проектировать схему данных, нужно определиться с моделью сбора средств. От этого зависит логика платежей и статусов.
| Модель | Описание | Техническая сложность |
|---|---|---|
| All-or-Nothing (AON) | Средства списываются только при достижении цели. Требует pre-authorization или отложенный capture. | Высокая |
| Keep-it-All (KIA) | Средства списываются сразу, создатель получает всё. Проще юридически и технически. | Средняя |
| Гибридная | Цель фиксирована, при превышении открываются stretch goals. Сложная логика состояний. | Очень высокая |
Для стартапов чаще выбирают KIA из-за простоты реализации, но AON даёт больше доверия у бекеров. Необходимо учитывать требования платёжных систем к срокам холдирования. Например, Stripe держит авторизацию максимум 7 дней для карт — для более длинных кампаний нужен другой подход.
Схема данных: от кампаний до наград
CREATE TABLE campaigns (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
creator_id UUID NOT NULL REFERENCES users(id),
title VARCHAR(200) NOT NULL,
slug VARCHAR(200) UNIQUE NOT NULL,
description TEXT,
goal_amount NUMERIC(15,2) NOT NULL,
currency CHAR(3) NOT NULL DEFAULT 'RUB',
model VARCHAR(20) NOT NULL CHECK (model IN ('aon','kia','hybrid')),
status VARCHAR(20) NOT NULL DEFAULT 'draft'
CHECK (status IN ('draft','active','funded','failed','cancelled')),
starts_at TIMESTAMPTZ NOT NULL,
ends_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE pledges (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
campaign_id UUID NOT NULL REFERENCES campaigns(id),
backer_id UUID NOT NULL REFERENCES users(id),
amount NUMERIC(15,2) NOT NULL,
reward_id UUID REFERENCES rewards(id),
status VARCHAR(20) NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending','authorized','captured','refunded','failed')),
payment_intent VARCHAR(200),
captured_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE rewards (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
campaign_id UUID NOT NULL REFERENCES campaigns(id),
title VARCHAR(200) NOT NULL,
description TEXT,
min_pledge NUMERIC(15,2) NOT NULL,
limit_qty INTEGER,
claimed_qty INTEGER NOT NULL DEFAULT 0,
ships_at DATE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_pledges_campaign_status
ON pledges(campaign_id, status)
WHERE status IN ('authorized','captured');
Индекс idx_pledges_campaign_status критичен для быстрого подсчёта собранной суммы в real-time. Без него каждый рендер прогресс-бара вызывает полный seq scan. Подробнее: Stripe Payment Intents.
Как реализовать платёжный флоу для AON?
AON требует двухэтапной оплаты: авторизация (холдирование) и capture при успехе кампании. Stripe предоставляет capture_method='manual' для этого.
import stripe
stripe.api_key = settings.STRIPE_SECRET_KEY
def authorize_pledge(pledge, card_token):
intent = stripe.PaymentIntent.create(
amount=int(pledge.amount * 100),
currency=pledge.campaign.currency.lower(),
payment_method=card_token,
capture_method='manual',
confirm=True,
metadata={
'pledge_id': str(pledge.id),
'campaign_id': str(pledge.campaign_id),
}
)
pledge.payment_intent = intent.id
pledge.status = 'authorized'
pledge.save()
return intent
def capture_pledges_for_campaign(campaign_id):
pledges = Pledge.objects.filter(
campaign_id=campaign_id,
status='authorized'
)
for pledge in pledges:
try:
stripe.PaymentIntent.capture(pledge.payment_intent)
pledge.status = 'captured'
pledge.captured_at = timezone.now()
pledge.save()
except stripe.error.InvalidRequestError as e:
pledge.status = 'failed'
pledge.save()
logger.error(f'Capture failed for pledge {pledge.id}: {e}')
def cancel_pledges_for_campaign(campaign_id):
pledges = Pledge.objects.filter(
campaign_id=campaign_id,
status='authorized'
)
for pledge in pledges:
stripe.PaymentIntent.cancel(pledge.payment_intent)
pledge.status = 'refunded'
pledge.save()
Важно: Stripe держит authorization максимум 7 дней для карт. Для кампаний длиннее 7 дней нужна стратегия с сохранением платёжного метода и capture в последний день. Иначе pledge переходит в статус requires_payment_method, и бэкеру приходится повторно вводить карту.
Сохранение платёжных методов для длинных кампаний
Если кампания длится более недели, лучше сохранить карту через SetupIntent и выполнить capture в конце. Это снижает риск истечения авторизации. Мы используем off_session платежи для списания без участия бэкера.
Автоматизация завершения кампаний с Celery
Фоновые задачи проверяют дедлайны каждые 15 минут. При завершении кампании система запускает capture или cancel для всех pledge, а затем уведомляет создателя. Это гарантирует корректную обработку даже при пиковых нагрузках.
Типичные ошибки и как их избежать
- N+1 запросы: При выводе списка кампаний с суммой собранных средств каждый запрос к pledges вызывает отдельный SQL. Решение — агрегация через
SUMс индексомidx_pledges_campaign_statusили кэширование суммы в Redis. - Race condition при завершении кампании: Если платёж поступает в момент перехода кампании в статус 'funded', возможен конфликт. Используйте блокировки на уровне базы данных или Redis-замки.
Выплаты создателям через Stripe Connect
Stripe Connect — стандарт для маркетплейсов. Создатели проходят KYC-онбординг, после чего платформа может переводить средства напрямую на их счёт.
def create_connect_account(creator):
account = stripe.Account.create(
type='express',
country='RU',
email=creator.email,
capabilities={
'card_payments': {'requested': True},
'transfers': {'requested': True},
},
)
creator.stripe_account_id = account.id
creator.save()
link = stripe.AccountLink.create(
account=account.id,
refresh_url='https://site.com/dashboard/connect/refresh',
return_url='https://site.com/dashboard/connect/complete',
type='account_onboarding',
)
return link.url
def transfer_to_creator(campaign, net_amount):
transfer = stripe.Transfer.create(
amount=int(net_amount * 100),
currency=campaign.currency.lower(),
destination=campaign.creator.stripe_account_id,
metadata={'campaign_id': str(campaign.id)},
)
return transfer
Комиссионная модель платформы
Стандартная комиссия платформы составляет 5–8% от собранной суммы плюс транзакционные издержки. Stripe позволяет удержать комиссию автоматически через application_fee_amount. Мы помогаем клиентам оптимизировать комиссии: например, для проектов с оборотом свыше 5 млн рублей удаётся договориться о снижении ставки.
Что входит в работу
- Документация: техническое задание, описание API, инструкция по администрированию.
- Исходный код: полный код платформы с комментариями, CI/CD пайплайны.
- Доступы: права администратора, доступ к хостингу, Stripe Dashboard, репозиторию.
- Обучение: два сеанса для команды по администрированию и добавлению кампаний.
- Поддержка: 1 месяц гарантийной поддержки после деплоя, исправление багов.
Процесс работы
- Аналитика: изучаем бизнес-модель, целевую аудиторию, юридические ограничения.
- Проектирование: схема данных, выбор стека (React/Next.js, Laravel, PostgreSQL, Redis).
- Разработка: реализация MVP (авторизация, кампании, платежи).
- Тестирование: нагрузочное тестирование, проверка крайних случаев (AON, race conditions).
- Деплой: настройка CI/CD, мониторинг, обучение команды.
Сроки разработки
| Этап | Срок |
|---|---|
| MVP (KIA, базовые кампании, награды, Stripe) | 6–8 недель |
| Полная AON-платформа + Connect + stretch goals + email-уведомления + аналитика | 3–4 месяца |
| Интеграция дополнительных платёжных шлюзов (ЮKassa, PayPal) | +2–4 недели |
Сроки варьируются в зависимости от сложности требований и необходимости кастомизации. Стоимость рассчитывается индивидуально и зависит от объёма работ.
Свяжитесь с нами для оценки вашего проекта — мы подготовим детальное коммерческое предложение за 2 рабочих дня.







