Build a logistics payment system by generating a dynamic quote based on route and cargo specs, creating a Paystack payment link for the accepted quote, triggering dispatch via webhook when payment is confirmed, and paying the assigned courier via Paystack Transfers after delivery is confirmed.
Tables: zones (name, cities[]), pricing_matrix (origin_zone_id, dest_zone_id, base_rate, rate_per_kg), shipments (shipper_id, origin, destination, weight_kg, dimensions, quote_amount, status, paystack_ref, courier_id, tracking_number), couriers (name, phone, vehicle_type, paystack_recipient_code).
async function generateQuote(origin, destination, weightKg, packageType) {
var originZone = await db.zones.findByCity(origin);
var destZone = await db.zones.findByCity(destination);
var pricing = await db.pricingMatrix.findByZones(originZone.id, destZone.id);
var baseRate = pricing.base_rate;
var weightCharge = Math.ceil(weightKg) * pricing.rate_per_kg;
var total = baseRate + weightCharge;
// Additional surcharges
if (packageType === 'fragile') total = Math.floor(total * 1.15); // 15% fragile surcharge
return { origin, destination, weight_kg: weightKg, quote_amount: total, expires_in_mins: 30 };
}
async function acceptQuoteAndPay(shipperEmail, shipperId, quoteData) {
var trackingNumber = 'TRK-' + Date.now().toString(36).toUpperCase();
var reference = 'LOG-' + Date.now();
var shipment = await db.shipments.create({
shipper_id: shipperId,
origin: quoteData.origin,
destination: quoteData.destination,
weight_kg: quoteData.weight_kg,
quote_amount: quoteData.quote_amount,
status: 'pending_payment',
paystack_ref: reference,
tracking_number: trackingNumber,
});
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: shipperEmail,
amount: quoteData.quote_amount,
currency: 'NGN',
reference,
metadata: { shipment_id: shipment.id, shipper_id: shipperId, tracking_number: trackingNumber },
}),
});
return { shipment, authorizationUrl: (await res.json()).data.authorization_url };
}
// Webhook: payment confirmed — dispatch job
if (event.event === 'charge.success') {
var meta = event.data.metadata;
if (!meta.shipment_id) return res.sendStatus(200);
await db.shipments.update(meta.shipment_id, { status: 'awaiting_pickup' });
var courier = await assignCourier(meta.shipment_id);
await sendCourierJobNotification(courier.phone, meta.shipment_id, meta.tracking_number);
await sendShipperConfirmation(meta.shipper_id, meta.tracking_number);
}
// Courier app updates shipment status
async function updateShipmentStatus(courierId, shipmentId, newStatus) {
var validStatuses = ['picked_up', 'in_transit', 'delivered', 'failed_delivery'];
if (!validStatuses.includes(newStatus)) throw new Error('Invalid status');
var shipment = await db.shipments.findById(shipmentId);
if (shipment.courier_id !== courierId) throw new Error('Not your shipment');
await db.shipments.update(shipmentId, { status: newStatus, updated_at: new Date() });
await notifyShipper(shipment.shipper_id, 'Your shipment ' + shipment.tracking_number + ' is now: ' + newStatus);
// Pay courier on confirmed delivery
if (newStatus === 'delivered') {
var courier = await db.couriers.findById(courierId);
var platformFee = Math.floor(shipment.quote_amount * 0.20); // 20% platform fee
var courierPayout = shipment.quote_amount - platformFee;
await fetch('https://api.paystack.co/transfer', {
method: 'POST',
headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({
source: 'balance', amount: courierPayout,
recipient: courier.paystack_recipient_code,
reason: 'Delivery payout: ' + shipment.tracking_number,
}),
});
}
}
// Shipper can track their package
async function getTrackingInfo(trackingNumber) {
var shipment = await db.shipments.findByTrackingNumber(trackingNumber);
var history = await db.shipmentEvents.findByShipment(shipment.id);
return { status: shipment.status, tracking_number: trackingNumber, events: history };
}
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 grocery delivery app with rider payouts for a related delivery model with item-based orders instead of shipment quotes.
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