При розробці краудфандингової платформи інженер стикається з двома класами проблем: гарантія коректного списання коштів за моделлю 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 робочі дні.







