Mercur uses Stripe’s Separate Charges and Transfers model. The platform collects the full payment from the customer, then creates transfers to each seller’s connected account. This makes the platform the Merchant of Record, responsible for VAT, disputes, and chargebacks.
Prerequisites
- A Stripe account with Connect enabled
- Stripe Secret API key and Publishable API key
- A running Mercur project (installation guide)
- Node.js 20+
Architecture overview
The payment lifecycle in a Mercur marketplace flows through two distinct Stripe integrations: The two integrations serve different purposes:Stripe Dashboard setup
1
Enable Connect
In your Stripe Dashboard, go to Settings → Connect settings and enable Connect. If you’re starting fresh, Stripe will walk you through an onboarding flow to activate your platform account.
2
Find your API keys
Go to Developers → API keys. You’ll need:
- Secret key: starts with
sk_test_(test mode) orsk_live_(production) - Publishable key: starts with
pk_test_orpk_live_
3
Set environment variables
Add the following to your
.env file:Configure Medusa payment provider
The Medusa payment provider handles customer-facing charges. Add it to yourmedusa-config.ts:
Provider options
After configuring the provider, enable Stripe as a payment method in your Admin Panel: Settings → Regions → Edit region → Payment providers → Stripe.
Configure Mercur payout provider
The payout provider handles transfers from the platform to seller connected accounts. Add it alongside the payment provider inmedusa-config.ts:
Payout provider options
Account validation options
These options determine what conditions a Stripe connected account must meet before Mercur marks it asACTIVE and allows payouts:
Payout module options
The payout module itself accepts timing options that control the capture and payout pipeline:Set up webhooks
You need two separate webhook endpoints in Stripe: one for payment events, one for payout events.1
Add the payment webhook (Medusa)
In the Stripe Dashboard, go to Developers → Webhooks → Add endpoint.
- Endpoint URL:
https://{your-backend-url}/hooks/payment/stripe_stripe - Events to subscribe to:
payment_intent.succeededpayment_intent.amount_capturable_updatedpayment_intent.payment_failedcharge.refunded
STRIPE_WEBHOOK_SECRET in your .env.2
Add the payout webhook (Mercur)
Add a second webhook endpoint in the Stripe Dashboard.
- Endpoint URL:
https://{your-backend-url}/hooks/payout - Events to subscribe to:
account.updated
STRIPE_PAYOUT_WEBHOOK_SECRET in your .env.How the payment flow works
Here’s a concrete example. A customer buys items from two sellers:- Seller A: €40 (product)
- Seller B: €30 (product)
- Shipping: €10
- Cart total: €80
Step 1: Authorize payment
At checkout, a singlePaymentIntent is created for €80 with capture_method: "manual". The customer authenticates once (SCA-compliant). No money moves yet. The funds are held on the customer’s card.
Step 2: Split orders
Mercur’scompleteCartWithSplitOrdersWorkflow groups items by seller and creates separate orders: one for Seller A (€40) and one for Seller B (€30), plus shipping allocation.
Step 3: Seller acceptance and fulfillment
Each seller reviews and fulfills their order through the Vendor Portal. The payout module’ssellerActionWindowMs (default: 72 hours) defines how long sellers have to act.
Step 4: Capture payment
Once orders meet therequiredFulfillmentStatus (default: "fulfilled"), the platform captures the authorized payment. The capture-check job runs automatically and respects the captureSafetyBufferMs to ensure capture happens before authorization expiry.
Step 5: Commission calculation and transfers
After capture, Mercur calculates commission for each order and creates Stripe Transfers for the net amounts:source_transaction and grouped by transfer_group (the order ID).
Step 6: Bank payouts
Stripe automatically pays out connected account balances to sellers’ bank accounts on the configured payout schedule. Mercur tracks payout status changes via theaccount.updated webhook.
Seller onboarding
When a seller creates a payout account, the following lifecycle begins:-
Account creation. Mercur calls
stripe.accounts.create({ type: "express" }), creating a Stripe Express connected account. The payout account starts inPENDINGstatus. -
Onboarding link. The seller receives a Stripe-hosted onboarding URL via
stripe.accountLinks.create(). They complete identity verification, bank account setup, and any required compliance steps directly on Stripe. -
Webhook activation. When the seller completes onboarding, Stripe sends an
account.updatedwebhook. The provider evaluates the account against theaccountValidationoptions and transitions the status:- All validation checks pass →
ACTIVE - Missing requirements or disabled reason →
RESTRICTED - Disabled reason starts with
rejected.→REJECTED
- All validation checks pass →
-
Ongoing monitoring. Stripe may send additional
account.updatedevents if requirements change. The provider re-evaluates and updates the status accordingly.
Transfers vs payouts
Stripe uses two distinct concepts for moving money, and it’s important to understand the difference:
When Mercur’s
createPayout method is called, the Stripe Connect provider creates a Transfer (platform → connected account). The actual bank payout (connected account → bank) happens automatically on Stripe’s schedule.
Refunds and reversals
Refunding a charge does not automatically reverse the associated transfers. These are two separate operations:- Refund the PaymentIntent. Returns funds to the customer’s payment method
- Reverse the Transfer(s). Claws back funds from the connected account(s)
Refund and transfer reversal orchestration is an area where custom workflow logic may be needed depending on your business rules. See Stripe’s documentation on reversing transfers for the API details.
EU/EEA considerations
Merchant of Record
Using Separate Charges and Transfers makes the platform the Merchant of Record. This means:- The platform collects and remits VAT
- The platform handles consumer disputes and chargebacks
- The platform is responsible for PSD2 compliance
Strong Customer Authentication (SCA)
SCA is enforced at authorization time, not capture time. Since the customer authenticates once during checkout, no additional SCA step is needed when the platform captures later. The single authorization at checkout satisfies the SCA requirement for the entire order.Authorization windows
Card authorizations in Europe typically last 7 days. Design your seller acceptance workflow to complete well within this window. The payout module options give you control over the timing:Testing
Test mode
Use Stripe test mode keys (sk_test_, pk_test_) during development. All connected accounts and payments created in test mode are isolated from production.
Test card number: 4242 4242 4242 4242 (any future expiry, any CVC)
Webhook forwarding with Stripe CLI
Since webhooks need to reach your local machine during development, use the Stripe CLI to forward events. You need two separate listeners, one for each webhook endpoint:Testing the full flow
- Create a seller and complete Stripe’s test onboarding flow
- Add products from the seller to a cart
- Complete checkout using the test card
- Verify orders are split by seller in the Admin Panel
- Fulfill the orders through the Vendor Portal
- Confirm transfers appear in the Stripe Dashboard under the connected account
FAQ
Why do I need two webhook endpoints with two secrets?
Why do I need two webhook endpoints with two secrets?
They belong to two different integrations: the payment webhook feeds Medusa’s payment provider (charges, captures, refunds), while the payout webhook feeds Mercur’s payout provider (connected-account status, transfer status). Each endpoint verifies its own signing secret. Combining them silently breaks whichever side’s signature doesn’t match.
Why must capture be set to false on the payment provider?
Why must capture be set to false on the payment provider?
The marketplace flow authorizes at checkout and captures later, after sellers fulfill. Automatic capture would take the money before the split-order pipeline (fulfillment checks, commission, payouts) has run.
capture: false hands that timing to the payout module’s capture job.A seller finished Stripe onboarding but is still not ACTIVE. Why?
A seller finished Stripe onboarding but is still not ACTIVE. Why?
Check the
accountValidation options: by default the account must have details submitted, charges and payouts enabled, and no outstanding requirements. Stripe often adds follow-up requirements (e.g. extra KYC) after initial onboarding. The account shows as RESTRICTED until they’re cleared.Who handles VAT, disputes, and chargebacks?
Who handles VAT, disputes, and chargebacks?
The platform. Separate Charges and Transfers makes the platform the Merchant of Record. See EU/EEA considerations for what that entails.
Next steps
Payout
Payout accounts, onboarding, and the transfer pipeline.
The payout pipeline
Statuses, jobs, and webhook events.