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. Annual vs Monthly Billing Logic on Paystack
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 20, 2026

Annual vs Monthly Billing Logic on Paystack

Each billing interval needs its own Paystack plan. A product with two tiers and two intervals needs four plans. Annual plans are typically priced at 80-85% of the monthly equivalent to incentivize commitment. Switching between intervals requires cancelling the old subscription and creating a new one, with proration for any mid-cycle changes.

In this article

  1. 1. Setting Up Monthly and Annual Plans
  2. 2. The Pricing Page Toggle
  3. 3. Switching from Monthly to Annual
  4. 4. Switching from Annual to Monthly
  5. 5. Annual Billing Considerations for African Markets

Setting Up Monthly and Annual Plans

var plans = [
  { name: 'Basic Monthly', amount: 200000, interval: 'monthly' },
  { name: 'Basic Annual', amount: 2000000, interval: 'annually' },
  { name: 'Pro Monthly', amount: 500000, interval: 'monthly' },
  { name: 'Pro Annual', amount: 5000000, interval: 'annually' },
];

// Basic Monthly: 2,000 NGN/month
// Basic Annual: 20,000 NGN/year (saves 4,000 NGN = 17% discount)
// Pro Monthly: 5,000 NGN/month
// Pro Annual: 50,000 NGN/year (saves 10,000 NGN = 17% discount)

Common discount levels for annual billing: 15-20% off the monthly equivalent. A 17% discount (10 months for the price of 12) is popular and easy to communicate: "Get 2 months free with annual billing."

Store the plan mappings in your config so your application knows which monthly plan corresponds to which annual plan:

var PLAN_MAP = {
  basic: {
    monthly: { code: 'PLN_basic_monthly_xxx', amount: 200000, interval: 'monthly' },
    annual: { code: 'PLN_basic_annual_xxx', amount: 2000000, interval: 'annually' },
  },
  pro: {
    monthly: { code: 'PLN_pro_monthly_xxx', amount: 500000, interval: 'monthly' },
    annual: { code: 'PLN_pro_annual_xxx', amount: 5000000, interval: 'annually' },
  },
};

This article is part of the subscriptions and recurring billing guide.

The Pricing Page Toggle

Most SaaS pricing pages have a "Monthly / Annual" toggle. When toggled, prices update to show the annual rate and the savings.

// API endpoint returning plan data for the pricing page
router.get('/api/pricing', function(req, res) {
  var tiers = [
    {
      name: 'Basic',
      features: ['5 projects', '10 GB storage'],
      monthly: {
        amount: 2000,
        display: '2,000',
        planCode: PLAN_MAP.basic.monthly.code,
      },
      annual: {
        amount: 20000,
        display: '20,000',
        monthlyEquivalent: Math.round(20000 / 12),
        monthlyEquivalentDisplay: '1,667',
        savings: '17%',
        savingsLabel: 'Save 4,000/year',
        planCode: PLAN_MAP.basic.annual.code,
      },
    },
    {
      name: 'Pro',
      features: ['Unlimited projects', '100 GB storage', 'API access'],
      monthly: {
        amount: 5000,
        display: '5,000',
        planCode: PLAN_MAP.pro.monthly.code,
      },
      annual: {
        amount: 50000,
        display: '50,000',
        monthlyEquivalent: Math.round(50000 / 12),
        monthlyEquivalentDisplay: '4,167',
        savings: '17%',
        savingsLabel: 'Save 10,000/year',
        planCode: PLAN_MAP.pro.annual.code,
      },
    },
  ];

  res.json({ tiers: tiers, currency: 'NGN' });
});

On the annual pricing card, show the monthly equivalent prominently: "4,167 NGN/month billed annually" alongside "50,000 NGN/year." This helps customers compare the two options intuitively.

Get weekly developer tips

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

No spam. Unsubscribe anytime.

Switching from Monthly to Annual

When a monthly customer wants to switch to annual, you charge a prorated annual amount for the remaining time until the next annual renewal, minus the unused portion of their current monthly payment.

async function switchToAnnual(userId) {
  var billing = await getCurrentBilling(userId);
  var currentPlan = await getPlan(billing.plan_id);
  var annualPlan = getAnnualCounterpart(currentPlan);

  // Calculate what they owe
  var daysLeftInMonth = Math.ceil(
    (new Date(billing.current_period_end) - new Date()) / (24 * 60 * 60 * 1000)
  );
  var dailyRateMonthly = Math.round(currentPlan.base_amount / 30);
  var monthlyCredit = dailyRateMonthly * daysLeftInMonth;

  // Annual charge minus the monthly credit
  var chargeAmount = annualPlan.base_amount - monthlyCredit;

  var user = await getUser(userId);
  var paymentMethod = await getDefaultPaymentMethod(userId);
  var authCode = await getAuthorizationCode(paymentMethod.id);

  // Charge the prorated annual amount
  var reference = 'ANNUAL_SWITCH_' + userId + '_' + Date.now();
  var result = await chargeAuthorization(user.email, chargeAmount, authCode, reference);

  if (result.status !== 'success') {
    return { success: false, reason: result.gateway_response };
  }

  // Cancel old monthly subscription
  await disableSubscription(billing.paystack_subscription_code, billing.paystack_email_token);

  // Create new annual subscription
  var newSub = await createPaystackSubscription(user.email, annualPlan.paystack_plan_code, authCode);

  await updateBillingAccount(userId, {
    planId: annualPlan.id,
    subscriptionCode: newSub.subscription_code,
    emailToken: newSub.email_token,
  });

  return {
    success: true,
    charged: chargeAmount,
    newPlan: annualPlan.name,
    nextRenewal: newSub.next_payment_date,
  };
}

Switching from Annual to Monthly

Annual-to-monthly switches are simpler but the business decision is important. The customer paid for a year. Switching to monthly mid-year raises the question: do you refund the unused annual portion?

Most SaaS products use this approach: the customer keeps their annual plan for the remainder of the year. The monthly plan starts when the annual period ends.

async function switchToMonthlyAtRenewal(userId) {
  var billing = await getCurrentBilling(userId);

  // Schedule the switch for end of annual period
  await db.query(
    'UPDATE billing_accounts SET pending_plan_change = $1, pending_change_date = $2, updated_at = NOW() WHERE user_id = $3',
    ['monthly', billing.current_period_end, userId]
  );

  // Cancel auto-renewal of the annual plan
  await disableSubscription(billing.paystack_subscription_code, billing.paystack_email_token);

  return {
    status: 'scheduled',
    currentPlanActiveUntil: billing.current_period_end,
    message: 'Your annual plan will continue until ' + formatDate(billing.current_period_end) + '. Monthly billing will start after that.',
  };
}

// Run daily: check for pending plan changes
async function processPendingIntervalChanges() {
  var pending = await db.query(
    'SELECT user_id, pending_plan_change FROM billing_accounts WHERE pending_plan_change IS NOT NULL AND pending_change_date <= NOW()',
    []
  );

  for (var i = 0; i < pending.rows.length; i++) {
    var row = pending.rows[i];
    var user = await getUser(row.user_id);
    var paymentMethod = await getDefaultPaymentMethod(row.user_id);
    var monthlyPlan = getMonthlyPlanForTier(row.pending_plan_change);

    var authCode = await getAuthorizationCode(paymentMethod.id);
    var newSub = await createPaystackSubscription(user.email, monthlyPlan.paystack_plan_code, authCode);

    await db.query(
      'UPDATE billing_accounts SET plan_id = $1, paystack_subscription_code = $2, paystack_email_token = $3, pending_plan_change = NULL, pending_change_date = NULL, updated_at = NOW() WHERE user_id = $4',
      [monthlyPlan.id, newSub.subscription_code, newSub.email_token, row.user_id]
    );
  }
}

Offering immediate annual-to-monthly switches with a prorated refund is possible but adds complexity. Most products avoid it by letting the annual term complete first.

Annual Billing Considerations for African Markets

Annual billing in African markets has specific dynamics:

Higher failure risk. Annual charges are large. A 50,000 NGN charge is more likely to fail due to insufficient funds than a 5,000 NGN monthly charge. Consider offering the option to split the annual payment into quarterly payments via manual charges.

Currency fluctuations. For products priced in USD but charged in local currency, annual billing locks in the exchange rate for a year. This can be good or bad depending on currency movement. Consider whether to price in local currency to avoid this issue.

Cash flow patterns. In many African markets, customers may prefer to pay annually if they receive annual bonuses or if their business has seasonal cash flow peaks. Offer annual billing prominently during these periods.

The "2 months free" framing. "Save 17%" sounds abstract. "Get 2 months free" is concrete and compelling. Frame the annual discount in terms the customer can visualize.

For price change management across intervals, see grandfathering old prices on plans.

Key Takeaways

  • ✓Create separate Paystack plans for each billing interval. Monthly and annual plans for the same tier are two distinct plans with different amounts and intervals.
  • ✓Price annual plans at 80-85% of the monthly equivalent to incentivize longer commitments and reduce churn.
  • ✓Switching intervals requires cancelling the old subscription and creating a new one. Handle proration for the transition.
  • ✓Annual-to-monthly switches should take effect at the end of the annual period. Do not refund the remaining annual term.
  • ✓Monthly-to-annual switches can happen immediately with a prorated annual charge for the remaining period.
  • ✓Display the monthly equivalent on the annual pricing card so customers can see the savings.

Frequently Asked Questions

How many Paystack plans do I need for monthly and annual billing?
Two plans per tier. If you have Basic, Pro, and Enterprise tiers, each with monthly and annual options, you need six plans. Each unique price-interval combination requires its own plan.
Can I change a plan from monthly to annual?
No. Paystack plan intervals are immutable. You cannot change a plan's interval after creation. Create separate plans for each interval and switch customers between them by cancelling one subscription and creating another.
How much of a discount should I offer for annual billing?
The industry standard is 15-20% off the monthly price. A 17% discount is popular because it works out to "2 months free" for a 12-month commitment. Test different discount levels and measure which drives more annual signups without sacrificing too much revenue.
What if the annual charge fails due to insufficient funds?
Large charges fail more often. Options: retry according to your dunning schedule, offer to split the annual payment into two or four instalments via charge_authorization, or suggest the customer switch to monthly billing instead.

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

Paystack Subscriptions and Recurring Billing: Complete Guide

Everything you need to build recurring billing on Paystack. Covers the subscriptions API, plans, authorization codes for manual charges, dunning, lifecycle webhooks, cancellation, and real-world patterns for SaaS, memberships, school fees, and loan collection across Africa.

Read more

Building a SaaS Billing Page on Paystack

Your SaaS billing page is where customers choose plans, manage subscriptions, view invoices, and update payment methods. This guide covers the complete implementation: Paystack plan setup, the pricing page, the checkout flow, the account billing dashboard, upgrade/downgrade logic, and the APIs that connect it all.

Read more

Proration and Plan Upgrades with Paystack

Paystack does not handle proration automatically. When a customer upgrades or downgrades mid-cycle, you calculate the prorated amount, charge or credit the difference, cancel the old subscription, and create a new one. This guide provides the math, the code, and the edge cases for implementing proration on Paystack.

Read more

Grandfathering Old Prices on Paystack Plans

Paystack plans are immutable: you cannot change the amount. When you raise prices, existing subscribers stay on the old plan at the old price unless you explicitly migrate them. This guide covers plan versioning, the grandfather strategy, how to migrate subscribers when needed, and communicating price changes.

Read more

Cancelling and Pausing Paystack Subscriptions

Paystack lets you cancel subscriptions but has no native pause feature. This guide covers the disable API for cancellation, the difference between immediate and end-of-period cancellation, how to implement pause and resume in your application, and the reactivation flow for customers who come back.

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.