diff --git a/src/common/enums/message.enum.ts b/src/common/enums/message.enum.ts index d21c8c7..714d804 100755 --- a/src/common/enums/message.enum.ts +++ b/src/common/enums/message.enum.ts @@ -268,6 +268,7 @@ export const enum WalletMessage { INSUFFICIENT_BALANCE = "موجودی کیف پول کافی نیست", SUBSCRIPTION_WALLET_TRANSFER = "پرداخت اشتراک از طریق کیف پول", INVOICE_WALLET_TRANSFER = "پرداخت صورت حساب از طریق کیف پول", + TRANSACTION_NOT_FOUND = "تراکنش پیدا نشد", } export const enum PaymentMessage { diff --git a/src/modules/payments/entities/deposit-request.entity.ts b/src/modules/payments/entities/deposit-request.entity.ts index 26d3659..4375809 100755 --- a/src/modules/payments/entities/deposit-request.entity.ts +++ b/src/modules/payments/entities/deposit-request.entity.ts @@ -1,10 +1,11 @@ import Decimal from "decimal.js"; -import { Column, Entity, ManyToOne } from "typeorm"; +import { Column, Entity, ManyToOne, OneToMany } from "typeorm"; import { BankAccount } from "./bank-account.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; import { DecimalTransformer } from "../../../common/transformers/decimal.transformer"; import { User } from "../../users/entities/user.entity"; +import { WalletTransaction } from "../../wallets/entities/transaction.entity"; import { DepositRequestStatus } from "../enums/deposit-request-status.enum"; import { TransferType } from "../enums/payment-type.enum"; @@ -30,4 +31,7 @@ export class DepositRequest extends BaseEntity { @Column({ type: "enum", enum: DepositRequestStatus, default: DepositRequestStatus.PENDING }) status: DepositRequestStatus; + + @OneToMany(() => WalletTransaction, (transaction) => transaction.depositRequest) + transactions: WalletTransaction[]; } diff --git a/src/modules/payments/entities/payment-gateway.entity.ts b/src/modules/payments/entities/payment-gateway.entity.ts index 50e486c..7118629 100755 --- a/src/modules/payments/entities/payment-gateway.entity.ts +++ b/src/modules/payments/entities/payment-gateway.entity.ts @@ -5,7 +5,7 @@ import { GatewayEnum } from "../enums/gateway.enum"; @Entity() export class PaymentGateway extends BaseEntity { - @Column({ type: "enum", enum: GatewayEnum, nullable: false }) + @Column({ type: "enum", enum: GatewayEnum, nullable: false, unique: true }) name: GatewayEnum; @Column({ type: "varchar", length: 150, nullable: false }) diff --git a/src/modules/payments/entities/payment.entity.ts b/src/modules/payments/entities/payment.entity.ts index c7cc4d8..d4321bd 100755 --- a/src/modules/payments/entities/payment.entity.ts +++ b/src/modules/payments/entities/payment.entity.ts @@ -1,11 +1,12 @@ // eslint-disable-next-line import/no-named-as-default import Decimal from "decimal.js"; -import { Column, Entity, ManyToOne } from "typeorm"; +import { Column, Entity, ManyToOne, OneToMany } from "typeorm"; import { PaymentGateway } from "./payment-gateway.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; import { DecimalTransformer } from "../../../common/transformers/decimal.transformer"; import { User } from "../../users/entities/user.entity"; +import { WalletTransaction } from "../../wallets/entities/transaction.entity"; import { PaymentStatus } from "../enums/payment-status.enum"; @Entity() @@ -27,4 +28,7 @@ export class Payment extends BaseEntity { @ManyToOne(() => PaymentGateway, { nullable: false, onDelete: "RESTRICT" }) paymentGateway: PaymentGateway; + + @OneToMany(() => WalletTransaction, (transaction) => transaction.payment) + transactions: WalletTransaction[]; } diff --git a/src/modules/payments/providers/payments.service.ts b/src/modules/payments/providers/payments.service.ts index 629df31..e8b3d66 100755 --- a/src/modules/payments/providers/payments.service.ts +++ b/src/modules/payments/providers/payments.service.ts @@ -139,10 +139,17 @@ export class PaymentsService { try { const paymentGateway = this.gatewayFactory.getPaymentGateway(gateway); - const payment = await queryRunner.manager.findOne(Payment, { - where: { reference: queryDto.Authority }, - relations: ["user"], - }); + // const payment = await queryRunner.manager.findOne(Payment, { + // where: { reference: queryDto.Authority }, + // relations: ["user"], + // }); + + const payment = await queryRunner.manager + .createQueryBuilder(Payment, "payment") + .innerJoinAndSelect("payment.user", "user") + .where("payment.reference = :reference", { reference: queryDto.Authority }) + .setLock("pessimistic_write") + .getOne(); if (!payment) throw new BadRequestException(PaymentMessage.PAYMENT_NOT_FOUND_WITH_REF); if (payment.status === PaymentStatus.COMPLETED) throw new BadRequestException(PaymentMessage.VALIDATED_BEFORE); @@ -162,7 +169,7 @@ export class PaymentsService { if (verifyData.code === 100) { const { wallet } = await this.handleSuccessfulPayment( - payment.id, + payment, payment.user.id, payment.amount, `${verifyData.ref_id}`, @@ -248,9 +255,9 @@ export class PaymentsService { } //*********************************** */ - async handleSuccessfulPayment(paymentId: string, userId: string, amount: Decimal, transactionId: string, queryRunner: QueryRunner) { - await queryRunner.manager.update(Payment, { id: paymentId }, { status: PaymentStatus.COMPLETED, transactionId }); - return this.walletsService.createPaymentTransaction(userId, amount, queryRunner); + async handleSuccessfulPayment(payment: Payment, userId: string, amount: Decimal, transactionId: string, queryRunner: QueryRunner) { + await queryRunner.manager.update(Payment, { id: payment.id }, { status: PaymentStatus.COMPLETED, transactionId }); + return this.walletsService.createPaymentTransaction(userId, amount, payment, queryRunner); } //*********************************** */ @@ -399,7 +406,7 @@ export class PaymentsService { await queryRunner.manager.update(DepositRequest, { id: depositRequest.id }, { status: DepositRequestStatus.APPROVED }); - await this.walletsService.createDepositTransaction(depositRequest.amount, depositRequest.user.id, queryRunner); + await this.walletsService.createDepositTransaction(depositRequest.user.id, depositRequest.amount, depositRequest, queryRunner); await queryRunner.commitTransaction(); return { diff --git a/src/modules/wallets/entities/transaction.entity.ts b/src/modules/wallets/entities/transaction.entity.ts index fe42b8f..085356a 100755 --- a/src/modules/wallets/entities/transaction.entity.ts +++ b/src/modules/wallets/entities/transaction.entity.ts @@ -5,6 +5,8 @@ import { Column, Entity, ManyToOne } from "typeorm"; import { Wallet } from "./wallet.entity"; import { BaseEntity } from "../../../common/entities/base.entity"; import { DecimalTransformer } from "../../../common/transformers/decimal.transformer"; +import { DepositRequest } from "../../payments/entities/deposit-request.entity"; +import { Payment } from "../../payments/entities/payment.entity"; import { TransactionType } from "../enums/transaction-type.enum"; @Entity() @@ -23,4 +25,10 @@ export class WalletTransaction extends BaseEntity { @Column({ type: "text", nullable: false }) description: string; + + @ManyToOne(() => Payment, (payment) => payment.transactions, { onDelete: "CASCADE", nullable: true }) + payment: Payment | null; + + @ManyToOne(() => DepositRequest, (depositRequest) => depositRequest.transactions, { onDelete: "CASCADE", nullable: true }) + depositRequest: DepositRequest | null; } diff --git a/src/modules/wallets/providers/wallets.service.ts b/src/modules/wallets/providers/wallets.service.ts index a47dfa5..2ff28a6 100755 --- a/src/modules/wallets/providers/wallets.service.ts +++ b/src/modules/wallets/providers/wallets.service.ts @@ -7,6 +7,8 @@ import { PaginationDto } from "../../../common/DTO/pagination.dto"; import { WalletMessage } from "../../../common/enums/message.enum"; import { decimal } from "../../../common/transformers/decimal.transformer"; import { SearchTransactionQueryDto } from "../../payments/DTO/search-transaction-query.dto"; +import { DepositRequest } from "../../payments/entities/deposit-request.entity"; +import { Payment } from "../../payments/entities/payment.entity"; import { PaginationUtils } from "../../utils/providers/pagination.utils"; import { WalletTransaction } from "../entities/transaction.entity"; import { Wallet } from "../entities/wallet.entity"; @@ -39,7 +41,7 @@ export class WalletsService { return wallet; } //*********************************** */ - async createPaymentTransaction(userId: string, amount: Decimal, queryRunner: QueryRunner) { + async createPaymentTransaction(userId: string, amount: Decimal, payment: Payment, queryRunner: QueryRunner) { const wallet = await queryRunner.manager.findOne(Wallet, { where: { user: { id: userId } }, lock: { mode: "pessimistic_write" } }); if (!wallet) throw new BadRequestException(WalletMessage.WALLET_NOT_FOUND); @@ -48,6 +50,7 @@ export class WalletsService { wallet: { id: wallet.id }, type: TransactionType.DEPOSIT, description: WalletMessage.DEPOSIT_WALLET_IPG, + payment, }); wallet.balance = new Decimal(wallet.balance).add(transaction.amount); @@ -59,7 +62,7 @@ export class WalletsService { } //*********************************** */ - async createDepositTransaction(amount: Decimal, userId: string, queryRunner: QueryRunner) { + async createDepositTransaction(userId: string, amount: Decimal, depositRequest: DepositRequest, queryRunner: QueryRunner) { const wallet = await queryRunner.manager.findOne(Wallet, { where: { user: { id: userId } }, lock: { mode: "pessimistic_write" } }); if (!wallet) throw new BadRequestException(WalletMessage.WALLET_NOT_FOUND); @@ -68,6 +71,7 @@ export class WalletsService { wallet: { id: wallet.id }, type: TransactionType.DEPOSIT, description: WalletMessage.DEPOSIT_WALLET_TRANSFER, + depositRequest, }); wallet.balance = new Decimal(wallet.balance).add(transaction.amount); @@ -149,13 +153,29 @@ export class WalletsService { count, totalDeposits: decimal(totals.totalDeposits) || 0, totalWithdrawals: decimal(totals.totalWithdrawals) || 0, - //TODO:fix this - balancePercentChange: 5, - totalDepositsPercentChange: 4, - totalWithdrawalsPercentChange: 10, paginate: true, }; } + //*********************************** */ + + async getTransactionById(transactionId: string, userId: string) { + const transaction = await this.walletsTransactionRepo + .createQueryBuilder("transaction") + .leftJoinAndSelect("transaction.wallet", "wallet") + .leftJoin("wallet.user", "user") + .addSelect(["user.id", "user.firstName", "user.lastName"]) + .leftJoinAndSelect("transaction.payment", "payment", "payment.id IS NOT NULL") + .leftJoinAndSelect("payment.paymentGateway", "paymentGateway", "payment.id IS NOT NULL") + .leftJoinAndSelect("transaction.depositRequest", "depositRequest", "depositRequest.id IS NOT NULL") + .leftJoinAndSelect("depositRequest.bankAccount", "bankAccount", "depositRequest.id IS NOT NULL") + .where("transaction.id = :transactionId", { transactionId }) + .andWhere("wallet.userId = :userId", { userId }) + .getOne(); + + if (!transaction) throw new BadRequestException(WalletMessage.TRANSACTION_NOT_FOUND); + + return { transaction }; + } //*********************************** */ diff --git a/src/modules/wallets/wallets.controller.ts b/src/modules/wallets/wallets.controller.ts index 3c24f77..0f37b74 100755 --- a/src/modules/wallets/wallets.controller.ts +++ b/src/modules/wallets/wallets.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Query } from "@nestjs/common"; +import { Controller, Get, Param, Query } from "@nestjs/common"; import { ApiOperation, ApiTags } from "@nestjs/swagger"; import { WalletsService } from "./providers/wallets.service"; @@ -6,6 +6,7 @@ import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { Pagination } from "../../common/decorators/pagination.decorator"; import { UserDec } from "../../common/decorators/user.decorator"; import { PaginationDto } from "../../common/DTO/pagination.dto"; +import { ParamDto } from "../../common/DTO/param.dto"; @Controller("wallets") @ApiTags("wallets") @@ -26,4 +27,11 @@ export class WalletsController { getTransactions(@UserDec("id") userId: string, @Query() paginationDto: PaginationDto) { return this.walletsService.getTransaction(userId, paginationDto); } + + @ApiOperation({ summary: "get transaction by id" }) + @AuthGuards() + @Get("transactions/:id") + getTransactionById(@Param() paramDto: ParamDto, @UserDec("id") userId: string) { + return this.walletsService.getTransactionById(paramDto.id, userId); + } }