/stripe-guides

How to split payments between two users in Stripe?

Learn how to split payments between two users in Stripe using Connect, direct charges, destination charges, and transfers. Step-by-step guide for developers.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free consultation

How to split payments between two users in Stripe?

How to Split Payments Between Two Users in Stripe

 

In this tutorial, I'll walk you through the process of implementing payment splitting between two users using Stripe. This is commonly needed for marketplace platforms, affiliate systems, or any application where funds need to be distributed between multiple parties.

 

Step 1: Set Up Your Stripe Account

 

Before you can split payments, you need to have a Stripe account and the necessary API keys:

  1. Sign up for a Stripe account at https://stripe.com if you haven't already
  2. Navigate to the Developers section and locate your API keys
  3. Make note of your publishable key and secret key for later use

 

Step 2: Install the Stripe SDK

 

Install the Stripe library in your project using npm, pip, or another package manager depending on your programming language:

For Node.js:

npm install stripe

For Python:

pip install stripe

 

Step 3: Set Up Connect Accounts for Recipients

 

Stripe Connect allows you to split payments by creating accounts for each recipient:

  1. Enable Stripe Connect in your Stripe Dashboard
  2. Create Connect accounts for your recipients (Standard, Express, or Custom)

For Node.js, creating a Connect account looks like this:

const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');

async function createConnectAccount(email, name) {
  const account = await stripe.accounts.create({
    type: 'express',
    country: 'US',
    email: email,
    business\_type: 'individual',
    capabilities: {
      card\_payments: {requested: true},
      transfers: {requested: true},
    },
    business\_profile: {
      name: name,
    },
  });
  
  return account.id;
}

 

Step 4: Implement Direct Charges with Automatic Transfers

 

This method charges the customer directly and automatically transfers a portion to another account:

const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');

async function splitPayment(amount, customerPaymentMethodId, connectedAccountId, platformFeePercentage) {
  // Convert amount to cents for Stripe
  const amountInCents = amount \* 100;
  
  // Calculate platform fee (your commission)
  const platformFeeAmount = Math.round(amountInCents \* (platformFeePercentage / 100));
  
  // Create a payment intent with automatic transfer
  const paymentIntent = await stripe.paymentIntents.create({
    amount: amountInCents,
    currency: 'usd',
    payment\_method: customerPaymentMethodId,
    confirmation\_method: 'manual',
    confirm: true,
    application_fee_amount: platformFeeAmount,
    transfer\_data: {
      destination: connectedAccountId,
    },
  });
  
  return paymentIntent;
}

 

Step 5: Implement Destination Charges

 

An alternative approach is to use destination charges, where your platform is the merchant of record:

const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');

async function destinationCharge(amount, customerPaymentMethodId, destinationAccountId, platformFeePercentage) {
  // Convert amount to cents
  const amountInCents = amount \* 100;
  
  // Calculate platform fee
  const platformFeeAmount = Math.round(amountInCents \* (platformFeePercentage / 100));
  
  // Create a payment intent with destination
  const paymentIntent = await stripe.paymentIntents.create({
    amount: amountInCents,
    currency: 'usd',
    payment\_method: customerPaymentMethodId,
    confirmation\_method: 'manual',
    confirm: true,
    application_fee_amount: platformFeeAmount,
    transfer\_data: {
      destination: destinationAccountId,
    },
    on_behalf_of: destinationAccountId,
  });
  
  return paymentIntent;
}

 

Step 6: Split Payments Using Transfers

 

If you want more control over the payment flow, you can first charge the customer and then distribute the funds:

const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');

async function chargeAndSplit(amount, customerPaymentMethodId, recipientAccountId, recipientPercentage) {
  // Convert amount to cents
  const amountInCents = amount \* 100;
  
  // Calculate recipient amount
  const recipientAmount = Math.round(amountInCents \* (recipientPercentage / 100));
  const platformAmount = amountInCents - recipientAmount;
  
  try {
    // 1. Charge the customer (funds go to your platform account)
    const paymentIntent = await stripe.paymentIntents.create({
      amount: amountInCents,
      currency: 'usd',
      payment\_method: customerPaymentMethodId,
      confirmation\_method: 'manual',
      confirm: true,
    });
    
    // 2. Transfer the recipient's share
    if (paymentIntent.status === 'succeeded') {
      const transfer = await stripe.transfers.create({
        amount: recipientAmount,
        currency: 'usd',
        destination: recipientAccountId,
        transfer\_group: `order_${paymentIntent.id}`,
        source\_transaction: paymentIntent.charges.data[0].id,
      });
      
      return {
        paymentIntent,
        transfer,
        platformAmount,
        recipientAmount
      };
    }
  } catch (error) {
    console.error('Error processing payment split:', error);
    throw error;
  }
}

 

Step 7: Implement a Checkout Flow with Payment Splitting

 

Here's how to integrate payment splitting with Stripe Checkout:

const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');
const express = require('express');
const app = express();

app.use(express.json());
app.use(express.static('public'));

app.post('/create-checkout-session', async (req, res) => {
  const { amount, recipientId, recipientPercentage, orderId } = req.body;
  
  // Calculate the application fee amount
  const amountInCents = amount \* 100;
  const applicationFeeAmount = Math.round(amountInCents \* (1 - recipientPercentage / 100));
  
  try {
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ['card'],
      line\_items: [
        {
          price\_data: {
            currency: 'usd',
            product\_data: {
              name: 'Order #' + orderId,
            },
            unit\_amount: amountInCents,
          },
          quantity: 1,
        },
      ],
      mode: 'payment',
      success_url: 'https://yourwebsite.com/success?session_id={CHECKOUT_SESSION_ID}',
      cancel\_url: 'https://yourwebsite.com/cancel',
      payment_intent_data: {
        application_fee_amount: applicationFeeAmount,
        transfer\_data: {
          destination: recipientId,
        },
      }
    });

    res.json({ id: session.id });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => console.log('Server running on port 3000'));

 

Step 8: Handle Frontend Integration

 

Add this JavaScript to your frontend to redirect customers to the Stripe Checkout page:

// Include Stripe.js


 

Step 9: Implement Stripe Webhooks for Payment Events

 

Set up webhooks to track payment statuses and handle notifications:

const express = require('express');
const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');
const app = express();

// Use JSON parser for webhook events
app.use('/webhook', express.raw({type: 'application/json'}));
app.use(express.json());

app.post('/webhook', async (req, res) => {
  const sig = req.headers['stripe-signature'];
  const endpointSecret = 'whsec_YOUR_WEBHOOK\_SECRET';
  
  let event;
  
  try {
    event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
  } catch (err) {
    console.error(`Webhook Error: ${err.message}`);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }
  
  // Handle specific event types
  switch (event.type) {
    case 'payment\_intent.succeeded':
      const paymentIntent = event.data.object;
      console.log(`PaymentIntent ${paymentIntent.id} succeeded. Amount: ${paymentIntent.amount}`);
      
      // Here you can update your database, notify users, etc.
      // If using the transfer approach, you could trigger your transfer here
      
      break;
    case 'transfer.created':
      const transfer = event.data.object;
      console.log(`Transfer ${transfer.id} created. Amount: ${transfer.amount}`);
      break;
    // Add more event handlers as needed
  }
  
  res.json({received: true});
});

 

Step 10: Handle Reporting and Reconciliation

 

Create functions to track and report on the payment splits:

const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');

async function getTransactionReport(startDate, endDate) {
  // Get all payments in the date range
  const paymentIntents = await stripe.paymentIntents.list({
    created: {
      gte: Math.floor(startDate.getTime() / 1000),
      lte: Math.floor(endDate.getTime() / 1000)
    },
    limit: 100
  });
  
  // Get all transfers in the date range
  const transfers = await stripe.transfers.list({
    created: {
      gte: Math.floor(startDate.getTime() / 1000),
      lte: Math.floor(endDate.getTime() / 1000)
    },
    limit: 100
  });
  
  // Process and format the data for reporting
  const report = {
    totalProcessed: 0,
    platformRevenue: 0,
    transferredToPartners: 0,
    transactions: []
  };
  
  // Process payment intents
  for (const pi of paymentIntents.data) {
    report.totalProcessed += pi.amount;
    
    // Find matching transfers
    const relatedTransfers = transfers.data.filter(t => 
      t.source\_transaction === (pi.charges.data[0] ? pi.charges.data[0].id : null)
    );
    
    const transferAmount = relatedTransfers.reduce((sum, t) => sum + t.amount, 0);
    const platformAmount = pi.amount - transferAmount;
    
    report.platformRevenue += platformAmount;
    report.transferredToPartners += transferAmount;
    
    report.transactions.push({
      id: pi.id,
      amount: pi.amount,
      platformAmount,
      transferAmount,
      date: new Date(pi.created \* 1000),
      transfers: relatedTransfers.map(t => ({
        id: t.id,
        amount: t.amount,
        destination: t.destination
      }))
    });
  }
  
  return report;
}

 

Step 11: Test Your Implementation

 

Before going live, test your payment splitting implementation using Stripe's test mode:

  1. Use Stripe's test cards (e.g., 4242 4242 4242 4242)
  2. Check that payments are properly recorded in the Stripe dashboard
  3. Verify that funds are correctly split between accounts
  4. Test error cases and make sure your error handling works

 

Step 12: Go Live with Production

 

When you're ready to go live:

  1. Switch from test API keys to production API keys
  2. Update your webhook endpoints to production URLs
  3. Ensure all Connect accounts have completed their onboarding
  4. Monitor your first few transactions closely
  5. Set up appropriate alerting for failed payments or transfers

 

Conclusion

 

You now have a comprehensive system for splitting payments between two users in Stripe. This implementation supports several approaches including direct charges with automatic transfers, destination charges, and manual transfers. The approach you choose will depend on your specific business requirements and user experience needs.

Remember to keep your Stripe libraries updated and regularly check Stripe's documentation for any API changes or new features that might improve your payment splitting implementation.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022