Stripe Connect: The Charge Type Decides Who Eats the Chargeback
Direct, destination, and separate charges and transfers are not three styles of the same thing. They decide whose balance a dispute comes out of, and only one splits a cart.
A single charge, a commission column and a manual payout run on Fridays is a legitimate way to run a marketplace, and it is how most of them start. It stops being legitimate at the point where the Friday run takes an afternoon, somebody transposes two digits, and you discover you have no audit trail that distinguishes a payment you sent from a payment you meant to send.
That is the moment Stripe Connect earns its complexity. What the integration guides tend to skip is that Connect does not offer one way to take money — it offers three, they are not interchangeable styles, and the one you pick decides whose balance a refund and a chargeback come out of. Nobody discovers that at integration time. They discover it at their first dispute.
One question, asked three ways
Direct charges, destination charges, and separate charges and transfers differ on the surface in where the API call is aimed. Underneath they differ on three things that matter far more: who the merchant of record is, whose Stripe balance the money lands in first, and who is holding the liability when a customer calls their bank.
charge type merchant refunds hit splits to N vendors
direct vendor vendor balance no
destination platform platform balance no
separate + transfers platform platform balance yesRead the middle column as the real question. Everything else about a Connect integration can be changed later with a migration. Which balance goes negative when a customer disputes a £48 grocery order is a decision you are making about your own working capital, and it is unpleasant to revisit once you have vendors onboarded under one model.
Direct charges: the vendor is the merchant
The charge is created on the connected account. The payment appears in the vendor's balance, not yours, and your revenue arrives as an application fee that transfers to your account when they collect. The customer's statement shows the vendor. Funds settle in the vendor's country.
Crucially, refunds and chargebacks reduce the vendor's balance rather than yours. For a platform that is genuinely a tool — the vendor has their own brand, their own customers, and your software sits behind the transaction — that is the honest allocation, and it is why Shopify and similar platforms work this way.
It is the wrong model for a delivery marketplace, because it is not true there. Your customer thinks they bought from you. They will dispute with you, complain to you, and never learn the restaurant's legal entity. Two smaller notes: direct charges need the card_payments capability active on the connected account, and Stripe no longer recommends them for legacy v1 Express and Custom accounts at all.
Destination charges: you are the merchant, and the disputes are yours
Here the charge is created on your platform account and a portion is transferred to the connected account immediately. You keep the difference. This is the model for a branded marketplace — the customer transacts with you, and the vendor is a supplier they may not think about at all.
const intent = await stripe.paymentIntents.create({
amount: order.totalCents,
currency: "gbp",
// What we keep. The remainder lands in the vendor's balance
// the moment the charge succeeds.
application_fee_amount: order.commissionCents,
transfer_data: { destination: vendor.stripeAccountId },
});The trade is explicit in the docs and worth internalising before you ship: Stripe debits its fees from your balance, and refunds and chargebacks reduce your balance. You can reverse the transfer to recover your side of a refund, but the recovery is a second action that can fail, and between the dispute and the reversal the money is gone from your account rather than theirs.
There is a second limitation that decides the question for anyone reading this: a destination charge has exactly one destination. One charge, one vendor. A cart spanning three stores cannot be expressed this way at all.
Separate charges and transfers: the only one that splits a cart
Charge on your own account, then move money out to each vendor in its own transfer. Stripe names a restaurant delivery platform as the example use case for exactly this, and it is the only one of the three that handles the one-to-many shape a multi-vendor basket actually has.
const group = `order_${order.id}`;
// One charge the customer recognises.
await stripe.paymentIntents.create({
amount: order.totalCents,
currency: "gbp",
transfer_group: group,
});
// N transfers, one per sub-order, each carrying the commission
// rate snapshotted at checkout rather than today's rate.
for (const sub of order.subOrders) {
await stripe.transfers.create({
amount: sub.subtotalCents + sub.deliveryCents - sub.commissionCents,
currency: "gbp",
destination: sub.vendor.stripeAccountId,
transfer_group: group,
});
}The transfer_group is not decoration. It is what lets you answer "which payouts belong to this order" in the dashboard eight months later, when a vendor is disputing a settlement and the only thing that will end the conversation is a list.
The freedom here is also the hazard. Transfers are decoupled from the charge, so you can transfer before the payment settles, or transfer more than you collected. Stripe will let you. Your balance simply goes negative, and it is your responsibility to watch it — which is why you associate transfers with the charge and let the funds become available before they move, unless you have a deliberate reason not to.
What on_behalf_of quietly changes
Setting on_behalf_of to a connected account makes that account the business of record for the payment while leaving the funds flow alone. It reads like a cosmetic flag and is not. It changes the settlement country, the fee structure applied, the statement descriptor the customer sees, and the address and phone number on their statement when the account is in a different country to yours. It also makes payouts follow that account's delays_days rather than yours.
Use it when vendors are in a different country to the platform, because settling in the vendor's country reduces declines and avoids a currency conversion on every order. Do not reach for it to make receipts look nicer, because you will have changed four things to fix one.
Negative balances, reversals and onboarding
When you refund a destination or separate charge, your balance is debited and you recover your share by reversing the transfer. If your platform balance is short at that moment, the refund is created with status pending and completes when funds arrive — which is tolerable. If the reversal is attempted against a vendor whose balance is short, you get an error instead.
A vendor can therefore end up owing you money they no longer hold, typically because they were paid out on Tuesday and the chargeback landed on Thursday. Stripe will attempt to debit their bank account for a negative balance only if debit_negative_balances is set on the account. If it is not, the balance sits there and nets off against future sales — fine for a busy vendor, uncollectable from one who has left.
And then the part with no API surface at all. Every vendor now completes KYC before they can be paid, which means identity documents, a bank account and a business classification. A meaningful fraction will stall halfway. Your admin needs a screen showing who is unverified and what they still owe Stripe, because "why have I not been paid" will otherwise arrive as a phone call and be answered by someone reading a dashboard aloud.
Where to start
Pick the charge type from the liability question, not the API surface: if customers believe they are buying from you, the disputes are yours, and that means destination charges for a single vendor per order or separate charges and transfers for a basket that can span several. Then build onboarding before you build transfers, because a transfer to an unverified account is the failure you will hit on day one, and it is much easier to face it with no real money moving.
GreenCart — $19
One cart across many stores, with the commission ledger and per-vendor sub-orders already in place — the schema a Connect migration needs before it can transfer anything.