При розробці краудфандингової платформи інженер стикається з двома класами проблем: гарантія коректного списання коштів за моделлю 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. Ми допомагаємо клієнтам оптимізувати комісії: наприклад, для проектів з великим оборотом вдається домовитися про зниження ставки.
Що входить у роботу
- Документація: технічне завдання, опис 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 місяці |
| Інтеграція додаткових платіжних шлюзів (WayForPay, PayPal) | +2–4 тижні |
Терміни варіюються залежно від складності вимог та необхідності кастомізації. Вартість розраховується індивідуально та залежить від обсягу робіт.
Зв'яжіться з нами для оцінки вашого проекту — ми підготуємо детальну комерційну пропозицію за 2 робочі дні.







