{
  "slug": "Introduction-to-the-Parsian-Bank-Gateway-Implementation-in-NestJS-9077a5441b5a",
  "title": "Introduction to the Parsian Bank Gateway Implementation in NestJS",
  "subtitle": "پیاده سازی درگاه پارسیان در نست جی اس",
  "excerpt": "پیاده سازی درگاه پارسیان در نست جی اس",
  "date": "2024-08-22",
  "tags": [
    "Node.js"
  ],
  "readingTime": "5 min",
  "url": "https://medium.com/@mobinshaterian/introduction-to-the-parsian-bank-gateway-implementation-in-nestjs-9077a5441b5a",
  "hero": "https://cdn-images-1.medium.com/max/800/1*DH7HLlb7fBHk4lcYcVPu7g.png",
  "content": [
    {
      "type": "heading",
      "level": 2,
      "text": "Introduction to the Parsian Bank Gateway Implementation in NestJS"
    },
    {
      "type": "paragraph",
      "html": "پیاده سازی درگاه پارسیان در نست جی اس"
    },
    {
      "type": "paragraph",
      "html": "This guide outlines integrating the Parsian payment gateway into a NestJS application. The implementation covers two main steps:"
    },
    {
      "type": "paragraph",
      "html": "1. Initial Transaction: Opening a transaction and obtaining a token from the Parsian gateway.<br>2. Transaction Confirmation: Verifying the transaction after the user completes the payment process."
    },
    {
      "type": "paragraph",
      "html": "The integration utilizes the SOAP protocol for communication with the Parsian gateway. The guide includes:"
    },
    {
      "type": "paragraph",
      "html": "- Required dependencies and setup<br>- Data Transfer Objects (DTOs) for request and response handling<br>- Entity definitions for storing payment information<br>- Controller setup for handling payment requests<br>- Service implementation for interacting with the Parsian bank gateway<br>- Configuration details for the Parsian gateway"
    },
    {
      "type": "paragraph",
      "html": "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."
    },
    {
      "type": "image",
      "src": "https://cdn-images-1.medium.com/max/800/1*9O52ZHYUMJYzUlle_g-k8Q.png",
      "alt": "Introduction to the Parsian Bank Gateway Implementation in NestJS",
      "caption": "",
      "width": 695,
      "height": 355
    },
    {
      "type": "image",
      "src": "https://cdn-images-1.medium.com/max/800/1*DH7HLlb7fBHk4lcYcVPu7g.png",
      "alt": "Introduction to the Parsian Bank Gateway Implementation in NestJS",
      "caption": "",
      "width": 667,
      "height": 462
    },
    {
      "type": "image",
      "src": "https://cdn-images-1.medium.com/max/800/1*Jdy9OoNX7kQqn06yMtNrEQ.png",
      "alt": "Introduction to the Parsian Bank Gateway Implementation in NestJS",
      "caption": "",
      "width": 696,
      "height": 404
    },
    {
      "type": "heading",
      "level": 2,
      "text": "SOAP"
    },
    {
      "type": "code",
      "lang": "bash",
      "code": "npm i nestjs-soap"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Initial Transaction"
    },
    {
      "type": "paragraph",
      "html": "Opening a transaction and getting a token is mandatory in the first step."
    },
    {
      "type": "paragraph",
      "html": "Make Service, controller, and model for payment."
    },
    {
      "type": "heading",
      "level": 3,
      "text": "DTO"
    },
    {
      "type": "code",
      "lang": "typescript",
      "code": "import {\n  ApiProperty\n}\nfrom '@nestjs/swagger';\nimport {\n  PaymentStatus\n}\nfrom '../entities/payment.entity';\nexport class CreatePaymentParsianDto {\n  @ApiProperty() amount: number;\n  @ApiProperty() uniqueID: number;\n  @ApiProperty() additionalData: string;\n}\nexport class CreatePaymentParsianResultDto {\n  @ApiProperty() paymentLink: string;\n}"
    },
    {
      "type": "heading",
      "level": 3,
      "text": "entity => parisan.type.ts"
    },
    {
      "type": "code",
      "lang": "javascript",
      "code": "export const ParsianConfiguration = {\n  Address: 'https://pec.shaparak.ir/NewIPG/?token=', WsdlInitial:\n  'https://pec.shaparak.ir/NewIPGServices/Sale/SaleService.asmx?wsdl', WsdlConfirm:\n  'https://pec.shaparak.ir/NewIPGServices/Confirm/ConfirmService.asmx?wsdl',\n};"
    },
    {
      "type": "heading",
      "level": 3,
      "text": "entity => payment.entity.ts for store result in database"
    },
    {
      "type": "code",
      "lang": "typescript",
      "code": "import {\n  Prop, Schema, SchemaFactory\n}\nfrom '@nestjs/mongoose';\nimport {\n  ApiProperty, ApiPropertyOptional\n}\nfrom '@nestjs/swagger';\nimport mongoose, {\n  Document\n}\nfrom 'mongoose';\nimport {\n  User\n}\nfrom 'src/users/schemas/user.schema';\nexport enum PaymentStatus {\n  Initial = 'Initial', Pending = 'Pending', Success = 'Success', Failed = 'Failed',\n}\nexport enum PaymentGateway {\n  Parsian = 'Parsian',\n}\n@Schema({\n  timestamps: true, collection: 'payment',\n})\nexport class Payment extends Document {\n  @ApiProperty() _id: string;\n  @ApiProperty({\n    type: () => User\n  }) @Prop({\n    type: mongoose.Schema.Types.ObjectId, ref: 'user'\n  }) user: User;\n  @ApiProperty() @Prop({\n    required: true\n  }) amount: number;\n  @ApiProperty() @Prop({\n    required: true\n  }) uniqueID: string;\n  @ApiProperty({\n    type: () => [PaymentStatus]\n  }) @Prop({\n    required: true\n  }) status: string;\n  @ApiProperty({\n    type: () => [PaymentGateway]\n  }) @Prop({\n    required: true\n  }) gateway: string;\n  @ApiProperty() @Prop({\n    required: true\n  }) authority_code: string;\n  @ApiPropertyOptional() @Prop({\n    type: Object\n  }) authority: Record<string, any>;\n  @ApiPropertyOptional() @Prop({\n    type: Object\n  }) response: Record<string, any>;\n}\nconst PaymentSchema = SchemaFactory.createForClass(Payment);\nexport {\n  PaymentSchema\n};"
    },
    {
      "type": "heading",
      "level": 3,
      "text": "payment.controller.ts"
    },
    {
      "type": "code",
      "lang": "typescript",
      "code": "import {\n  Controller, Post, Body, UseGuards, Req, Query, Get,\n}\nfrom '@nestjs/common';\nimport {\n  ApiBearerAuth, ApiOkResponse, ApiTags\n}\nfrom '@nestjs/swagger';\nimport {\n  SkipThrottle\n}\nfrom '@nestjs/throttler';\nimport {\n  CustomerGuard\n}\nfrom 'src/auth/customers-auth.guard';\nimport {\n  User\n}\nfrom 'src/users/schemas/user.schema';\nimport {\n  CreatePaymentParsianDto, CreatePaymentParsianResultDto, VerifyPaymentParsianDto,\n  VerifyPaymentParsianResponseDto,\n}\nfrom './dto/create.parsian.dto';\nimport {\n  PaymentParsianService\n}\nfrom './payment.parsian.service';\n@ApiTags('payments')@SkipThrottle()@Controller('payment')\nexport class PaymentController {\n  constructor(private readonly paymentParsianService: PaymentParsianService,) {\n  }\n  @Post('/parsian/openTransaction') @UseGuards(CustomerGuard) @ApiOkResponse({\n    type: () => CreatePaymentParsianResultDto\n  }) @ApiBearerAuth() createParsian(@Body() createPaymentDto: CreatePaymentParsianDto, @Req() req: {\n    user: Partial<User>\n  },) {\n    return this.paymentParsianService.openTransaction(createPaymentDto, req.user,);\n  }\n}"
    },
    {
      "type": "heading",
      "level": 3,
      "text": "payment.parsian.service.ts"
    },
    {
      "type": "code",
      "lang": "typescript",
      "code": "import {\n  Injectable, InternalServerErrorException, NotFoundException,\n}\nfrom '@nestjs/common';\nimport {\n  Payment, PaymentGateway, PaymentStatus,\n}\nfrom './entities/payment.entity';\nimport {\n  Model\n}\nfrom 'mongoose';\nimport {\n  InjectModel\n}\nfrom '@nestjs/mongoose';\nimport {\n  User\n}\nfrom 'src/users/schemas/user.schema';\nimport {\n  CreatePaymentParsianDto, VerifyPaymentParsianDto,\n}\nfrom './dto/create.parsian.dto';\nconst soap = require('soap');\nconst moment = require('moment');\nimport {\n  ParsianConfiguration\n}\nfrom './entities/parsian.type';\nimport configuration from 'src/config/configuration';\n@Injectable()\nexport class PaymentParsianService {\n  constructor(@InjectModel(Payment.name) private paymentModel: Model<Payment>,) {\n  }\n  async openTransaction(createPaymentDto: CreatePaymentParsianDto, user: Partial<User>,) {\n    let token = '';\n    // Make a SOAP request\n    const requestData = {\n      requestData: {\n        LoginAccount: configuration().parsian.loginAccount, OrderId: createPaymentDto.uniqueID,\n        Amount: createPaymentDto.amount, CallBackUrl: configuration().parsian.callBackUrl,\n        Originator: user.phoneNumber, AdditionalData: createPaymentDto.additionalData,\n      },\n    };\n    try {\n      const resolve = await this.SendRequestInitial(requestData);\n      if (!resolve.hasOwnProperty('SalePaymentRequestResult')) {\n        console.log('SalePaymentRequestResult not found');\n        throw new InternalServerErrorException();\n      }\n      if (resolve.SalePaymentRequestResult.Token != 0) {\n        token = resolve?.SalePaymentRequestResult?.Token;\n      }\n      else {\n        console.log(resolve.SalePaymentRequestResult.Message);\n        throw new InternalServerErrorException();\n      }\n      await this.paymentModel.create({\n        user: user, amount: createPaymentDto.amount, uniqueID: createPaymentDto.uniqueID, status:\n        PaymentStatus.Initial, gateway: PaymentGateway.Parsian, authority_code: token, authority:\n        resolve,\n      });\n      return {\n        paymentLink: ParsianConfiguration.Address + token\n      };\n    }\n    catch (error) {\n      console.log(error);\n      throw new InternalServerErrorException();\n    }\n  }\n  async SendRequestInitial(requestData: any): Promise<any> {\n    moment.locale('en');\n    const options = {\n      // overrideRootElement: {\n        // namespace: 'ns1', //\n      },\n    };\n    return await new Promise<any>((resolve, reject) => {\n      soap.createClient(ParsianConfiguration.WsdlInitial, options, function (err: any, client: any)\n      {\n        if (err) {\n          console.error('Error creating SOAP client:', err);\n          reject(err);\n        }\n        client.SalePaymentRequest(requestData, function (err, result) {\n          if (err) {\n            console.error('Error making SOAP request:', err);\n            reject(err);\n          }\n          resolve(result);\n        });\n      },);\n    });\n  }\n}"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "payment.module.ts"
    },
    {
      "type": "code",
      "lang": "typescript",
      "code": "import {\n  Module\n}\nfrom '@nestjs/common';\nimport {\n  PaymentZarinpalService\n}\nfrom './payment.zarinpal.service';\nimport {\n  PaymentController\n}\nfrom './payment.controller';\nimport {\n  AuthModule\n}\nfrom 'src/auth/auth.module';\nimport {\n  MongooseModule\n}\nfrom '@nestjs/mongoose';\nimport {\n  Payment, PaymentSchema\n}\nfrom './entities/payment.entity';\nimport {\n  HttpModule\n}\nfrom '@nestjs/axios';\nimport {\n  PaymentParsianService\n}\nfrom './payment.parsian.service';\n@Module({\n  imports: [AuthModule, MongooseModule.forFeature([{\n    name: Payment.name, schema: PaymentSchema\n  }]), HttpModule,], controllers: [PaymentController], providers: [PaymentZarinpalService,\n  PaymentParsianService],\n})\nexport class PaymentModule {\n}"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "configuration.ts"
    },
    {
      "type": "code",
      "lang": "typescript",
      "code": "parsian: {    callBackUrl: process.env.PARSIAN_CALLBACK_URL,    loginAccount:\nprocess.env.PARSIAN_PIN,  },"
    },
    {
      "type": "image",
      "src": "https://cdn-images-1.medium.com/max/800/1*J0Uk4Iymb12TwpXZKyce4Q.png",
      "alt": "Introduction to the Parsian Bank Gateway Implementation in NestJS",
      "caption": "",
      "width": 574,
      "height": 357
    },
    {
      "type": "image",
      "src": "https://cdn-images-1.medium.com/max/800/1*TDZfk1WrXCJZwba2j3kF3A.png",
      "alt": "Introduction to the Parsian Bank Gateway Implementation in NestJS",
      "caption": "",
      "width": 683,
      "height": 238
    },
    {
      "type": "image",
      "src": "https://cdn-images-1.medium.com/max/800/1*fQXCkMpyI94Fq8hzItMGsA.png",
      "alt": "Introduction to the Parsian Bank Gateway Implementation in NestJS",
      "caption": "",
      "width": 784,
      "height": 916
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Confirm transaction"
    },
    {
      "type": "image",
      "src": "https://cdn-images-1.medium.com/max/800/1*mZti4wiqy8OWcBjeCNtt0g.png",
      "alt": "Introduction to the Parsian Bank Gateway Implementation in NestJS",
      "caption": "",
      "width": 710,
      "height": 408
    },
    {
      "type": "image",
      "src": "https://cdn-images-1.medium.com/max/800/1*C424Mk7ccoIFLkm0IXPsZw.png",
      "alt": "Introduction to the Parsian Bank Gateway Implementation in NestJS",
      "caption": "",
      "width": 685,
      "height": 244
    },
    {
      "type": "paragraph",
      "html": "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."
    },
    {
      "type": "heading",
      "level": 2,
      "text": "DTO"
    },
    {
      "type": "code",
      "lang": "typescript",
      "code": "export class VerifyPaymentParsianDto {\n  @ApiProperty() Token: string;\n  @ApiProperty() OrderId: string;\n  @ApiProperty() TerminalNo: string;\n  @ApiProperty() RRN: string;\n  @ApiProperty() status: string;\n  @ApiProperty() Amount: string;\n  @ApiProperty() STraceNo: string;\n}\nexport class VerifyPaymentParsianResponseDto {\n  @ApiProperty() Status: PaymentStatus;\n  @ApiProperty() Message: string;\n}"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Controller"
    },
    {
      "type": "code",
      "lang": "typescript",
      "code": "@Post('/parsian/verify') @ApiOkResponse({\n  type: () => VerifyPaymentParsianResponseDto\n}) async verifyTransactionParsian(@Body() query: VerifyPaymentParsianDto) {\n  return this.paymentParsianService.verifyTransaction(query);\n}"
    },
    {
      "type": "heading",
      "level": 2,
      "text": "Service"
    },
    {
      "type": "code",
      "lang": "typescript",
      "code": "async verifyTransaction(query: VerifyPaymentParsianDto) {    const payment = await\nthis.paymentModel.findOne({      authority_code: query.Token,    });    if (!payment) {      throw\nnew NotFoundException();    }    if (      payment.status == PaymentStatus.Failed ||\npayment.status == PaymentStatus.Success    ) {      return new NotFoundException();    }    if\n(Number(query.RRN) < 0 || Number(query.status) != 0) {      payment.status = PaymentStatus.Failed;\npayment.response = query;      payment.save();      return { status: PaymentStatus.Failed, message:\nPaymentStatus.Failed };    }    payment.status = PaymentStatus.Pending;    await payment.save();\n// Make a SOAP request    const requestData = {      requestData: {        LoginAccount:\nconfiguration().parsian.loginAccount,        Token: query.Token,      },    };    try {      const\nresolve = await this.SendRequestConfirm(requestData);      if\n(!resolve.hasOwnProperty('ConfirmPaymentResult')) {\nconsole.log('ConfirmPaymentResult not found');        throw new InternalServerErrorException();\n}      const result = resolve?.ConfirmPaymentResult;      if (resolve?.ConfirmPaymentResult?.Status\n== 0) {        payment.response = result;        payment.status = PaymentStatus.Success;\nawait payment.save();        return { status: PaymentStatus.Success, message: result.RRN };      }\nelse {        payment.response = result;        payment.status = PaymentStatus.Failed;        await\npayment.save();        return {          status: result.status,          message:\nPaymentStatus.Failed,        };      }    } catch (e) {      console.error(e);      payment.response\n= e;      payment.status = PaymentStatus.Failed;      await payment.save();      throw\nInternalServerErrorException;    }  }  async SendRequestConfirm(requestData: any): Promise<any> {\nmoment.locale('en');    const options = {      // overrideRootElement: {      // namespace: 'ns1',\n// },    };    return await new Promise<any>((resolve, reject) => {      soap.createClient(\nParsianConfiguration.WsdlConfirm,        options,        function (err: any, client: any) {\nif (err) {            console.error('Error creating SOAP client:', err);            reject(err);\n}          client.ConfirmPayment(requestData, function (err, result) {            if (err) {\nconsole.error('Error making SOAP request:', err);              reject(err);            }\nresolve(result);          });        },      );    });  }"
    },
    {
      "type": "paragraph",
      "html": "Visit us at <a href=\"https://www.datadriveninvestor.com/\" target=\"_blank\" rel=\"noreferrer noopener\"><em>DataDrivenInvestor.com</em></a>"
    },
    {
      "type": "paragraph",
      "html": "Subscribe to DDIntel <a href=\"https://www.ddintel.com/\" target=\"_blank\" rel=\"noreferrer noopener\"><em>here</em></a>."
    },
    {
      "type": "paragraph",
      "html": "Join our creator ecosystem <a href=\"https://join.datadriveninvestor.com/\" target=\"_blank\" rel=\"noreferrer noopener\"><em>here</em></a>."
    },
    {
      "type": "paragraph",
      "html": "DDI Official Telegram Channel: <a href=\"https://t.me/+tafUp6ecEys4YjQ1\" target=\"_blank\" rel=\"noreferrer noopener\">https://t.me/+tafUp6ecEys4YjQ1</a>"
    },
    {
      "type": "paragraph",
      "html": "Follow us on <a href=\"https://www.linkedin.com/company/data-driven-investor\" target=\"_blank\" rel=\"noreferrer noopener\"><em>LinkedIn</em></a>, <a href=\"https://twitter.com/@DDInvestorHQ\" target=\"_blank\" rel=\"noreferrer noopener\"><em>Twitter</em></a>, <a href=\"https://www.youtube.com/c/datadriveninvestor\" target=\"_blank\" rel=\"noreferrer noopener\"><em>YouTube</em></a>, and <a href=\"https://www.facebook.com/datadriveninvestor\" target=\"_blank\" rel=\"noreferrer noopener\"><em>Facebook</em></a>."
    }
  ]
}
