McTaba Labs logo
Software EngineeringThe WhiteboardProjectsEventsMasterclassesWebinarsLearn
Book Call

500+ developers already in the community

Get answers, find accountability partners, and stay ahead

Join Discord →
McTaba Labs logo

Educating 1 million students in tech by 2030.

Programs

  • HighSchool Graduates Launchpad
  • AI & Business Automation
  • Corporate Training
  • McTaba Story & Mission
  • Full-Stack Curriculum
  • Course Pricing & Plans
  • Frequently Asked Questions

Resources

  • Join Discord Community
  • Tech Events in Nairobi
  • Masterclasses
  • Free Resources
  • Learn
  • Campus
  • Africa Tech Talent Report
  • Partner With Us
  • Start a Project
  • Our Work

Contact Us

  • 124 Manyani East Road, Lavington, Nairobi
  • +254727795603
  • Book a Discovery Call

Office Hours:

Mon-Fri: 9am - 2pm EAT

Sat: 10am - 12pm EAT

© 2027 McTaba Labs - A product of Edtech Industries. All rights reserved.

Privacy PolicyTerms of ServiceRefund Policy
  1. Learn
  2. Paystack for African Developers
  3. Building a Driver Payout System for a Ride Hailing App
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 20, 2026

Building a Driver Payout System for a Ride Hailing App

A ride-hailing driver payout system on Paystack calculates each driver net earnings from completed trips (fare minus platform commission), aggregates them into daily or weekly settlement batches, and transfers the net amount to the driver M-Pesa wallet or bank account. Handle cash-collected trips by deducting the platform commission from the driver next payout. Use bulk transfers for scheduled settlements and single transfers for instant withdrawals.

In this article

  1. 1. The Driver Earnings Model
  2. 2. Daily Settlement Calculation
  3. 3. Batching Driver Payouts
  4. 4. M-Pesa as the Default Driver Payout Method
  5. 5. Instant Withdrawal Feature
  6. 6. Real-Time Driver Balance Tracking
  7. 7. Build Products Drivers Trust

The Driver Earnings Model

A ride-hailing driver earns money per trip. Each trip has a fare that the rider pays. The platform takes a commission (typically 15-25%), and the driver keeps the rest. But the calculation is complicated by payment method: did the rider pay digitally (card, M-Pesa) or with cash?

function calculateTripEarnings(trip) {
  var fare = trip.fareAmount; // In minor units (cents)
  var commissionRate = trip.driver.commissionRate || 0.20; // 20% platform commission
  var platformCommission = Math.round(fare * commissionRate);
  var driverNet = fare - platformCommission;

  if (trip.paymentMethod === 'cash') {
    // Driver already collected the full fare in cash
    // Platform is owed its commission
    return {
      driverNet: -platformCommission, // Negative: driver owes platform
      platformCommission: platformCommission,
      cashCollected: fare,
      digitalPayment: 0,
    };
  }

  // Digital payment: money is in the platform Paystack balance
  return {
    driverNet: driverNet, // Positive: platform owes driver
    platformCommission: platformCommission,
    cashCollected: 0,
    digitalPayment: fare,
  };
}

Cash trips are the tricky part. When a rider pays cash, the driver has the money. The platform needs to collect its commission. The standard approach: deduct the platform commission from the driver's next digital payout. If the driver only does cash trips, they accumulate a negative balance (they owe the platform), and the platform needs a process to collect.

Daily Settlement Calculation

async function calculateDailySettlement(driverId, date) {
  var trips = await db.trips.findCompletedByDriverAndDate(driverId, date);

  var totalDigitalEarnings = 0;
  var totalCashOwed = 0;
  var tripCount = 0;

  for (var i = 0; i < trips.length; i++) {
    var earnings = calculateTripEarnings(trips[i]);
    if (earnings.driverNet > 0) {
      totalDigitalEarnings += earnings.driverNet;
    } else {
      totalCashOwed += Math.abs(earnings.driverNet);
    }
    tripCount++;
  }

  // Previous outstanding balance (cash owed from prior days)
  var outstandingBalance = await db.driverBalances.getOutstanding(driverId);

  // Net settlement = digital earnings - cash owed - previous outstanding
  var netSettlement = totalDigitalEarnings - totalCashOwed - outstandingBalance;

  if (netSettlement <= 0) {
    // Driver owes more than they earned digitally
    // Carry the negative balance forward
    await db.driverBalances.setOutstanding(
      driverId,
      Math.abs(netSettlement)
    );
    return {
      driverId: driverId,
      date: date,
      tripCount: tripCount,
      digitalEarnings: totalDigitalEarnings,
      cashOwed: totalCashOwed,
      outstandingBalance: outstandingBalance,
      netSettlement: 0,
      carryForward: Math.abs(netSettlement),
      payoutNeeded: false,
    };
  }

  // Clear outstanding balance and create payout
  await db.driverBalances.setOutstanding(driverId, 0);

  return {
    driverId: driverId,
    date: date,
    tripCount: tripCount,
    digitalEarnings: totalDigitalEarnings,
    cashOwed: totalCashOwed,
    outstandingBalance: outstandingBalance,
    netSettlement: netSettlement,
    carryForward: 0,
    payoutNeeded: true,
  };
}

Get weekly developer tips

Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.

No spam. Unsubscribe anytime.

Batching Driver Payouts

async function processDailyDriverPayouts(date) {
  var activeDrivers = await db.drivers.findActive();
  var payouts = [];

  for (var i = 0; i < activeDrivers.length; i++) {
    var settlement = await calculateDailySettlement(activeDrivers[i].id, date);

    if (settlement.payoutNeeded && settlement.netSettlement > 0) {
      payouts.push({
        amount: settlement.netSettlement,
        recipient: activeDrivers[i].paystackRecipientCode,
        reason: 'Daily settlement - ' + date + ' (' + settlement.tripCount + ' trips)',
        reference: 'driver_' + activeDrivers[i].id + '_' + date,
      });
    }
  }

  if (payouts.length === 0) {
    console.log('No driver payouts needed for ' + date);
    return;
  }

  // Check balance
  var totalNeeded = 0;
  for (var j = 0; j < payouts.length; j++) {
    totalNeeded += payouts[j].amount;
  }

  var balance = await getAvailableBalance('KES');
  if (balance.total < totalNeeded) {
    await alertFinance('Insufficient KES balance for driver payouts. Need: '
      + (totalNeeded / 100) + ', Available: ' + (balance.total / 100));
    return;
  }

  // Execute in batches of 100
  await executeBulkTransfers(payouts, 'KES');
  console.log('Processed ' + payouts.length + ' driver payouts for ' + date);
}

Run this as a cron job in the early morning (e.g., 6 AM) for the previous day's trips. Early morning execution gives the transfers time to settle before the driver starts their next shift. For M-Pesa payouts, settlement is instant regardless of time.

M-Pesa as the Default Driver Payout Method

In Kenya, M-Pesa is the natural choice for driver payouts. It is instant (drivers see the money in seconds), available 24/7, and every driver has M-Pesa. Bank transfers are an alternative for drivers who prefer bank settlement, but M-Pesa is the default.

During driver onboarding, collect the M-Pesa phone number, validate it, and create the recipient:

async function onboardDriver(driverData) {
  // Validate phone number
  var phoneResult = validateAndNormalizePhone(driverData.phoneNumber);
  if (!phoneResult.valid) {
    throw new Error(phoneResult.error);
  }

  // Create M-Pesa recipient
  var recipient = await createMpesaRecipient(
    driverData.name,
    phoneResult.normalized
  );

  // Create driver record
  var driver = await db.drivers.create({
    name: driverData.name,
    phone: phoneResult.normalized,
    vehicleType: driverData.vehicleType,
    paystackRecipientCode: recipient.recipient_code,
    recipientType: 'mpesa',
    commissionRate: 0.20, // 20% platform commission
    status: 'active',
  });

  return driver;
}

For the full M-Pesa transfer guide, see transfers to M-Pesa wallets, Paybills, and Tills.

Instant Withdrawal Feature

async function driverInstantWithdrawal(driverId, amount) {
  var driver = await db.drivers.findById(driverId);

  // Calculate available balance
  var available = await calculateDriverAvailableBalance(driverId);
  if (amount > available) {
    throw new Error('Requested amount exceeds available balance');
  }

  // Daily limit check
  var todayWithdrawals = await db.payouts.sumTodayByDriver(driverId);
  var dailyLimit = 5000000; // 50,000 KES
  if (todayWithdrawals + amount > dailyLimit) {
    throw new Error('Daily withdrawal limit exceeded');
  }

  // Minimum check
  if (amount < 10000) { // 100 KES minimum
    throw new Error('Minimum withdrawal is 100 KES');
  }

  // Execute immediate transfer
  var reference = 'instant_' + driverId + '_' + Date.now();
  var result = await initiateTransfer(
    driver.paystackRecipientCode,
    amount,
    'Instant withdrawal',
    reference
  );

  await db.payouts.create({
    driverId: driverId,
    recipientCode: driver.paystackRecipientCode,
    amountMinorUnit: amount,
    reference: reference,
    transferCode: result.transferCode,
    status: 'processing',
    type: 'instant_withdrawal',
  });

  return { reference: reference, status: 'processing' };
}

Instant withdrawals are a competitive advantage. Drivers choose platforms that let them access their money quickly. But implement proper controls: daily limits prevent abuse, minimum amounts prevent micro-transactions that incur fees, and balance validation ensures you do not pay out more than the driver has earned.

Real-Time Driver Balance Tracking

async function getDriverBalance(driverId) {
  // Today's completed trips
  var todayTrips = await db.trips.findCompletedByDriverToday(driverId);
  var todayEarnings = 0;
  var todayCashOwed = 0;

  for (var i = 0; i < todayTrips.length; i++) {
    var earnings = calculateTripEarnings(todayTrips[i]);
    if (earnings.driverNet > 0) {
      todayEarnings += earnings.driverNet;
    } else {
      todayCashOwed += Math.abs(earnings.driverNet);
    }
  }

  // Unsettled balance from previous days
  var unsettled = await db.driverBalances.getUnsettled(driverId);

  // Today's withdrawals
  var todayWithdrawals = await db.payouts.sumTodayByDriver(driverId);

  var totalAvailable = unsettled + todayEarnings - todayCashOwed - todayWithdrawals;

  return {
    todayEarnings: todayEarnings / 100,
    todayTrips: todayTrips.length,
    todayCashOwed: todayCashOwed / 100,
    unsettledBalance: unsettled / 100,
    todayWithdrawals: todayWithdrawals / 100,
    availableForWithdrawal: Math.max(0, totalAvailable) / 100,
    currency: 'KES',
  };
}

Show this balance in the driver app. Update it after each completed trip. Drivers check their balance frequently, and seeing it grow throughout the day is motivating. The "available for withdrawal" number is what they can cash out right now. Make it prominent.

Build Products Drivers Trust

Driver payouts are the most time-sensitive transfer use case. Drivers depend on these payouts for daily living expenses. Building a reliable, fast payout system is not just good engineering; it is how you attract and retain drivers.

The McTaba bootcamp covers building real marketplace products, including the payment infrastructure that makes them work.

Create a free McTaba account

Key Takeaways

  • ✓Trip-based earnings require per-trip calculation: fare amount minus platform commission equals driver net for that trip.
  • ✓Cash-collected trips create a negative balance. The driver collected cash from the rider, so the platform commission is deducted from their next digital payout.
  • ✓M-Pesa is the preferred payout method for drivers in Kenya because it is instant and drivers can access the money immediately.
  • ✓Daily settlements keep drivers happy. Calculate the day earnings at midnight, batch the payouts, and execute in the early morning.
  • ✓Instant withdrawal is a competitive feature. Let drivers cash out their available balance at any time with daily limits.
  • ✓Real-time balance tracking shows drivers their current earnings, pending settlements, and available withdrawal amount throughout the day.

Frequently Asked Questions

How do I handle drivers who only do cash trips and owe the platform money?
Track the outstanding balance (platform commission owed) and deduct it from future digital payouts. If the balance grows too large, disable the driver from accepting cash trips until the balance is cleared. Some platforms set a maximum outstanding balance threshold.
Should I pay drivers daily or weekly?
Daily is better for driver retention. Drivers prefer frequent, smaller payouts over waiting a week. If operational overhead is a concern, start with weekly and add daily as a premium feature or once your automation is mature. In markets with M-Pesa (like Kenya), daily payouts are near-zero effort because M-Pesa transfers are instant.
How do I handle tips in the payout calculation?
Tips paid digitally should be passed through to the driver in full (no platform commission on tips). Add the tip amount to the driver net for the trip. Tips paid in cash are already in the driver hands and do not need to be included in the payout calculation.

Learn Paystack Integration

KES 2,999

The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.

Start Paystack Course

Not ready? Create Free Account

Keep reading

Paystack Transfers and Payouts: Complete Guide

Everything you need to send money out of your Paystack account: creating transfer recipients, initiating single and bulk transfers, handling OTP requirements, managing failures, and building automated payout systems for bank accounts, M-Pesa wallets, and mobile money across Africa.

Read more

Transfer OTP: Enabling, Disabling and Automating

Paystack requires OTP confirmation for transfers by default. This guide explains the OTP flow, how to disable it for automated payout systems, the security trade-offs of disabling OTP, and how to build your own application-level approval controls to replace OTP with something better suited to production automation.

Read more

Funding Your Paystack Balance Programmatically

Your Paystack balance needs funds before you can send transfers. This guide covers the different ways to fund your balance, setting up dedicated virtual accounts, monitoring balance levels, building automated funding alerts, and integrating balance management into your payout pipeline so you never run out mid-payout.

Read more

Building a Vendor Payout Portal on Paystack

Your vendors need visibility into their earnings and payouts. This guide covers building a vendor-facing payout portal: an earnings dashboard showing pending and paid amounts, payout history with status tracking, self-service bank detail management, on-demand withdrawal requests, and the backend architecture to support it all.

Read more

Double-Entry Bookkeeping for African Payment Integrations

Double-entry bookkeeping is not just for accountants. When you integrate Paystack (or any African payment gateway), recording every transaction as matching debit and credit entries catches errors that single-entry systems miss. This guide shows how to apply the principle in code, with practical PostgreSQL schema and Node.js examples.

Read more

Explore related topics

M-Pesa & African Stack

M-Pesa Daraja API tutorials, USSD development, and WhatsApp Business API guides. The African Stack that global bootcamps do not teach.

Learn to Code in Kenya

Everything a Kenyan needs to know before writing their first line of code. Career paths, real costs, hardware requirements, and honest answers about what it takes.

Learning to Code

Roadmaps, language guides, and practical advice for learning to code and launching a programming career.

Breaking Into Tech

Practical, no-nonsense guides for anyone looking to start or change their career in tech. From choosing your first language to acing interviews.

The African Stack: Skills for This Market

The skills that make Kenyan developers hard to replace: M-Pesa, USSD, WhatsApp, fintech, and building for the African market.

Bonaventure Ogeto

Bonaventure Ogeto

Founder, McTaba Labs

Bonaventure is the founder of McTaba Labs, a coding bootcamp in Nairobi, Kenya. He has spent years building products for the African market and mentoring the next generation of developers across the continent.