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. Accept Payments with Paystack in Angular
Bonaventure OgetoBy Bonaventure Ogeto|Updated July 22, 2026

Accept Payments with Paystack in Angular

To accept Paystack payments in Angular, create a backend API that initializes transactions with your Paystack secret key. Your Angular app calls that API through an HttpClient service, gets an access code, and opens the Paystack inline popup. After payment, call your backend to verify the transaction before granting value.

In this article

  1. 1. Architecture Overview
  2. 2. Load Paystack Inline.js
  3. 3. PaystackService
  4. 4. Checkout Component
  5. 5. Backend Setup
  6. 6. Redirect Checkout
  7. 7. Production Checklist
  8. 8. Ship Payments Faster

Architecture Overview

Angular is a frontend framework. Like React and Vue, it runs in the browser. Paystack requires a secret key for initialization and verification, so you need a backend server. The flow:

  1. Angular component collects the customer email and knows the amount.
  2. Angular calls your backend API with the email and amount via HttpClient.
  3. Your backend calls Paystack's Initialize Transaction endpoint and returns the access code.
  4. Angular opens the Paystack inline popup with the access code.
  5. After payment, Angular calls your backend to verify the transaction.

This guide uses Angular 17+ with standalone components. The backend examples use Express, but any server-side framework works. If your backend is in a different language, the Angular code stays the same.

Load Paystack Inline.js

Add the Paystack script to your index.html:

<!-- src/index.html -->
<script src="https://js.paystack.co/v2/inline.js" defer></script>

Then declare the global type so TypeScript does not complain:

// src/types/paystack.d.ts
declare class PaystackPop {
  checkout(options: {
    accessCode: string;
    onSuccess: (transaction: { reference: string }) => void;
    onCancel: () => void;
  }): void;
}

interface Window {
  PaystackPop: typeof PaystackPop;
}

Alternatively, load the script dynamically in your service to avoid loading it on pages that do not need payments.

Get weekly developer tips

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

No spam. Unsubscribe anytime.

PaystackService

Create a service that handles initialization, popup, and verification:

// src/app/services/paystack.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
import { environment } from '../../environments/environment';

interface InitResponse {
  access_code: string;
  authorization_url: string;
  reference: string;
}

interface VerifyResponse {
  verified: boolean;
  amount: number;
  currency: string;
  reference: string;
}

@Injectable({ providedIn: 'root' })
export class PaystackService {
  private http = inject(HttpClient);
  private apiUrl = environment.apiUrl;

  async initializeTransaction(email: string, amount: number): Promise<InitResponse> {
    return firstValueFrom(
      this.http.post<InitResponse>(this.apiUrl + '/api/pay', { email, amount })
    );
  }

  openPopup(accessCode: string): Promise<string> {
    return new Promise(function(resolve, reject) {
      var popup = new window.PaystackPop();
      popup.checkout({
        accessCode: accessCode,
        onSuccess: function(transaction) {
          resolve(transaction.reference);
        },
        onCancel: function() {
          reject(new Error('Payment cancelled'));
        },
      });
    });
  }

  async verifyTransaction(reference: string): Promise<VerifyResponse> {
    return firstValueFrom(
      this.http.get<VerifyResponse>(this.apiUrl + '/api/verify', {
        params: { reference: reference },
      })
    );
  }

  async pay(email: string, amount: number): Promise<VerifyResponse> {
    var init = await this.initializeTransaction(email, amount);
    var reference = await this.openPopup(init.access_code);
    return this.verifyTransaction(reference);
  }
}

The pay() method chains the entire flow: initialize, popup, verify. Components only need to call one method.

Checkout Component

// src/app/checkout/checkout.component.ts
import { Component, inject, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { PaystackService } from '../services/paystack.service';

@Component({
  selector: 'app-checkout',
  standalone: true,
  imports: [FormsModule],
  template: '
    <div>
      <h1>Checkout</h1>
      <input [(ngModel)]="email" type="email" placeholder="Email address" />
      <p>Amount: NGN {{ amount | number }}</p>
      <button (click)="handlePay()" [disabled]="loading() || !email">
        {{ loading() ? "Processing..." : "Pay Now" }}
      </button>

      <div *ngIf="result()">
        <h2>Payment Confirmed</h2>
        <p>{{ result()!.currency }} {{ result()!.amount }}</p>
      </div>

      <div *ngIf="error()">
        <p>{{ error() }}</p>
      </div>
    </div>
  ',
})
export class CheckoutComponent {
  private paystack = inject(PaystackService);

  email = '';
  amount = 5000;
  loading = signal(false);
  result = signal<{ amount: number; currency: string } | null>(null);
  error = signal<string | null>(null);

  async handlePay() {
    this.loading.set(true);
    this.error.set(null);

    try {
      var verified = await this.paystack.pay(this.email, this.amount);
      this.result.set(verified);
    } catch (err: any) {
      this.error.set(err.message || 'Payment failed');
    } finally {
      this.loading.set(false);
    }
  }
}

The component uses Angular signals for reactive state. The loading signal disables the button during processing. The result signal shows the confirmation once verified.

Backend Setup

Create the Express endpoints that Angular calls:

// server.js
var express = require('express');
var cors = require('cors');
var app = express();

app.use(cors({ origin: 'http://localhost:4200' }));
app.use(express.json());

var PAYSTACK_SECRET = process.env.PAYSTACK_SECRET_KEY;

app.post('/api/pay', async function(req, res) {
  var response = await fetch('https://api.paystack.co/transaction/initialize', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ' + PAYSTACK_SECRET,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      email: req.body.email,
      amount: Math.round(req.body.amount * 100),
      reference: 'ng_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8),
    }),
  });

  var data = await response.json();
  if (!data.status) return res.status(400).json({ error: data.message });

  res.json({
    access_code: data.data.access_code,
    authorization_url: data.data.authorization_url,
    reference: data.data.reference,
  });
});

app.get('/api/verify', async function(req, res) {
  var response = await fetch(
    'https://api.paystack.co/transaction/verify/' + encodeURIComponent(req.query.reference),
    { headers: { Authorization: 'Bearer ' + PAYSTACK_SECRET } }
  );

  var data = await response.json();

  if (data.status && data.data.status === 'success') {
    return res.json({
      verified: true,
      amount: data.data.amount / 100,
      currency: data.data.currency,
      reference: data.data.reference,
    });
  }

  res.status(400).json({ verified: false });
});

app.listen(4000);

Redirect Checkout

For redirect checkout where the customer leaves your Angular app:

async handleRedirectPay() {
  var init = await this.paystack.initializeTransaction(this.email, this.amount);

  if (init.authorization_url) {
    window.location.href = init.authorization_url;
  }
}

Create a callback route in Angular Router and verify on initialization:

// src/app/payment-callback/payment-callback.component.ts
import { Component, OnInit, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { PaystackService } from '../services/paystack.service';

@Component({
  selector: 'app-payment-callback',
  standalone: true,
  template: '
    <div *ngIf="loading()">Verifying payment...</div>
    <div *ngIf="result()">
      <h1>Payment Confirmed</h1>
      <p>{{ result()!.currency }} {{ result()!.amount }}</p>
    </div>
    <div *ngIf="error()">
      <p>Verification failed. Contact support.</p>
    </div>
  ',
})
export class PaymentCallbackComponent implements OnInit {
  private route = inject(ActivatedRoute);
  private paystack = inject(PaystackService);

  loading = signal(true);
  result = signal<any>(null);
  error = signal<string | null>(null);

  async ngOnInit() {
    var reference = this.route.snapshot.queryParams['reference']
      || this.route.snapshot.queryParams['trxref'];

    if (!reference) {
      this.error.set('No reference found');
      this.loading.set(false);
      return;
    }

    try {
      var verified = await this.paystack.verifyTransaction(reference);
      this.result.set(verified);
    } catch (err) {
      this.error.set('Verification failed');
    } finally {
      this.loading.set(false);
    }
  }
}

Production Checklist

  1. Environment files. Use Angular's environment.ts for API URLs. Point to your production backend.
  2. CORS. Restrict backend CORS to your Angular app's production domain.
  3. Amount validation. Validate amounts on the backend from your database. Do not trust frontend values.
  4. Webhooks. Set up webhook endpoints on your backend for reliable payment confirmation.
  5. Error handling. Use Angular's HttpInterceptor to handle 401/403 errors globally.
  6. HTTPS. Both your Angular app and backend must use HTTPS in production.

Ship Payments Faster

This guide is part of the Paystack integration guides by language and framework series.

Sign up for the McTaba newsletter

Key Takeaways

  • ✓Angular runs in the browser. Your Paystack secret key must live on a separate backend server. Never import it in an Angular component or service.
  • ✓Create a PaystackService that handles initialization, popup, and verification. Inject it wherever you need payments.
  • ✓Load Paystack Inline.js by adding the script tag to index.html or loading it dynamically in a service.
  • ✓After the popup onSuccess callback fires, verify the transaction on your backend. The callback alone is not proof of payment.
  • ✓Amounts must be in the smallest currency unit (kobo for NGN, pesewas for GHS, cents for ZAR/KES/USD).
  • ✓Use Angular signals or BehaviorSubject for loading states to prevent double submissions.

Frequently Asked Questions

Do I need an Angular Paystack npm package?
No. Community packages exist but are often outdated. Loading the official Paystack Inline.js from the CDN and creating your own service gives you full control and fewer dependencies.
Can I use Angular Universal (SSR) with Paystack?
Yes, but the Paystack popup only runs in the browser. Use isPlatformBrowser to guard Paystack-related code in your service. The initialization and verification calls work server-side, but the popup must be client-side.
Should I use Observables or Promises for Paystack calls?
Either works. The examples use firstValueFrom to convert HttpClient Observables to Promises for cleaner async/await code. If you prefer Observables, use the HttpClient methods directly and handle the subscription lifecycle.
How do I handle multiple products with different amounts?
Pass the amount from your component to the PaystackService. Your backend should look up the correct price from the database rather than trusting the amount sent from Angular. The frontend amount is for display purposes only.

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 Integration Guides by Language and Framework

A complete directory of Paystack integration guides organized by programming language and framework. Each guide covers accepting payments, verifying transactions, handling webhooks, and building subscriptions in your specific stack.

Read more

Verify Paystack Payments in Svelte and SvelteKit

Verify Paystack transactions in SvelteKit using server endpoints for popup verification and load functions for redirect callback verification. Covers amount validation, idempotent fulfillment, and SSR verification for instant results.

Read more

Handle Paystack Webhooks in Flask

Receive and verify Paystack webhooks in your Flask application. This guide covers accessing request.data for raw bytes, HMAC SHA-512 signature verification, processing charge.success events, and making handlers idempotent.

Read more

Build Paystack Subscriptions in ASP.NET Core

Implement Paystack recurring billing in ASP.NET Core. This guide covers creating plans, subscribing customers through transaction initialization, handling renewal webhooks with C#, and cancelling subscriptions.

Read more

Accepting Payments with Paystack: Complete Guide

Everything you need to accept your first payment with Paystack: choosing between Initialize Transaction and the Charge API, handling amounts in kobo, attaching metadata, setting callback URLs, and designing transaction references that keep your system clean.

Read more

Paystack Errors and Troubleshooting: The Complete Reference

Every Paystack error you will hit in production, what causes it, and how to fix it. Authentication failures, webhook signature problems, transaction errors, transfer issues, and frontend bugs. Code-first solutions for each one.

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.