Files
dsc-api/src/modules/payments/providers/payments.service.ts
T
2025-02-04 16:13:47 +03:30

185 lines
6.9 KiB
TypeScript

import { InjectQueue } from "@nestjs/bullmq";
import { BadRequestException, HttpException, Injectable, InternalServerErrorException } from "@nestjs/common";
import { Queue } from "bullmq";
// eslint-disable-next-line import/no-named-as-default
import Decimal from "decimal.js";
import { DataSource, QueryRunner } from "typeorm";
import { PaymentMessage, WalletMessage } from "../../../common/enums/message.enum";
import { User } from "../../users/entities/user.entity";
import { Wallet } from "../../wallets/entities/wallet.entity";
import { WalletsService } from "../../wallets/providers/wallets.service";
import { PAYMENT } from "../constants";
import { GatewayDepositDto } from "../DTO/deposit-wallet.dto";
import { VerifyQueryDto } from "../DTO/verify-payment.dto";
import { PaymentMethod } from "../entities/payment-method.entity";
import { Payment } from "../entities/payment.entity";
import { PaymentStatus } from "../enums/payment-status.enum";
import { PaymentType } from "../enums/payment-type.enum";
import { PaymentGatewayFactory } from "../factories/payment.factory";
import { PaymentsRepository } from "../repositories/payments.repository";
import { GatewayType } from "../types/gateway.type";
@Injectable()
export class PaymentsService {
constructor(
@InjectQueue(PAYMENT.PAYMENT_QUEUE_NAME) private readonly paymentQueue: Queue,
private readonly gatewayFactory: PaymentGatewayFactory,
private readonly paymentsRepository: PaymentsRepository,
private readonly walletsService: WalletsService,
private dataSource: DataSource,
) {}
//*********************************** */
async chargeWalletWithGateway(chargeDto: GatewayDepositDto, userId: string) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const { amount, gateway } = chargeDto;
const wallet = await queryRunner.manager.findOne(Wallet, { where: { user: { id: userId } }, relations: ["user"] });
if (!wallet) throw new BadRequestException(WalletMessage.WALLET_NOT_FOUND);
const gatewayData = await this.processPayment(
gateway,
amount,
WalletMessage.DEPOSIT_WALLET_IPG,
wallet.user.email,
wallet.user.phone,
);
const payment = await this.createPaymentForUser(wallet.user, amount, gatewayData.reference, gateway, queryRunner);
await queryRunner.commitTransaction();
return {
payment,
...gatewayData,
};
} catch (error) {
await queryRunner.rollbackTransaction();
if (error instanceof HttpException) throw error;
throw new InternalServerErrorException(WalletMessage.ERROR_IN_CHARGE_WALLET);
} finally {
await queryRunner.release();
}
}
//*********************************** */
//*********************************** */
async processPayment(provider: GatewayType, amount: number, description: string, email?: string, mobile?: string) {
const paymentGateway = this.gatewayFactory.getPaymentGateway(provider);
return paymentGateway.processPayment({ amount, description, email, mobile });
}
//*********************************** */
//*********************************** */
async verifyPayment(gateway: GatewayType, queryDto: VerifyQueryDto) {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const paymentGateway = this.gatewayFactory.getPaymentGateway(gateway);
const payment = await queryRunner.manager.findOne(Payment, {
where: { reference: queryDto.Authority },
relations: ["user"],
// lock: { mode: "pessimistic_write" },
});
if (!payment) throw new BadRequestException(PaymentMessage.PAYMENT_NOT_FOUND_WITH_REF);
if (payment.status === PaymentStatus.COMPLETED) {
return {
message: "completed before",
};
}
if (queryDto.Status !== "OK") {
await this.handleFailedPayment(payment.id, queryRunner);
await queryRunner.commitTransaction();
return { message: "nok" };
}
const verifyData = await paymentGateway.verifyPayment({ reference: queryDto.Authority, amount: payment.amount });
//
if (verifyData.code === 100) {
const transaction = await this.handleSuccessfulPayment(
payment.id,
payment.user.id,
payment.amount,
`${verifyData.ref_id}`,
queryRunner,
);
await queryRunner.commitTransaction();
return {
message: "success",
transaction,
};
}
if (verifyData.code === 101) {
await queryRunner.commitTransaction();
return {
message: "not valid payment",
};
}
await this.handleFailedPayment(payment.id, queryRunner);
await queryRunner.commitTransaction();
return { message: "nok" };
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
}
//*********************************** */
//*********************************** */
async createPaymentForUser(user: User, amount: number, reference: string, gateway: GatewayType, queryRunner: QueryRunner) {
const paymentMethod = queryRunner.manager.create(PaymentMethod, {
gatewayName: gateway,
description: WalletMessage.DEPOSIT_WALLET_IPG,
});
await queryRunner.manager.save(PaymentMethod, paymentMethod);
const payment = queryRunner.manager.create(Payment, {
type: PaymentType.GATEWAY,
amount,
reference,
user,
paymentMethod,
});
await queryRunner.manager.save(Payment, payment);
this.paymentQueue.add(PAYMENT.PAYMENT_START, { payment }, { delay: 1000 });
return payment;
}
//*********************************** */
//*********************************** */
async getAvailableGateways() {
return this.gatewayFactory.getAvailablePaymentGateways();
}
//*********************************** */
//*********************************** */
async getPaymentWithReference(reference: string) {
const payment = await this.paymentsRepository.findOneWithReference(reference);
if (!payment) throw new BadRequestException(PaymentMessage.PAYMENT_NOT_FOUND_WITH_REF);
return { payment };
}
//*********************************** */
//*********************************** */
async handleFailedPayment(paymentId: string, queryRunner: QueryRunner) {
await queryRunner.manager.update(Payment, { id: paymentId }, { status: PaymentStatus.CANCELLED });
}
//*********************************** */
async handleSuccessfulPayment(paymentId: string, userId: string, amount: Decimal, transactionId: string, queryRunner: QueryRunner) {
await queryRunner.manager.update(Payment, { id: paymentId }, { status: PaymentStatus.COMPLETED, transactionId });
const transaction = await this.walletsService.createPaymentTransaction(userId, amount, queryRunner);
return transaction;
}
}