Stripe Connect Marketplace: архитектура Direct vs Express vs Custom

Практическая архитектура маркетплейса на Stripe Connect. Когда выбирать Direct/Standard, Express или Custom, как устроить потоки денег, комиссии, онбординг, вебхуки и бухгалтерию.
Ошибиться с моделью Stripe Connect на старте — это год технического долга: мигрировать продавцов, перестраивать флоу возвратов и спорив, переписывать учёт. В этой статье разложим архитектуру маркетплейса на Stripe Connect и разницу Direct (Standard), Express и Custom так, чтобы решение можно было принять за час, а не за квартал.
Короткий ответ: если вам нужен минимальный комплаенс и продавцы сами управляют выплатами — Standard/Direct. Если важны быстрый онбординг, контроль комиссий и бренд платформы — Express с destination charges. Если требуется полный white‑label и кастомные пайплайны выплат/рисков — Custom со separate charges and transfers и своим леджером. Детали — ниже, с кодом и производственными граблями.
Когда что выбирать
Простая матрица выбора
-
Вы — каталог/лид‑генератор, продавцы самостоятельны, вам нужна только комиссия за транзакцию, а поддержку держать не хотите.
- Выбор: Standard (Direct). Плюсы — быстрый запуск, меньше обязанностей по комплаенсу. Минусы — ограниченный контроль комиссий и UX разнится между продавцами.
-
Вы — маркетплейс с единой корзиной, возвратами через платформу, прозрачной комиссией, хотите контролировать расписание выплат.
- Выбор: Express + destination charges (или separate + transfers в сложных сценариях). Плюсы — контроль потоков денег и бренд, упрощённый онбординг. Минусы — больше обязанностей по рискам/спорам.
-
Вы — платёжный слой под капотом SaaS/супер‑маркетплейс, нужен полный white‑label, сложные схемы сплитов, удержания эскроу, кастомный скрининг.
- Выбор: Custom + separate charges and transfers. Плюсы — максимальная гибкость. Минусы — максимальная ответственность и время вывода в прод.
Одна инженерная шутка на дорожку: если вы не знаете, какой вариант вам нужен, почти наверняка вам пока не нужен Custom.
Архитектура потоков денег
Три базовых схемы
- Direct charges (на connected аккаунте)
- Чардж создаётся на аккаунте продавца. Stripe комиссию списывает с него. Площадка видит транзакцию, но почти не влияет на выплаты.
- Место: Standard чаще всего, но возможен и для Express/Custom.
- Комиссию платформы удерживать сложно: нет автоматического
application_fee_amountв некоторых режимах Standard; обычно её реализуют как отдельный инвойс/подписку или переходят на destination charges.
- Destination charges (чардж на платформе, автоперевод продавцу)
PaymentIntentсоздаётся на платформе, деньги зачисляются на баланс платформы, а затем Stripe автоматически переводит нетто‑сумму продавцу с учётомapplication_fee_amount.- Ответственность за споры/чарджбеки и процессинг — у платформы. Stripe может автоматически реверсировать трансфер при возврате/споре.
- Удобно для Express (и Custom), позволяет удерживать комиссию прозрачно.
- Separate charges and transfers (раздельные чардж и трансфер)
- Платформа создаёт чардж на себя, а затем отдельный
Transferна продавца. Вы гибко управляете таймингом перевода, частичными/отложенными выплатами, эскроу. - При возврате нужно вручную делать
Transfer Reversal. Больше контроля = больше обязанностей.
Референс‑архитектура (Express + destination charges)
Инструменты в продакшене, которые мы видим чаще всего:
- Клиент: веб/мобайл, Stripe Elements/Checkout, SCA/3DS встроен.
- Бэкенд: Node.js/TypeScript, Stripe SDK; авторизация продавцов через Connect Onboarding.
- Леджер: Postgres (двойная запись), денормализованный репортинг в ClickHouse/BigQuery.
- Очереди: Redis + BullMQ/RabbitMQ для асинхронных выплат/реверсов.
- Вебхуки: публичная конечная точка за Cloudflare/NGINX, валидация сигнатуры, ретраи с идемпотентностью. Для надёжности уместно вынести приём вебхуков в edge‑слой (см. наш разбор про инфраструктуру Cloudflare).
- Мониторинг: Sentry + Prometheus + Stripe Radar сигналы модерации.
Поток:
- Покупатель инициирует оплату. Клиент получает
client_secretдляPaymentIntent. - Платформа создаёт
PaymentIntentсtransfer_data.destination= connected аккаунт продавца иapplication_fee_amount= комиссия платформы. - Stripe выполняет SCA/3DS, подтверждает платёж, генерит вебхуки
payment_intent.succeeded/charge.succeeded. - Ваш бэкенд валидирует вебхук, фиксирует транзакции в леджере, обновляет статусы заказа, выстраивает SLA на доставку.
- Платформа управляет возвратами. Stripe реверсирует трансфер продавцу автоматически при
destination charges.
Пример кода: создание PaymentIntent c destination charges
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16',
});
// Суммы в центах. Продавец — connected account.
export async function createIntent({
amountCents,
currency,
connectedAccountId,
platformFeeCents,
customerId,
}: {
amountCents: number;
currency: string;
connectedAccountId: string;
platformFeeCents: number;
customerId?: string;
}) {
const intent = await stripe.paymentIntents.create({
amount: amountCents,
currency,
customer: customerId,
payment_method_types: ['card'],
capture_method: 'automatic', // для отложенного — 'manual'
description: `Order to seller ${connectedAccountId}`,
transfer_data: {
destination: connectedAccountId,
},
application_fee_amount: platformFeeCents,
});
return intent.client_secret;
}
Леджер маркетплейса (двойная запись)
Внутренний леджер — не роскошь, а необходимость. Он позволяет сверять итоговые балансы с Stripe и корректно считать доход платформы.
- Таблицы:
ledger_entries(debit/credit),ledger_accounts(platform_revenue, seller_payable, fees, cash),transactions(ссылка на Stripecharge/pi). - Инвариант: сумма дебетов = сумма кредитов.
- Для destination charges логируем: дебет
cash(брутто), кредитseller_payable(нетто продавцу), кредитplatform_revenue(комиссия), дебетfees(процессинг). Баланс с Stripe сверяется по вебхукам.
Простейший SQL‑эскиз зависимостей:
-- Упрощённо: две проводки на комиссию и выплату продавцу
insert into ledger_entries (tx_id, account, side, amount_cents, currency)
values
(:tx, 'cash', 'debit', :amount, :cur),
(:tx, 'platform_revenue', 'credit', :platform_fee, :cur),
(:tx, 'seller_payable', 'credit', :amount - :platform_fee - :processing_fee, :cur),
(:tx, 'processing_fees', 'debit', :processing_fee, :cur);
Онбординг и комплаенс: Standard vs Express vs Custom
Standard (часто называют Direct)
- Продавец создаёт/подключает свой стандартный Stripe‑аккаунт через OAuth.
- Своим дашбордом и выплатами управляет сам продавец, поддержка — на его стороне.
- KYC/AML и верификация — напрямую со Stripe. Платформа почти не вовлечена.
- Платформенная комиссия — ограниченно: надёжнее реализуется не через прямые списания, а через destination charges или абонентскую модель.
Express
- Хостед‑онбординг от Stripe, но платформа контролирует ключевые параметры аккаунта и расписание выплат.
- Продавец видит упрощённый Express Dashboard (бренд платформы), часть поддержки — на стороне платформы.
- Легко удерживать комиссию через
application_fee_amount. - Хороший баланс между скоростью запуска и контролем.
Custom
- Полный white‑label: вы собираете KYC, загружаете документы, управляете capability (
card_payments,transfers). - Максимальная гибкость: сложные сплиты, эскроу, собственные антифрод‑правила поверх Stripe Radar.
- Также максимальная ответственность: поддержка, споры, отчётность налоговых форм (напр., 1099 в США), регуляторные обязанности в юрисдикциях.
Управление требованиями (requirements)
- Любой Connect‑аккаунт имеет поля
requirements.currently_due,eventually_due,past_due. - В проде критично синхронизировать статусы и блокировать приёмы платежей при
past_due. - Онбординг запускается через
account_links(refresh/return URL), для Custom — собственные формы +stripe.accounts.update.
Сравнение Direct (Standard) vs Express vs Custom
| Критерий | Standard (Direct) | Express | Custom |
|---|---|---|---|
| Онбординг | OAuth на Stripe, продавец владеет аккаунтом | Хостед‑онбординг, бренд платформы | Полный кастом, вы собираете KYC |
| Контроль выплат | Продавец | Платформа задаёт расписание | Платформа полностью контролирует |
| Где создаётся чардж | Обычно на аккаунте продавца (direct) | На платформе (destination/sep+transfer) | На платформе (чаще sep+transfer) |
| Удержание комиссии | Ограниченно/опосредованно | Простое через application_fee_amount | Любая логика через transfers |
| Ответственность за споры | Чаще продавец (direct) | Платформа (destination/sep) | Платформа |
| Поддержка и UX | Разнородный, Stripe UI | Единый Express UI | Полный white‑label |
| Комплаенс нагрузка | Минимальная | Средняя | Высокая |
| Сложность реализации | Низкая | Средняя | Высокая |
Примечание: для Standard с destination charges часть строк меняется (споры и комиссия на стороне платформы), но часто Standard выбирают именно ради «минимум обязанностей», то есть direct charges и слабое вмешательство платформы.
Возвраты, чарджбеки и реверсы: не перепутайте механики
- Destination charges: при
refund/disputeStripe может автоматически реверсировать перевод продавцу. Ваш леджер должен создать контр‑проводки, но ручнойTransfer Reversalобычно не нужен. - Separate charges and transfers: возврат денег покупателю НЕ реверсирует перевод продавцу. Нужно вызывать
transfers.reversals.createна сумму возврата. Не забудьте о частичных возвратах и комиссии платформы.
Пример вебхука с реверсом трансфера для separate‑модели:
import { Request, Response } from 'express';
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2023-10-16' });
export async function stripeWebhook(req: Request, res: Response) {
const sig = req.headers['stripe-signature'] as string;
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
} catch (err) {
return res.status(400).send(`Webhook Error: ${(err as Error).message}`);
}
if (event.type === 'charge.refunded') {
const charge = event.data.object as Stripe.Charge;
const transferId = charge.transfer as string | null; // Для sep+transfer может быть null, храните связь у себя
// Находим связанный transfer у себя в БД
const xfer = await findTransferByCharge(charge.id);
if (xfer) {
const refunded = charge.amount_refunded ?? 0;
// Идемпотентность обязательна при ретраях вебхуков
await stripe.transfers.createReversal(xfer.transferId, {
amount: refunded,
}, {
idempotencyKey: `rev_${xfer.transferId}_${refunded}`,
});
await markTransferReversedInLedger(xfer.transferId, refunded);
}
}
res.json({ received: true });
}
Что ломается в продакшене
- Идемпотентность: любые операции, которые может триггерить повторный вебхук/ретрай клиента, — только с идемпотентными ключами. Иначе двойные возвраты и дубли выплат.
- Порядок вебхуков:
payment_intent.succeededможет прийти раньше, чем вы сохраните заказ. Стройте обработку как конечный автомат с повторяемыми шагами. - Негативные балансы: при волнe чарджбеков/возвратов у продавца уходит баланс в минус. Решайте заранее: удержание из будущих выплат, дебетование карты продавца, стоп продаж.
- Конвертации и копейки: рубли → евро → доллары — округления порождают «хвосты». Леджер должен уметь учитывать остатки в минимальных единицах валют.
- Частичный capture: при
capture_method=manualследите за окном дедлайна capture и корректным распределением комиссий на «захваченную» сумму. - SCA/3DS: off_session платежи для подписок/отложенных списаний требуют
setup_future_usageи fallback на email/ин‑апп подтверждение. - Capabilities и KYC‑сроки: если требования перешли в
past_due, выплаты блокируются внезапно. Нужны фоновые проверки и коммуникация с продавцом. - Тест/боевой режимы: перепутанные ключи или
stripe_accountзаголовок — классика. Введите принудительный отдельный проект/пайплайн для live‑ключей. - Отказы выплат: неверные реквизиты, изменения банка, лимиты — готовьте автоматическую реактивацию/повторные выплаты и тикеты на поддержку.
Стоимость и ROI: когда окупается контроль над платежами
Сколько стоит правильная реализация — вопрос с дельтой, зато сама дельта объясняет выбор модели.
- Discovery и схемы потоков, оценка рисков/страны/налогов: 2–4 недели.
- MVP (Express + destination charges, одна страна, карты): 6–10 недель разработки, плюс 2–4 недели сертификаций/юридических процедур.
- Custom/sep+transfer с эскроу, многосторонними сплитами, сложным леджером: 12–20 недель.
Затраты по Stripe: стандартная комиссия процессинга плюс надбавка за Connect (за активные аккаунты/выплаты и др.). Чистые цифры зависят от региона и способов оплаты — проверяйте актуальные прайсы в вашем аккаунте Stripe.
Когда окупается Express/Custom:
- Комиссия платформы > 0.5–1% оборота и/или нужен единый UX возвратов и споров — контроль начала окупаться.
- Нужны альтернативные рельсы (банковские переводы, локальные методы, комбинированные корзины). Здесь Connect облегчает маршрутизацию. Мы нередко обсуждаем и локальные схемы вроде PIX в Бразилии — см. наш контекстный обзор Brazil: Pix vs Visa/Mastercard.
- Требуется масштаб в нескольких странах: Express/Custom проще расширять матрицей capabilities и правилом маршрутизации валют.
Скрытые расходы, которые недооценивают:
- Поддержка продавцов: блокировки KYC, фейлы выплат, споры — это время команды.
- Внутренний учёт и сверки: BI/финансы захотят отчёты в разрезе дня/валют/продавцов. Готовьте пайплайн экспорта (Stripe Sigma/Reports → DWH).
- Налоги: в США — 1099/K; в ЕС — НДС/OSS. Для Custom ответственность выше. Проконсультируйтесь с налоговыми консультантами до релиза, а не после первой проверки.
Пошаговый план внедрения (для Express)
- Проектирование
- Решить про charge‑тип: для 80% сценариев достаточно destination charges.
- Спроектировать леджер с двойной записью и событиями возвратов/реверсов.
- Онбординг продавцов
- Создавать
accountc типомexpress, capabilitiescard_payments,transfers. - Генерировать
account_links(refresh/return) и держать state‑машину требований.
- Платёжный поток
- Создание
PaymentIntentсtransfer_data.destinationиapplication_fee_amount. - Включить SCA,
setup_future_usageдля повторных платежей.
- Вебхуки и очереди
- Обрабатывать
payment_intent.succeeded,charge.dispute.created,charge.refunded,payout.*. - Идемпотентность + повторная обработка.
- Возвраты/споры
- Для destination — опираться на авто‑реверс трансферов, обновлять леджер.
- Для separate — реализовать
transfer reversal.
- Выплаты
- Контролировать расписание (
weekly,manual), negative balance policy.
- Отчётность
- Экспортировать в DWH, автоматизировать сверки по датам и валютам, SLA на закрытие периода.
Частые архитектурные вопросы
Какую модель выбрать для смешанной корзины (несколько продавцов в одном заказе)?
- Чаще всего: на каждую группу товаров по продавцу — отдельный
PaymentIntentс собственнымtransfer_data.destination. Если нужен один чардж с разбиением — это уже separate charges and transfers и ваш леджер обязан правильно делить комиссию/налоги.
Как брать комиссию платформы поверх подписок?
- Используйте Billing с
application_fee_percentдля подписок на connected аккаунтах (Express/Custom) илиapplication_fee_amountна инвойсах. В Standard с direct‑моделью это сделать сложнее — рассмотрите смену схемы на destination charges.
Что с альтернативными платёжными методами (банковские переводы, wallets)?
- Выбирайте по странам: SEPA/ACH — удобны для payouts и больших чеков; локальные кошельки улучшают конверсию. В Connect нужно проверить поддержку метода для типа аккаунта и страны.
Можно ли мигрировать со Standard на Express/Custom потом?
- Да, но это дороже, чем выбрать правильно сразу. Придётся заново онбордить продавцов, менять charge‑тип и логику возвратов/споров. Планируйте миграцию пакетами с бэкап‑планом.
Заключение
- Standard/Direct — быстро и почти без забот, но с ограниченным контролем и монетизацией.
- Express — оптимум для большинства маркетплейсов: понятный онбординг, прозрачный сплит, адекватная нагрузка на поддержку.
- Custom — берите только когда точно понимаете, зачем вам полный контроль леджера и рисков.
Если вы строите долгоживущий маркетплейс, не экономьте на леджере, вебхуках и сценариях реверсов. Это окупится в первую же волну возвратов.
FAQ
Q: Можно ли в Standard удерживать комиссию платформы автоматически? A: Надёжнее это делается через destination charges или отдельные биллинговые механики. В direct‑модели Standard автоматическое удержание комиссии ограничено и зависит от схемы.
Q: Что выбрать для мгновенных выплат продавцам? A: Express или Custom. Для instant payouts проверьте доступность по стране и карточным рельсам, плюс политику рисков и negative balance.
Q: Как обработать частичный возврат при separate charges and transfers?
A: Делаете refund чарджа на нужную сумму, затем создаёте transfer reversal на ту же сумму (или на пропорциональную часть), обновляете леджер.
Q: Кто отвечает за чарджбеки при destination charges? A: Платформа. Поэтому закладывайте бюджет на риски, процессы доказательств и SLA поддержки.
Q: Можно ли совмещать модели для разных сегментов продавцов? A: Да. Например, мелких вести на Express/destination, крупных — на Custom/sep+transfer. Важно изолировать логику в коде и отчётности.
Q: Как учитывать комиссии Stripe в леджере?
A: Или по вебхукам balance.transaction, или по ежедневным отчётам Stripe (Reports). Комиссии — отдельный счёт расходов, связанный с чарджами и валютами.
Key takeaways
- Выбор между Standard, Express, Custom — это баланс контроля, комплаенса и скорости запуска.
- Для 80% маркетплейсов Express + destination charges закрывает потребности по сплитам и UX.
- Separate charges and transfers нужны для эскроу, отложенных выплат и сложных сплитов, но требуют ручных реверсов и крепкого леджера.
- Леджер с двойной записью, идемпотентные вебхуки и сценарии реверсов — must‑have в продакшене.
- Планируйте поддержку KYC/проверок и негативных балансов до запуска, а не после первой бури чарджбеков.
- Миграция между моделями возможна, но дорогая — лучше выбрать правильно на старте.
Если вы строите маркетплейс на Stripe Connect и нужна архитектура, которая переживёт реальный трафик и проверки, напишите нам — команда MTBYTE помогает спроектировать и внедрить такие системы. Свяжитесь через /contact, разберём ваш кейс и соберём реалистичный план запуска.