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.

Introduction to the Parsian Bank Gateway Implementation in NestJS
Introduction to the Parsian Bank Gateway Implementation in NestJS

SOAP

bash
npm i nestjs-soap

Initial Transaction

Opening a transaction and getting a token is mandatory in the first step.

Make Service, controller, and model for payment.

DTO

typescript
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

javascript
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

typescript
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

typescript
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

typescript
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

typescript
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

typescript
parsian: {    callBackUrl: process.env.PARSIAN_CALLBACK_URL,    loginAccount:
process.env.PARSIAN_PIN,  },
Introduction to the Parsian Bank Gateway Implementation in NestJS
Introduction to the Parsian Bank Gateway Implementation in NestJS
Introduction to the Parsian Bank Gateway Implementation in NestJS

Confirm transaction

Introduction to the Parsian Bank Gateway Implementation in NestJS
Introduction to the Parsian Bank Gateway Implementation in NestJS

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

typescript
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

typescript
@Post('/parsian/verify') @ApiOkResponse({
  type: () => VerifyPaymentParsianResponseDto
}) async verifyTransactionParsian(@Body() query: VerifyPaymentParsianDto) {
  return this.paymentParsianService.verifyTransaction(query);
}

Service

typescript
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

Follow us on LinkedIn, Twitter, YouTube, and Facebook.