Stripe Connect provides multiple approaches for splitting payments between parties: destination charges with application_fee_amount for simple two-way splits, and separate charges-and-transfers for complex multi-party splits. This guide covers both methods with working code, plus how to handle refunds, variable fees, and real-time split calculations.
Splitting Payments Between Multiple Parties with Stripe
Payment splitting is a core requirement for marketplaces, platforms, and SaaS products that facilitate transactions between buyers and sellers. Stripe Connect offers two main approaches: destination charges (simple, built-in splitting) and separate charges-and-transfers (flexible, multi-party). Your choice depends on the number of recipients and the complexity of your fee structure.
Prerequisites
- Stripe account with Connect enabled
- At least one connected account created
- Node.js 18 or later installed
- Stripe Node.js SDK: npm install stripe
- Express.js: npm install express
Step-by-step guide
Simple two-way split with destination charges
Simple two-way split with destination charges
The easiest way to split a payment between your platform and one seller. Use application_fee_amount to set your platform's cut. The rest goes to the seller.
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);23async function twoWaySplit() {4 const paymentIntent = await stripe.paymentIntents.create({5 amount: 10000, // $100.00 total6 currency: 'usd',7 payment_method_types: ['card'],8 application_fee_amount: 2000, // $20.00 platform fee9 transfer_data: {10 destination: 'acct_sellerAccount',11 },12 });13 // Seller receives $80.00, platform receives $20.0014 console.log('PaymentIntent:', paymentIntent.id);15 console.log('Client secret:', paymentIntent.client_secret);16 return paymentIntent;17}1819twoWaySplit();Expected result: A PaymentIntent is created. When confirmed, $80.00 goes to the seller and $20.00 to your platform.
Variable percentage-based fees
Variable percentage-based fees
Calculate the platform fee as a percentage of the payment amount. This is common for marketplace models with tiered pricing.
1function calculatePlatformFee(amountInCents, feePercentage) {2 return Math.round(amountInCents * (feePercentage / 100));3}45async function chargeWithPercentageFee(amount, sellerAccountId, feePercent) {6 const fee = calculatePlatformFee(amount, feePercent);78 const paymentIntent = await stripe.paymentIntents.create({9 amount: amount,10 currency: 'usd',11 payment_method_types: ['card'],12 application_fee_amount: fee,13 transfer_data: {14 destination: sellerAccountId,15 },16 metadata: {17 fee_percentage: feePercent.toString(),18 fee_amount: fee.toString(),19 },20 });2122 console.log(`Total: $${(amount/100).toFixed(2)}`);23 console.log(`Fee (${feePercent}%): $${(fee/100).toFixed(2)}`);24 console.log(`Seller receives: $${((amount-fee)/100).toFixed(2)}`);25 return paymentIntent;26}2728chargeWithPercentageFee(15000, 'acct_seller123', 15); // 15% fee on $150Expected result: A $150 charge with a 15% ($22.50) platform fee. Seller receives $127.50.
Multi-party split with separate charges and transfers
Multi-party split with separate charges and transfers
For orders involving multiple sellers, charge the buyer once and create separate transfers to each seller.
1async function multiPartySplit(orderItems) {2 // orderItems: [{ sellerAccountId, amount }, ...]3 const totalAmount = orderItems.reduce((sum, item) => sum + item.amount, 0);4 const platformFee = calculatePlatformFee(totalAmount, 10); // 10% platform fee56 // Step 1: Charge the buyer7 const paymentIntent = await stripe.paymentIntents.create({8 amount: totalAmount + platformFee,9 currency: 'usd',10 payment_method: 'pm_card_visa', // Use test card11 confirm: true,12 automatic_payment_methods: { enabled: true, allow_redirects: 'never' },13 });1415 // Step 2: Transfer to each seller16 const chargeId = paymentIntent.latest_charge;17 const transfers = [];1819 for (const item of orderItems) {20 const transfer = await stripe.transfers.create({21 amount: item.amount,22 currency: 'usd',23 destination: item.sellerAccountId,24 source_transaction: chargeId,25 metadata: { order_id: paymentIntent.id },26 });27 transfers.push(transfer);28 }2930 console.log(`Charged: $${((totalAmount + platformFee)/100).toFixed(2)}`);31 console.log(`Platform keeps: $${(platformFee/100).toFixed(2)}`);32 transfers.forEach(t => {33 console.log(`Transfer ${t.id}: $${(t.amount/100).toFixed(2)} → ${t.destination}`);34 });3536 return { paymentIntent, transfers };37}3839multiPartySplit([40 { sellerAccountId: 'acct_sellerA', amount: 5000 },41 { sellerAccountId: 'acct_sellerB', amount: 8000 },42]);Expected result: One charge to the buyer, two transfers to sellers, and the platform retains its 10% fee.
Handle refunds on split payments
Handle refunds on split payments
When refunding a destination charge, Stripe automatically reverses the transfer. For separate charges-and-transfers, you handle reversals manually.
1// Refund a destination charge (automatic transfer reversal)2async function refundDestinationCharge(paymentIntentId, refundAmount) {3 const refund = await stripe.refunds.create({4 payment_intent: paymentIntentId,5 amount: refundAmount, // Partial refund in cents6 reverse_transfer: true, // Reverse the seller's transfer7 refund_application_fee: true, // Also refund the platform fee8 });9 console.log('Refund created:', refund.id);10 return refund;11}1213// Refund with manual transfer reversals14async function refundWithReversals(paymentIntentId, transferIds) {15 const refund = await stripe.refunds.create({16 payment_intent: paymentIntentId,17 });1819 for (const transferId of transferIds) {20 await stripe.transfers.createReversal(transferId);21 }2223 console.log('Refund and reversals complete');24 return refund;25}Expected result: The charge is refunded and the transfers to connected accounts are reversed.
Complete working example
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);2const express = require('express');3const app = express();45app.use(express.json());67function calculateFee(amount, percent) {8 return Math.round(amount * (percent / 100));9}1011// Destination charge (two-way split)12app.post('/api/pay/single-seller', async (req, res) => {13 try {14 const { amount, sellerAccountId, feePercent } = req.body;15 const fee = calculateFee(amount, feePercent || 10);16 const pi = await stripe.paymentIntents.create({17 amount,18 currency: 'usd',19 payment_method_types: ['card'],20 application_fee_amount: fee,21 transfer_data: { destination: sellerAccountId },22 metadata: { fee_percent: String(feePercent || 10) },23 });24 res.json({ clientSecret: pi.client_secret, fee });25 } catch (err) {26 res.status(400).json({ error: err.message });27 }28});2930// Separate charges-and-transfers (multi-seller split)31app.post('/api/pay/multi-seller', async (req, res) => {32 try {33 const { items, feePercent } = req.body;34 const sellerTotal = items.reduce((s, i) => s + i.amount, 0);35 const fee = calculateFee(sellerTotal, feePercent || 10);36 const total = sellerTotal + fee;3738 const pi = await stripe.paymentIntents.create({39 amount: total,40 currency: 'usd',41 payment_method_types: ['card'],42 });4344 res.json({45 clientSecret: pi.client_secret,46 paymentIntentId: pi.id,47 breakdown: { total, sellerTotal, platformFee: fee },48 });49 } catch (err) {50 res.status(400).json({ error: err.message });51 }52});5354// Execute transfers after payment succeeds55app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {56 const sig = req.headers['stripe-signature'];57 let event;58 try {59 event = stripe.webhooks.constructEvent(60 req.body, sig, process.env.STRIPE_WEBHOOK_SECRET61 );62 } catch (err) {63 return res.status(400).send(`Webhook Error: ${err.message}`);64 }6566 if (event.type === 'payment_intent.succeeded') {67 const pi = event.data.object;68 // Look up order items from your database and create transfers69 console.log('Payment succeeded:', pi.id);70 }7172 res.json({ received: true });73});7475app.listen(3000, () => console.log('Split payments server on port 3000'));Common mistakes when splitting payments between two users in Stripe
Why it's a problem: Using floating-point math for fee calculations
How to avoid: Always use integer math in cents and Math.round() to avoid precision errors. 10000 * 0.15 = 1500 cents, not $15.00.
Why it's a problem: Setting application_fee_amount higher than the payment amount
How to avoid: The fee cannot exceed the total charge amount. Validate your fee calculation before creating the PaymentIntent.
Why it's a problem: Not handling refunds correctly for split payments
How to avoid: For destination charges, use reverse_transfer: true. For separate charges-and-transfers, manually reverse each transfer.
Why it's a problem: Creating transfers before the payment is confirmed
How to avoid: Only create transfers after payment_intent.succeeded webhook fires, or use source_transaction to tie transfers to the charge.
Best practices
- Use destination charges for simple two-party splits and separate charges-and-transfers for multi-party splits
- Always calculate fees in cents using integer math with Math.round()
- Store the fee percentage and calculated amount in PaymentIntent metadata for auditing
- Use idempotency keys on transfers to prevent double-splits on retries
- Create transfers in the payment_intent.succeeded webhook handler for reliability
- Build a reconciliation system that matches charges to transfers
- Log all split calculations for financial reporting and dispute resolution
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I need to split payments between two sellers on my marketplace using Stripe Connect. Show me how to use destination charges for simple splits and separate charges-and-transfers for multi-seller orders. Node.js code please.
Help me implement payment splitting in my marketplace. I need two-way splits using destination charges with percentage-based fees, and multi-seller splits using separate charges-and-transfers. Include refund handling. Use Node.js and Express.
Frequently asked questions
Can I split a payment between more than two parties?
Yes. Use separate charges-and-transfers. Charge the buyer once and create individual transfers to each party. The total transfers cannot exceed the charge amount.
Who pays the Stripe processing fees on a split payment?
On destination charges, processing fees are deducted from the platform's application_fee_amount. On separate charges, fees come from the platform's cut. You can structure your pricing to pass fees to sellers by adjusting the split.
Can I change the split ratio after the payment is processed?
For destination charges, the split is fixed at payment time. For separate charges-and-transfers, you control when and how much you transfer, so you can adjust the split as long as you have not already transferred the full amount.
What happens if a seller's account is not verified when I try to split a payment?
For destination charges, the payment fails if the connected account cannot accept charges. For separate charges-and-transfers, the charge succeeds on your platform, and you can delay the transfer until the seller is verified.
How do I handle tips or variable fees in a split payment?
Add the tip to the total charge amount and adjust your transfer amounts accordingly. Store the tip amount in metadata for reconciliation.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation