Building Marketplaces with open-source tools
This guide outlines the technical implementation of a multi-party payment system for a two-sided marketplace using Stripe Connect. We focus on the 'Destination Charges' model, which allows a platform to facilitate transactions between buyers and sellers while automatically deducting a commission fee before funds reach the seller's account.
Extend User Schema for Connect Account Mapping
To facilitate payments, you must map your application's 'Seller' entity to a Stripe Connect Account ID. Update your database schema to store the Stripe Account ID and a status flag indicating if the seller has completed the onboarding process.
ALTER TABLE users
ADD COLUMN stripe_connect_id TEXT UNIQUE,
ADD COLUMN stripe_onboarding_complete BOOLEAN DEFAULT FALSE;⚠ Common Pitfalls
- •Storing the Stripe Secret Key in the database instead of environment variables.
- •Failing to index the stripe_connect_id column, leading to slow lookups during webhook processing.
Implement the Onboarding Link Flow
Create an Express or Next.js API route that generates a Stripe Account Link. This link redirects the seller to Stripe's hosted onboarding UI. Upon completion, Stripe redirects the user back to your platform.
const accountLink = await stripe.accountLinks.create({
account: sellerStripeId,
refresh_url: 'https://yourmarket.com/reauth',
return_url: 'https://yourmarket.com/dashboard',
type: 'account_onboarding',
});
return res.json({ url: accountLink.url });⚠ Common Pitfalls
- •Assuming a single redirect means onboarding is finished; you must verify the 'details_submitted' state via webhooks or account retrieval.
- •Hardcoding return URLs instead of using environment-specific base URLs.
Create Checkout Sessions with Application Fees
When a buyer initiates a purchase, create a Stripe Checkout Session. Use the 'transfer_data' parameter to specify the seller's account and the 'application_fee_amount' to define your platform's commission in cents.
const session = await stripe.checkout.sessions.create({
mode: 'payment',
line_items: [{ price: 'price_id', quantity: 1 }],
payment_intent_data: {
application_fee_amount: 500, // $5.00 fee
transfer_data: {
destination: sellerStripeId,
},
},
success_url: 'https://yourmarket.com/success',
cancel_url: 'https://yourmarket.com/cancel',
});⚠ Common Pitfalls
- •Calculating fees on the client-side, which allows users to manipulate the commission amount.
- •Neglecting to handle currency precision; Stripe requires amounts in the smallest currency unit (e.g., cents).
Handle Webhook Events for State Reconciliation
Listen for 'checkout.session.completed' and 'account.updated' events. Use these to update your internal database records for order fulfillment and seller verification status.
const event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
if (event.type === 'checkout.session.completed') {
const session = event.data.object;
await db.orders.update({ where: { sessionId: session.id }, data: { status: 'PAID' } });
}
if (event.type === 'account.updated') {
const account = event.data.object;
if (account.details_submitted) {
await db.users.update({ where: { stripe_connect_id: account.id }, data: { stripe_onboarding_complete: true } });
}
}⚠ Common Pitfalls
- •Processing webhooks without verifying the 'stripe-signature' header, exposing your DB to spoofed requests.
- •Not returning a 200 status code immediately, causing Stripe to retry and potentially double-process events.
Implement Refund Logic with Fee Reversal
When a buyer requests a refund, you must decide if the platform fee is also refunded. Use the 'reverse_transfer' and 'refund_application_fee' flags to manage the flow of funds back from the seller's Connect account.
const refund = await stripe.refunds.create({
payment_intent: paymentIntentId,
reverse_transfer: true,
refund_application_fee: true,
});⚠ Common Pitfalls
- •Attempting to refund more than the seller's current Stripe balance, which results in a failed refund and requires manual intervention.
- •Forgetting to reverse the transfer, which leaves the seller with the money while the platform absorbs the refund cost.
What you built
By implementing this flow, you have established a scalable payment architecture that handles automated splitting and seller onboarding. Ensure you monitor your Stripe Dashboard for 'Requirements Due' flags on Connect accounts to prevent payout blocks as regulations evolve.