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 Waste Collection Subscription Service
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Build a Waste Collection Subscription Service

Build a waste collection subscription by creating Paystack Plans for monthly billing, subscribing customers via the transaction initialize endpoint, scheduling collections when subscription.create fires, suspending collections when invoice.payment_failed enters the grace period, and resuming service when the next invoice.create success event arrives.

In this article

  1. 1. Subscription Plans and Collection Scheduling
  2. 2. Driver Routes and Service Suspension
  3. 3. Learn More

Subscription Plans and Collection Scheduling

Tables: collection_plans (name, frequency, collections_per_month, price, paystack_plan_code), subscribers (name, phone, email, address, zone_id, plan_id, paystack_subscription_code, status, next_collection_date), collections (subscriber_id, driver_id, scheduled_date, status, completed_at, notes).

var PLANS = {
  weekly:   { collections: 4, price: 200000, label: 'Weekly — 4 collections/month' },    // NGN 2,000/month
  biweekly: { collections: 2, price: 120000, label: 'Biweekly — 2 collections/month' },  // NGN 1,200/month
  monthly:  { collections: 1, price: 70000,  label: 'Monthly — 1 collection/month' },    // NGN 700/month
};

async function subscribe(customerEmail, customerId, planName, address, zoneId) {
  var plan = await db.collectionPlans.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, zone_id: zoneId },
    }),
  });
  return (await res.json()).data.authorization_url;
}

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

// Each billing cycle renewal
if (event.event === 'invoice.create' && event.data.status === 'success') {
  var code = event.data.subscription.subscription_code;
  var subscriber = await db.subscribers.findBySubscription(code);
  if (subscriber) {
    await db.subscribers.update(subscriber.id, { status: 'active' });
    await scheduleCollectionsForCycle(subscriber.id, subscriber.plan_id);
  }
}

async function scheduleCollectionsForCycle(subscriberId, planId) {
  var plan = await db.collectionPlans.findById(planId);
  var subscriber = await db.subscribers.findById(subscriberId);
  var today = new Date();

  for (var i = 0; i < plan.collections_per_month; i++) {
    var collectionDate = getNextCollectionDay(subscriber.zone_id, today, i);
    await db.collections.create({
      subscriber_id: subscriberId, scheduled_date: collectionDate, status: 'scheduled',
    });
  }
}

Driver Routes and Service Suspension

// Build daily route for each zone
async function buildDriverRoute(zoneId, date) {
  var collections = await db.collections.findByZoneAndDate(zoneId, date);
  var sortedByAddress = collections.sort(function(a, b) {
    return a.subscriber.address_order - b.subscriber.address_order;
  });
  return sortedByAddress;
}

// Driver marks collection complete
async function markCollected(driverId, collectionId, notes) {
  await db.collections.update(collectionId, {
    driver_id: driverId, status: 'completed',
    completed_at: new Date(), notes: notes || '',
  });
}

// Failed payment — enter grace period then suspend
if (event.event === 'invoice.payment_failed') {
  var code = event.data.subscription.subscription_code;
  var subscriber = await db.subscribers.findBySubscription(code);
  await db.subscribers.update(subscriber.id, { status: 'grace_period' });

  // Let current scheduled collections complete within 7 days
  var graceCutoff = new Date();
  graceCutoff.setDate(graceCutoff.getDate() + 7);
  await sendSMS(subscriber.phone,
    'Payment failed. Collections continue for 7 days. Pay to keep service: yourapp.com/billing'
  );
}

// Cron: suspend after grace period
async function suspendGraceExpired() {
  var graceDeadline = new Date();
  graceDeadline.setDate(graceDeadline.getDate() - 7);

  var gracePeriodSubs = await db.subscribers.findAll({ status: 'grace_period', updated_at_lt: graceDeadline });
  for (var sub of gracePeriodSubs) {
    await db.subscribers.update(sub.id, { status: 'suspended' });
    await db.collections.cancelFuture(sub.id);
    await sendSMS(sub.phone, 'Service suspended due to non-payment. Pay to restore: yourapp.com/billing');
  }
}

if (event.event === 'subscription.disable') {
  var code = event.data.subscription_code;
  var subscriber = await db.subscribers.findBySubscription(code);
  if (subscriber) {
    await db.subscribers.update(subscriber.id, { status: 'cancelled' });
    await db.collections.cancelFuture(subscriber.id);
  }
}

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 water delivery subscription service for the same recurring billing and route management pattern applied to water delivery.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Use Paystack Plans for monthly recurring billing — one plan per collection frequency (weekly, biweekly, monthly).
  • ✓Tie collection scheduling directly to billing: invoice.create success = schedule next collection.
  • ✓Suspend service after a 7-day grace period on failed payment — resume automatically when payment clears.
  • ✓Group collections by zone and day to build efficient driver routes.
  • ✓Track missed collections and credit customers or reschedule to maintain service quality.

Frequently Asked Questions

How do I handle customer complaints about missed collections?
Build a complaints flow: customers tap "We missed a collection" in the app or text a shortcode. Log the complaint with date and subscriber details. The driver lead reviews and schedules a makeup collection within 48 hours. Offer a one-day credit (prorated from the monthly fee) for each confirmed missed collection. Track missed collection rate by driver and zone as a service quality metric.
Can I support businesses with higher collection frequency and volume?
Yes. Create commercial plans with daily or custom frequency collection, priced by bin size or tonnage. Commercial clients often pay monthly via bank transfer rather than card — for this, generate a Paystack payment link and send it to their accounts payable contact monthly. Track commercial contracts separately from residential subscriptions in your database.
How do I track which waste went to which recycling center or dumpsite?
Add a disposal_site_id to the collections table. Drivers select the drop-off location after each route. Track tonnage per disposal site for environmental reporting. If you operate a recycling stream (paper, plastic), record the material type at pickup and track the recycling diversion rate per zone. This data is valuable for corporate ESG reporting partnerships.

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

Pay with Pesalink on Paystack: Developer Guide

A developer guide to accepting Pesalink payments through Paystack. Covers what Pesalink is, how it appears at checkout, which banks support it, transaction limits, advantages over card and M-Pesa for certain use cases, and the integration code for enabling bank_transfer as a payment channel.

Read more

Paystack Split Payments Explained

A clear explanation of how Paystack split payments work from the moment a customer pays to the moment each party receives their share. Covers the settlement flow, key API parameters, how Paystack calculates each party's portion, and the difference between single-split and multi-split transactions.

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.