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 Savings Goal App with Scheduled Debits
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Build a Savings Goal App with Scheduled Debits

Build a savings goal app by collecting the user's first deposit via Paystack (which tokenizes their card), saving the authorization_code, and running scheduled debits using Charge Authorization on a cron job. When the goal is reached, pay out via Paystack Transfers. The user never has to re-enter their card.

In this article

  1. 1. Goal Setup and Card Tokenization
  2. 2. Scheduled Debits and Goal Completion
  3. 3. Learn More

Goal Setup and Card Tokenization

Tables: savings_goals (user_id, name, target_amount, saved_amount, frequency, next_debit_date, authorization_code, status), savings_transactions (goal_id, type, amount, paystack_ref, status, created_at).

// Step 1: First deposit — tokenizes the card
async function createGoalAndFirstDeposit(userId, userEmail, goalName, targetAmount, firstDepositAmount, frequency) {
  var goal = await db.savingsGoals.create({
    user_id: userId, name: goalName, target_amount: targetAmount,
    saved_amount: 0, frequency, status: 'pending_first_deposit',
  });

  var reference = 'SAVE-' + goal.id + '-' + Date.now();
  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: userEmail, amount: firstDepositAmount, currency: 'NGN', reference,
      metadata: { goal_id: goal.id, user_id: userId, deposit_type: 'first' },
    }),
  });
  return (await res.json()).data.authorization_url;
}

// Webhook: save authorization_code from first deposit
if (event.event === 'charge.success' && event.data.metadata.deposit_type === 'first') {
  var goalId = event.data.metadata.goal_id;
  var authCode = event.data.authorization.authorization_code;
  var nextDebit = getNextDebitDate(new Date(), goal.frequency); // 'weekly' or 'monthly'

  await db.savingsGoals.update(goalId, {
    authorization_code: authCode,
    saved_amount: event.data.amount,
    next_debit_date: nextDebit,
    status: 'active',
  });
  await db.savingsTransactions.create({ goal_id: goalId, type: 'deposit', amount: event.data.amount, paystack_ref: event.data.reference, status: 'success' });
}

Scheduled Debits and Goal Completion

// Cron job — runs daily, processes goals due today
async function processScheduledDebits() {
  var today = new Date().toISOString().split('T')[0];
  var dueGoals = await db.savingsGoals.findDueToday(today);

  for (var goal of dueGoals) {
    var user = await db.users.findById(goal.user_id);
    var debitAmount = goal.debit_amount; // fixed amount per period

    try {
      var res = await fetch('https://api.paystack.co/charge/authorization', {
        method: 'POST',
        headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY, 'Content-Type': 'application/json' },
        body: JSON.stringify({
          email: user.email,
          amount: debitAmount,
          authorization_code: goal.authorization_code,
          metadata: { goal_id: goal.id, user_id: goal.user_id, deposit_type: 'scheduled' },
        }),
      });
      var data = await res.json();

      if (data.data.status === 'success') {
        var newSaved = goal.saved_amount + debitAmount;
        await db.savingsGoals.update(goal.id, {
          saved_amount: newSaved,
          next_debit_date: getNextDebitDate(new Date(), goal.frequency),
        });
        if (newSaved >= goal.target_amount) {
          await db.savingsGoals.update(goal.id, { status: 'goal_reached' });
          await notifyGoalReached(goal.user_id, goal);
        }
      } else {
        await db.savingsTransactions.create({ goal_id: goal.id, type: 'deposit', amount: debitAmount, status: 'failed' });
        await sendDebitFailedNotification(user, goal);
      }
    } catch (err) {
      console.error('Debit failed for goal', goal.id, err);
    }
  }
}

// User requests withdrawal after goal is reached
async function withdrawSavings(goalId, userId) {
  var goal = await db.savingsGoals.findById(goalId);
  if (goal.status !== 'goal_reached') throw new Error('Goal not yet reached');
  var user = await db.users.findById(userId);

  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: goal.saved_amount, recipient: user.paystack_recipient_code, reason: 'Savings goal: ' + goal.name }),
  });
  await db.savingsGoals.update(goalId, { status: 'withdrawn' });
}

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 a related recurring charge pattern.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Use card tokenization (authorization_code from the first charge) for all subsequent scheduled debits.
  • ✓Run scheduled debits via a cron job using the Paystack Charge Authorization endpoint.
  • ✓Handle debit failures gracefully: retry once, then notify the user to update their card.
  • ✓Pay out savings via Paystack Transfers when the goal is met — to bank account or M-Pesa.
  • ✓A savings product holds user funds — ensure you have appropriate licensing if you hold funds overnight.

Frequently Asked Questions

Is holding customer savings funds legal without a licence in Nigeria or Kenya?
Holding customer funds overnight (acting as a store of value) generally requires a financial services licence — a Payment Service Bank (PSB) licence in Nigeria or an e-money licence in Kenya. A simpler approach for early-stage products: use a regulated custodian partner (a licensed bank or fintech) to hold funds while your app handles the UX. Consult a financial regulatory lawyer before launching a savings product.
What happens if the user's card expires between debits?
The charge will fail with an expired card error. Send the user a notification to update their payment method. Provide a re-authorization link (new Paystack checkout) where they can pay with a new card — this gives you a new authorization_code to replace the old one. Until they update, pause the scheduled debits.
Can users withdraw early (before reaching the goal)?
Yes, but make this a deliberate decision with clear consequences. An early withdrawal could include a penalty (e.g., lose 2% of saved amount) or simply cancel the goal with a full withdrawal. State the early withdrawal policy clearly at goal creation. This policy should balance user flexibility with the savings discipline you want to encourage.

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 Farmers Market Ordering Platform

A build guide for a farmers market ordering platform where customers order fresh produce from multiple farmers in one cart, the platform collects the combined payment via Paystack, and each farmer receives their share via Paystack Split Payments or Transfers. Covers inventory management, order routing to individual farmers, and delivery coordination.

Read more

Building a Salon and Barbershop Booking and Payment App

Salons and barbershops in Kenya lose money every week to no-shows, double bookings, and cash management headaches. A booking app that collects deposits upfront, manages time slots, tracks customer history, and handles walk-in payments through M-Pesa changes the economics of running a grooming business. This guide covers the full build with Paystack handling the payment side.

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.