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 Loyalty and Points System Tied to Payments
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Build a Loyalty and Points System Tied to Payments

Build a loyalty points system by listening to the charge.success webhook to award points (e.g., 1 point per NGN 100 spent), maintaining a points balance per customer, and allowing points redemption at checkout by reducing the Paystack transaction amount. Always calculate points on the backend — never trust client-submitted point values.

In this article

  1. 1. Earning Points on Payments
  2. 2. Points Redemption at Checkout
  3. 3. Learn More

Earning Points on Payments

Tables: loyalty_accounts (customer_id, points_balance, lifetime_points, tier), point_transactions (customer_id, type, points, order_id, paystack_ref, description, expires_at, created_at).

var POINTS_PER_KOBO = 1 / 10000; // 1 point per NGN 100 (10000 kobo)
var POINT_VALUE_IN_KOBO = 500; // 1 point = NGN 5 (500 kobo) when redeemed

// Webhook: award points on successful payment
if (event.event === 'charge.success') {
  var customerId = event.data.metadata.customer_id;
  if (!customerId) return res.sendStatus(200);

  var paidAmount = event.data.amount; // in kobo
  var pointsEarned = Math.floor(paidAmount * POINTS_PER_KOBO);

  if (pointsEarned > 0) {
    var expires = new Date();
    expires.setFullYear(expires.getFullYear() + 1);

    await db.pointTransactions.create({
      customer_id: customerId,
      type: 'earn',
      points: pointsEarned,
      paystack_ref: event.data.reference,
      description: 'Points earned on purchase',
      expires_at: expires,
    });

    await db.loyaltyAccounts.increment(customerId, { points_balance: pointsEarned, lifetime_points: pointsEarned });

    // Update tier based on lifetime spend
    var account = await db.loyaltyAccounts.findByCustomer(customerId);
    var tier = account.lifetime_points >= 10000 ? 'gold' : account.lifetime_points >= 3000 ? 'silver' : 'bronze';
    if (tier !== account.tier) await db.loyaltyAccounts.update(customerId, { tier });
  }
}

Points Redemption at Checkout

async function redeemPoints(customerId, cartTotal, pointsToRedeem) {
  var account = await db.loyaltyAccounts.findByCustomer(customerId);
  if (account.points_balance < pointsToRedeem) throw new Error('Insufficient points');

  var discountInKobo = pointsToRedeem * POINT_VALUE_IN_KOBO;
  var maxDiscount = Math.floor(cartTotal * 0.3); // max 30% off via points
  var actualDiscount = Math.min(discountInKobo, maxDiscount);
  var actualPointsUsed = Math.ceil(actualDiscount / POINT_VALUE_IN_KOBO);
  var remainingToPay = cartTotal - actualDiscount;

  return { actualPointsUsed, actualDiscount, remainingToPay };
}

async function checkoutWithPoints(customerId, customerEmail, cartTotal, pointsToRedeem, orderId) {
  var { actualPointsUsed, actualDiscount, remainingToPay } = await redeemPoints(customerId, cartTotal, pointsToRedeem);

  // Deduct points immediately
  await db.loyaltyAccounts.decrement(customerId, 'points_balance', actualPointsUsed);
  await db.pointTransactions.create({
    customer_id: customerId, type: 'redeem',
    points: -actualPointsUsed, order_id: orderId, description: 'Points redeemed at checkout',
  });

  if (remainingToPay <= 0) {
    await db.orders.update(orderId, { status: 'paid', payment_method: 'points' });
    return { fullyPaid: true };
  }

  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: remainingToPay,
      metadata: { customer_id: customerId, order_id: orderId, points_redeemed: actualPointsUsed },
    }),
  });
  return { fullyPaid: false, authorizationUrl: (await res.json()).data.authorization_url };
}

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 gift card system on Paystack for a complementary stored-value mechanism alongside loyalty points.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Award points only after charge.success webhook — never on checkout initiation.
  • ✓Calculate points redemption value server-side; never trust the client to report how many points to apply.
  • ✓Store point transactions (earned, redeemed) for a complete audit trail and customer balance history.
  • ✓Implement point expiry (12 months from earning) to manage liability and encourage spending.
  • ✓Support tiered membership (Bronze, Silver, Gold) based on cumulative spend with different earn rates.

Frequently Asked Questions

What if the customer's Paystack payment fails after I deducted their points?
Listen for the charge.failed webhook. If a payment fails for an order where points were redeemed, restore the points to the customer's account immediately. Implement this as a compensating transaction in your point_transactions table (type: "restore") and increment the points_balance accordingly.
How do I handle point expiry?
Each point_transaction row has an expires_at date. When calculating the available balance, sum only non-expired points. Run a nightly cron job that marks expired points as "expired" and adjusts the loyalty_accounts points_balance accordingly. Notify customers 30 days before their points expire to encourage spending.
Should I display the points balance on every page or just at checkout?
Display the points balance prominently in the customer's account header and on the checkout page. A customer who sees "You have 850 points = NGN 4,250" is more likely to make a purchase to use those points. On the product page, consider showing "Earn 25 points with this purchase" to incentivize buying.

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 an Affiliate Commission Engine

A build guide for an affiliate commission engine where affiliates share tracking links, earn commissions when their referrals convert (pay via Paystack), and withdraw their balance via bank transfer. Covers click tracking, last-click attribution, commission ledger, and Paystack Transfers payout.

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 Rent Collection Platform for Kenyan Landlords

A practical guide to building a rent collection platform for the Kenyan rental market. Covers property and tenant management, monthly rent schedules, automatic payment reminders, M-Pesa and card collection, split payments for agent commissions via Paystack Splits, late payment penalties, receipt generation, vacancy tracking, and disbursement to landlord accounts.

Read more

Migrating from Manual Payouts to Paystack Splits

Many marketplaces start with manual bank transfers for vendor payouts — logging into internet banking every Friday to send money. Paystack splits automate this. This guide covers the migration path: creating subaccounts for existing vendors, running in parallel to validate, and cutting over to automated splits.

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.