To accept Paystack payments in NestJS, create a PaystackService that wraps the Paystack REST API calls. Inject it into a PaymentController that exposes endpoints for initialization and verification. Use ConfigService to manage your secret key. Initialize transactions server-side and redirect customers to Paystack checkout.
Create a new NestJS project or use an existing one:
npx @nestjs/cli new paystack-nestjs
cd paystack-nestjs
npm install @nestjs/config
Add your Paystack keys to a .env file:
PAYSTACK_SECRET_KEY=sk_test_xxxxxxxxxxxxxxxxxxxxx
BASE_URL=http://localhost:3000
Register ConfigModule in your AppModule:
// src/app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { PaymentModule } from './payment/payment.module';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
PaymentModule,
],
})
export class AppModule {}
Create a service that wraps the Paystack API calls:
// src/payment/paystack.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class PaystackService {
private readonly secretKey: string;
private readonly baseUrl = 'https://api.paystack.co';
constructor(private configService: ConfigService) {
this.secretKey = this.configService.get('PAYSTACK_SECRET_KEY');
}
async initializeTransaction(email: string, amount: number, reference: string, callbackUrl: string) {
var response = await fetch(this.baseUrl + '/transaction/initialize', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + this.secretKey,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: email,
amount: Math.round(amount * 100),
reference: reference,
callback_url: callbackUrl,
}),
});
return response.json();
}
async verifyTransaction(reference: string) {
var response = await fetch(
this.baseUrl + '/transaction/verify/' + encodeURIComponent(reference),
{
headers: {
Authorization: 'Bearer ' + this.secretKey,
},
}
);
return response.json();
}
}
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
// src/payment/payment.controller.ts
import { Controller, Post, Get, Body, Query, Res } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Response } from 'express';
import { PaystackService } from './paystack.service';
@Controller('api/payment')
export class PaymentController {
constructor(
private paystackService: PaystackService,
private configService: ConfigService,
) {}
@Post('initialize')
async initialize(@Body() body: { email: string; amount: number }) {
var reference = 'order_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8);
var callbackUrl = this.configService.get('BASE_URL') + '/api/payment/callback';
var data = await this.paystackService.initializeTransaction(
body.email,
body.amount,
reference,
callbackUrl,
);
if (!data.status) {
return { error: data.message };
}
// TODO: Save reference + amount to database
return {
authorization_url: data.data.authorization_url,
access_code: data.data.access_code,
reference: data.data.reference,
};
}
@Get('callback')
async callback(@Query('reference') reference: string, @Res() res: Response) {
if (!reference) {
return res.redirect('/payment/failed?error=missing_reference');
}
var data = await this.paystackService.verifyTransaction(reference);
if (data.status && data.data.status === 'success') {
// TODO: Verify amount matches, mark order as paid
return res.redirect('/payment/success?reference=' + reference);
}
return res.redirect('/payment/failed?reference=' + reference);
}
@Get('verify')
async verify(@Query('reference') reference: string) {
if (!reference) {
return { error: 'Missing reference' };
}
var data = await this.paystackService.verifyTransaction(reference);
if (data.status && data.data.status === 'success') {
return {
success: true,
amount: data.data.amount / 100,
currency: data.data.currency,
};
}
return { success: false };
}
}
// src/payment/payment.module.ts
import { Module } from '@nestjs/common';
import { PaymentController } from './payment.controller';
import { PaystackService } from './paystack.service';
@Module({
controllers: [PaymentController],
providers: [PaystackService],
exports: [PaystackService],
})
export class PaymentModule {}
Exporting PaystackService lets other modules (like an OrderModule or SubscriptionModule) inject it and use it for their own payment needs.
NestJS supports request validation with class-validator. Create a DTO for the payment initialization:
// src/payment/dto/initialize-payment.dto.ts
import { IsEmail, IsNumber, Min } from 'class-validator';
export class InitializePaymentDto {
@IsEmail()
email: string;
@IsNumber()
@Min(1)
amount: number;
}
Install the required packages and enable the global validation pipe:
npm install class-validator class-transformer
// src/main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
var app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
await app.listen(3000);
}
bootstrap();
Now your endpoint automatically rejects requests with invalid emails or negative amounts before your code runs.
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