chore: parsian
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
|
||||
export function parsianConfig() {
|
||||
return {
|
||||
inject: [ConfigService],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
loginAccount: configService.getOrThrow<string>("PARSIAN_LOGIN_ACCOUNT"),
|
||||
callBackUrl: configService.getOrThrow<string>("CALLBACK_URL"),
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export interface IParsianConfig {
|
||||
loginAccount: string;
|
||||
callBackUrl: string;
|
||||
}
|
||||
|
||||
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",
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsEnum, IsInt, IsNotEmpty, IsOptional, IsUUID, IsUrl, Min } from "class-validator";
|
||||
import { IsEnum, IsInt, IsNotEmpty, IsOptional, IsUUID, IsUrl } from "class-validator";
|
||||
|
||||
import { WalletMessage } from "../../../common/enums/message.enum";
|
||||
import { TransferType } from "../enums/payment-type.enum";
|
||||
@@ -7,7 +7,7 @@ import { TransferType } from "../enums/payment-type.enum";
|
||||
export class DepositDto {
|
||||
@IsNotEmpty({ message: WalletMessage.AMOUNT_REQUIRED })
|
||||
@IsInt({ message: WalletMessage.AMOUNT_MUST_BE_INTEGER })
|
||||
@Min(100_000, { message: WalletMessage.AMOUNT_MINIMUM })
|
||||
// @Min(100_000, { message: WalletMessage.AMOUNT_MINIMUM })
|
||||
@ApiProperty({ description: "Amount to charge", example: 100_000 })
|
||||
amount: number;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
export const ZARINPAL_CONFIG = "ZARINPAL_CONFIG";
|
||||
export const PARSIAN_CONFIG = "PARSIAN_CONFIG";
|
||||
|
||||
// SOAP Client Names
|
||||
export const PARSIAN_SALE_CLIENT = "PARSIAN_SALE_CLIENT";
|
||||
export const PARSIAN_CONFIRM_CLIENT = "PARSIAN_CONFIRM_CLIENT";
|
||||
|
||||
//===============================================
|
||||
|
||||
export const PAYMENT = Object.freeze({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export enum GatewayEnum {
|
||||
ZARINPAL = "zarinpal",
|
||||
PARSIAN = "parsian",
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { BadGatewayException, Injectable } from "@nestjs/common";
|
||||
|
||||
import { PaymentMessage } from "../../../common/enums/message.enum";
|
||||
import { GatewayEnum } from "../enums/gateway.enum";
|
||||
import { ParsianGateway } from "../gateways/parsian.gateway";
|
||||
import { ZarinpalGateway } from "../gateways/zarinpal.gateway";
|
||||
import { IPaymentGateway, IPaymentGatewayFactory } from "../interfaces/IPayment";
|
||||
import { GatewayType } from "../types/gateway.type";
|
||||
@@ -10,13 +11,17 @@ import { GatewayType } from "../types/gateway.type";
|
||||
export class PaymentGatewayFactory implements IPaymentGatewayFactory {
|
||||
private readonly gateways: Map<GatewayType, IPaymentGateway>;
|
||||
|
||||
constructor(private readonly zarinpalGateway: ZarinpalGateway) {
|
||||
constructor(
|
||||
private readonly zarinpalGateway: ZarinpalGateway,
|
||||
private readonly parsianGateway: ParsianGateway,
|
||||
) {
|
||||
this.gateways = new Map<GatewayType, IPaymentGateway>();
|
||||
this.initializeGateways();
|
||||
}
|
||||
|
||||
private initializeGateways(): void {
|
||||
this.gateways.set(GatewayEnum.ZARINPAL, this.zarinpalGateway);
|
||||
this.gateways.set(GatewayEnum.PARSIAN, this.parsianGateway);
|
||||
}
|
||||
|
||||
getPaymentGateway(provider: GatewayType): IPaymentGateway {
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
import { Inject, Injectable, InternalServerErrorException, Logger } from "@nestjs/common";
|
||||
import { Client } from "nestjs-soap";
|
||||
|
||||
import { PaymentMessage } from "../../../common/enums/message.enum";
|
||||
import { IParsianConfig, ParsianConfiguration } from "../../../configs/parsian.config";
|
||||
import { PARSIAN_CONFIG, PARSIAN_CONFIRM_CLIENT, PARSIAN_SALE_CLIENT } from "../constants";
|
||||
import { GatewayEnum } from "../enums/gateway.enum";
|
||||
import {
|
||||
IPaymentGateway,
|
||||
IPaymentVerifyParams,
|
||||
IProcessPaymentParams,
|
||||
ParsianCallbackData,
|
||||
ParsianConfirmPaymentResponse,
|
||||
ParsianSalePaymentResponse,
|
||||
} from "../interfaces/IPayment";
|
||||
|
||||
@Injectable()
|
||||
export class ParsianGateway implements IPaymentGateway {
|
||||
private readonly logger = new Logger(ParsianGateway.name);
|
||||
|
||||
constructor(
|
||||
@Inject(PARSIAN_CONFIG) private readonly config: IParsianConfig,
|
||||
@Inject(PARSIAN_SALE_CLIENT) private readonly saleClient: Client,
|
||||
@Inject(PARSIAN_CONFIRM_CLIENT) private readonly confirmClient: Client,
|
||||
) {
|
||||
// Log the initialization status
|
||||
this.logger.log(`ParsianGateway initialized - Sale client: ${!!this.saleClient}, Confirm client: ${!!this.confirmClient}`);
|
||||
}
|
||||
|
||||
async processPayment(processParams: IProcessPaymentParams) {
|
||||
try {
|
||||
// Check if SOAP client is available
|
||||
if (!this.saleClient) {
|
||||
this.logger.error("SOAP sale client is not initialized");
|
||||
throw new InternalServerErrorException({
|
||||
message: "Payment service temporarily unavailable. Please try again later.",
|
||||
code: "SOAP_CLIENT_NOT_INITIALIZED",
|
||||
});
|
||||
}
|
||||
|
||||
const requestData = {
|
||||
requestData: {
|
||||
LoginAccount: this.config.loginAccount,
|
||||
OrderId: processParams.invoiceId || Date.now().toString(),
|
||||
Amount: processParams.amount,
|
||||
CallBackUrl: `${this.config.callBackUrl}/${GatewayEnum.PARSIAN}`,
|
||||
Originator: processParams.mobile,
|
||||
AdditionalData: processParams.description,
|
||||
},
|
||||
};
|
||||
|
||||
this.logger.log(`Processing payment request:`, {
|
||||
LoginAccount: this.config.loginAccount,
|
||||
OrderId: requestData.requestData.OrderId,
|
||||
Amount: processParams.amount,
|
||||
CallBackUrl: `${this.config.callBackUrl}/${GatewayEnum.PARSIAN}`,
|
||||
});
|
||||
|
||||
const result = (await this.saleClient.SalePaymentRequestAsync(requestData))[0] as ParsianSalePaymentResponse;
|
||||
|
||||
if (!result || !result.SalePaymentRequestResult) {
|
||||
this.logger.error("SalePaymentRequestResult not found in response");
|
||||
throw new InternalServerErrorException({ message: PaymentMessage.ERROR_IN_PROCESS_PAYMENT, result });
|
||||
}
|
||||
|
||||
const saleResult = result.SalePaymentRequestResult;
|
||||
|
||||
if (!saleResult.Token || Number(saleResult.Token) === 0) {
|
||||
this.logger.error(`Parsian payment request failed: ${saleResult.Message}`);
|
||||
throw new InternalServerErrorException({ message: PaymentMessage.ERROR_IN_PROCESS_PAYMENT, result });
|
||||
}
|
||||
|
||||
this.logger.log(`Payment request successful - Token: ${saleResult.Token}`);
|
||||
|
||||
return {
|
||||
redirectUrl: ParsianConfiguration.Address + saleResult.Token,
|
||||
message: saleResult.Message || "Payment initialized successfully",
|
||||
reference: saleResult.Token,
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(`Payment processing error:`, error);
|
||||
throw new InternalServerErrorException(PaymentMessage.ERROR_IN_PROCESS_PAYMENT);
|
||||
}
|
||||
}
|
||||
|
||||
async verifyPayment(verifyParams: IPaymentVerifyParams) {
|
||||
try {
|
||||
// Check if SOAP client is available
|
||||
if (!this.confirmClient) {
|
||||
this.logger.error("SOAP confirm client is not initialized");
|
||||
throw new InternalServerErrorException({
|
||||
message: "Payment verification service temporarily unavailable. Please try again later.",
|
||||
code: "SOAP_CLIENT_NOT_INITIALIZED",
|
||||
});
|
||||
}
|
||||
|
||||
// For Parsian, the verification should happen in the callback
|
||||
// This method might be called with callback data
|
||||
const callbackData = verifyParams as unknown as ParsianCallbackData;
|
||||
|
||||
// Check if this is a successful callback
|
||||
if (Number(callbackData.RRN) < 0 || Number(callbackData.status) !== 0) {
|
||||
this.logger.warn(`Payment failed in callback - RRN: ${callbackData.RRN}, Status: ${callbackData.status}`);
|
||||
return {
|
||||
code: Number(callbackData.status) || -1,
|
||||
message: "Payment failed",
|
||||
ref_id: 0,
|
||||
card_hash: "",
|
||||
card_pan: "",
|
||||
fee_type: "",
|
||||
fee: 0,
|
||||
};
|
||||
}
|
||||
|
||||
// Confirm the payment with Parsian
|
||||
const requestData = {
|
||||
requestData: {
|
||||
LoginAccount: this.config.loginAccount,
|
||||
Token: callbackData.Token,
|
||||
},
|
||||
};
|
||||
|
||||
this.logger.log(`Confirming payment:`, {
|
||||
LoginAccount: this.config.loginAccount,
|
||||
Token: callbackData.Token,
|
||||
});
|
||||
|
||||
const result = (await this.confirmClient.ConfirmPaymentAsync(requestData)) as ParsianConfirmPaymentResponse;
|
||||
|
||||
if (!result || !result.ConfirmPaymentResult) {
|
||||
this.logger.error("ConfirmPaymentResult not found in response");
|
||||
throw new InternalServerErrorException(PaymentMessage.ERROR_IN_VERIFY_PAYMENT);
|
||||
}
|
||||
|
||||
const confirmResult = result.ConfirmPaymentResult;
|
||||
|
||||
if (confirmResult.Status === 0) {
|
||||
this.logger.log(`Payment confirmed successfully - RRN: ${confirmResult.RRN}`);
|
||||
return {
|
||||
code: 100, // Success code
|
||||
message: confirmResult.Message || "Payment confirmed successfully",
|
||||
ref_id: Number(confirmResult.RRN) || 0,
|
||||
card_hash: confirmResult.CardNumberMasked || "",
|
||||
card_pan: confirmResult.CardNumberMasked || "",
|
||||
fee_type: "",
|
||||
fee: 0,
|
||||
};
|
||||
} else {
|
||||
this.logger.warn(`Payment confirmation failed - Status: ${confirmResult.Status}, Message: ${confirmResult.Message}`);
|
||||
return {
|
||||
code: confirmResult.Status,
|
||||
message: confirmResult.Message || "Payment confirmation failed",
|
||||
ref_id: 0,
|
||||
card_hash: confirmResult.CardNumberMasked || "",
|
||||
card_pan: confirmResult.CardNumberMasked || "",
|
||||
fee_type: "",
|
||||
fee: 0,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Payment verification error:`, error);
|
||||
throw new InternalServerErrorException(PaymentMessage.ERROR_IN_VERIFY_PAYMENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,7 @@ interface IZarinPalPGRequestData {
|
||||
interface ZarinpalError {
|
||||
code: number;
|
||||
message: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
validations?: any[];
|
||||
}
|
||||
|
||||
@@ -91,6 +92,50 @@ export interface ZarinPalPGVerifyData {
|
||||
errors: ZarinpalError[];
|
||||
}
|
||||
|
||||
//--------------------- Parsian --------------------------------
|
||||
|
||||
export interface ParsianSalePaymentRequest {
|
||||
LoginAccount: string;
|
||||
OrderId: string;
|
||||
Amount: number;
|
||||
CallBackUrl: string;
|
||||
Originator?: string;
|
||||
AdditionalData?: string;
|
||||
}
|
||||
|
||||
export interface ParsianSalePaymentResponse {
|
||||
SalePaymentRequestResult: {
|
||||
Token: string;
|
||||
Status: number;
|
||||
Message: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ParsianConfirmPaymentRequest {
|
||||
LoginAccount: string;
|
||||
Token: string;
|
||||
}
|
||||
|
||||
export interface ParsianConfirmPaymentResponse {
|
||||
ConfirmPaymentResult: {
|
||||
Status: number;
|
||||
Token: string;
|
||||
RRN: string;
|
||||
Message: string;
|
||||
CardNumberMasked: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ParsianCallbackData {
|
||||
Token: string;
|
||||
OrderId: string;
|
||||
TerminalNo: string;
|
||||
RRN: string;
|
||||
status: string;
|
||||
Amount: string;
|
||||
STraceNo: string;
|
||||
}
|
||||
|
||||
export interface PaymentVerificationResult {
|
||||
status: PaymentStatus;
|
||||
payment: Payment;
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
import { BullModule } from "@nestjs/bullmq";
|
||||
import { Module } from "@nestjs/common";
|
||||
import { TypeOrmModule } from "@nestjs/typeorm";
|
||||
import { SoapModule } from "nestjs-soap";
|
||||
|
||||
import { PAYMENT, ZARINPAL_CONFIG } from "./constants";
|
||||
import { PARSIAN_CONFIG, PARSIAN_CONFIRM_CLIENT, PARSIAN_SALE_CLIENT, PAYMENT, ZARINPAL_CONFIG } from "./constants";
|
||||
import { BankAccount } from "./entities/bank-account.entity";
|
||||
import { DepositRequest } from "./entities/deposit-request.entity";
|
||||
import { PaymentGateway } from "./entities/payment-gateway.entity";
|
||||
import { Payment } from "./entities/payment.entity";
|
||||
import { PaymentGatewayFactory } from "./factories/payment.factory";
|
||||
import { ParsianGateway } from "./gateways/parsian.gateway";
|
||||
import { ZarinpalGateway } from "./gateways/zarinpal.gateway";
|
||||
import { PaymentsController } from "./payments.controller";
|
||||
import { PaymentsService } from "./providers/payments.service";
|
||||
import { PaymentProcessor } from "./queue/payment.processor";
|
||||
import { BankAccountsRepository } from "./repositories/bank-accounts.repository";
|
||||
import { PaymentsRepository } from "./repositories/payments.repository";
|
||||
import { zarinpalConfig } from "../../configs/zarinpal.config";
|
||||
import { WalletsModule } from "../wallets/wallets.module";
|
||||
import { DepositRequest } from "./entities/deposit-request.entity";
|
||||
import { PaymentGateway } from "./entities/payment-gateway.entity";
|
||||
import { DepositRequestsRepository } from "./repositories/deposit-requests.repository";
|
||||
import { PaymentGatewaysRepository } from "./repositories/payment-gateway.repository";
|
||||
import { PaymentsRepository } from "./repositories/payments.repository";
|
||||
import { ParsianConfiguration, parsianConfig } from "../../configs/parsian.config";
|
||||
import { zarinpalConfig } from "../../configs/zarinpal.config";
|
||||
import { NotificationModule } from "../notifications/notifications.module";
|
||||
import { ReferralsModule } from "../referrals/referrals.module";
|
||||
|
||||
@@ -31,6 +34,18 @@ import { ReferralsModule } from "../referrals/referrals.module";
|
||||
removeOnFail: false,
|
||||
},
|
||||
}),
|
||||
SoapModule.registerAsync({
|
||||
clientName: PARSIAN_SALE_CLIENT,
|
||||
useFactory: () => ({
|
||||
uri: ParsianConfiguration.WsdlInitial,
|
||||
}),
|
||||
}),
|
||||
SoapModule.registerAsync({
|
||||
clientName: PARSIAN_CONFIRM_CLIENT,
|
||||
useFactory: () => ({
|
||||
uri: ParsianConfiguration.WsdlConfirm,
|
||||
}),
|
||||
}),
|
||||
WalletsModule,
|
||||
NotificationModule,
|
||||
ReferralsModule,
|
||||
@@ -40,6 +55,7 @@ import { ReferralsModule } from "../referrals/referrals.module";
|
||||
PaymentsService,
|
||||
PaymentGatewayFactory,
|
||||
ZarinpalGateway,
|
||||
ParsianGateway,
|
||||
PaymentsRepository,
|
||||
BankAccountsRepository,
|
||||
PaymentGatewaysRepository,
|
||||
@@ -50,6 +66,11 @@ import { ReferralsModule } from "../referrals/referrals.module";
|
||||
useFactory: zarinpalConfig().useFactory,
|
||||
inject: zarinpalConfig().inject,
|
||||
},
|
||||
{
|
||||
provide: PARSIAN_CONFIG,
|
||||
useFactory: parsianConfig().useFactory,
|
||||
inject: parsianConfig().inject,
|
||||
},
|
||||
],
|
||||
exports: [PaymentsService],
|
||||
})
|
||||
|
||||
@@ -1 +1 @@
|
||||
export type GatewayType = "zarinpal";
|
||||
export type GatewayType = "zarinpal" | "parsian";
|
||||
|
||||
Reference in New Issue
Block a user