McTaba Labs logo
Software EngineeringThe WhiteboardProjectsEventsMasterclassesWebinarsLearn
Book Call

500+ developers already in the community

Get answers, find accountability partners, and stay ahead

Join Discord →
McTaba Labs logo

Educating 1 million students in tech by 2030.

Programs

  • HighSchool Graduates Launchpad
  • AI & Business Automation
  • Corporate Training
  • McTaba Story & Mission
  • Full-Stack Curriculum
  • Course Pricing & Plans
  • Frequently Asked Questions

Resources

  • Join Discord Community
  • Tech Events in Nairobi
  • Masterclasses
  • Free Resources
  • Learn
  • Campus
  • Africa Tech Talent Report
  • Partner With Us
  • Start a Project
  • Our Work

Contact Us

  • 124 Manyani East Road, Lavington, Nairobi
  • +254727795603
  • Book a Discovery Call

Office Hours:

Mon-Fri: 9am - 2pm EAT

Sat: 10am - 12pm EAT

© 2027 McTaba Labs - A product of Edtech Industries. All rights reserved.

Privacy PolicyTerms of ServiceRefund Policy
  1. Learn
  2. Paystack for African Developers
  3. Build a Water Delivery Subscription Service
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Build a Water Delivery Subscription Service

Build a water delivery subscription by creating Paystack Plans per delivery frequency (weekly, biweekly, monthly), subscribing customers via the transaction initialize endpoint, triggering a delivery schedule on each invoice.create success webhook, and pausing deliveries when invoice.payment_failed fires until the customer updates their payment method.

In this article

  1. 1. Subscription Plans and Customer Enrollment
  2. 2. Delivery Management and Failed Payment Handling
  3. 3. Learn More

Subscription Plans and Customer Enrollment

Tables: delivery_plans (name, jerrycans_per_cycle, interval, paystack_plan_code, price), customers (name, phone, address, delivery_zone, paystack_subscription_code, status, next_delivery_date), deliveries (customer_id, scheduled_date, driver_id, status, jerrycans_delivered, empties_collected).

var DELIVERY_PLANS = {
  weekly:   { cans: 2, price: 100000, label: '2 cans weekly' },     // NGN 1,000/week
  biweekly: { cans: 4, price: 180000, label: '4 cans biweekly' },   // NGN 1,800/biweek
  monthly:  { cans: 8, price: 320000, label: '8 cans monthly' },    // NGN 3,200/month
};

async function enrollCustomer(customerEmail, customerId, planName) {
  var plan = await db.deliveryPlans.findByName(planName);

  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: plan.price,
      plan: plan.paystack_plan_code,
      metadata: { customer_id: customerId, plan_id: plan.id },
    }),
  });
  return (await res.json()).data.authorization_url;
}

// First subscription created
if (event.event === 'subscription.create') {
  var meta = event.data.metadata;
  await db.customers.update(meta.customer_id, {
    paystack_subscription_code: event.data.subscription_code,
    status: 'active',
  });
}

// Each billing cycle — schedule the next delivery
if (event.event === 'invoice.create' && event.data.status === 'success') {
  var code = event.data.subscription.subscription_code;
  var customer = await db.customers.findBySubscription(code);
  var plan = await db.deliveryPlans.findById(customer.plan_id);

  // Schedule delivery for next available delivery day in the customer's zone
  var deliveryDate = await nextDeliveryDayForZone(customer.delivery_zone);
  await db.deliveries.create({
    customer_id: customer.id,
    scheduled_date: deliveryDate,
    status: 'scheduled',
    jerrycans_to_deliver: plan.cans_per_cycle,
  });
  await sendSMS(customer.phone, 'Your water delivery is scheduled for ' + deliveryDate.toDateString() + '. Please have your empty cans ready.');
}

Delivery Management and Failed Payment Handling

// Driver confirms delivery
async function confirmDelivery(driverId, deliveryId, emptiesCollected) {
  await db.deliveries.update(deliveryId, {
    status: 'delivered',
    driver_id: driverId,
    delivered_at: new Date(),
    empties_collected: emptiesCollected,
  });
  var delivery = await db.deliveries.findById(deliveryId);
  await sendDeliveryReceipt(delivery.customer_id, emptiesCollected);
}

// Failed payment — pause deliveries
if (event.event === 'invoice.payment_failed') {
  var code = event.data.subscription.subscription_code;
  var customer = await db.customers.findBySubscription(code);

  await db.customers.update(customer.id, { status: 'payment_failed' });
  // Cancel any upcoming deliveries
  await db.deliveries.cancelUpcoming(customer.id);
  await sendSMS(customer.phone,
    'Your water delivery is paused — payment failed. Please pay your subscription to resume deliveries: ' +
    'https://yourapp.com/account/billing'
  );
}

// Subscription resumed (customer pays outstanding invoice)
if (event.event === 'invoice.create' && event.data.status === 'success') {
  // Runs for both regular renewals and outstanding invoice payments
  var customer = await db.customers.findBySubscription(event.data.subscription.subscription_code);
  if (customer.status === 'payment_failed') {
    await db.customers.update(customer.id, { status: 'active' });
    // Re-schedule missed deliveries or just resume from next cycle
    await scheduleNextDelivery(customer.id);
  }
}

// Zone-based route grouping for drivers
async function buildDailyRoute(zone, date) {
  var deliveries = await db.deliveries.findByZoneAndDate(zone, date);
  deliveries.sort(function(a, b) { return a.customer.address_order - b.customer.address_order; });
  return deliveries;
}

Get weekly developer tips

Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.

No spam. Unsubscribe anytime.

Learn More

See build a subscription box business backend for the same Paystack Plans subscription model applied to physical product deliveries.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Use Paystack Plans for recurring billing — match plan intervals to delivery frequency (weekly, monthly).
  • ✓Schedule the next delivery in the invoice.create success webhook, not subscription.create.
  • ✓Pause deliveries on invoice.payment_failed — resume when the customer pays the outstanding invoice.
  • ✓Group deliveries by zone and day to optimize driver routes and reduce operational cost.
  • ✓Track empty jerry can returns — deliveries depend on the driver picking up empties from the previous cycle.

Frequently Asked Questions

How do I handle customers who want to skip a delivery week?
Add a "Skip next delivery" option in the customer portal. This does not pause the subscription — billing continues. Instead, mark the next delivery as "skipped" and do not create a delivery record for that cycle. Some services offer a maximum of 2 skips per year to prevent revenue loss. Communicate the skip deadline clearly (e.g., skip before Monday 5 PM for the following week's delivery).
How do I manage the empty jerry can return system?
Track empties_on_hand per customer. When a delivery goes out, add cans delivered to the customer's total. When a driver collects empties, deduct from the total. If a customer accumulates more than 2 unreturned cans above their subscription quantity, charge a deposit (NGN 500 per can) via a one-time Paystack payment link. This incentivizes returns without blocking deliveries.
Can I support on-demand orders (not on a subscription)?
Yes. Build a one-time order flow alongside the subscription: customer selects quantity, pays via Paystack standard checkout, delivery is scheduled in the webhook. Offer a discount on subscriptions vs one-time orders to encourage recurring revenue. Track the conversion rate from one-time orders to subscriptions — target 30%+ of one-time customers converting to subscriptions within 60 days.

Learn Paystack Integration

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

Not ready? Create Free Account

Keep reading

Things You Can Build with Paystack: Project Guides

A complete map of real-world projects you can build using the Paystack API. From online store checkouts and subscription platforms to savings apps and solar pay-as-you-go systems, each category links to step-by-step build guides with working code.

Read more

Build a Parking Payment App

A build guide for a parking payment app where drivers scan a QR code on entry, pay for their session via Paystack (card or M-Pesa), and the system validates payment before the barrier lifts for exit. Covers time-based pricing, session management, and overstay handling.

Read more

Build a Referral Payout System

A build guide for a referral payout system where users share referral links, earn commissions when referrals convert, and withdraw their balance via Paystack Transfers to their bank account or M-Pesa wallet.

Read more

Build a Veterinary Clinic Booking and Payment System

A build guide for a veterinary clinic booking and payment system where pet owners book appointments online, pay a deposit via Paystack to confirm the slot, and pay the balance at the clinic after the consultation. Includes pet health records, vaccination schedules, and automated reminders.

Read more

Paystack vs Daraja: When to Use a Gateway and When to Go Direct

A practical decision framework for Kenyan developers choosing between Paystack and Safaricom Daraja. Covers cost trade-offs, integration complexity, payment channel coverage, UX control, maintenance burden, and real-world scenarios where each option wins.

Read more

Building a Multi-Vendor Store with Paystack

A multi-vendor store lets customers buy products from different sellers in one cart. This guide covers splitting a mixed-cart payment across multiple vendor subaccounts using Paystack Transaction Splits, collecting platform commission, and managing vendor payouts.

Read more

Explore related topics

M-Pesa & African Stack

M-Pesa Daraja API tutorials, USSD development, and WhatsApp Business API guides. The African Stack that global bootcamps do not teach.

Learn to Code in Kenya

Everything a Kenyan needs to know before writing their first line of code. Career paths, real costs, hardware requirements, and honest answers about what it takes.

Learning to Code

Roadmaps, language guides, and practical advice for learning to code and launching a programming career.

Breaking Into Tech

Practical, no-nonsense guides for anyone looking to start or change their career in tech. From choosing your first language to acing interviews.

The African Stack: Skills for This Market

The skills that make Kenyan developers hard to replace: M-Pesa, USSD, WhatsApp, fintech, and building for the African market.

Bonaventure Ogeto

Bonaventure Ogeto

Founder, McTaba Labs

Bonaventure is the founder of McTaba Labs, a coding bootcamp in Nairobi, Kenya. He has spent years building products for the African market and mentoring the next generation of developers across the continent.