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. Building a Payment Provider Abstraction Layer
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Building a Payment Provider Abstraction Layer

Right abstraction: a PaymentGateway interface with methods matching your business operations (initializePayment, verifyPayment, refundPayment, createWebhookHandler). Each gateway is an adapter implementing this interface. Your application calls the interface; the adapter calls the gateway. Wrong abstraction: mapping every Paystack feature to a generic interface — you lose gateway-specific features (Paystack subaccounts, Paystack Plans) that do not map cleanly to a generic interface. Principle: abstract at the level of your business operations, not at the level of every API endpoint.

Payment Gateway Interface and Paystack Adapter

// Payment gateway interface — what your application code depends on
class PaymentGateway {
  async initializePayment({ amount, email, reference, callbackUrl, metadata }) {
    throw new Error('Not implemented');
  }

  async verifyPayment(reference) {
    throw new Error('Not implemented');
  }

  async refundPayment({ transactionId, amount }) {
    throw new Error('Not implemented');
  }

  validateWebhookSignature(rawBody, signature) {
    throw new Error('Not implemented');
  }

  parseWebhookEvent(body) {
    throw new Error('Not implemented');
  }
}

// Paystack adapter
class PaystackAdapter extends PaymentGateway {
  constructor(secretKey) {
    super();
    this.secretKey = secretKey;
    this.baseUrl = 'https://api.paystack.co';
  }

  get headers() {
    return { Authorization: 'Bearer ' + this.secretKey, 'Content-Type': 'application/json' };
  }

  async initializePayment({ amount, email, reference, callbackUrl, metadata }) {
    var res = await fetch(this.baseUrl + '/transaction/initialize', {
      method: 'POST',
      headers: this.headers,
      body: JSON.stringify({ amount, email, reference, callback_url: callbackUrl, metadata }),
    });
    var data = (await res.json()).data;
    return {
      authorizationUrl: data.authorization_url,
      accessCode: data.access_code,
      reference: data.reference,
    };
  }

  async verifyPayment(reference) {
    var res = await fetch(this.baseUrl + '/transaction/verify/' + reference, { headers: this.headers });
    var data = (await res.json()).data;
    return {
      status: data.status === 'success' ? 'success' : 'failed',
      amount: data.amount, // in kobo
      reference: data.reference,
      gatewayResponse: data.gateway_response,
    };
  }

  async refundPayment({ transactionId, amount }) {
    var res = await fetch(this.baseUrl + '/refund', {
      method: 'POST',
      headers: this.headers,
      body: JSON.stringify({ transaction: transactionId, amount }),
    });
    return (await res.json()).data;
  }

  validateWebhookSignature(rawBody, signature) {
    var crypto = require('crypto');
    var expected = crypto.createHmac('sha512', process.env.PAYSTACK_WEBHOOK_SECRET)
      .update(rawBody).digest('hex');
    return signature === expected;
  }

  parseWebhookEvent(body) {
    var event = JSON.parse(body);
    return { eventType: event.event, data: event.data };
  }
}

// Your application uses the interface, not the Paystack adapter directly
var gateway = new PaystackAdapter(process.env.PAYSTACK_SECRET_KEY);
var { authorizationUrl } = await gateway.initializePayment({ amount: 500000, email: 'user@example.com', reference: 'PAY_001', callbackUrl: 'https://yourapp.com/callback' });

Learn More

See gateway failover design to understand the failover pattern this abstraction enables.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Abstraction lives at your business operation level: initializePayment, verifyPayment, refundPayment, listTransactions.
  • ✓Each gateway is an adapter that maps the interface to the gateway's actual API.
  • ✓Gateway-specific features (Paystack subaccounts) should be used directly — do not abstract them generically.
  • ✓Build the abstraction with one gateway (Paystack). Add the second adapter only when you have a concrete need.
  • ✓The interface is the contract your application code depends on — it should not change when you add a new adapter.

Frequently Asked Questions

Should I build the abstraction layer before I need a second gateway?
Build a thin version of it early: the interface definition and one adapter (Paystack). The interface is just a class with method signatures — it costs almost nothing to add. The value is that when you need a second gateway (for failover or country coverage), you implement the second adapter instead of rewriting your checkout code. Build the interface now, implement additional adapters when you actually need them.
How do I handle gateway-specific features like Paystack subaccounts?
Use them directly. Gateway-specific features that do not generalize should not be forced through the abstraction. Your marketplace payout code can call the PaystackAdapter directly for subaccount operations — that code is already Paystack-specific by nature. The abstraction layer is for your standard checkout flow (initialize → verify → refund), which is common across gateways. Forcing subaccounts into a generic interface creates a leaky abstraction that is worse than no abstraction.
Does this pattern work for subscription management too?
Subscription APIs differ too much across gateways to abstract cleanly. Paystack uses Plans and Subscriptions; Stripe uses Products, Prices, and Subscriptions; Flutterwave uses a different model. Attempting a unified subscription interface produces a lowest-common-denominator design that loses important features from each gateway. Keep subscription management gateway-specific and encapsulate it separately from the standard checkout abstraction.

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 Alternatives and Comparisons: Honest Engineering Analysis

An honest engineering analysis of Paystack alternatives for African developers. Compares API design, documentation quality, webhook models, SDK coverage, settlement speed, and country reach across Flutterwave, Stripe, IntaSend, Pesapal, Monnify, Squad, and DPO Pay. Includes multi-gateway architecture, provider abstraction, and migration strategies.

Read more

Paystack vs Squad: Engineering Comparison

Squad is the payment gateway from GTBank (Guaranty Trust Bank), one of Nigeria's largest commercial banks. It is the product most commonly chosen by enterprise clients who want a bank-affiliated payment gateway. The engineering comparison with Paystack reveals where Squad wins and where Paystack leads.

Read more

Choosing a Payment Gateway: An Engineering Decision Framework

Choosing a payment gateway is not a brand preference. It is an engineering decision with long-term consequences for coverage, maintenance, and migration cost. Here is the framework to make it systematically.

Read more

Payment Gateway Due Diligence Checklist for CTOs

Signing a payment gateway contract without due diligence is a decision you will live with for years. A gateway can freeze your account, lose your transaction data, or get shut down by a regulator. This checklist helps you evaluate before you commit.

Read more

Using Paystack Custom Fields to Collect Extra Checkout Information

Paystack custom fields let you attach structured, human-readable data to a transaction that shows up on the checkout page, in your dashboard, and in webhook payloads. This guide covers display_name vs variable_name, common field patterns, and how to extract custom field data when the webhook arrives.

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.