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.
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:
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.
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.
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.
// 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.
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);
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);
}
}
}
environment.ts for API URLs. Point to your production backend.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