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 = "روش انتفال اجباری است",
|
TRANSFER_METHOD_REQUIRED = "روش انتفال اجباری است",
|
||||||
GATEWAY_ID_SHOULD_BE_UUID = "آیدی درگاه پرداخت باید UUID باشد",
|
GATEWAY_ID_SHOULD_BE_UUID = "آیدی درگاه پرداخت باید UUID باشد",
|
||||||
DEPOSIT_WALLET_TRANSFER = "شارژ کیف پول از طریق انتقال بانکی",
|
DEPOSIT_WALLET_TRANSFER = "شارژ کیف پول از طریق انتقال بانکی",
|
||||||
|
WITHDRAWAL_WALLET_TRANSFER = "برداشت از کیف پول بابت واریز پول به حساب مشتری",
|
||||||
INSUFFICIENT_BALANCE = "موجودی کیف پول کافی نیست",
|
INSUFFICIENT_BALANCE = "موجودی کیف پول کافی نیست",
|
||||||
SUBSCRIPTION_WALLET_TRANSFER = "پرداخت اشتراک از طریق کیف پول",
|
SUBSCRIPTION_WALLET_TRANSFER = "پرداخت اشتراک از طریق کیف پول",
|
||||||
SUPPORT_PLAN_WALLET_TRANSFER = "پرداخت پلن پشتیبانی از طریق کیف پول",
|
SUPPORT_PLAN_WALLET_TRANSFER = "پرداخت پلن پشتیبانی از طریق کیف پول",
|
||||||
@@ -356,6 +357,7 @@ export const enum WalletMessage {
|
|||||||
REFERRAL_REWARD_WALLET_TRANSFER = "پرداخت پاداش ارجاع از طریق کیف پول",
|
REFERRAL_REWARD_WALLET_TRANSFER = "پرداخت پاداش ارجاع از طریق کیف پول",
|
||||||
INVOICE_ID_REQUIRED = "شناسه صورت حساب مورد نیاز است",
|
INVOICE_ID_REQUIRED = "شناسه صورت حساب مورد نیاز است",
|
||||||
INVOICE_ID_SHOULD_BE_UUID = "شناسه صورت حساب باید یک UUID معتبر باشد",
|
INVOICE_ID_SHOULD_BE_UUID = "شناسه صورت حساب باید یک UUID معتبر باشد",
|
||||||
|
BALANCE_IS_NOT_ENOUGH="مقدار برداشت بیشتر از موجودی است"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum PaymentMessage {
|
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 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 { BaseEntity } from "../../../common/entities/base.entity";
|
||||||
import { DecimalTransformer } from "../../../common/transformers/decimal.transformer";
|
import { DecimalTransformer } from "../../../common/transformers/decimal.transformer";
|
||||||
import { User } from "../../users/entities/user.entity";
|
import { User } from "../../users/entities/user.entity";
|
||||||
|
import { WalletTransaction } from "../../wallets/entities/transaction.entity";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class RewardWithdrawRequest extends BaseEntity {
|
export class RewardWithdrawRequest extends BaseEntity {
|
||||||
@ManyToOne(() => User, (user) => user.referralRewards)
|
@ManyToOne(() => User, (user) => user.referralRewards)
|
||||||
user: User;
|
user: User;
|
||||||
|
|
||||||
@Column({ type: "decimal", precision: 10, scale: 2, nullable: true, transformer: new DecimalTransformer() })
|
@Column({ type: "decimal", precision: 10, scale: 2, transformer: new DecimalTransformer() })
|
||||||
requestedAmount?: Decimal;
|
requestedAmount: Decimal;
|
||||||
|
|
||||||
@Column({ type: "timestamp", nullable: true })
|
@Column({ type: "timestamp", nullable: true })
|
||||||
paidAt: Date;
|
paidAt: Date;
|
||||||
|
|
||||||
@Column({ type: "varchar", length: 255, nullable: true })
|
@Column({ type: "varchar", length: 255, nullable: true })
|
||||||
transactionId?: string;
|
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 { ResellerService } from './reseller.service';
|
||||||
import { ApiOperation } from '@nestjs/swagger';
|
import { ApiOperation } from '@nestjs/swagger';
|
||||||
import { PermissionsDec } from '../../common/decorators/permission.decorator';
|
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 { CreateWithdrawRequestDto } from './dto/create-withdraw-request.dto';
|
||||||
import { CreateUserBankAccountDto } from './dto/create-user-bank-account.dto';
|
import { CreateUserBankAccountDto } from './dto/create-user-bank-account.dto';
|
||||||
import { ResellerType } from '../auth/DTO/verify-reseller-otp.dto';
|
import { ResellerType } from '../auth/DTO/verify-reseller-otp.dto';
|
||||||
|
import { ConfirmwithdrawRequestDto } from './dto/confirm-withdraw-request.dto';
|
||||||
|
|
||||||
@Controller('reseller')
|
@Controller('reseller')
|
||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
@@ -45,12 +46,20 @@ export class ResellerController {
|
|||||||
|
|
||||||
//=========== Reward Requests =========
|
//=========== Reward Requests =========
|
||||||
|
|
||||||
@ApiOperation({ summary: "get reseller reward requests ==> admin route" })
|
@ApiOperation({ summary: "get withdraw requests ==> admin route" })
|
||||||
@AdminRoute()
|
@AdminRoute()
|
||||||
@PermissionsDec(PermissionEnum.RESELLER)
|
@PermissionsDec(PermissionEnum.RESELLER)
|
||||||
@Get('reward/request')
|
@Get('withdraw-request/admin')
|
||||||
getRewardRequests() {
|
getRewardRequests(@Query() query: SearchWithdrawRequestQueryDto) {
|
||||||
return this.resellerService.findAll()
|
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 ***********
|
// ************** Reseller Panel Endpoints ***********
|
||||||
@@ -148,7 +157,7 @@ export class ResellerController {
|
|||||||
@ResellerRoute([ResellerType.agent, ResellerType.reseller])
|
@ResellerRoute([ResellerType.agent, ResellerType.reseller])
|
||||||
@Get('withdraw-request')
|
@Get('withdraw-request')
|
||||||
getPayments(@UserDec("id") userId: string, @Body() dto: SearchWithdrawRequestQueryDto) {
|
getPayments(@UserDec("id") userId: string, @Body() dto: SearchWithdrawRequestQueryDto) {
|
||||||
return this.resellerService.getRewrdsRequests(userId, dto)
|
return this.resellerService.getWithdrawRequests(userId, dto)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============ Create Bank Account =============
|
// ============ 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 { UsersService } from '../users/providers/users.service';
|
||||||
import { CreateResellerDto } from './dto/create-reseller.dto';
|
import { CreateResellerDto } from './dto/create-reseller.dto';
|
||||||
import { ResellerRepository } from './repositories/reseller.repository';
|
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 { PaginationDto } from '../../common/DTO/pagination.dto';
|
||||||
import { WalletsService } from '../wallets/providers/wallets.service';
|
import { WalletsService } from '../wallets/providers/wallets.service';
|
||||||
import Decimal from 'decimal.js';
|
import Decimal from 'decimal.js';
|
||||||
import { IsNull } from 'typeorm';
|
import { DataSource, IsNull } from 'typeorm';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ResellerService {
|
export class ResellerService {
|
||||||
|
protected readonly logger = new Logger(ResellerService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly usersService: UsersService,
|
private readonly usersService: UsersService,
|
||||||
private readonly referralService: ReferralsService,
|
private readonly referralService: ReferralsService,
|
||||||
@@ -27,6 +29,7 @@ export class ResellerService {
|
|||||||
private readonly withdrawRequestRepository: WithdrawRequestRepository,
|
private readonly withdrawRequestRepository: WithdrawRequestRepository,
|
||||||
private readonly userBankAccountRepository: UserBankAccountRepository,
|
private readonly userBankAccountRepository: UserBankAccountRepository,
|
||||||
private readonly walletsService: WalletsService,
|
private readonly walletsService: WalletsService,
|
||||||
|
private readonly dataSource: DataSource,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async create(dto: CreateResellerDto) {
|
async create(dto: CreateResellerDto) {
|
||||||
@@ -111,6 +114,7 @@ export class ResellerService {
|
|||||||
|
|
||||||
return this.referralService.findPaginatedReferedUsers(agentIds, query)
|
return this.referralService.findPaginatedReferedUsers(agentIds, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
// *************** Invoice ***************
|
// *************** Invoice ***************
|
||||||
async findInvoices(userId: string, type: ResellerType, query: SearchResellerInvoicesQueryDto) {
|
async findInvoices(userId: string, type: ResellerType, query: SearchResellerInvoicesQueryDto) {
|
||||||
if (type == ResellerType.agent) {
|
if (type == ResellerType.agent) {
|
||||||
@@ -167,14 +171,14 @@ export class ResellerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//************* withdraw request ***********/
|
//************* withdraw request ***********/
|
||||||
async getRewrdsRequestsForAdmin(queryDto: SearchWithdrawRequestQueryDto) {
|
async getwithdrawRequestsForAdmin(queryDto: SearchWithdrawRequestQueryDto) {
|
||||||
|
|
||||||
const [requests, count] = await this.withdrawRequestRepository.findPaginatedListForAdmin(queryDto);
|
const [requests, count] = await this.withdrawRequestRepository.findPaginatedListForAdmin(queryDto);
|
||||||
|
|
||||||
return { requests, count, paginate: true };
|
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);
|
const [requests, count] = await this.withdrawRequestRepository.findPaginatedList(userId, queryDto);
|
||||||
|
|
||||||
@@ -230,6 +234,44 @@ export class ResellerService {
|
|||||||
return this.withdrawRequestRepository.remove(request)
|
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 ***********/
|
//********* Bank Account ***********/
|
||||||
async getUserBankAccount(userId: string) {
|
async getUserBankAccount(userId: string) {
|
||||||
|
|
||||||
@@ -242,6 +284,16 @@ export class ResellerService {
|
|||||||
async createUserBankAccount(userId: string, dto: CreateUserBankAccountDto) {
|
async createUserBankAccount(userId: string, dto: CreateUserBankAccountDto) {
|
||||||
const { user } = await this.usersService.findOneById(userId)
|
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({
|
const account = this.userBankAccountRepository.create({
|
||||||
user,
|
user,
|
||||||
...dto
|
...dto
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { DecimalTransformer } from "../../../common/transformers/decimal.transfo
|
|||||||
import { DepositRequest } from "../../payments/entities/deposit-request.entity";
|
import { DepositRequest } from "../../payments/entities/deposit-request.entity";
|
||||||
import { Payment } from "../../payments/entities/payment.entity";
|
import { Payment } from "../../payments/entities/payment.entity";
|
||||||
import { TransactionType } from "../enums/transaction-type.enum";
|
import { TransactionType } from "../enums/transaction-type.enum";
|
||||||
|
import { RewardWithdrawRequest } from "../../reseller/entities/rewards-withdraw-request.entity";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class WalletTransaction extends BaseEntity {
|
export class WalletTransaction extends BaseEntity {
|
||||||
@@ -30,4 +31,7 @@ export class WalletTransaction extends BaseEntity {
|
|||||||
|
|
||||||
@ManyToOne(() => DepositRequest, (depositRequest) => depositRequest.transactions, { onDelete: "CASCADE", nullable: true })
|
@ManyToOne(() => DepositRequest, (depositRequest) => depositRequest.transactions, { onDelete: "CASCADE", nullable: true })
|
||||||
depositRequest: DepositRequest | null;
|
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 { TransactionType } from "../enums/transaction-type.enum";
|
||||||
import { WalletTransactionsRepository } from "../repositories/wallet-transactions.repository";
|
import { WalletTransactionsRepository } from "../repositories/wallet-transactions.repository";
|
||||||
import { WalletsRepository } from "../repositories/wallets.repository";
|
import { WalletsRepository } from "../repositories/wallets.repository";
|
||||||
|
import { RewardWithdrawRequest } from "../../reseller/entities/rewards-withdraw-request.entity";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WalletsService {
|
export class WalletsService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly walletsRepository: WalletsRepository,
|
private readonly walletsRepository: WalletsRepository,
|
||||||
private readonly walletsTransactionRepo: WalletTransactionsRepository,
|
private readonly walletsTransactionRepo: WalletTransactionsRepository,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
//*********************************** */
|
//*********************************** */
|
||||||
async getBalance(userId: string) {
|
async getBalance(userId: string) {
|
||||||
@@ -256,4 +257,28 @@ export class WalletsService {
|
|||||||
if (!wallet) throw new BadRequestException(WalletMessage.WALLET_NOT_FOUND);
|
if (!wallet) throw new BadRequestException(WalletMessage.WALLET_NOT_FOUND);
|
||||||
return wallet;
|
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