Learn how to set up and use Stripe Connect for marketplace payments, from account creation to payment flows, webhooks, payouts, and security best practices.
Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
How to Use Stripe Connect for Marketplace Payments: A Comprehensive Guide
Introduction
Stripe Connect is a powerful platform that enables marketplace businesses to facilitate payments between customers and service providers or sellers. This guide will walk you through setting up and implementing Stripe Connect for your marketplace application, covering everything from account setup to handling complex payment flows.
Step 1: Understanding Stripe Connect Models
Before implementation, understand the three Stripe Connect account types:
For most marketplaces, Express accounts offer the best balance of user experience and development complexity.
Step 2: Setting Up Your Stripe Account
Step 3: Installing Stripe Libraries
Install the Stripe server-side library for your programming language:
For Node.js:
npm install stripe
For Python:
pip install stripe
For PHP:
composer require stripe/stripe-php
For Ruby:
gem install stripe
Step 4: Initialize Stripe in Your Application
Node.js:
const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');
Python:
import stripe
stripe.api_key = "sk_test_YOUR_SECRET\_KEY"
PHP:
\Stripe\Stripe::setApiKey('sk_test_YOUR_SECRET_KEY');
Ruby:
require 'stripe'
Stripe.api_key = 'sk_test_YOUR_SECRET\_KEY'
Step 5: Creating Connected Accounts (Express)
For Express accounts, you'll create an account and then redirect users to the Stripe-hosted onboarding:
Node.js:
app.post('/create-express-account', async (req, res) => {
try {
// Create a new Express account
const account = await stripe.accounts.create({
type: 'express',
capabilities: {
card\_payments: {requested: true},
transfers: {requested: true},
},
business\_type: 'individual',
business\_profile: {
mcc: '5734', // Computer Software Stores
url: 'https://example.com',
},
metadata: {
user\_id: req.body.userId // Store your internal user ID
}
});
// Save the account ID to your database
// yourDatabase.saveStripeAccountId(req.body.userId, account.id);
// Generate an account link for onboarding
const accountLink = await stripe.accountLinks.create({
account: account.id,
refresh\_url: 'https://example.com/reauth',
return\_url: 'https://example.com/success',
type: 'account\_onboarding',
});
// Redirect to the Stripe hosted onboarding
res.redirect(accountLink.url);
} catch (error) {
console.error('Error creating Express account:', error);
res.status(500).send({ error: error.message });
}
});
Step 6: Setting Up Webhooks
Webhooks are essential for receiving updates about connected accounts:
app.post('/stripe-webhook', express.raw({type: 'application/json'}), async (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
sig,
'whsec_YOUR_WEBHOOK\_SECRET'
);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle specific events
switch (event.type) {
case 'account.updated':
const account = event.data.object;
console.log('Account updated:', account.id);
// Update your database with account details
break;
case 'account.application.deauthorized':
// The user has disconnected their account
break;
// Handle other events...
}
res.json({received: true});
});
Step 7: Processing Direct Charges
For marketplaces where customers pay your platform and you pay sellers:
app.post('/create-payment', async (req, res) => {
try {
const { amount, currency, customer, description, connected_account_id, application_fee_amount } = req.body;
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency,
customer,
description,
application_fee_amount, // Your platform fee
transfer\_data: {
destination: connected_account_id, // The seller's account ID
},
});
res.send({
clientSecret: paymentIntent.client\_secret,
});
} catch (error) {
res.status(500).send({ error: error.message });
}
});
Step 8: Implementing Payment Intent on the Client Side
Add Stripe.js to your client:
Use Elements to collect payment information:
// Initialize Stripe
const stripe = Stripe('pk_test_YOUR_PUBLISHABLE_KEY');
const elements = stripe.elements();
// Create card element
const cardElement = elements.create('card');
cardElement.mount('#card-element');
// Handle form submission
document.getElementById('payment-form').addEventListener('submit', async (event) => {
event.preventDefault();
const { error, paymentIntent } = await stripe.confirmCardPayment(
clientSecret, // Received from server
{
payment\_method: {
card: cardElement,
billing\_details: {
name: document.getElementById('name').value,
},
},
}
);
if (error) {
// Show error to customer
const errorElement = document.getElementById('card-errors');
errorElement.textContent = error.message;
} else if (paymentIntent.status === 'succeeded') {
// Payment successful
window.location.href = '/payment-success';
}
});
Step 9: Implementing Transfers for Existing Payments
If you need to transfer funds after a payment has been processed:
app.post('/create-transfer', async (req, res) => {
try {
const { amount, destination, source\_transaction } = req.body;
const transfer = await stripe.transfers.create({
amount,
currency: 'usd',
destination, // Connected account ID
source\_transaction, // Payment Intent ID if transferring from a specific payment
transfer_group: req.body.order_id, // Optional grouping identifier
});
res.send({ success: true, transfer });
} catch (error) {
res.status(500).send({ error: error.message });
}
});
Step 10: Implementing Destination Charges
For marketplaces where charges occur directly on the connected account:
// First, create a payment method on your platform
app.post('/create-payment-method', async (req, res) => {
try {
const paymentMethod = await stripe.paymentMethods.create({
type: 'card',
card: {
number: '4242424242424242',
exp\_month: 12,
exp\_year: 2023,
cvc: '123',
},
billing\_details: {
name: 'Customer Name',
},
});
res.send({ paymentMethod });
} catch (error) {
res.status(500).send({ error: error.message });
}
});
// Then create a charge on the connected account
app.post('/create-destination-charge', async (req, res) => {
try {
const { amount, connected_account_id, payment_method_id } = req.body;
// Create a PaymentIntent on the connected account
const paymentIntent = await stripe.paymentIntents.create(
{
amount,
currency: 'usd',
payment_method: payment_method\_id,
confirmation\_method: 'manual',
confirm: true,
application_fee_amount: Math.round(amount \* 0.1), // 10% platform fee
},
{
stripeAccount: connected_account_id, // Specify the connected account
}
);
res.send({ success: true, paymentIntent });
} catch (error) {
res.status(500).send({ error: error.message });
}
});
Step 11: Managing Payouts
Control when funds are paid out to your connected accounts:
// Update payout schedule for a connected account
app.post('/update-payout-schedule', async (req, res) => {
try {
const { connected_account_id, interval, monthly_anchor, weekly_anchor } = req.body;
const account = await stripe.accounts.update(
connected_account_id,
{
settings: {
payouts: {
schedule: {
interval: interval, // 'manual', 'daily', 'weekly', 'monthly'
monthly_anchor: monthly_anchor, // Required if interval is 'monthly'
weekly_anchor: weekly_anchor, // Required if interval is 'weekly'
},
},
},
}
);
res.send({ success: true, account });
} catch (error) {
res.status(500).send({ error: error.message });
}
});
// Create an instant payout for a connected account
app.post('/create-payout', async (req, res) => {
try {
const { connected_account_id, amount } = req.body;
const payout = await stripe.payouts.create(
{
amount,
currency: 'usd',
},
{
stripeAccount: connected_account_id,
}
);
res.send({ success: true, payout });
} catch (error) {
res.status(500).send({ error: error.message });
}
});
Step 12: Handling Disputes and Refunds
Implement refund functionality:
app.post('/refund-payment', async (req, res) => {
try {
const { payment_intent_id, amount } = req.body;
const refund = await stripe.refunds.create({
payment_intent: payment_intent\_id,
amount: amount, // Optional: partial refund amount
});
res.send({ success: true, refund });
} catch (error) {
res.status(500).send({ error: error.message });
}
});
Set up dispute handling webhook:
// In your webhook handler
case 'charge.dispute.created':
const dispute = event.data.object;
// Notify the seller about the dispute
// Store dispute information in your database
await notifySeller(dispute.payment\_intent, 'A dispute has been filed against your charge');
break;
Step 13: Managing Connected Accounts
Retrieve account details:
app.get('/account/:id', async (req, res) => {
try {
const account = await stripe.accounts.retrieve(req.params.id);
res.send({ account });
} catch (error) {
res.status(500).send({ error: error.message });
}
});
Generate a new account link for incomplete onboarding:
app.post('/account-links', async (req, res) => {
try {
const { account\_id } = req.body;
const accountLink = await stripe.accountLinks.create({
account: account\_id,
refresh\_url: 'https://example.com/reauth',
return\_url: 'https://example.com/return',
type: 'account\_onboarding',
});
res.send({ url: accountLink.url });
} catch (error) {
res.status(500).send({ error: error.message });
}
});
Step 14: Implementing Split Payments
For complex scenarios where funds need to be split among multiple recipients:
app.post('/create-split-payment', async (req, res) => {
try {
const { amount, customer, description, recipients } = req.body;
// First create a payment intent on the platform
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency: 'usd',
customer,
description,
payment_method_types: ['card'],
capture\_method: 'manual', // Authorize now, capture later
});
// Store payment intent and recipient information for later distribution
// yourDatabase.storeSplitPaymentInfo(paymentIntent.id, recipients);
res.send({
clientSecret: paymentIntent.client\_secret
});
} catch (error) {
res.status(500).send({ error: error.message });
}
});
// After payment is authorized, capture and distribute funds
app.post('/capture-and-distribute', async (req, res) => {
try {
const { payment_intent_id } = req.body;
// Capture the payment
const paymentIntent = await stripe.paymentIntents.capture(payment_intent_id);
// Get the recipients data from your database
// const recipients = await yourDatabase.getSplitPaymentRecipients(payment_intent_id);
const recipients = req.body.recipients; // For demo purposes
// Create transfers to each recipient
const transfers = [];
for (const recipient of recipients) {
const transfer = await stripe.transfers.create({
amount: recipient.amount,
currency: 'usd',
destination: recipient.account\_id,
source\_transaction: paymentIntent.charges.data[0].id,
description: `Payment for order ${req.body.order_id}`,
});
transfers.push(transfer);
}
res.send({ success: true, transfers });
} catch (error) {
res.status(500).send({ error: error.message });
}
});
Step 15: Reporting and Analytics
Implement reporting to track marketplace performance:
app.get('/platform-balance', async (req, res) => {
try {
const balance = await stripe.balance.retrieve();
res.send({ balance });
} catch (error) {
res.status(500).send({ error: error.message });
}
});
app.get('/connected-account-balance/:id', async (req, res) => {
try {
const balance = await stripe.balance.retrieve({
stripeAccount: req.params.id,
});
res.send({ balance });
} catch (error) {
res.status(500).send({ error: error.message });
}
});
app.get('/transaction-history', async (req, res) => {
try {
const { limit, starting\_after } = req.query;
const balanceTransactions = await stripe.balanceTransactions.list({
limit: limit || 10,
starting_after: starting_after || undefined,
});
res.send({ transactions: balanceTransactions });
} catch (error) {
res.status(500).send({ error: error.message });
}
});
Step 16: Security and Compliance
Implement best practices for security:
// Load environment variables
require('dotenv').config();
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
app.post('/stripe-webhook', express.raw({type: 'application/json'}), async (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Process event...
res.json({received: true});
});
Step 17: Testing Your Integration
sk_test_
and pk_test_
// Test script for creating and paying out to a connected account
async function testConnectFlow() {
try {
// 1. Create a connected account
const account = await stripe.accounts.create({
type: 'express',
capabilities: {
card\_payments: {requested: true},
transfers: {requested: true},
},
business\_type: 'individual',
});
console.log('Created account:', account.id);
// 2. Create a payment on the platform
const paymentIntent = await stripe.paymentIntents.create({
amount: 1000, // $10.00
currency: 'usd',
payment_method: 'pm_card\_visa', // Test payment method
confirm: true,
});
console.log('Created payment:', paymentIntent.id);
// 3. Transfer funds to the connected account
const transfer = await stripe.transfers.create({
amount: 800, // $8.00 (keeping $2 as platform fee)
currency: 'usd',
destination: account.id,
source\_transaction: paymentIntent.charges.data[0].id,
});
console.log('Created transfer:', transfer.id);
return { success: true, account, paymentIntent, transfer };
} catch (error) {
console.error('Test failed:', error);
return { success: false, error };
}
}
stripe listen --forward-to localhost:3000/stripe-webhook
Step 18: Going Live
Before switching to production:
sk_live_
and pk_live_
)
Step 19: Advanced Features
Implement additional marketplace features:
app.post('/create-seller-subscription', async (req, res) => {
try {
const { connected_account_id, price\_id } = req.body;
// Create a Customer object for the connected account
const customer = await stripe.customers.create({
email: req.body.email,
metadata: {
connected_account_id,
},
});
// Attach a payment method
await stripe.paymentMethods.attach(req.body.payment_method_id, {
customer: customer.id,
});
// Set as default payment method
await stripe.customers.update(customer.id, {
invoice\_settings: {
default_payment_method: req.body.payment_method_id,
},
});
// Create subscription
const subscription = await stripe.subscriptions.create({
customer: customer.id,
items: [{ price: price\_id }],
expand: ['latest_invoice.payment_intent'],
});
res.send({ subscription });
} catch (error) {
res.status(500).send({ error: error.message });
}
});
app.post('/create-escrow-payment', async (req, res) => {
try {
const { amount, currency, customer, connected_account_id } = req.body;
// 1. Create a payment intent
const paymentIntent = await stripe.paymentIntents.create({
amount,
currency,
customer,
payment_method_types: ['card'],
capture\_method: 'manual', // Authorize only, capture later
});
// 2. Store the payment intent and connected account in your database
// await yourDatabase.storeEscrowPayment({
// payment_intent_id: paymentIntent.id,
// connected_account_id,
// amount,
// status: 'pending'
// });
res.send({
clientSecret: paymentIntent.client\_secret,
});
} catch (error) {
res.status(500).send({ error: error.message });
}
});
// Later, when service is completed:
app.post('/release-escrow', async (req, res) => {
try {
const { payment_intent_id, connected_account_id } = req.body;
// 1. Capture the payment
const paymentIntent = await stripe.paymentIntents.capture(payment_intent_id);
// 2. Create a transfer to the connected account
const platformFee = Math.round(paymentIntent.amount \* 0.1); // 10% fee
const transferAmount = paymentIntent.amount - platformFee;
const transfer = await stripe.transfers.create({
amount: transferAmount,
currency: paymentIntent.currency,
destination: connected_account_id,
source\_transaction: paymentIntent.charges.data[0].id,
});
// 3. Update your database
// await yourDatabase.updateEscrowPayment(payment_intent_id, 'completed');
res.send({ success: true, transfer });
} catch (error) {
res.status(500).send({ error: error.message });
}
});
Step 20: Scaling Your Marketplace
As your marketplace grows:
app.post('/create-payment', async (req, res) => {
try {
const idempotencyKey = req.body.order\_id; // Use a unique identifier
const paymentIntent = await stripe.paymentIntents.create(
{
amount: req.body.amount,
currency: req.body.currency,
customer: req.body.customer,
// other parameters...
},
{
idempotencyKey: idempotencyKey,
}
);
res.send({ clientSecret: paymentIntent.client\_secret });
} catch (error) {
res.status(500).send({ error: error.message });
}
});
async function retryStripeOperation(operation, maxRetries = 3) {
let lastError;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
lastError = error;
// Only retry certain types of errors
if (error.type === 'StripeConnectionError' || error.type === 'StripeAPIError') {
// Exponential backoff
const delay = Math.pow(2, attempt) \* 100;
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
// For other types of errors, don't retry
throw error;
}
}
// If we've exhausted all retries
throw lastError;
}
// Usage
app.post('/create-transfer', async (req, res) => {
try {
const transfer = await retryStripeOperation(() =>
stripe.transfers.create({
amount: req.body.amount,
currency: 'usd',
destination: req.body.connected_account_id,
})
);
res.send({ success: true, transfer });
} catch (error) {
res.status(500).send({ error: error.message });
}
});
Conclusion
Implementing Stripe Connect for your marketplace involves many components, from account creation to payment processing and funds distribution. This guide covered the essential steps to build a robust marketplace payment system with Stripe Connect. Remember to thoroughly test your implementation and follow Stripe's best practices for security and compliance.
As your marketplace grows, continuously monitor and optimize your payment flows to provide the best experience for both your customers and sellers while minimizing costs and risks.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.