Build a telemedicine payment flow by requiring patients to pay the consultation fee via Paystack before the video call link is revealed, confirming payment via the charge.success webhook, and disbursing the doctor's share via Paystack Transfers after the consultation is completed and marked by the doctor. Use Paystack Subaccounts for automatic per-doctor settlement if you have many doctors.
Tables: doctors (name, specialty, consultation_fee, paystack_subaccount_code, paystack_recipient_code), consultations (patient_id, doctor_id, scheduled_at, duration_mins, status, video_link, paystack_ref, paid_at).
async function bookConsultation(patientEmail, patientId, doctorId, scheduledAt) {
var doctor = await db.doctors.findById(doctorId);
var reference = 'TELE-' + Date.now();
var consultation = await db.consultations.create({
patient_id: patientId,
doctor_id: doctorId,
scheduled_at: scheduledAt,
status: 'pending_payment',
paystack_ref: reference,
});
// Payment required before video link is revealed
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: patientEmail,
amount: doctor.consultation_fee,
currency: 'NGN',
reference,
subaccount: doctor.paystack_subaccount_code, // doctor gets their share automatically
bearer: 'subaccount', // platform fees deducted from doctor's share
metadata: { consultation_id: consultation.id, patient_id: patientId, doctor_id: doctorId },
}),
});
return { consultation, authorizationUrl: (await res.json()).data.authorization_url };
}
// Webhook: payment confirmed — generate and reveal video link
if (event.event === 'charge.success') {
var meta = event.data.metadata;
if (!meta.consultation_id) return res.sendStatus(200);
var videoLink = await createVideoRoom(meta.consultation_id); // e.g., Daily.co or Jitsi
await db.consultations.update(meta.consultation_id, {
status: 'confirmed',
video_link: videoLink,
paid_at: new Date(),
});
await sendConsultationConfirmation(meta.patient_id, meta.doctor_id, videoLink);
}
// Doctor marks consultation complete
async function completeConsultation(doctorId, consultationId, notes) {
var consultation = await db.consultations.findById(consultationId);
if (consultation.doctor_id !== doctorId) throw new Error('Not your consultation');
if (consultation.status !== 'confirmed') throw new Error('Consultation not confirmed');
await db.consultations.update(consultationId, {
status: 'completed',
doctor_notes: notes,
completed_at: new Date(),
});
// If not using subaccounts, manually pay doctor via Transfer
var doctor = await db.doctors.findById(doctorId);
if (!doctor.paystack_subaccount_code && doctor.paystack_recipient_code) {
var platformFee = Math.floor(consultation.fee * 0.15); // 15% platform fee
var doctorPayout = consultation.fee - 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: doctorPayout, recipient: doctor.paystack_recipient_code, reason: 'Consultation payout' }),
});
}
}
// Doctor cancels — refund patient in full
async function cancelByDoctor(doctorId, consultationId) {
var consultation = await db.consultations.findById(consultationId);
if (consultation.doctor_id !== doctorId) throw new Error('Not your consultation');
await fetch('https://api.paystack.co/refund', {
method: 'POST',
headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ transaction: consultation.paystack_ref }),
});
await db.consultations.update(consultationId, { status: 'cancelled_by_doctor' });
await notifyPatient(consultation.patient_id, 'Your consultation was cancelled. Full refund issued.');
}
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 veterinary clinic booking and payment system for a related booking + deposit pattern applied to in-person appointments.
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