Bonaventure OgetoBy Bonaventure Ogeto|

Build a Car Hire Booking System with Preauthorization

Build a car hire booking system using Paystack's two-step charge: first, authorize a card to hold the damage deposit (the customer sees a pending amount but is not yet charged); on return, capture the exact amount due. Use the Paystack Charge endpoint with authorization_code for returning customers, or a one-time card authorization flow for new customers.

Card Authorization and Deposit Hold

Tables: vehicles (make, model, plate, daily_rate, deposit_amount, status), rentals (vehicle_id, customer_id, pickup_date, return_date, rental_fee, deposit_held, actual_charge, status, paystack_ref, authorization_code).

Paystack card tokenization: when the customer completes their first payment, Paystack returns an authorization.authorization_code in the charge.success webhook. Save this code — it lets you charge the same card again without the customer re-entering their details.

// Step 1: Initial booking — collect rental fee + authorize deposit
async function initiateBooking(customerId, customerEmail, vehicleId, pickupDate, returnDate) {
  var vehicle = await db.vehicles.findById(vehicleId);
  var days = Math.ceil((new Date(returnDate) - new Date(pickupDate)) / 86400000);
  var rentalFee = vehicle.daily_rate * days;

  // Charge rental fee now, note the authorization for deposit later
  var res = 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: customerEmail,
      amount: rentalFee,
      currency: 'NGN',
      metadata: { customer_id: customerId, vehicle_id: vehicleId, pickup_date: pickupDate, return_date: returnDate, purpose: 'rental_fee' },
    }),
  });
  return (await res.json()).data.authorization_url;
}

// Webhook: save authorization_code for future charges
if (event.event === 'charge.success' && event.data.metadata.purpose === 'rental_fee') {
  var authCode = event.data.authorization.authorization_code;
  var meta = event.data.metadata;

  await db.rentals.create({
    customer_id: meta.customer_id, vehicle_id: meta.vehicle_id,
    pickup_date: meta.pickup_date, return_date: meta.return_date,
    rental_fee: event.data.amount, status: 'confirmed',
    authorization_code: authCode, // save this
  });
}

Return Processing and Damage Charges

// On vehicle return — charge damage deposit or excess fees using saved auth code
async function processReturn(rentalId, damageAssessment) {
  var rental = await db.rentals.findById(rentalId);
  var depositAmount = rental.deposit_amount;

  if (damageAssessment.total > 0) {
    // Charge damage using saved authorization code (no card re-entry needed)
    var chargeAmount = Math.min(damageAssessment.total, depositAmount);

    var res = await fetch('https://api.paystack.co/charge/authorization', {
      method: 'POST',
      headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        email: rental.customer_email,
        amount: chargeAmount,
        authorization_code: rental.authorization_code,
        metadata: { rental_id: rentalId, purpose: 'damage_charge', description: damageAssessment.notes },
      }),
    });

    var data = await res.json();
    await db.rentals.update(rentalId, {
      status: 'returned_with_damage',
      damage_charge: chargeAmount,
      paystack_damage_ref: data.data.reference,
    });
  } else {
    // No damage — just close the rental
    await db.rentals.update(rentalId, { status: 'returned_clean' });
  }

  await db.vehicles.update(rental.vehicle_id, { status: 'available' });
}

The charge/authorization endpoint charges the card without the customer needing to re-enter their details. This is what makes car hire feasible on Paystack — you charge the rental up front and charge damages on return without a second checkout flow.

Learn More

See build a hotel booking and deposit system for the deposit-and-balance pattern in accommodation bookings.

Sign up for the McTaba newsletter

Key Takeaways

  • Use Paystack charge authorization to hold a damage deposit without actually charging the card until return.
  • Tokenize the customer's card on the first transaction to enable follow-up charges for damages.
  • Collect rental fees at pickup and damage/excess fees on return using the saved authorization_code.
  • Store the authorization_code securely — it is a reusable token tied to the customer's card.
  • Handle no-shows by capturing the deposit; handle early returns by calculating the actual rental period.

Frequently Asked Questions

Does Paystack support pre-authorization holds (block funds without charging)?
Paystack does not natively support pre-authorization holds (where funds are reserved but not yet captured) the way Stripe does with payment_intent. The practical pattern for car hire on Paystack is to charge the rental fee up front and use the saved authorization_code to charge damages on return. Communicate clearly to customers that the deposit will only be charged if damages are found.
What if the customer's card is declined when charging for damages?
A declined damage charge is a business risk. Have a policy: if the charge fails, contact the customer for an alternative payment method. Maintain a record of the damage assessment and the failed charge. If the customer refuses to pay, this becomes a collections or small claims matter. Your terms and conditions should make this liability explicit at booking time.
Can I verify vehicle damage with photos?
Yes. Build a vehicle condition checklist that the staff completes at pickup and return, with photo uploads at each stage. Store photos in cloud storage and link them to the rental record. These photos are your evidence in case of a damage charge dispute. A timestamped photo of the vehicle before and after rental resolves most disputes quickly.

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