withdraw request
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class WithDraaRequest1776325917845 implements MigrationInterface {
|
||||
name = 'WithDraaRequest1776325917845'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "wallet_transaction" ADD "withdrawRequestId" uuid`);
|
||||
await queryRunner.query(`ALTER TABLE "reward_withdraw_request" ALTER COLUMN "requestedAmount" SET NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "wallet_transaction" ADD CONSTRAINT "FK_91eedc96b8ae9c6246208c6e96c" FOREIGN KEY ("withdrawRequestId") REFERENCES "reward_withdraw_request"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "wallet_transaction" DROP CONSTRAINT "FK_91eedc96b8ae9c6246208c6e96c"`);
|
||||
await queryRunner.query(`ALTER TABLE "reward_withdraw_request" ALTER COLUMN "requestedAmount" DROP NOT NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "wallet_transaction" DROP COLUMN "withdrawRequestId"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -348,6 +348,7 @@ export const enum WalletMessage {
|
||||
TRANSFER_METHOD_REQUIRED = "روش انتفال اجباری است",
|
||||
GATEWAY_ID_SHOULD_BE_UUID = "آیدی درگاه پرداخت باید UUID باشد",
|
||||
DEPOSIT_WALLET_TRANSFER = "شارژ کیف پول از طریق انتقال بانکی",
|
||||
WITHDRAWAL_WALLET_TRANSFER = "برداشت از کیف پول بابت واریز پول به حساب مشتری",
|
||||
INSUFFICIENT_BALANCE = "موجودی کیف پول کافی نیست",
|
||||
SUBSCRIPTION_WALLET_TRANSFER = "پرداخت اشتراک از طریق کیف پول",
|
||||
SUPPORT_PLAN_WALLET_TRANSFER = "پرداخت پلن پشتیبانی از طریق کیف پول",
|
||||
@@ -356,6 +357,7 @@ export const enum WalletMessage {
|
||||
REFERRAL_REWARD_WALLET_TRANSFER = "پرداخت پاداش ارجاع از طریق کیف پول",
|
||||
INVOICE_ID_REQUIRED = "شناسه صورت حساب مورد نیاز است",
|
||||
INVOICE_ID_SHOULD_BE_UUID = "شناسه صورت حساب باید یک UUID معتبر باشد",
|
||||
BALANCE_IS_NOT_ENOUGH="مقدار برداشت بیشتر از موجودی است"
|
||||
}
|
||||
|
||||
export const enum PaymentMessage {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { IsNotEmpty, IsString, Length } from "class-validator";
|
||||
|
||||
|
||||
export class ConfirmwithdrawRequestDto {
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
@Length(2, 50,)
|
||||
@ApiProperty({ description: "name", example: "mahyar" })
|
||||
transactionId: string;
|
||||
|
||||
}
|
||||
@@ -1,20 +1,24 @@
|
||||
import Decimal from "decimal.js";
|
||||
import { Column, Entity, ManyToOne } from "typeorm";
|
||||
import { Column, Entity, ManyToOne, OneToMany } from "typeorm";
|
||||
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";
|
||||
|
||||
@Entity()
|
||||
export class RewardWithdrawRequest extends BaseEntity {
|
||||
@ManyToOne(() => User, (user) => user.referralRewards)
|
||||
user: User;
|
||||
|
||||
@Column({ type: "decimal", precision: 10, scale: 2, nullable: true, transformer: new DecimalTransformer() })
|
||||
requestedAmount?: Decimal;
|
||||
@Column({ type: "decimal", precision: 10, scale: 2, transformer: new DecimalTransformer() })
|
||||
requestedAmount: Decimal;
|
||||
|
||||
@Column({ type: "timestamp", nullable: true })
|
||||
paidAt: Date;
|
||||
|
||||
@Column({ type: "varchar", length: 255, nullable: true })
|
||||
transactionId?: string;
|
||||
|
||||
@OneToMany(() => WalletTransaction, (walletTransaction) => walletTransaction.withdrawRequest)
|
||||
transactions: WalletTransaction[]
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Controller, Get, Post, Body, Delete, Param, Query } from '@nestjs/common';
|
||||
import { Controller, Get, Post, Body, Delete, Param, Query, Patch } from '@nestjs/common';
|
||||
import { ResellerService } from './reseller.service';
|
||||
import { ApiOperation } from '@nestjs/swagger';
|
||||
import { PermissionsDec } from '../../common/decorators/permission.decorator';
|
||||
@@ -17,6 +17,7 @@ import { WalletsService } from '../wallets/providers/wallets.service';
|
||||
import { CreateWithdrawRequestDto } from './dto/create-withdraw-request.dto';
|
||||
import { CreateUserBankAccountDto } from './dto/create-user-bank-account.dto';
|
||||
import { ResellerType } from '../auth/DTO/verify-reseller-otp.dto';
|
||||
import { ConfirmwithdrawRequestDto } from './dto/confirm-withdraw-request.dto';
|
||||
|
||||
@Controller('reseller')
|
||||
@AuthGuards()
|
||||
@@ -45,12 +46,20 @@ export class ResellerController {
|
||||
|
||||
//=========== Reward Requests =========
|
||||
|
||||
@ApiOperation({ summary: "get reseller reward requests ==> admin route" })
|
||||
@ApiOperation({ summary: "get withdraw requests ==> admin route" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.RESELLER)
|
||||
@Get('reward/request')
|
||||
getRewardRequests() {
|
||||
return this.resellerService.findAll()
|
||||
@Get('withdraw-request/admin')
|
||||
getRewardRequests(@Query() query: SearchWithdrawRequestQueryDto) {
|
||||
return this.resellerService.getwithdrawRequestsForAdmin(query)
|
||||
}
|
||||
|
||||
@ApiOperation({ summary: "get withdraw requests ==> admin route" })
|
||||
@AdminRoute()
|
||||
@PermissionsDec(PermissionEnum.RESELLER)
|
||||
@Patch('withdraw-request/:id')
|
||||
confirmWithdrawRequests(@Param('id') requestId: string, @Body() dto: ConfirmwithdrawRequestDto) {
|
||||
return this.resellerService.confirmWithdrawRequest(requestId, dto.transactionId)
|
||||
}
|
||||
|
||||
// ************** Reseller Panel Endpoints ***********
|
||||
@@ -148,7 +157,7 @@ export class ResellerController {
|
||||
@ResellerRoute([ResellerType.agent, ResellerType.reseller])
|
||||
@Get('withdraw-request')
|
||||
getPayments(@UserDec("id") userId: string, @Body() dto: SearchWithdrawRequestQueryDto) {
|
||||
return this.resellerService.getRewrdsRequests(userId, dto)
|
||||
return this.resellerService.getWithdrawRequests(userId, dto)
|
||||
}
|
||||
|
||||
// ============ Create Bank Account =============
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||
import { UsersService } from '../users/providers/users.service';
|
||||
import { CreateResellerDto } from './dto/create-reseller.dto';
|
||||
import { ResellerRepository } from './repositories/reseller.repository';
|
||||
@@ -15,10 +15,12 @@ import { ResellerType } from '../auth/DTO/verify-reseller-otp.dto';
|
||||
import { PaginationDto } from '../../common/DTO/pagination.dto';
|
||||
import { WalletsService } from '../wallets/providers/wallets.service';
|
||||
import Decimal from 'decimal.js';
|
||||
import { IsNull } from 'typeorm';
|
||||
import { DataSource, IsNull } from 'typeorm';
|
||||
|
||||
@Injectable()
|
||||
export class ResellerService {
|
||||
protected readonly logger = new Logger(ResellerService.name);
|
||||
|
||||
constructor(
|
||||
private readonly usersService: UsersService,
|
||||
private readonly referralService: ReferralsService,
|
||||
@@ -27,6 +29,7 @@ export class ResellerService {
|
||||
private readonly withdrawRequestRepository: WithdrawRequestRepository,
|
||||
private readonly userBankAccountRepository: UserBankAccountRepository,
|
||||
private readonly walletsService: WalletsService,
|
||||
private readonly dataSource: DataSource,
|
||||
) { }
|
||||
|
||||
async create(dto: CreateResellerDto) {
|
||||
@@ -111,6 +114,7 @@ export class ResellerService {
|
||||
|
||||
return this.referralService.findPaginatedReferedUsers(agentIds, query)
|
||||
}
|
||||
|
||||
// *************** Invoice ***************
|
||||
async findInvoices(userId: string, type: ResellerType, query: SearchResellerInvoicesQueryDto) {
|
||||
if (type == ResellerType.agent) {
|
||||
@@ -167,14 +171,14 @@ export class ResellerService {
|
||||
}
|
||||
|
||||
//************* withdraw request ***********/
|
||||
async getRewrdsRequestsForAdmin(queryDto: SearchWithdrawRequestQueryDto) {
|
||||
async getwithdrawRequestsForAdmin(queryDto: SearchWithdrawRequestQueryDto) {
|
||||
|
||||
const [requests, count] = await this.withdrawRequestRepository.findPaginatedListForAdmin(queryDto);
|
||||
|
||||
return { requests, count, paginate: true };
|
||||
}
|
||||
|
||||
async getRewrdsRequests(userId: string, queryDto: SearchWithdrawRequestQueryDto) {
|
||||
async getWithdrawRequests(userId: string, queryDto: SearchWithdrawRequestQueryDto) {
|
||||
|
||||
const [requests, count] = await this.withdrawRequestRepository.findPaginatedList(userId, queryDto);
|
||||
|
||||
@@ -197,7 +201,7 @@ export class ResellerService {
|
||||
if (pendingWithdrawRequest) {
|
||||
throw new BadRequestException("امکان ایجاد درخواست برداشت جدید نیست")
|
||||
}
|
||||
|
||||
|
||||
const { balance } = await this.walletsService.getBalance(userId)
|
||||
|
||||
if (new Decimal(balance).lessThan(requestedAmount)) {
|
||||
@@ -230,6 +234,44 @@ export class ResellerService {
|
||||
return this.withdrawRequestRepository.remove(request)
|
||||
}
|
||||
|
||||
async confirmWithdrawRequest(requestId: string,transactionId:string) {
|
||||
const request = await this.withdrawRequestRepository.findOne({ where: { id: requestId }, relations: ['user'] })
|
||||
if (!request) {
|
||||
throw new BadRequestException("درخواست برداشت یافت نشد .")
|
||||
}
|
||||
|
||||
if (request.paidAt) {
|
||||
throw new BadRequestException("قبلا تایید شده است")
|
||||
}
|
||||
|
||||
this.logger.log(`confirm withdraw request for request id ${requestId} `);
|
||||
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
try {
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
const amount = new Decimal(request.requestedAmount)
|
||||
|
||||
await this.walletsService.createWithdrawTransaction(request.user.id, amount, request, queryRunner)
|
||||
|
||||
request.paidAt = new Date()
|
||||
request.transactionId = transactionId
|
||||
|
||||
await queryRunner.manager.save(request)
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to send announcement:`, error);
|
||||
await queryRunner.rollbackTransaction();
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
|
||||
//********* Bank Account ***********/
|
||||
async getUserBankAccount(userId: string) {
|
||||
|
||||
@@ -242,6 +284,16 @@ export class ResellerService {
|
||||
async createUserBankAccount(userId: string, dto: CreateUserBankAccountDto) {
|
||||
const { user } = await this.usersService.findOneById(userId)
|
||||
|
||||
const exitingBankAccount = await this.userBankAccountRepository.findOne({
|
||||
where: {
|
||||
IBan: dto.IBan
|
||||
}
|
||||
})
|
||||
|
||||
if (exitingBankAccount) {
|
||||
throw new BadRequestException("شماره شبا موجود می باشد .")
|
||||
}
|
||||
|
||||
const account = this.userBankAccountRepository.create({
|
||||
user,
|
||||
...dto
|
||||
|
||||
@@ -7,6 +7,7 @@ import { DecimalTransformer } from "../../../common/transformers/decimal.transfo
|
||||
import { DepositRequest } from "../../payments/entities/deposit-request.entity";
|
||||
import { Payment } from "../../payments/entities/payment.entity";
|
||||
import { TransactionType } from "../enums/transaction-type.enum";
|
||||
import { RewardWithdrawRequest } from "../../reseller/entities/rewards-withdraw-request.entity";
|
||||
|
||||
@Entity()
|
||||
export class WalletTransaction extends BaseEntity {
|
||||
@@ -30,4 +31,7 @@ export class WalletTransaction extends BaseEntity {
|
||||
|
||||
@ManyToOne(() => DepositRequest, (depositRequest) => depositRequest.transactions, { onDelete: "CASCADE", nullable: true })
|
||||
depositRequest: DepositRequest | null;
|
||||
|
||||
@ManyToOne(() => RewardWithdrawRequest, (depositRequest) => depositRequest.transactions, { onDelete: "CASCADE", nullable: true })
|
||||
withdrawRequest: DepositRequest | null;
|
||||
}
|
||||
|
||||
@@ -14,13 +14,14 @@ import { Wallet } from "../entities/wallet.entity";
|
||||
import { TransactionType } from "../enums/transaction-type.enum";
|
||||
import { WalletTransactionsRepository } from "../repositories/wallet-transactions.repository";
|
||||
import { WalletsRepository } from "../repositories/wallets.repository";
|
||||
import { RewardWithdrawRequest } from "../../reseller/entities/rewards-withdraw-request.entity";
|
||||
|
||||
@Injectable()
|
||||
export class WalletsService {
|
||||
constructor(
|
||||
private readonly walletsRepository: WalletsRepository,
|
||||
private readonly walletsTransactionRepo: WalletTransactionsRepository,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
//*********************************** */
|
||||
async getBalance(userId: string) {
|
||||
@@ -256,4 +257,28 @@ export class WalletsService {
|
||||
if (!wallet) throw new BadRequestException(WalletMessage.WALLET_NOT_FOUND);
|
||||
return wallet;
|
||||
}
|
||||
|
||||
async createWithdrawTransaction(userId: string, amount: Decimal, withdrawRequest: RewardWithdrawRequest, 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);
|
||||
|
||||
if (wallet.balance.lessThan(amount)) {
|
||||
throw new BadRequestException(WalletMessage.BALANCE_IS_NOT_ENOUGH)
|
||||
}
|
||||
|
||||
const transaction = queryRunner.manager.create(WalletTransaction, {
|
||||
amount,
|
||||
wallet: { id: wallet.id },
|
||||
type: TransactionType.WITHDRAWAL,
|
||||
description: WalletMessage.WITHDRAWAL_WALLET_TRANSFER,
|
||||
withdrawRequest,
|
||||
});
|
||||
|
||||
wallet.balance = new Decimal(wallet.balance).minus(transaction.amount);
|
||||
|
||||
await queryRunner.manager.save(Wallet, wallet);
|
||||
await queryRunner.manager.save(WalletTransaction, transaction);
|
||||
|
||||
return transaction;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user