Build a home services marketplace by registering each provider as a Paystack Subaccount, routing service bookings through the subaccount split at payment time, and confirming the booking only after the charge.success webhook fires. The provider receives their share automatically; your platform keeps the service fee. Use Paystack Transfers for bonus payouts or adjustments.
Tables: providers (name, phone, email, category, skills[], bio, avg_rating, paystack_subaccount_code, is_verified, is_available), services (provider_id, name, category, base_price, duration_hrs, description), bookings (customer_id, provider_id, service_id, address, scheduled_at, status, total_amount, paystack_ref).
var PLATFORM_FEE_PERCENT = 20; // platform keeps 20%, provider gets 80%
// Onboard a new provider with Paystack Subaccount
async function onboardProvider(providerData) {
var provider = await db.providers.create({ ...providerData, is_verified: false });
var res = await fetch('https://api.paystack.co/subaccount', {
method: 'POST',
headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
business_name: providerData.name,
settlement_bank: providerData.bank_code,
account_number: providerData.account_number,
percentage_charge: PLATFORM_FEE_PERCENT,
}),
});
var subaccountCode = (await res.json()).data.subaccount_code;
await db.providers.update(provider.id, { paystack_subaccount_code: subaccountCode });
return provider;
}
// Customer books a service
async function bookService(customerEmail, customerId, serviceId, address, scheduledAt) {
var service = await db.services.findById(serviceId);
var provider = await db.providers.findById(service.provider_id);
if (!provider.is_available) throw new Error('Provider is not currently available');
// Travel surcharge based on distance from provider base
var travelFee = await estimateTravelFee(provider, address);
var total = service.base_price + travelFee;
var reference = 'HOME-' + serviceId + '-' + Date.now();
var booking = await db.bookings.create({
customer_id: customerId, provider_id: provider.id, service_id: serviceId,
address, scheduled_at: scheduledAt, status: 'pending_payment',
total_amount: total, paystack_ref: reference,
});
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: total, currency: 'NGN', reference,
subaccount: provider.paystack_subaccount_code,
bearer: 'account', // platform bears transaction fee
metadata: { booking_id: booking.id, customer_id: customerId, provider_id: provider.id, service: service.name },
}),
});
return (await res.json()).data.authorization_url;
}
// Webhook: confirm booking
if (event.event === 'charge.success') {
var meta = event.data.metadata;
if (!meta.booking_id) return res.sendStatus(200);
await db.bookings.update(meta.booking_id, { status: 'confirmed' });
var booking = await db.bookings.findById(meta.booking_id);
var provider = await db.providers.findById(meta.provider_id);
await sendBookingConfirmation(meta.customer_id, provider.name, booking.scheduled_at, booking.address);
await sendJobNotification(provider.phone, meta.booking_id, booking.scheduled_at, booking.address);
}
// Provider marks job complete
async function completeJob(providerId, bookingId) {
var booking = await db.bookings.findById(bookingId);
if (booking.provider_id !== providerId) throw new Error('Not your booking');
if (booking.status !== 'confirmed') throw new Error('Booking not confirmed');
await db.bookings.update(bookingId, { status: 'completed', completed_at: new Date() });
// Send rating request to customer
var customer = await db.customers.findById(booking.customer_id);
await sendRatingRequest(customer.email, customer.name, booking.id, providerId);
}
// Customer rates the provider
async function rateProvider(customerId, bookingId, rating, review) {
if (rating < 1 || rating > 5) throw new Error('Rating must be between 1 and 5');
var booking = await db.bookings.findById(bookingId);
if (booking.customer_id !== customerId) throw new Error('Not your booking');
await db.reviews.create({ booking_id: bookingId, provider_id: booking.provider_id, customer_id: customerId, rating, review, created_at: new Date() });
// Update provider average rating
var allRatings = await db.reviews.findByProvider(booking.provider_id);
var avgRating = allRatings.reduce(function(sum, r) { return sum + r.rating; }, 0) / allRatings.length;
await db.providers.update(booking.provider_id, { avg_rating: Math.round(avgRating * 10) / 10 });
}
// Customer disputes the job — escalate to admin
async function disputeJob(customerId, bookingId, reason) {
var booking = await db.bookings.findById(bookingId);
if (booking.customer_id !== customerId) throw new Error('Not your booking');
await db.bookings.update(bookingId, { status: 'disputed' });
await db.disputes.create({ booking_id: bookingId, raised_by: customerId, reason, status: 'open' });
await notifyAdmin('New dispute on booking #' + bookingId + ': ' + reason);
}
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
See build a tutoring marketplace with split payments for the same provider subaccount split pattern applied to educational services.
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