Building a Food Delivery Marketplace on Paystack
Create a Paystack Transaction Split group with the restaurant subaccount (food price share) and rider subaccount (delivery fee share). Pass the split_code when initializing a customer order. Your platform keeps the commission residual. On charge.success, mark the order as paid and notify both restaurant and rider. Rider payouts can be real-time via the split or batched daily via transfers.
Three-Way Split: Restaurant, Rider, Platform
// Create a Transaction Split group for an order
async function createOrderSplit(restaurantSubaccount, riderSubaccount, orderTotal, deliveryFee) {
var restaurantAmount = orderTotal - deliveryFee;
var platformCommissionRate = 0.08; // 8%
var restaurantShare = restaurantAmount * (1 - platformCommissionRate);
var response = await fetch('https://api.paystack.co/split', {
method: 'POST',
headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Order split',
type: 'flat', // flat kobo amounts
currency: 'NGN',
subaccounts: [
{ subaccount: restaurantSubaccount, share: Math.round(restaurantShare * 100) },
{ subaccount: riderSubaccount, share: Math.round(deliveryFee * 100) },
],
bearer_type: 'account',
}),
});
var data = await response.json();
return data.data.split_code; // e.g., SPL_xxxxxx
}
// Initialize order payment with the split
app.post('/api/orders', async (req, res) => {
var { restaurantId, riderId, items, email } = req.body;
var restaurant = await db.restaurants.findById(restaurantId);
var rider = await db.riders.findById(riderId);
var orderTotal = items.reduce((s, i) => s + i.price * i.qty, 0);
var deliveryFee = 500; // NGN 500
var splitCode = await createOrderSplit(
restaurant.subaccount_code,
rider.subaccount_code,
orderTotal,
deliveryFee
);
var response = await fetch('https://api.paystack.co/transaction/initialize', {
method: 'POST',
headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
email,
amount: Math.round((orderTotal + deliveryFee) * 100),
split_code: splitCode,
metadata: { order_id: 'ord_' + Date.now(), restaurant_id: restaurantId },
}),
});
var data = await response.json();
res.json({ authorization_url: data.data.authorization_url });
});
Order Status via Webhook
if (event.event === 'charge.success') {
var orderId = event.data.metadata?.order_id;
if (!orderId) return;
await db.orders.update({ status: 'paid', paid_at: new Date() }, { where: { reference: event.data.reference } });
// Notify restaurant to start preparing
await notifyRestaurant(orderId);
// Notify rider to go pick up
await notifyRider(orderId);
}
Learn More
This guide is part of the Paystack split payments guide.
Key Takeaways
- ✓A food delivery order splits three ways: restaurant (food), rider (delivery fee), platform (commission) — use a multi-split Transaction Split group.
- ✓Create a split group per restaurant or use a dynamic split_code generated per order based on current delivery fee.
- ✓On charge.success webhook, notify the restaurant to prepare the order and the rider to pick it up.
- ✓Use real-time splits for restaurants but batch weekly rider payouts to reduce transfer costs.
- ✓Handle order cancellations via Paystack refunds — partial refunds are supported (refund the food price, keep the delivery fee if rider is en route).
- ✓Store order_id in transaction metadata for webhook handlers to match charges to orders.
Frequently Asked Questions
- Do I need to create a new split group for every order?
- Only if the amounts differ per order (variable delivery fee, different restaurant). If all orders to the same restaurant have the same split ratio, create the split group once and reuse the split_code. For variable amounts, create a new split group per order.
- How do I handle a partial refund when only the food is cancelled but rider already departed?
- Use the Paystack refund API with a specific amount (food total only). Paystack partial refunds come from your main account balance — the already-settled subaccount shares are not automatically clawed back. Compensate the rider from your platform balance.
- How do I batch rider payouts weekly to reduce transfer costs?
- Instead of using a rider subaccount in the split (real-time settlement), route all rider fees to your platform account. Track rider earnings in your database. At end of week, run a bulk transfer to all riders. This reduces transfer count from per-order to per-rider-per-week.
- What exchange rate does Paystack use for food delivery platforms charging in USD?
- Paystack settles in the transaction currency. For NGN transactions, settlement is in NGN. If you charge customers in USD (for cross-border orders), your subaccounts must also be configured in USD. Most local food delivery platforms charge in local currency.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.
Apply to the McTaba Marathon