Angular cannot verify Paystack payments directly because verification requires your secret key. Build a backend endpoint that calls Paystack's Verify Transaction API, then call that endpoint from Angular using HttpClient after the popup callback or on the redirect callback page. Always check both the status and amount.
When a customer finishes paying through the Paystack popup, your Angular component receives a callback with the transaction reference. This callback runs in the browser. It tells you the customer completed the payment form, but it does not prove the money actually landed.
The Paystack Verify Transaction API (GET /transaction/verify/:reference) requires your secret key in the Authorization header. That key must never appear in Angular code. Angular is a client-side framework. Everything in your Angular bundle is visible in the browser. So your Angular app calls your backend, and your backend calls Paystack.
Create a dedicated verification method in your PaystackService:
// 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';
export interface VerifyResult {
verified: boolean;
amount: number;
currency: string;
reference: string;
channel: string;
paidAt: string;
}
@Injectable({ providedIn: 'root' })
export class PaystackService {
private http = inject(HttpClient);
private apiUrl = environment.apiUrl;
async verify(reference: string): Promise<VerifyResult> {
return firstValueFrom(
this.http.get<VerifyResult>(this.apiUrl + '/api/verify', {
params: { reference: reference },
})
);
}
}
The service sends the reference to your backend. Your backend calls Paystack, checks the result, and returns a clean response. The Angular service never touches the Paystack API directly.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
// In your checkout component
import { Component, inject, signal } from '@angular/core';
import { PaystackService, VerifyResult } from '../services/paystack.service';
@Component({
selector: 'app-checkout',
standalone: true,
template: '
<button (click)="handlePay()" [disabled]="verifying()">
{{ verifying() ? "Verifying..." : "Pay NGN 5,000" }}
</button>
<div *ngIf="result()">
<h2>Payment Confirmed</h2>
<p>{{ result()!.currency }} {{ result()!.amount }}</p>
<p>Reference: {{ result()!.reference }}</p>
</div>
<div *ngIf="error()">
<p>{{ error() }}</p>
</div>
',
})
export class CheckoutComponent {
private paystack = inject(PaystackService);
verifying = signal(false);
result = signal<VerifyResult | null>(null);
error = signal<string | null>(null);
async handlePay() {
// After popup onSuccess:
// var reference = transaction.reference;
this.verifying.set(true);
try {
var reference = 'example_ref'; // from popup callback
var verified = await this.paystack.verify(reference);
if (verified.verified) {
this.result.set(verified);
} else {
this.error.set('Payment could not be verified');
}
} catch (err) {
this.error.set('Verification failed. Contact support.');
} finally {
this.verifying.set(false);
}
}
}
For redirect checkout, create a callback component that reads the reference from the URL and verifies immediately:
// src/app/payment-callback/payment-callback.component.ts
import { Component, OnInit, inject, signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { PaystackService, VerifyResult } from '../services/paystack.service';
@Component({
selector: 'app-payment-callback',
standalone: true,
template: '
<div *ngIf="loading()">
<p>Verifying your payment...</p>
</div>
<div *ngIf="result()">
<h1>Payment Successful</h1>
<p>Amount: {{ result()!.currency }} {{ result()!.amount }}</p>
<p>Reference: {{ result()!.reference }}</p>
<p>Paid via: {{ result()!.channel }}</p>
</div>
<div *ngIf="error()">
<h1>Verification Failed</h1>
<p>{{ error() }}</p>
<p>If you were charged, contact support.</p>
</div>
',
})
export class PaymentCallbackComponent implements OnInit {
private route = inject(ActivatedRoute);
private paystack = inject(PaystackService);
loading = signal(true);
result = signal<VerifyResult | null>(null);
error = signal<string | null>(null);
async ngOnInit() {
var params = this.route.snapshot.queryParams;
var reference = params['reference'] || params['trxref'];
if (!reference) {
this.error.set('No payment reference found in URL');
this.loading.set(false);
return;
}
try {
var verified = await this.paystack.verify(reference);
this.result.set(verified);
} catch (err) {
this.error.set('Could not verify payment');
} finally {
this.loading.set(false);
}
}
}
Register the route:
// app.routes.ts
{ path: 'payment/callback', component: PaymentCallbackComponent }
Your backend endpoint calls the Paystack Verify API and validates the result:
// server.js (Express)
app.get('/api/verify', async function(req, res) {
var reference = req.query.reference;
if (!reference) {
return res.status(400).json({ error: 'Reference required' });
}
var response = await fetch(
'https://api.paystack.co/transaction/verify/' + encodeURIComponent(reference),
{ headers: { Authorization: 'Bearer ' + process.env.PAYSTACK_SECRET_KEY } }
);
var data = await response.json();
var txn = data.data;
// Look up expected amount from database
// var order = await db.orders.findOne({ reference: reference });
// if (txn.amount !== order.amountInKobo) {
// return res.status(400).json({ verified: false, reason: 'Amount mismatch' });
// }
if (data.status && txn.status === 'success') {
return res.json({
verified: true,
amount: txn.amount / 100,
currency: txn.currency,
reference: txn.reference,
channel: txn.channel,
paidAt: txn.paid_at,
});
}
res.status(400).json({ verified: false, status: txn.status });
});
Always compare the verified amount against your database. A successful status alone does not guarantee the correct amount was paid.
Use an Angular interceptor to handle common errors globally:
// src/app/interceptors/error.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
import { catchError, throwError } from 'rxjs';
export const errorInterceptor: HttpInterceptorFn = function(req, next) {
return next(req).pipe(
catchError(function(error) {
if (error.status === 0) {
// Network error
console.error('Network error during payment verification');
}
if (error.status === 400) {
// Verification failed
console.error('Payment verification failed:', error.error);
}
return throwError(function() { return error; });
})
);
};
Register it in your app config:
// app.config.ts
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { errorInterceptor } from './interceptors/error.interceptor';
export const appConfig = {
providers: [
provideHttpClient(withInterceptors([errorInterceptor])),
],
};
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