From b1a6af6feb8a7a1b4e7ecabcf7f4289ee20d1f54 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Sun, 18 Jan 2026 18:28:09 +0330 Subject: [PATCH] find all --- .../payment/controllers/payment.controller.ts | 111 ++++++------------ .../payment/dto/create-payment-method.dto.ts | 35 ------ src/modules/payment/dto/find-payments.dto.ts | 80 +++++++++++++ src/modules/payment/dto/payment-chart.dto.ts | 41 ------- .../payment/dto/update-payment-method.dto.ts | 4 - src/modules/payment/dto/update-payment.dto.ts | 8 -- src/modules/payment/dto/verify-payment.dto.ts | 12 -- .../repositories/payment.repository.ts | 97 ++++++++++++++- .../payment/services/payments.service.ts | 26 ++-- 9 files changed, 222 insertions(+), 192 deletions(-) delete mode 100644 src/modules/payment/dto/create-payment-method.dto.ts create mode 100644 src/modules/payment/dto/find-payments.dto.ts delete mode 100644 src/modules/payment/dto/payment-chart.dto.ts delete mode 100644 src/modules/payment/dto/update-payment-method.dto.ts delete mode 100644 src/modules/payment/dto/update-payment.dto.ts delete mode 100644 src/modules/payment/dto/verify-payment.dto.ts diff --git a/src/modules/payment/controllers/payment.controller.ts b/src/modules/payment/controllers/payment.controller.ts index 9931f43..e91371f 100644 --- a/src/modules/payment/controllers/payment.controller.ts +++ b/src/modules/payment/controllers/payment.controller.ts @@ -1,25 +1,18 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards, Query } from '@nestjs/common'; +import { + Controller, Get, Post, Body, + Param, + UseGuards, +} from '@nestjs/common'; import { PaymentService } from '../services/payments.service'; import { ApiTags, ApiOperation, - ApiNotFoundResponse, - ApiBody, - ApiParam, ApiBearerAuth, - ApiHeader, } from '@nestjs/swagger'; -import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard'; -import { CreatePaymentMethodDto } from '../dto/create-payment-method.dto'; -import { UpdatePaymentMethodDto } from '../dto/update-payment-method.dto'; -import { VerifyPaymentDto } from '../dto/verify-payment.dto'; -import { PaymentChartDto } from '../dto/payment-chart.dto'; import { UserId } from 'src/common/decorators'; import { AuthGuard } from 'src/modules/auth/guards/auth.guard'; - -import { Permissions } from 'src/common/decorators/permissions.decorator'; -import { Permission } from 'src/common/enums/permission.enum'; import { PayOrderDto } from '../dto/pay-order.dto'; +import { FindPaymentsDto } from '../dto/find-payments.dto'; @ApiTags('payments') @Controller() @@ -37,76 +30,38 @@ export class PaymentController { return this.paymentsService.payOrder(orderId, dto); } - // @Post('public/payments/verify') - // @ApiOperation({ summary: 'Verify a payment' }) + @Post('public/payments/:token/verify/online') + @UseGuards(AuthGuard) + @ApiBearerAuth() + @ApiOperation({ summary: 'Verify online payment' }) + verifyOnlinePayment(@Param('paymentId') token: string) { + return this.paymentsService.verifyOnlinePayment(token); + } - // @ApiBody({ type: VerifyPaymentDto }) - // @ApiNotFoundResponse({ description: 'Payment not found' }) - // verifyPayment(@Body() verifyPaymentDto: VerifyPaymentDto) { - // return this.paymentsService.verifyOnlinePayment(verifyPaymentDto.authority, verifyPaymentDto.orderId); - // } + @ApiBearerAuth() + @Get('admin/payments') + @ApiOperation({ summary: 'get all payments as admin' }) + findAllByUser(@UserId() userId: string, @Body() dto: FindPaymentsDto) { + return this.paymentsService.findAllByUser(dto, userId); + } + /*===============Admin=============== */ + @Post('admin/payments/:paymentId/verify/cash') + @UseGuards(AuthGuard) + @ApiBearerAuth() + @ApiOperation({ summary: 'Verify cash payment' }) + verifyPayment(@Param('paymentId') paymentId: string) { + return this.paymentsService.verifyCashPayment(paymentId); + } // @UseGuards(AdminAuthGuard) // @Permissions(Permission.MANAGE_PAYMENTS) - // @ApiBearerAuth() - // @Post('admin/payments/methods') - // @ApiOperation({ summary: 'Create a new restaurant payment method' }) - // @ApiBody({ type: CreatePaymentMethodDto }) - // createRestaurantPaymentMethod( @Body() createPaymentMethodDto: CreatePaymentMethodDto) { - // return this.paymentMethodService.create( createPaymentMethodDto); - // } - - // @UseGuards(AdminAuthGuard) - // @Permissions(Permission.MANAGE_PAYMENTS) - // @ApiBearerAuth() - // @Get('admin/payments/methods/:paymentMethodId') - // @ApiOperation({ summary: 'Get restaurant payment method by ID' }) - // @ApiParam({ name: 'paymentMethodId', type: 'string', description: 'Payment method ID' }) - // findOneRestaurantPaymentMethod(@Param('paymentMethodId') paymentMethodId: string) { - // return this.paymentMethodService.findOne(paymentMethodId); - // } - - // @UseGuards(AdminAuthGuard) - // @Permissions(Permission.MANAGE_PAYMENTS) - // @ApiBearerAuth() - // @Patch('admin/payments/methods/:paymentMethodId') - // @ApiOperation({ summary: 'Update a restaurant payment method' }) - // @ApiParam({ name: 'paymentMethodId', type: 'string', description: 'Payment method ID' }) - // @ApiBody({ type: UpdatePaymentMethodDto }) - // updateRestaurantPaymentMethod( - // @Param('paymentMethodId') paymentMethodId: string, - // @Body() updatePaymentMethodDto: UpdatePaymentMethodDto, - // ) { - // return this.paymentMethodService.update(paymentMethodId, updatePaymentMethodDto); - // } - - // @UseGuards(AdminAuthGuard) - // @Permissions(Permission.MANAGE_PAYMENTS) - // @ApiBearerAuth() - // @Delete('admin/payments/methods/:paymentMethodId') - // @ApiOperation({ summary: 'Delete a restaurant payment method' }) - // @ApiParam({ name: 'paymentMethodId', type: 'string', description: 'Payment method ID' }) - // removeRestaurantPaymentMethod(@Param('paymentMethodId') paymentMethodId: string) { - // return this.paymentMethodService.remove(paymentMethodId); - // } - - // @UseGuards(AdminAuthGuard) - // @Permissions(Permission.MANAGE_PAYMENTS) - // @ApiBearerAuth() - // @Post('admin/payments/verify-cash-method/:paymentId') - // @ApiOperation({ summary: 'Verify cash payment ' }) - // @ApiParam({ name: 'paymentId', type: 'string', description: 'Payment ID' }) - // verifyCashPayment(@Param('paymentId') paymentId: string) { - // return this.paymentsService.verifyCashPayment(paymentId); - // } - - // @UseGuards(AdminAuthGuard) - // @Permissions(Permission.VIEW_REPORTS) - // @ApiBearerAuth() - // @Get('admin/payments/chart') - // @ApiOperation({ summary: 'Get payment chart data with date and period filters' }) - + @ApiBearerAuth() + @Get('admin/payments') + @ApiOperation({ summary: 'get all payments as admin' }) + findAllPayments(@Body() dto: FindPaymentsDto) { + return this.paymentsService.findAll(dto); + } } diff --git a/src/modules/payment/dto/create-payment-method.dto.ts b/src/modules/payment/dto/create-payment-method.dto.ts deleted file mode 100644 index 24acc82..0000000 --- a/src/modules/payment/dto/create-payment-method.dto.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { IsString, IsOptional, IsBoolean, IsNumber, IsEnum } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; -import { PaymentMethodEnum, PaymentGatewayEnum } from '../interface/payment'; - -export class CreatePaymentMethodDto { - - @ApiProperty({ description: 'Payment method', enum: PaymentMethodEnum }) - @IsEnum(PaymentMethodEnum) - method!: PaymentMethodEnum; - - @ApiProperty({ description: 'Payment gateway', enum: PaymentGatewayEnum, required: false }) - @IsOptional() - @IsEnum(PaymentGatewayEnum) - gateway?: PaymentGatewayEnum | null; - - @ApiProperty({ description: 'Payment method description', required: false }) - @IsOptional() - @IsString() - description?: string; - - @ApiProperty({ description: 'Is payment method enabled', required: false, default: true }) - @IsOptional() - @IsBoolean() - enabled?: boolean; - - @ApiProperty({ description: 'Display order', required: false, default: 0 }) - @IsOptional() - @IsNumber() - order?: number; - - @ApiProperty({ description: 'Merchant ID', required: false }) - @IsOptional() - @IsString() - merchantId?: string; -} diff --git a/src/modules/payment/dto/find-payments.dto.ts b/src/modules/payment/dto/find-payments.dto.ts new file mode 100644 index 0000000..41d8eba --- /dev/null +++ b/src/modules/payment/dto/find-payments.dto.ts @@ -0,0 +1,80 @@ +import { + IsOptional, + IsString, + IsNumber, + Min, + IsIn, + IsEnum, + IsDateString, + IsArray, +} from 'class-validator'; +import { Type, Transform } from 'class-transformer'; +import { ApiPropertyOptional } from '@nestjs/swagger'; + +import { PaymentStatusEnum } from '../interface/payment'; + +const sortOrderOptions = ['asc', 'desc'] as const; +type SortOrder = (typeof sortOrderOptions)[number]; + +export class FindPaymentsDto { + @ApiPropertyOptional({ default: 1, minimum: 1 }) + @IsOptional() + @Type(() => Number) + @IsNumber() + @Min(1) + page: number = 1; + + @ApiPropertyOptional({ default: 10, minimum: 1 }) + @IsOptional() + @Type(() => Number) + @IsNumber() + @Min(1) + limit: number = 10; + + + @ApiPropertyOptional({ + description: 'Filter by order statuses', + enum: PaymentStatusEnum, + isArray: true, + }) + @IsOptional() + @Transform(({ value }) => + Array.isArray(value) ? value : value?.split(',') + ) + @IsArray() + @IsEnum(PaymentStatusEnum, { each: true }) + statuses?: PaymentStatusEnum[]; + + // @ApiPropertyOptional({ enum: PaymentStatusEnum }) + // @IsOptional() + // @IsEnum(PaymentStatusEnum) + // paymentStatus?: PaymentStatusEnum; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + search?: string; + + @ApiPropertyOptional({ format: 'date-time' }) + @IsOptional() + @IsDateString() + from?: string; + + @ApiPropertyOptional({ format: 'date-time' }) + @IsOptional() + @IsDateString() + to?: string; + + @ApiPropertyOptional({ default: 'createdAt' }) + @IsOptional() + @IsString() + orderBy: string = 'createdAt'; + + @ApiPropertyOptional({ + enum: sortOrderOptions, + default: 'desc', + }) + @IsOptional() + @IsIn(sortOrderOptions) + order: SortOrder = 'desc'; +} diff --git a/src/modules/payment/dto/payment-chart.dto.ts b/src/modules/payment/dto/payment-chart.dto.ts deleted file mode 100644 index ae1f87b..0000000 --- a/src/modules/payment/dto/payment-chart.dto.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { IsOptional, IsString, IsEnum, IsDateString } from 'class-validator'; -import { ApiPropertyOptional } from '@nestjs/swagger'; - -export enum ChartPeriodEnum { - Daily = 'daily', - Weekly = 'weekly', - Monthly = 'monthly', -} - -export class PaymentChartDto { - @ApiPropertyOptional({ - description: 'Start date for filtering (ISO date string)', - type: String, - format: 'date-time', - example: '2024-01-01T00:00:00Z', - }) - @IsOptional() - @IsDateString() - startDate?: string; - - @ApiPropertyOptional({ - description: 'End date for filtering (ISO date string)', - type: String, - format: 'date-time', - example: '2024-12-31T23:59:59Z', - }) - @IsOptional() - @IsDateString() - endDate?: string; - - @ApiPropertyOptional({ - description: 'Period type for grouping data', - enum: ChartPeriodEnum, - default: ChartPeriodEnum.Daily, - example: ChartPeriodEnum.Daily, - }) - @IsOptional() - @IsEnum(ChartPeriodEnum) - type?: ChartPeriodEnum = ChartPeriodEnum.Daily; -} - diff --git a/src/modules/payment/dto/update-payment-method.dto.ts b/src/modules/payment/dto/update-payment-method.dto.ts deleted file mode 100644 index 7151364..0000000 --- a/src/modules/payment/dto/update-payment-method.dto.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { PartialType } from '@nestjs/mapped-types'; -import { CreatePaymentMethodDto } from './create-payment-method.dto'; - -export class UpdatePaymentMethodDto extends PartialType(CreatePaymentMethodDto) {} diff --git a/src/modules/payment/dto/update-payment.dto.ts b/src/modules/payment/dto/update-payment.dto.ts deleted file mode 100644 index a3d0fd2..0000000 --- a/src/modules/payment/dto/update-payment.dto.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { IsNumber } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; - -export class UpdatePaymentDto { - @ApiProperty({ description: 'Payment ID' }) - @IsNumber() - id: number; -} diff --git a/src/modules/payment/dto/verify-payment.dto.ts b/src/modules/payment/dto/verify-payment.dto.ts deleted file mode 100644 index 06d5ecb..0000000 --- a/src/modules/payment/dto/verify-payment.dto.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { IsString } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; - -export class VerifyPaymentDto { - @ApiProperty({ description: 'Payment authority token' }) - @IsString() - authority: string; - - @ApiProperty({ description: 'Payment order id' }) - @IsString() - orderId: string; -} diff --git a/src/modules/payment/repositories/payment.repository.ts b/src/modules/payment/repositories/payment.repository.ts index f157dd3..fb54aa4 100644 --- a/src/modules/payment/repositories/payment.repository.ts +++ b/src/modules/payment/repositories/payment.repository.ts @@ -1,10 +1,105 @@ import { Injectable } from '@nestjs/common'; -import { EntityManager, EntityRepository } from '@mikro-orm/postgresql'; +import { EntityManager, EntityRepository, FilterQuery } from '@mikro-orm/postgresql'; import { Payment } from '../entities/payment.entity'; +import { FindPaymentsDto } from '../dto/find-payments.dto'; +import { PaginatedResult } from 'src/common/interfaces/pagination.interface'; +import { PaymentStatusEnum } from '../interface/payment'; + +class FindPaymentsOpts extends FindPaymentsDto { + userId?: string; +}; @Injectable() export class PaymentRepository extends EntityRepository { constructor(readonly em: EntityManager) { super(em, Payment); } + + async findAllPaginated(opts: FindPaymentsOpts): Promise> { + const { + page = 1, + limit = 10, + statuses, + search, + orderBy = 'createdAt', + order = 'desc', + userId, + from, + to + } = opts; + + const offset = (page - 1) * limit; + + const where: FilterQuery = {}; + + // Filter by statuses + if (statuses) { + // Ensure statuses is always an array and normalize it + const normalizedStatuses = Array.isArray(statuses) ? statuses : [statuses]; + // Filter out any empty values + const validStatuses = normalizedStatuses.filter((s): s is PaymentStatusEnum => !!s); + + if (validStatuses.length > 0) { + // Use direct equality for single value, $in for multiple values to avoid SQL generation issues + if (validStatuses.length === 1) { + where.status = validStatuses[0]; + } else { + where.status = { $in: validStatuses }; + } + } + } + + if (userId) { + where.order = { user: { id: userId } }; + } + + // Filter by date range + if (from || to) { + where.createdAt = {}; + if (from) { + where.createdAt.$gte = new Date(from); + } + if (to) { + where.createdAt.$lte = new Date(to); + } + } + + // Search by order number or user information + if (search) { + const searchPattern = `%${search}%`; + const searchConditions: any[] = [ + { user: { firstName: { $ilike: searchPattern } } }, + { user: { lastName: { $ilike: searchPattern } } }, + { user: { phone: { $ilike: searchPattern } } }, + ]; + + // If search is numeric, also search by orderNumber + const numericSearch = Number(search); + if (!isNaN(numericSearch) && isFinite(numericSearch)) { + searchConditions.push({ orderNumber: numericSearch }); + } + + where.$or = searchConditions; + } + + // First, fetch orders without reviews + const [data, total] = await this.findAndCount(where, { + limit, + offset, + orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' }, + populate: [], + }); + + const totalPages = Math.ceil(total / limit); + + return { + data, + meta: { + total, + page, + limit, + totalPages, + }, + }; + } } diff --git a/src/modules/payment/services/payments.service.ts b/src/modules/payment/services/payments.service.ts index 026b858..f1b3491 100644 --- a/src/modules/payment/services/payments.service.ts +++ b/src/modules/payment/services/payments.service.ts @@ -15,6 +15,7 @@ import { CreditRepository } from 'src/modules/user/repositories/credit.repositor import { CreditService } from 'src/modules/user/providers/credit.service'; import { CreditTransactionType } from 'src/modules/user/interface/credit'; import { Payment } from '../entities/payment.entity'; +import { FindPaymentsDto } from '../dto/find-payments.dto'; @@ -170,11 +171,10 @@ export class PaymentService { }; } - async verifyOnlinePayment(transactionId: string): Promise { + async verifyOnlinePayment(token: string): Promise { const payment = await this.em.transactional(async em => { - const payment = await em.findOne( - Payment, - { transactionId }, + const payment = await this.paymentRepository.findOne( + { token }, { populate: ['order'] }, ); @@ -222,17 +222,17 @@ export class PaymentService { return payment; } - // findAllPaymentsBy(: string, userId: string) { - // return this.em.find( - // Payment, - // { order: { restaurant: { id: }, user: { id: userId } } }, - // { populate: ['order', 'order.paymentMethod'] }, - // ); - // } + findAll(dto: FindPaymentsDto) { + return this.paymentRepository.findAllPaginated(dto) + } - async verifyCashPayment(id: string): Promise { + findAllByUser(dto: FindPaymentsDto, userId: string) { + return this.paymentRepository.findAllPaginated({ ...dto, userId }) + } + + async verifyCashPayment(paymentId: string): Promise { const payment = await this.em.transactional(async em => { - const payment = await em.findOne(Payment, id, { populate: ['order'] }); + const payment = await em.findOne(Payment, paymentId, { populate: ['order'] }); if (!payment) { throw new NotFoundException(PaymentMessage.PAYMENT_NOT_FOUND); }