Skip to main content
RapidDev - Software Development Agency
stripe-guide

How to split payments between two users in Stripe

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.

What you'll learn

  • How to split payments using destination charges with application_fee_amount
  • How to use separate charges-and-transfers for multi-party splits
  • How to calculate and apply variable platform fees
  • How to handle refunds on split payments
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced6 min read20 minutesStripe API v2024-12+, Node.js 18+March 2026RapidDev Engineering Team
TL;DR

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

1

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.

typescript
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
2
3async function twoWaySplit() {
4 const paymentIntent = await stripe.paymentIntents.create({
5 amount: 10000, // $100.00 total
6 currency: 'usd',
7 payment_method_types: ['card'],
8 application_fee_amount: 2000, // $20.00 platform fee
9 transfer_data: {
10 destination: 'acct_sellerAccount',
11 },
12 });
13 // Seller receives $80.00, platform receives $20.00
14 console.log('PaymentIntent:', paymentIntent.id);
15 console.log('Client secret:', paymentIntent.client_secret);
16 return paymentIntent;
17}
18
19twoWaySplit();

Expected result: A PaymentIntent is created. When confirmed, $80.00 goes to the seller and $20.00 to your platform.

2

Variable percentage-based fees

Calculate the platform fee as a percentage of the payment amount. This is common for marketplace models with tiered pricing.

typescript
1function calculatePlatformFee(amountInCents, feePercentage) {
2 return Math.round(amountInCents * (feePercentage / 100));
3}
4
5async function chargeWithPercentageFee(amount, sellerAccountId, feePercent) {
6 const fee = calculatePlatformFee(amount, feePercent);
7
8 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 });
21
22 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}
27
28chargeWithPercentageFee(15000, 'acct_seller123', 15); // 15% fee on $150

Expected result: A $150 charge with a 15% ($22.50) platform fee. Seller receives $127.50.

3

Multi-party split with separate charges and transfers

For orders involving multiple sellers, charge the buyer once and create separate transfers to each seller.

typescript
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 fee
5
6 // Step 1: Charge the buyer
7 const paymentIntent = await stripe.paymentIntents.create({
8 amount: totalAmount + platformFee,
9 currency: 'usd',
10 payment_method: 'pm_card_visa', // Use test card
11 confirm: true,
12 automatic_payment_methods: { enabled: true, allow_redirects: 'never' },
13 });
14
15 // Step 2: Transfer to each seller
16 const chargeId = paymentIntent.latest_charge;
17 const transfers = [];
18
19 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 }
29
30 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 });
35
36 return { paymentIntent, transfers };
37}
38
39multiPartySplit([
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.

4

Handle refunds on split payments

When refunding a destination charge, Stripe automatically reverses the transfer. For separate charges-and-transfers, you handle reversals manually.

typescript
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 cents
6 reverse_transfer: true, // Reverse the seller's transfer
7 refund_application_fee: true, // Also refund the platform fee
8 });
9 console.log('Refund created:', refund.id);
10 return refund;
11}
12
13// Refund with manual transfer reversals
14async function refundWithReversals(paymentIntentId, transferIds) {
15 const refund = await stripe.refunds.create({
16 payment_intent: paymentIntentId,
17 });
18
19 for (const transferId of transferIds) {
20 await stripe.transfers.createReversal(transferId);
21 }
22
23 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

split-payments.js
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
2const express = require('express');
3const app = express();
4
5app.use(express.json());
6
7function calculateFee(amount, percent) {
8 return Math.round(amount * (percent / 100));
9}
10
11// 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});
29
30// 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;
37
38 const pi = await stripe.paymentIntents.create({
39 amount: total,
40 currency: 'usd',
41 payment_method_types: ['card'],
42 });
43
44 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});
53
54// Execute transfers after payment succeeds
55app.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_SECRET
61 );
62 } catch (err) {
63 return res.status(400).send(`Webhook Error: ${err.message}`);
64 }
65
66 if (event.type === 'payment_intent.succeeded') {
67 const pi = event.data.object;
68 // Look up order items from your database and create transfers
69 console.log('Payment succeeded:', pi.id);
70 }
71
72 res.json({ received: true });
73});
74
75app.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.

ChatGPT Prompt

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.

Stripe Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.