Build a pharmacy ordering app by separating OTC (over-the-counter) and prescription medicine flows: OTC medicines pay immediately via Paystack; prescription medicines require a prescription photo upload and pharmacist approval before payment is collected. Use webhooks to confirm payment and trigger order fulfillment.
Tables: medicines (name, category, price, requires_prescription, stock), orders (customer_id, items[], total, prescription_url, pharmacist_id, status, paystack_ref), deliveries (order_id, address, scheduled_time, rider_id, status).
OTC flow (most orders): customer selects medicines → cart total calculated → pay via Paystack → webhook confirms → order goes to pharmacy for picking.
Prescription flow: customer uploads prescription photo → order status: "awaiting_review" → pharmacist reviews (approves/rejects/modifies) → Paystack payment initialized → customer pays → order fulfilled.
async function createOrder(customerId, cartItems, prescriptionUrl) {
var hasPrescriptionItems = cartItems.some(item => item.requires_prescription);
var total = cartItems.reduce((sum, item) => sum + (item.price * item.qty), 0);
var order = await db.orders.create({
customer_id: customerId,
items: cartItems,
total,
prescription_url: prescriptionUrl || null,
status: hasPrescriptionItems ? 'awaiting_review' : 'pending_payment',
});
if (!hasPrescriptionItems) {
// OTC: go straight to payment
return await initializePayment(order);
}
// Prescription: wait for pharmacist to call initiatePaymentAfterApproval(orderId)
return { order, message: 'Prescription submitted for review. You will receive a payment link once approved.' };
}
async function approveAndCharge(orderId, pharmacistId, finalAmount) {
await db.orders.update(orderId, { status: 'approved', pharmacist_id: pharmacistId, total: finalAmount });
return await initializePayment(await db.orders.findById(orderId));
}
if (event.event === 'charge.success') {
var order = await db.orders.findByPaystackRef(event.data.reference);
await db.orders.update(order.id, { status: 'paid' });
// Notify pharmacy to start picking
await notifyPharmacy(order.id);
// Schedule delivery
await db.deliveries.create({
order_id: order.id,
address: order.delivery_address,
status: 'pending',
});
}
// Track delivery stages
async function updateDelivery(deliveryId, newStatus, riderId) {
var delivery = await db.deliveries.findById(deliveryId);
await db.deliveries.update(deliveryId, { status: newStatus, rider_id: riderId });
var statusMessages = {
'picked_up': 'Your order has been picked up by our rider',
'en_route': 'Your medicines are on the way',
'delivered': 'Your order has been delivered',
};
await sendSMS(delivery.customer_phone, statusMessages[newStatus]);
}
For controlled substances and high-value prescription medicines, require an OTP or digital signature from the customer at delivery. This creates an audit trail showing the customer received their medicine.
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 the delivery and rider payout side of this architecture.
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