Introduction to the Parsian Bank Gateway Implementation in NestJS
پیاده سازی درگاه پارسیان در نست جی اس
This guide outlines integrating the Parsian payment gateway into a NestJS application. The implementation covers two main steps:
1. Initial Transaction: Opening a transaction and obtaining a token from the Parsian gateway.
2. Transaction Confirmation: Verifying the transaction after the user completes the payment process.
The integration utilizes the SOAP protocol for communication with the Parsian gateway. The guide includes:
- Required dependencies and setup
- Data Transfer Objects (DTOs) for request and response handling
- Entity definitions for storing payment information
- Controller setup for handling payment requests
- Service implementation for interacting with the Parsian bank gateway
- Configuration details for the Parsian gateway
The code provides a structured approach to handling payments, including error handling, database interactions, and proper request/response management. This implementation allows for seamless integration of the Parsian payment gateway into a NestJS-based application, enabling secure and efficient payment processing.


SOAP
npm i nestjs-soapInitial Transaction
Opening a transaction and getting a token is mandatory in the first step.
Make Service, controller, and model for payment.
DTO
import {
ApiProperty
}
from '@nestjs/swagger';
import {
PaymentStatus
}
from '../entities/payment.entity';
export class CreatePaymentParsianDto {
@ApiProperty() amount: number;
@ApiProperty() uniqueID: number;
@ApiProperty() additionalData: string;
}
export class CreatePaymentParsianResultDto {
@ApiProperty() paymentLink: string;
}entity => parisan.type.ts
export const ParsianConfiguration = {
Address: 'https://pec.shaparak.ir/NewIPG/?token=', WsdlInitial:
'https://pec.shaparak.ir/NewIPGServices/Sale/SaleService.asmx?wsdl', WsdlConfirm:
'https://pec.shaparak.ir/NewIPGServices/Confirm/ConfirmService.asmx?wsdl',
};entity => payment.entity.ts for store result in database
import {
Prop, Schema, SchemaFactory
}
from '@nestjs/mongoose';
import {
ApiProperty, ApiPropertyOptional
}
from '@nestjs/swagger';
import mongoose, {
Document
}
from 'mongoose';
import {
User
}
from 'src/users/schemas/user.schema';
export enum PaymentStatus {
Initial = 'Initial', Pending = 'Pending', Success = 'Success', Failed = 'Failed',
}
export enum PaymentGateway {
Parsian = 'Parsian',
}
@Schema({
timestamps: true, collection: 'payment',
})
export class Payment extends Document {
@ApiProperty() _id: string;
@ApiProperty({
type: () => User
}) @Prop({
type: mongoose.Schema.Types.ObjectId, ref: 'user'
}) user: User;
@ApiProperty() @Prop({
required: true
}) amount: number;
@ApiProperty() @Prop({
required: true
}) uniqueID: string;
@ApiProperty({
type: () => [PaymentStatus]
}) @Prop({
required: true
}) status: string;
@ApiProperty({
type: () => [PaymentGateway]
}) @Prop({
required: true
}) gateway: string;
@ApiProperty() @Prop({
required: true
}) authority_code: string;
@ApiPropertyOptional() @Prop({
type: Object
}) authority: Record<string, any>;
@ApiPropertyOptional() @Prop({
type: Object
}) response: Record<string, any>;
}
const PaymentSchema = SchemaFactory.createForClass(Payment);
export {
PaymentSchema
};payment.controller.ts
import {
Controller, Post, Body, UseGuards, Req, Query, Get,
}
from '@nestjs/common';
import {
ApiBearerAuth, ApiOkResponse, ApiTags
}
from '@nestjs/swagger';
import {
SkipThrottle
}
from '@nestjs/throttler';
import {
CustomerGuard
}
from 'src/auth/customers-auth.guard';
import {
User
}
from 'src/users/schemas/user.schema';
import {
CreatePaymentParsianDto, CreatePaymentParsianResultDto, VerifyPaymentParsianDto,
VerifyPaymentParsianResponseDto,
}
from './dto/create.parsian.dto';
import {
PaymentParsianService
}
from './payment.parsian.service';
@ApiTags('payments')@SkipThrottle()@Controller('payment')
export class PaymentController {
constructor(private readonly paymentParsianService: PaymentParsianService,) {
}
@Post('/parsian/openTransaction') @UseGuards(CustomerGuard) @ApiOkResponse({
type: () => CreatePaymentParsianResultDto
}) @ApiBearerAuth() createParsian(@Body() createPaymentDto: CreatePaymentParsianDto, @Req() req: {
user: Partial<User>
},) {
return this.paymentParsianService.openTransaction(createPaymentDto, req.user,);
}
}payment.parsian.service.ts
import {
Injectable, InternalServerErrorException, NotFoundException,
}
from '@nestjs/common';
import {
Payment, PaymentGateway, PaymentStatus,
}
from './entities/payment.entity';
import {
Model
}
from 'mongoose';
import {
InjectModel
}
from '@nestjs/mongoose';
import {
User
}
from 'src/users/schemas/user.schema';
import {
CreatePaymentParsianDto, VerifyPaymentParsianDto,
}
from './dto/create.parsian.dto';
const soap = require('soap');
const moment = require('moment');
import {
ParsianConfiguration
}
from './entities/parsian.type';
import configuration from 'src/config/configuration';
@Injectable()
export class PaymentParsianService {
constructor(@InjectModel(Payment.name) private paymentModel: Model<Payment>,) {
}
async openTransaction(createPaymentDto: CreatePaymentParsianDto, user: Partial<User>,) {
let token = '';
// Make a SOAP request
const requestData = {
requestData: {
LoginAccount: configuration().parsian.loginAccount, OrderId: createPaymentDto.uniqueID,
Amount: createPaymentDto.amount, CallBackUrl: configuration().parsian.callBackUrl,
Originator: user.phoneNumber, AdditionalData: createPaymentDto.additionalData,
},
};
try {
const resolve = await this.SendRequestInitial(requestData);
if (!resolve.hasOwnProperty('SalePaymentRequestResult')) {
console.log('SalePaymentRequestResult not found');
throw new InternalServerErrorException();
}
if (resolve.SalePaymentRequestResult.Token != 0) {
token = resolve?.SalePaymentRequestResult?.Token;
}
else {
console.log(resolve.SalePaymentRequestResult.Message);
throw new InternalServerErrorException();
}
await this.paymentModel.create({
user: user, amount: createPaymentDto.amount, uniqueID: createPaymentDto.uniqueID, status:
PaymentStatus.Initial, gateway: PaymentGateway.Parsian, authority_code: token, authority:
resolve,
});
return {
paymentLink: ParsianConfiguration.Address + token
};
}
catch (error) {
console.log(error);
throw new InternalServerErrorException();
}
}
async SendRequestInitial(requestData: any): Promise<any> {
moment.locale('en');
const options = {
// overrideRootElement: {
// namespace: 'ns1', //
},
};
return await new Promise<any>((resolve, reject) => {
soap.createClient(ParsianConfiguration.WsdlInitial, options, function (err: any, client: any)
{
if (err) {
console.error('Error creating SOAP client:', err);
reject(err);
}
client.SalePaymentRequest(requestData, function (err, result) {
if (err) {
console.error('Error making SOAP request:', err);
reject(err);
}
resolve(result);
});
},);
});
}
}payment.module.ts
import {
Module
}
from '@nestjs/common';
import {
PaymentZarinpalService
}
from './payment.zarinpal.service';
import {
PaymentController
}
from './payment.controller';
import {
AuthModule
}
from 'src/auth/auth.module';
import {
MongooseModule
}
from '@nestjs/mongoose';
import {
Payment, PaymentSchema
}
from './entities/payment.entity';
import {
HttpModule
}
from '@nestjs/axios';
import {
PaymentParsianService
}
from './payment.parsian.service';
@Module({
imports: [AuthModule, MongooseModule.forFeature([{
name: Payment.name, schema: PaymentSchema
}]), HttpModule,], controllers: [PaymentController], providers: [PaymentZarinpalService,
PaymentParsianService],
})
export class PaymentModule {
}configuration.ts
parsian: { callBackUrl: process.env.PARSIAN_CALLBACK_URL, loginAccount:
process.env.PARSIAN_PIN, },


Confirm transaction


After the initial transaction and the client goes to the Parsian gateway, the bank will call back to verify the endpoint of your website. You must verify the transaction request with Parsian Bank. If you don’t, money will come back to the client's balance.
DTO
export class VerifyPaymentParsianDto {
@ApiProperty() Token: string;
@ApiProperty() OrderId: string;
@ApiProperty() TerminalNo: string;
@ApiProperty() RRN: string;
@ApiProperty() status: string;
@ApiProperty() Amount: string;
@ApiProperty() STraceNo: string;
}
export class VerifyPaymentParsianResponseDto {
@ApiProperty() Status: PaymentStatus;
@ApiProperty() Message: string;
}Controller
@Post('/parsian/verify') @ApiOkResponse({
type: () => VerifyPaymentParsianResponseDto
}) async verifyTransactionParsian(@Body() query: VerifyPaymentParsianDto) {
return this.paymentParsianService.verifyTransaction(query);
}Service
async verifyTransaction(query: VerifyPaymentParsianDto) { const payment = await
this.paymentModel.findOne({ authority_code: query.Token, }); if (!payment) { throw
new NotFoundException(); } if ( payment.status == PaymentStatus.Failed ||
payment.status == PaymentStatus.Success ) { return new NotFoundException(); } if
(Number(query.RRN) < 0 || Number(query.status) != 0) { payment.status = PaymentStatus.Failed;
payment.response = query; payment.save(); return { status: PaymentStatus.Failed, message:
PaymentStatus.Failed }; } payment.status = PaymentStatus.Pending; await payment.save();
// Make a SOAP request const requestData = { requestData: { LoginAccount:
configuration().parsian.loginAccount, Token: query.Token, }, }; try { const
resolve = await this.SendRequestConfirm(requestData); if
(!resolve.hasOwnProperty('ConfirmPaymentResult')) {
console.log('ConfirmPaymentResult not found'); throw new InternalServerErrorException();
} const result = resolve?.ConfirmPaymentResult; if (resolve?.ConfirmPaymentResult?.Status
== 0) { payment.response = result; payment.status = PaymentStatus.Success;
await payment.save(); return { status: PaymentStatus.Success, message: result.RRN }; }
else { payment.response = result; payment.status = PaymentStatus.Failed; await
payment.save(); return { status: result.status, message:
PaymentStatus.Failed, }; } } catch (e) { console.error(e); payment.response
= e; payment.status = PaymentStatus.Failed; await payment.save(); throw
InternalServerErrorException; } } async SendRequestConfirm(requestData: any): Promise<any> {
moment.locale('en'); const options = { // overrideRootElement: { // namespace: 'ns1',
// }, }; return await new Promise<any>((resolve, reject) => { soap.createClient(
ParsianConfiguration.WsdlConfirm, options, function (err: any, client: any) {
if (err) { console.error('Error creating SOAP client:', err); reject(err);
} client.ConfirmPayment(requestData, function (err, result) { if (err) {
console.error('Error making SOAP request:', err); reject(err); }
resolve(result); }); }, ); }); }Visit us at DataDrivenInvestor.com
Subscribe to DDIntel here.
Join our creator ecosystem here.
DDI Official Telegram Channel: https://t.me/+tafUp6ecEys4YjQ1
