Stripe Connect Marketplace Architecture: Direct vs Express vs Custom
Alegi intre Direct (Standard), Express si Custom in Stripe Connect. Arhitectura, capcane, si decizii practice pentru marketplace-uri care tin la cashflow si compliance.

Alegerea gresita intre Direct, Express si Custom in Stripe Connect te poate costa luni de refactor, blocaje KYC, si bani blocati in contul de platforma. Daca lansezi un marketplace, acum e momentul sa alegi corect fluxul de plati si responsabilitatile de compliance.
Pe scurt: pentru marketplace-uri clasice cu vanzatori multi si go-to-market rapid, Express este alegerea implicita. Custom se justifica cand ai cerinte stricte de UX, splituri complexe, verificari KYC atipice sau control fin al payout-urilor. "Direct" (adesea numit Standard) e ok pentru platforme SaaS unde fiecare comerciant isi detine contul Stripe si doar folosesti passthrough, dar iti limiteaza controlul marketplace.
Direct vs Express vs Custom: ce inseamna cu adevarat
In documentatie, Stripe vorbeste despre tipuri de conturi Connect: Standard (numit de multi "Direct" deoarece comerciantul incaseaza direct), Express si Custom. In paralel exista tipuri de incasare: direct charges, destination charges, separate charges and transfers. Multi le amesteca; hai sa separam clar:
- Direct (Standard): vanzatorul foloseste un cont Stripe complet propriu; onboarding si dashboard Stripe standard; platforma primeste doar comisionul prin
application_fee_amount. Control minim asupra payout-urilor. - Express: onboarding Stripe-hosted simplificat, dashboard Express pentru vanzatori; platforma gestioneaza mai mult, dar Stripe pastreaza parti mari din compliance UX. Control bun asupra fee-urilor si a fluxurilor.
- Custom: totul white-label; tu detii UX-ul si datele KYC, controlezi payout-urile, monedele, scheduling. Recompensa: flexibilitate maxima. Cost: complexitate si responsabilitate de compliance.
Comparație sintetica
| Criteriu | Direct (Standard) | Express | Custom |
|---|---|---|---|
| Cine detine contul | Comerciant | Comerciant (Express) | Comerciant (Custom), white-label |
| Onboarding | Stripe complet | Stripe hosted (scurt) | Tu construiesti UX complet |
| Payout control | Stripe (comerciantul) | Stripe/limitat de platforma | Complet la nivel de platforma |
| KYC/KYB responsabil | Stripe | Stripe (majoritar) | Tu colectezi si sincronizezi |
| PCI scope | Minim cu Stripe.js | Minim | Mai complex daca tokenizezi server-side |
| Suport & ops | Redus | Mediu | Ridicat (dispute, negative balance) |
| Viteza de lansare | Foarte rapida | Rapida | Cea mai lenta |
| Splituri complexe | Limitat | Bun | Avansat |
| Cazuri comune | SaaS passthrough | Marketplace standard | Gig marketplace, platforme reglementate |
Observatie: poti combina cu tipuri de incasare (direct, destination, separate_charges_and_transfers) pentru granularitate la fee-uri si splituri. In practica, pentru marketplace: Express + destination charges sau SCT acopera 80% din nevoi.
Modele de incasare: direct, destination, separate+transfers
Alegerea contului Connect este doar jumatate din poveste. Modelul de incasare iti dicteaza cine proceseaza cardul, unde apar disputele si cum modelezi comisioanele.
- Direct charges: plata este procesata in contul vanzatorului (
stripe_account), comisionul platformei se retine prinapplication_fee_amount. Disputele sunt la vanzator. Bun pentru SaaS cu comercianți autonomi. - Destination charges: plata este procesata in contul platformei, iar banii sunt transferati catre contul vanzatorului via
transfer_data. Disputele apar la platforma. Bun pentru control si reconciliere centralizata. - Separate charges and transfers (SCT): creezi charge in contul platformei sau al vanzatorului si apoi faci
transferseparat din balance. Cel mai flexibil, util pentru splituri multiple sau escrow simplu.
Un flux clasic pentru marketplace cu Express + destination charges arata asa:
- Clientul plateste pe site-ul tau (Stripe Elements / Payment Element) catre platforma ta.
- Creezi
PaymentIntentin contul platformei cutransfer_data[destination]=acct_xxxal vanzatorului si, optional,application_fee_amount. - La
succeeded, Stripe muta netul catre contul vanzatorului; comisionul tau ramane in balance-ul platformei. - Payout-urile catre bancile vanzatorilor sunt gestionate de Stripe conform schedule-ului setat pentru fiecare cont Connect.
Exemplu minimal TypeScript (Node) pentru destination charge cu fee de platforma:
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2024-06-20' });
export async function createCheckoutIntent(params: {
amount: number; // in cei mai mici unitati (ex: bani)
currency: string;
connectedAccountId: string; // acct_...
platformFeeAmount?: number;
customerId?: string;
}) {
return stripe.paymentIntents.create({
amount: params.amount,
currency: params.currency,
payment_method_types: ['card'],
transfer_data: { destination: params.connectedAccountId },
application_fee_amount: params.platformFeeAmount ?? 0,
customer: params.customerId,
metadata: { type: 'marketplace_order' }
});
}
Pentru direct charges (passthrough), creezi PaymentIntent pe contul vanzatorului si adaugi application_fee_amount si on_behalf_of pentru o reconciliere mai curata:
export async function createDirectCharge(params: {
amount: number; currency: string; connectedAccountId: string; fee?: number;
}) {
return stripe.paymentIntents.create({
amount: params.amount,
currency: params.currency,
payment_method_types: ['card'],
on_behalf_of: params.connectedAccountId,
application_fee_amount: params.fee ?? 0,
}, {
stripeAccount: params.connectedAccountId
});
}
SCT devine util cand ai nevoie de: split cu mai multi vanzatori, escrow temporar (retinere si eliberare mai tarziu), sau corectii post-plata. Capcana: managementul balantei si al disputelor este mai greu—fa-ti un tabel de event-uri si idempotenta stricta.
Cand alegi fiecare: un arbore de decizie pragmatic
- Ai marketplace clasic cu multi vanzatori, comisioane simple, vrei lansare rapida si UX decent pentru selleri? Express.
- Ai cerinte de brand/UX complet custom, controale de payout per tranzactie, splituri multi-party, payout-uri conditionate? Custom.
- Esti o platforma SaaS unde fiecare client este comerciant cu relatia lui cu Stripe si tu doar adaugi un comision pentru utilizarea platformei? Direct (Standard) cu direct charges.
- Ai risc de reglementare, underwriting propriu, sau domenii sensibile (finantare participativa, servicii reglementate)? Tindem spre Custom si consultanta de compliance devreme.
- Ai multe APM-uri regionale (Pix, iDEAL, Boleto) si cross-border? Express/Custom cu
destinationsauSCTpentru controlul convertirilor. Daca tintesti Brazilia, vezi comparatia noastra pentru plati alternative: Brazil: PIX vs Visa/Mastercard.
Un mic adevar incomod: daca crezi ca ai nevoie de Custom din prima, de multe ori nu—Express acopera 80% si te lasa sa validezi modelul. Treci la Custom dupa product-market fit si intelegerea exacta a operatiunilor (nu invers).
Arhitectura de referinta: Express + Destination charges
Componente recomandate:
- Frontend: Stripe Payment Element; capturare plata cu 3DS unde e nevoie.
- Backend API: Node/TypeScript (NestJS/Express), validare stricta, idempotency keys.
- Baza de date: Postgres (tabele pentru
accounts,payouts,charges,transfers,disputes). - Queue: SQS/Redis Streams/Kafka pentru procesarea webhooks si retrieri.
- Webhooks: endpoint dedicat, verificare semnatura, retry-safe. Pentru latente mici si filtrare, poti pune un worker la edge; vezi notele noastre despre infrastructura edge in Building for the future: Cloudflare.
- Observabilitate: logs structurati, metrici pe latenta, rate de eroare, dispute rate, funds_at_risk.
Tabele minime in Postgres:
stripe_accounts(id pk, stripe_account_id unique, type enum('express','custom','standard'), capabilities jsonb, requirements jsonb, payout_schedule jsonb, status)orders(id pk, buyer_id, seller_account_id fk, amount, currency, fee_amount, status)payments(id pk, order_id fk, payment_intent_id, charge_id, transfer_id, status, created_at)events(idempotency_key pk, type, payload jsonb, processed_at)
Onboarding pentru Express:
// Creare cont Express si link onboarding
export async function createExpressOnboarding(userId: string, email: string) {
const account = await stripe.accounts.create({
type: 'express',
email,
capabilities: { card_payments: { requested: true }, transfers: { requested: true } },
metadata: { userId }
});
const link = await stripe.accountLinks.create({
account: account.id,
refresh_url: process.env.BASE_URL + '/onboarding/refresh',
return_url: process.env.BASE_URL + '/onboarding/return',
type: 'account_onboarding',
});
// persista account.id in baza de date
return { accountId: account.id, url: link.url };
}
Procesare webhook-uri (fragment):
import type { Request, Response } from 'express';
export async function stripeWebhook(req: Request, res: Response) {
const sig = req.headers['stripe-signature'] as string;
let event;
try {
event = stripe.webhooks.constructEvent(
req.body, sig, process.env.STRIPE_WEBHOOK_SECRET!
);
} catch (err) {
return res.status(400).send('invalid signature');
}
// idempotenta
const already = await db.events.findFirst({ where: { idempotency_key: event.id } });
if (already) return res.json({ received: true });
switch (event.type) {
case 'account.updated': {
const acc = event.data.object as Stripe.Account;
await db.stripe_accounts.update({
where: { stripe_account_id: acc.id },
data: {
capabilities: acc.capabilities as any,
requirements: acc.requirements as any,
status: acc.charges_enabled && acc.payouts_enabled ? 'active' : 'pending'
}
});
break;
}
case 'payment_intent.succeeded': {
// marcheaza order ca paid si trimite in fulfillment
break;
}
case 'charge.dispute.created': {
// marcheaza funds_at_risk si notifica vanzatorul
break;
}
}
await db.events.create({ data: { idempotency_key: event.id, type: event.type, payload: event } });
return res.json({ received: true });
}
Practici de productie:
- Foloseste
metadataconsecvent pePaymentIntentsiTransferpentru reconcilieri. - Stocheaza
requirements.currently_duesi arata vanzatorilor ce lipseste in dashboard-ul tau. - Idempotency la fiecare mutare de bani + retries exponentiale in queue.
- Simuleaza
capabilitiessipayouts_enabled=falsein sandbox—nu lansa fara scenarii de freeze.
Ce se strica in productie
- Webhooks pierdute sau duplicate: Stripe retrimite evenimente; daca endpointul are latenta sau timeouts, te trezesti cu dublari. Solutie: idempotenta pe
event.id, queue cu retry si backoff, si timeouts scurte la HTTP (nu face business logic greu in request thread). requirements.duedinamice: KYC este incremental. Azi e ok, maine Stripe cere documente noi. Pregateste UI/cron care re-verifica conturile si notifica vanzatorii.- Negative balance si partial refunds: daca transferi banii catre vanzator si apoi vine refund/disputa, platforma poate ramane cu sold negativ. Solutie: retine un buffer (rezerva) sau foloseste
charge.destination_paymentsi delayed transfers. - Payout failure: cont bancar invalid, currency mismatch. Ai nevoie de un proces de retry + messaging clar pentru vanzator, si fallback la manual review.
- Conversii valutare si taxe: platile in alta moneda decat payout-ul adauga comisioane si variatii de suma. Normalizeaza in rapoarte si comunica net-ul clar.
- Test-mode != live-mode: in live apar 3DS, SCA, emitenți conservatori, carduri preplatite. Fa testare cu Payment Element si scenarii SCA.
- Raportare contabila: daca nu decizi de la inceput granulatia (order vs item vs vendor-split), extragerea pe EoM devine dureroasa. Define-ste rapoarte SQL/CSV si procese cron de inchidere luna.
Un pic de umor sec: daca nu ai idempotency, vei procesa aceeasi plata de suficiente ori incat sa-ti placa contabila.
Consideratii de securitate si PCI
- Foloseste Stripe Elements/Payment Element pentru a ramane in SAQ A (tokenizarea in browser, niciun PAN pe serverul tau).
- Verifica strict semnatura webhook si blocheaza IP/host spoofing (nu te baza pe IP-urile Stripe, semnatura este sursa adevarului).
- Protejeaza cheile:
sk_livedoar in backend si rotate cand schimbi pipeline-urile. - Creeaza conturi de serviciu separate per environment (dev/stg/prod) si separa webhook secrets.
- Rate limiting si WAF pe endpoint-urile publice. Daca rulezi la edge, aplica validari devreme.
Migrare intre tipuri: ce e realist
- Standard (Direct) -> Express: fezabil; creezi cont Express si inviti vanzatorii la onboarding; refaci charge mode din direct la destination/SCT. Pastreaza mapping intre vechiul account si noul
acct_...daca au existat deja conturi Stripe. - Express -> Custom: posibil, dar mai greu; transferi responsabilitatile de KYC/UX si reconfigurezi payout schedule. Calculeaza initial costul operational (suport, verificari documente, disputa management).
- Retrofitting escrow: foloseste SCT si balance management; testeaza intens partial refunds si dispute pe tranzactii cu multiple transferuri.
Planifica migrarea ca pe o lansare noua: toggles per vendor, cutover treptat, reconcilieri duble o luna.
Context de business: cost, ROI, echipa
Costurile intr-un marketplace pe Stripe Connect se impart in:
- Comisioane de procesare card + taxa fixa pe tranzactie (variaza pe tara/moneda/tip de card).
- Costuri Connect (per payout, per cont, sau pe volum—verifica pagina de preturi Stripe pentru regiunea ta).
- Costuri de conversie valuta, dispute si refund-uri.
- Costuri de dezvoltare si operatiuni (suport la vanzatori, finance ops, reconciliere, anti-frauda).
Model simplu de P&L per tranzactie:
- Net incasat = pret total - (fee card + taxa fixa) - fee Connect - cost conversie (daca e cazul) - comisioane de dispute/ refunds.
- Profit platforma = comision platforma - costuri de mai sus - costuri operationale per tranzactie.
Timp si bugete de constructie (in experienta noastra ca builderi, pentru SMB si mid-market):
- Express + destination charges, split simplu: ~3–6 saptamani pentru un MVP solid (backend, onboarding, plati, rapoarte de baza, webhooks robuste).
- Custom + SCT cu split multiplu si payout-uri conditionate: ~8–16 saptamani, in functie de cerinte de KYC si accounting.
- Anti-frauda si monitoring avansat: adauga 1–3 saptamani pentru reguli, threshold-uri si alerte.
Echipa minima recomandata:
- 1 inginer backend cu Stripe experience.
- 1 inginer frontend care stie Payment Element si state machine pentru plati.
- 0.5 FTE devops/infra pentru webhook robust, observabilitate, securitate.
- 0.5–1 FTE ops/finance pentru reconciliere si suport vanzatori in primele luni.
ROI pragmatic: Express iti reduce dramatic timpul de lansare si riscul de conformitate, deci merita pentru majoritatea marketplace-urilor in faza 0–1. Custom isi are sensul cand optimizarile de conversie, brand si controlul cashflow-ului compenseaza costul de build si ops. Nu subestima costul dispute management—e fix genul de detaliu care roade marja daca nu-l modelezi.
Exemplu de flux operational complet (Express + SCT mixt)
- Checkout captureaza plata in contul platformei (PaymentIntent succeeded).
- Creezi
Transfercatre vanzatorul A si B (split 70/30) cand marfa intra in fulfillment. - Pastrezi 5% rezerva in balance 14 zile pentru dispute; eliberezi printr-un
Transferfinal. - La refund partial, inversezi proportional transferurile (
transfer_reversal). - Rapoarte zilnice agregeaza
charges,fees,transfers,reversalsper vanzator.
Pseudocod pentru split multi-seller:
// dupa ce PI este succeeded
await stripe.transfers.create({
amount: Math.round(order.total * 0.7),
currency: order.currency,
destination: sellerA.accountId,
metadata: { orderId: order.id, split: '70' }
});
await stripe.transfers.create({
amount: Math.round(order.total * 0.25),
currency: order.currency,
destination: sellerB.accountId,
metadata: { orderId: order.id, split: '25' }
});
// 5% ramane rezerva in balance-ul platformei
Testare si lansare: checklist
- Sandbox: scenarii
charges_enabled=false,payouts_enabled=false,requirements.dueapar din senin; 3DS2 required; dispute create. - Idempotency si retry: toate operatiunile Stripe invelite cu chei de idempotenta + retries transiente.
- Monitoring: alerte pe cresterea
charge.failed,payment_intent.canceled,dispute.created, si pe latenta webhook. - Rapoarte: CSV lunar by vendor cu total processat, fee-uri Stripe, fee platforma, net si payout-uri.
- Runbook: playbook pentru payout failure, disputa, si seller re-onboarding.
FAQ
Q: Pot incepe cu Express si mai tarziu sa trec la Custom fara sa-mi stric istoricul? A: Da, dar planifica un cutover gradual. Creeaza conturile Custom, migreaza noii vanzatori pe noul flux, apoi muta batch-uri ale celor existenti. Pastreaza mapping intre conturi si reconciliaza lunar pentru a verifica soldurile.
Q: Cum aleg intre destination charges si SCT pentru un marketplace simplu? A: Daca ai un singur vanzator per comanda si fee simplu, destination charges e mai direct si reconciliaza automat fee-ul. Daca ai split multiplu sau escrow, SCT iti da flexibilitate la pretul unei complexitati mai mari in rapoarte si refunds.
Q: Ce implica din punct de vedere PCI daca folosesc Stripe Elements? A: Cu Stripe Elements/Payment Element ramai in SAQ A (datele de card nu ating serverul tau). Totusi, securizeaza webhook-ul, gestioneaza cheile cu grija si auditeaza accesul la dashboard-urile Stripe.
Q: Cum gestionez taxele (VAT/Sales tax) intr-un marketplace? A: Decide cine e "merchant of record". Daca platforma e MoR, foloseste Stripe Tax si colecteaza/remiti tu taxele. Daca vanzatorii sunt MoR, colecteaza tax profile per seller si reflecta taxele in preturi, apoi separa in rapoarte.
Q: Ce se intampla cu disputele la direct vs destination charges? A: La direct charges disputa e a vanzatorului (contul lui proceseaza cardul). La destination charges disputa este in contul platformei—ai control, dar si responsabilitate pentru dovezi si potentiale pierderi.
Q: Pot folosi metode locale (ex: Pix) cu Connect? A: Da, in functie de tara si tipul contului. Verifica disponibilitatea pe conturile Connected si modeleaza fluxurile (unele APM-uri au captive funds/settlement diferit). Pentru Brazilia, am discutat nuantele PIX versus carduri in articolul nostru: Brazil: PIX vs Visa/Mastercard.
Concluzii cheie
- Express este default-ul pragmatic pentru marketplace-uri: onboarding rapid, suficient control si risc operational moderat.
- Custom are sens cand UX-ul, spliturile complexe si controalele de payout iti cresc conversia si marja mai mult decat costul de build/ops.
- Alege modelul de incasare (destination vs SCT) dupa complexitatea splitului si nevoile de escrow/refund.
- Webhook-urile, idempotenta si rapoartele sunt coloana vertebrala a unei implementari Connect care rezista in productie.
- Planifica dispute, negative balance si cerinte KYC dinamice inca din MVP—altfel marja te va trada la scara.
Daca construiesti un marketplace sau migrezi intre Direct, Express si Custom, MTBYTE poate proiecta, implementa si testa fluxurile ca sa nu-ti blochezi cashflow-ul in productie. Scrie-ne: /contact
URMATORUL PAS
Ti-a placut abordarea?
Aplicam aceleasi principii in proiectele clientilor: AI, automatizari, produse care nu se sting dupa lansare.