// Writing · Payments

Marketplace split payments when Stripe Connect does not support your country

If your company is registered in Indonesia or Malaysia, there is a wall you hit on day one that almost nothing written in English will warn you about.

·6 min readStripe ConnectEscrowXenditMidtrans

If you are building a marketplace, the checkout is not the hard part. Taking one payment from a buyer and turning it into two or three payouts, on a delay, with refunds that can happen halfway through, is the hard part.

01

Stripe Connect does not cover you, and you find out late

Stripe Connect is the default answer everywhere online. Every tutorial, every Stack Overflow reply, every “how to build a marketplace” post assumes it. But Connect has a country list for platforms, and Indonesia and Malaysia are not on it.

Supported platform countriesSG · TH · PH · VN · JP · AU · US · GB · HK
Not supportedIndonesia · Malaysia

This is worth being precise about, because the confusion costs people weeks:

  • It is not about where your sellers live.
  • It is not about which currencies you charge in.
  • It is about where your platform entity is incorporated.

So an Indonesian PT building a marketplace for Indonesian sellers cannot be a Connect platform. You can discover this after you have written the onboarding flow, which is exactly when it hurts most.

There is one legitimate workaround, and you should treat it as a legal and tax question rather than an engineering one. If you genuinely have, or intend to have, an entity in a supported country such as Singapore, Connect becomes available to that entity. Do not incorporate in Singapore because a payments API told you to. Talk to an accountant first.

02

“Legacy” does not mean broken

If you do qualify for Connect, you will run into something confusing. Stripe now labels the older account types (Standard, Express and Custom) as legacy account types, and the documentation page for them carries a deprecation banner. New platforms get steered toward Accounts v2 and controller properties instead.

Read that carefully before you panic or rewrite anything. There is no published migration deadline, v1 accounts keep working, and Accounts v2 was still a preview API when I checked.

So the honest summary is “current but legacy”, not “broken”. If you are starting fresh today, build the newer pattern. If you have a working Express integration in production, you are not on fire, and a rewrite driven purely by the word “legacy” is a bad use of your quarter.

I mention this because it is a good test of whoever you hire. A payments developer who has read the docs will raise it with you unprompted. One who has only followed tutorials will not know it.

03

What to use instead in Indonesia and Malaysia

The honest local answer is a local rail. In Indonesia the two names that matter are Xendit (its multi party product, xenPlatform) and Midtrans. Both are built for the local reality: QRIS, virtual accounts, e-wallets, bank transfer, and payouts to local banks.

I want to be careful here rather than sound more certain than I am. I have built and shipped the Stripe side myself. On the local rails I have read the documentation and modelled the flows, and I would tell any client the same thing before quoting: confirm the specifics with the provider’s own solutions team, because commercial terms, KYC requirements for sub accounts and settlement timing differ per merchant and change over time.

What is reliably different from Connect, and what you should budget for:

  • Onboarding your sellers is more manual. Connect gives you a hosted flow that handles identity verification. Locally, expect to build more of the seller KYC collection yourself.
  • Holding funds is your job, not the provider’s. This is the big one, and the next section is entirely about it.
  • Payouts are a separate product from payments. Charging a buyer and disbursing to a seller are two integrations with two different failure modes.
04

The part that actually breaks: escrow

Strip away the provider and every marketplace with delayed payouts needs the same five things. This list is provider agnostic. It is identical on Stripe, on Xendit, on Midtrans, or on a direct bank integration.

01

Hold

The money has arrived and it is neither yours nor the seller's yet. You need a ledger that knows the difference between cash sitting in your account and cash you owe someone. If your escrow is a balance column you decrement, that is not escrow, it is a spreadsheet with extra steps.

02

Split

Platform fee and seller share, computed once and recorded, never recomputed at payout time. Recomputing is how a fee change silently rewrites history and your numbers stop reconciling.

03

Refund, including partial

The nasty case is a partial refund after a partial release. Decide up front who absorbs the platform fee on a refund, the platform or the seller, then make sure the contract and the code say the same thing.

04

Verify the webhook

Payment state does not arrive from your frontend, it arrives from the provider as a server to server callback. Without signature verification, anyone who learns the URL can tell your system a payment succeeded. Handlers must also be idempotent, because providers retry and a duplicate delivery must never pay a seller twice.

05

Release

Funds move when a condition is met: delivery confirmed, dispute window closed, job marked done. Usually a scheduled job rather than a user action, and it needs an audit trail of what released the money and when.

Every one of these is a place where real money goes wrong, and none of them is the checkout page.

05

What I built

I built a working demo of exactly this flow, on Stripe, so that everything above comes from having done it rather than from having read about it. It is a therapy booking marketplace: a client books and pays, the funds are held, the session happens, and then the money splits 75/25 to the therapist and the platform.

Under the hood it does Connect onboarding with account links, escrow payment intents, the fee split, full and partial refunds, signature verified webhooks, and a cron job that auto releases funds when the hold period expires. It runs on Next.js and Supabase with row level security, in Stripe test mode.

It is a demo I built and own, not a client project, and it does not move real money. I am saying that plainly because a portfolio piece that quietly implies production scale is the kind of thing that gets found out in the second meeting.

// Working through this?

If Connect covers your country, I will tell you to use it.

If it does not, I will tell you that on the first call rather than after you have paid me.