From f19326623592bceb0cbd6896c73ba3c5765483eb Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Tue, 2 Dec 2025 22:57:30 +0330 Subject: [PATCH] payment --- src/modules/orders/entities/order.entity.ts | 6 +- src/modules/orders/orders.service.ts | 4 +- .../controllers/payments.controller.ts | 102 +++++-------- .../payments/dto/create-payment-method.dto.ts | 38 +++-- .../payments/dto/create-payment.dto.ts | 19 ++- .../create-restaurant-payment-method.dto.ts | 18 --- .../payments/dto/update-payment-method.dto.ts | 8 +- .../payments/dto/update-payment.dto.ts | 1 - .../update-restaurant-payment-method.dto.ts | 5 - .../entities/payment-method.entity.ts | 31 ++-- .../payments/entities/payment.entity.ts | 14 +- .../restaurant-payment-method.entity.ts | 24 --- .../{payment-status.ts => payment.ts} | 14 +- src/modules/payments/payments.module.ts | 9 +- .../restaurant-payment-method.repository.ts | 19 --- .../services/payment-method.service.ts | 29 +++- .../payments/services/payments.service.ts | 144 +++++++++++------- .../restaurant-payment-method.service.ts | 135 ---------------- .../restaurants/entities/restaurant.entity.ts | 3 + 19 files changed, 219 insertions(+), 404 deletions(-) delete mode 100644 src/modules/payments/dto/create-restaurant-payment-method.dto.ts delete mode 100644 src/modules/payments/dto/update-restaurant-payment-method.dto.ts delete mode 100644 src/modules/payments/entities/restaurant-payment-method.entity.ts rename src/modules/payments/interface/{payment-status.ts => payment.ts} (54%) delete mode 100644 src/modules/payments/repositories/restaurant-payment-method.repository.ts delete mode 100644 src/modules/payments/services/restaurant-payment-method.service.ts diff --git a/src/modules/orders/entities/order.entity.ts b/src/modules/orders/entities/order.entity.ts index d07cf55..3ce3c32 100644 --- a/src/modules/orders/entities/order.entity.ts +++ b/src/modules/orders/entities/order.entity.ts @@ -1,6 +1,6 @@ import { Entity, ManyToOne, OneToMany, Property, Collection, Cascade, Enum } from '@mikro-orm/core'; import { BaseEntity } from '../../../common/entities/base.entity'; -import { PaymentStatus } from '../../payments/interface/payment-status'; +import { PaymentStatusEnum } from '../../payments/interface/payment'; import { OrderStatus } from '../interface/order-status'; import { User } from '../../users/entities/user.entity'; import { Restaurant } from '../../restaurants/entities/restaurant.entity'; @@ -55,6 +55,6 @@ export class Order extends BaseEntity { @Enum(() => OrderStatus) status!: OrderStatus; - @Enum(() => PaymentStatus) - paymentStatus!: PaymentStatus; + @Enum(() => PaymentStatusEnum) + paymentStatus!: PaymentStatusEnum; } diff --git a/src/modules/orders/orders.service.ts b/src/modules/orders/orders.service.ts index b2aca7b..f329855 100644 --- a/src/modules/orders/orders.service.ts +++ b/src/modules/orders/orders.service.ts @@ -9,7 +9,7 @@ import { UserAddress } from '../users/entities/user-address.entity'; import { RestaurantPaymentMethod } from '../payments/entities/restaurant-payment-method.entity'; import { CartService } from '../cart/providers/cart.service'; import { OrderStatus } from './interface/order-status'; -import { PaymentStatus } from '../payments/interface/payment-status'; +import { PaymentStatusEnum } from '../payments/interface/payment'; import { Cart } from '../cart/interfaces/cart.interface'; import { RestaurantPaymentMethodRepository } from '../payments/repositories/restaurant-payment-method.repository'; // import { PaymentGatewayService } from '../payments/services/payment-gateway.service.tss'; @@ -59,7 +59,7 @@ export class OrdersService { total: cart.total || 0, totalItems: cart.totalItems || 0, status: OrderStatus.Pending, - paymentStatus: PaymentStatus.Pending, + paymentStatus: PaymentStatusEnum.Pending, }); em.persist(order); diff --git a/src/modules/payments/controllers/payments.controller.ts b/src/modules/payments/controllers/payments.controller.ts index 6a528b3..c20143b 100644 --- a/src/modules/payments/controllers/payments.controller.ts +++ b/src/modules/payments/controllers/payments.controller.ts @@ -1,6 +1,5 @@ import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from '@nestjs/common'; import { PaymentsService } from '../services/payments.service'; -import { CreatePaymentDto } from '../dto/create-payment.dto'; import { UpdatePaymentDto } from '../dto/update-payment.dto'; import { ApiTags, @@ -14,9 +13,8 @@ import { } from '@nestjs/swagger'; import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard'; import { PaymentMethodService } from '../services/payment-method.service'; -import { RestaurantPaymentMethodService } from '../services/restaurant-payment-method.service'; -import { CreateRestaurantPaymentMethodDto } from '../dto/create-restaurant-payment-method.dto'; -import { UpdateRestaurantPaymentMethodDto } from '../dto/update-restaurant-payment-method.dto'; +import { CreatePaymentMethodDto } from '../dto/create-payment-method.dto'; +import { UpdatePaymentMethodDto } from '../dto/update-payment-method.dto'; import { RestId } from 'src/common/decorators'; import { AuthGuard } from 'src/modules/auth/guards/auth.guard'; @@ -26,7 +24,6 @@ export class PaymentsController { constructor( private readonly paymentsService: PaymentsService, private readonly paymentMethodService: PaymentMethodService, - private readonly restaurantPaymentMethodService: RestaurantPaymentMethodService, ) {} @UseGuards(AuthGuard) @@ -35,19 +32,9 @@ export class PaymentsController { @ApiOperation({ summary: 'Get the restaurant payment methods' }) @ApiNotFoundResponse({ description: 'Restaurant payment methods not found' }) getTheRestaurantPaymentMethods(@RestId() restId: string) { - return this.restaurantPaymentMethodService.findByRestaurant(restId); + return this.paymentMethodService.findByRestaurant(restId); } - // @UseGuards(AdminAuthGuard) - // @ApiBearerAuth() - // @Post('admin/payments') - // @ApiOperation({ summary: 'Create a new payment' }) - // @ApiBody({ type: CreatePaymentDto }) - // @ApiCreatedResponse({ description: 'Payment created successfully' }) - // createPayment(@Body() createPaymentDto: CreatePaymentDto) { - // return this.paymentsService.create(createPaymentDto); - // } - @UseGuards(AdminAuthGuard) @ApiBearerAuth() @Get('admin/payments') @@ -93,78 +80,57 @@ export class PaymentsController { @UseGuards(AdminAuthGuard) @ApiBearerAuth() @Get('admin/payments/methods') - @ApiOperation({ summary: 'Get all payment methods' }) - @ApiOkResponse({ description: 'List of all payment methods' }) - findAll() { - return this.paymentMethodService.findAll(); + @ApiOperation({ summary: 'Get restaurant all payment methods' }) + @ApiOkResponse({ description: 'List of restaurant payment methods' }) + @ApiNotFoundResponse({ description: 'Restaurant payment methods not found' }) + findByRestaurant(@RestId() restId: string) { + return this.paymentMethodService.findByRestaurant(restId); } @UseGuards(AdminAuthGuard) @ApiBearerAuth() - @Get('admin/payments/methods/:id') - @ApiOperation({ summary: 'Get a payment method by ID' }) - @ApiParam({ name: 'id', type: 'string', description: 'Payment method ID' }) + @Post('admin/payments/methods') + @ApiOperation({ summary: 'Create a new restaurant payment method' }) + @ApiBody({ type: CreatePaymentMethodDto }) + @ApiCreatedResponse({ description: 'Restaurant payment method created successfully' }) + createRestaurantPaymentMethod(@RestId() restId: string, @Body() createPaymentMethodDto: CreatePaymentMethodDto) { + return this.paymentMethodService.create(restId, createPaymentMethodDto); + } + + @UseGuards(AdminAuthGuard) + @ApiBearerAuth() + @Get('admin/payments/methods/:paymentMethodId') + @ApiOperation({ summary: 'Get restaurant payment method by ID' }) + @ApiParam({ name: 'paymentMethodId', type: 'string', description: 'Payment method ID' }) @ApiOkResponse({ description: 'Payment method found' }) @ApiNotFoundResponse({ description: 'Payment method not found' }) - findOne(@Param('id') id: string) { - return this.paymentMethodService.findOne(id); - } - - /** restaurant payment methods */ - - @UseGuards(AdminAuthGuard) - @ApiBearerAuth() - @Post('admin/payments/methods/restaurant') - @ApiOperation({ summary: 'Create a new restaurant payment method' }) - @ApiBody({ type: CreateRestaurantPaymentMethodDto }) - @ApiCreatedResponse({ description: 'Restaurant payment method created successfully' }) - create(@RestId() restId: string, @Body() createRestaurantPaymentMethodDto: CreateRestaurantPaymentMethodDto) { - return this.restaurantPaymentMethodService.create(restId, createRestaurantPaymentMethodDto); + findOneRestaurantPaymentMethod(@Param('paymentMethodId') paymentMethodId: string) { + return this.paymentMethodService.findOne(paymentMethodId); } @UseGuards(AdminAuthGuard) @ApiBearerAuth() - @Get('admin/payments/methods/restaurant/:restaurantPaymentMethodId') - @ApiOperation({ summary: 'Get restaurant payment method by payment method ID' }) - @ApiParam({ name: 'restaurantPaymentMethodId', type: 'string', description: 'restaurantPaymentMethodId method ID' }) - @ApiOkResponse({ description: 'List of restaurant payment methods for the payment method' }) - findByPaymentMethod(@Param('restaurantPaymentMethodId') restaurantPaymentMethodId: string) { - return this.restaurantPaymentMethodService.findByPaymentMethod(restaurantPaymentMethodId); - } - - @UseGuards(AdminAuthGuard) - @ApiBearerAuth() - @Get('admin/payments/methods/restaurant') - @ApiOperation({ summary: 'Get restaurant all payment methods ' }) - @ApiOkResponse({ description: 'Restaurant payment method found' }) - @ApiNotFoundResponse({ description: 'Restaurant payment method not found' }) - findByRestaurant(@RestId() restId: string) { - return this.restaurantPaymentMethodService.findByRestaurant(restId); - } - - @UseGuards(AdminAuthGuard) - @ApiBearerAuth() - @Patch('admin/payments/methods/restaurant/:restaurantPaymentMethodId') + @Patch('admin/payments/methods/:paymentMethodId') @ApiOperation({ summary: 'Update a restaurant payment method' }) - @ApiParam({ name: 'restaurantPaymentMethodId', type: 'string', description: 'Restaurant payment method ID' }) - @ApiBody({ type: UpdateRestaurantPaymentMethodDto }) + @ApiParam({ name: 'paymentMethodId', type: 'string', description: 'Payment method ID' }) + @ApiBody({ type: UpdatePaymentMethodDto }) @ApiOkResponse({ description: 'Restaurant payment method updated successfully' }) @ApiNotFoundResponse({ description: 'Restaurant payment method not found' }) - update( - @Param('restaurantPaymentMethodId') restaurantPaymentMethodId: string, - @Body() updateRestaurantPaymentMethodDto: UpdateRestaurantPaymentMethodDto, + updateRestaurantPaymentMethod( + @Param('paymentMethodId') paymentMethodId: string, + @Body() updatePaymentMethodDto: UpdatePaymentMethodDto, ) { - return this.restaurantPaymentMethodService.update(restaurantPaymentMethodId, updateRestaurantPaymentMethodDto); + return this.paymentMethodService.update(paymentMethodId, updatePaymentMethodDto); } @UseGuards(AdminAuthGuard) @ApiBearerAuth() - @Delete('admin/payments/methods/restaurant/:restaurantPaymentMethodId') + @Delete('admin/payments/methods/:paymentMethodId') @ApiOperation({ summary: 'Delete a restaurant payment method' }) - @ApiParam({ name: 'restaurantPaymentMethodId', type: 'string', description: 'Restaurant payment method ID' }) + @ApiParam({ name: 'paymentMethodId', type: 'string', description: 'Payment method ID' }) @ApiOkResponse({ description: 'Restaurant payment method deleted successfully' }) @ApiNotFoundResponse({ description: 'Restaurant payment method not found' }) - remove(@Param('restaurantPaymentMethodId') restaurantPaymentMethodId: string) { - return this.restaurantPaymentMethodService.remove(restaurantPaymentMethodId); + removeRestaurantPaymentMethod(@Param('paymentMethodId') paymentMethodId: string) { + return this.paymentMethodService.remove(paymentMethodId); } } diff --git a/src/modules/payments/dto/create-payment-method.dto.ts b/src/modules/payments/dto/create-payment-method.dto.ts index c6c7fc2..17a23f9 100644 --- a/src/modules/payments/dto/create-payment-method.dto.ts +++ b/src/modules/payments/dto/create-payment-method.dto.ts @@ -1,42 +1,38 @@ -import { IsString, IsOptional, IsBoolean, IsNumber } from 'class-validator'; +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 name' }) + @ApiProperty({ description: 'Payment method title' }) @IsString() - name!: string; + title!: string; - @ApiProperty({ description: 'English Payment method name' }) - @IsString() - keyName!: string; + @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: 'Payment method icon', required: false }) - @IsOptional() - @IsString() - icon?: string; - - @ApiProperty({ description: 'Is payment method active', required: false, default: true }) + @ApiProperty({ description: 'Is payment method enabled', required: false, default: true }) @IsOptional() @IsBoolean() - isActive?: boolean; + enabled?: boolean; - @ApiProperty({ description: 'Is payment method online', required: false, default: true }) - @IsOptional() - @IsBoolean() - isOnline?: boolean; - - @ApiProperty({ description: 'Display order', required: false }) + @ApiProperty({ description: 'Display order', required: false, default: 0 }) @IsOptional() @IsNumber() order?: number; - @ApiProperty({ description: 'Callback URL for payment method', required: false }) + @ApiProperty({ description: 'Merchant ID', required: false }) @IsOptional() @IsString() - paymentUrl?: string; + merchantId?: string; } diff --git a/src/modules/payments/dto/create-payment.dto.ts b/src/modules/payments/dto/create-payment.dto.ts index d52f73c..a4870d5 100644 --- a/src/modules/payments/dto/create-payment.dto.ts +++ b/src/modules/payments/dto/create-payment.dto.ts @@ -1,16 +1,27 @@ -import { IsNumber, IsString } from 'class-validator'; +import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; +import { PaymentGatewayEnum, PaymentMethodEnum } from '../interface/payment'; export class CreatePaymentDto { + @ApiProperty({ description: 'Order ID' }) + @IsString() + orderId: string; + @ApiProperty({ description: 'Payment amount' }) @IsNumber() amount: number; + @ApiProperty({ description: 'Payment method' }) + @IsEnum(PaymentMethodEnum) + paymentMethod: PaymentMethodEnum; + @ApiProperty({ description: 'Payment authority' }) + @IsOptional() @IsString() - restaurantPaymentId: string; + merchantId?: string | null; @ApiProperty({ description: 'Payment gateway' }) - @IsString() - gateway: string; + @IsOptional() + @IsEnum(PaymentGatewayEnum) + gateway?: PaymentGatewayEnum | null; } diff --git a/src/modules/payments/dto/create-restaurant-payment-method.dto.ts b/src/modules/payments/dto/create-restaurant-payment-method.dto.ts deleted file mode 100644 index 11589fd..0000000 --- a/src/modules/payments/dto/create-restaurant-payment-method.dto.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { IsString, IsOptional, IsBoolean } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; - -export class CreateRestaurantPaymentMethodDto { - @ApiProperty({ description: 'Payment method ID' }) - @IsString() - paymentMethodId!: string; - - @ApiProperty({ description: 'Merchant ID', required: false }) - @IsOptional() - @IsString() - merchantId?: string; - - @ApiProperty({ description: 'Is active', required: false, default: true }) - @IsOptional() - @IsBoolean() - isActive?: boolean; -} diff --git a/src/modules/payments/dto/update-payment-method.dto.ts b/src/modules/payments/dto/update-payment-method.dto.ts index 2892720..19ba7d8 100644 --- a/src/modules/payments/dto/update-payment-method.dto.ts +++ b/src/modules/payments/dto/update-payment-method.dto.ts @@ -1,11 +1,5 @@ import { PartialType } from '@nestjs/mapped-types'; import { CreatePaymentMethodDto } from './create-payment-method.dto'; -import { IsString } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; -export class UpdatePaymentMethodDto extends PartialType(CreatePaymentMethodDto) { - @ApiProperty({ description: 'Payment method ID' }) - @IsString() - id!: string; -} +export class UpdatePaymentMethodDto extends PartialType(CreatePaymentMethodDto) {} diff --git a/src/modules/payments/dto/update-payment.dto.ts b/src/modules/payments/dto/update-payment.dto.ts index 8e89bc6..13f3f5e 100644 --- a/src/modules/payments/dto/update-payment.dto.ts +++ b/src/modules/payments/dto/update-payment.dto.ts @@ -8,4 +8,3 @@ export class UpdatePaymentDto extends PartialType(CreatePaymentDto) { @IsNumber() id: number; } - diff --git a/src/modules/payments/dto/update-restaurant-payment-method.dto.ts b/src/modules/payments/dto/update-restaurant-payment-method.dto.ts deleted file mode 100644 index c395c92..0000000 --- a/src/modules/payments/dto/update-restaurant-payment-method.dto.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { PartialType } from '@nestjs/mapped-types'; -import { CreateRestaurantPaymentMethodDto } from './create-restaurant-payment-method.dto'; - - -export class UpdateRestaurantPaymentMethodDto extends PartialType(CreateRestaurantPaymentMethodDto) {} diff --git a/src/modules/payments/entities/payment-method.entity.ts b/src/modules/payments/entities/payment-method.entity.ts index bab1daf..4cb1ced 100644 --- a/src/modules/payments/entities/payment-method.entity.ts +++ b/src/modules/payments/entities/payment-method.entity.ts @@ -1,30 +1,31 @@ -import { Entity, Property } from '@mikro-orm/core'; +import { Entity, Enum, ManyToOne, Property } from '@mikro-orm/core'; import { BaseEntity } from '../../../common/entities/base.entity'; +import { Restaurant } from 'src/modules/restaurants/entities/restaurant.entity'; +import { PaymentGatewayEnum, PaymentMethodEnum } from '../interface/payment'; @Entity({ tableName: 'payment_methods' }) export class PaymentMethod extends BaseEntity { - @Property() - name!: string; + @ManyToOne(() => Restaurant) + restaurant!: Restaurant; @Property() - keyName!: string; + title!: string; + + @Enum(() => PaymentMethodEnum) + method!: PaymentMethodEnum; + + @Enum(() => PaymentGatewayEnum) + gateway: PaymentGatewayEnum | null = null; @Property({ nullable: true }) description?: string; - @Property({ nullable: true }) - icon?: string; - @Property({ default: true }) - isActive: boolean = true; - - @Property({ default: true }) - isOnline: boolean = true; - - @Property({ type: 'int', nullable: true }) - order?: number; + enabled: boolean = true; + @Property({ type: 'integer', default: 0 }) + order: number = 0; @Property({ nullable: true }) - paymentUrl?: string; + merchantId?: string; } diff --git a/src/modules/payments/entities/payment.entity.ts b/src/modules/payments/entities/payment.entity.ts index 7a579ee..4fec3e2 100644 --- a/src/modules/payments/entities/payment.entity.ts +++ b/src/modules/payments/entities/payment.entity.ts @@ -1,7 +1,7 @@ import { Entity, ManyToOne, Property, Enum } from '@mikro-orm/core'; import { BaseEntity } from '../../../common/entities/base.entity'; import { Order } from '../../orders/entities/order.entity'; -import { PaymentStatus } from '../interface/payment-status'; +import { PaymentGatewayEnum, PaymentStatusEnum } from '../interface/payment'; @Entity({ tableName: 'payments' }) export class Payment extends BaseEntity { @@ -14,17 +14,17 @@ export class Payment extends BaseEntity { @Property({ unique: true }) authority!: string; - @Property() - gateway!: string; + @Enum(() => PaymentGatewayEnum) + gateway?: PaymentGatewayEnum | null = null; @Property({ nullable: true }) - refId?: string; + refId?: string | null = null; - @Enum(() => PaymentStatus) - status!: PaymentStatus; + @Enum(() => PaymentStatusEnum) + status!: PaymentStatusEnum; @Property({ nullable: true }) - cardPan?: string; + cardPan?: string | null = null; @Property({ type: 'json' }) verifyResponse?: Record; diff --git a/src/modules/payments/entities/restaurant-payment-method.entity.ts b/src/modules/payments/entities/restaurant-payment-method.entity.ts deleted file mode 100644 index aeed169..0000000 --- a/src/modules/payments/entities/restaurant-payment-method.entity.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Entity, ManyToOne, Unique, Property } from '@mikro-orm/core'; -import { BaseEntity } from '../../../common/entities/base.entity'; -import { Restaurant } from '../../restaurants/entities/restaurant.entity'; -import { PaymentMethod } from './payment-method.entity'; - -@Entity({ tableName: 'restaurant_payment_methods' }) -@Unique({ properties: ['restaurant', 'paymentMethod'] }) -export class RestaurantPaymentMethod extends BaseEntity { - @ManyToOne(() => Restaurant) - restaurant!: Restaurant; - - @ManyToOne(() => PaymentMethod) - paymentMethod!: PaymentMethod; - - // Add merchant ID for payment gateway providers (e.g., ZarinPal) - @Property({ nullable: true }) - merchantId?: string; - - @Property({ nullable: true }) - callbackUrl?: string; - - @Property({ type: 'boolean', default: true }) - isActive: boolean = true; -} diff --git a/src/modules/payments/interface/payment-status.ts b/src/modules/payments/interface/payment.ts similarity index 54% rename from src/modules/payments/interface/payment-status.ts rename to src/modules/payments/interface/payment.ts index 3841bd5..26d5ceb 100644 --- a/src/modules/payments/interface/payment-status.ts +++ b/src/modules/payments/interface/payment.ts @@ -1,14 +1,26 @@ -export enum PaymentStatus { +export enum PaymentMethodEnum { + Online = 'Online', + Cash = 'Cash', + CardOnDelivery = 'CardOnDelivery', + Wallet = 'Wallet', +} +export enum PaymentStatusEnum { Pending = 'pending', Paid = 'paid', Failed = 'failed', } +export enum PaymentGatewayEnum { + ZarinPal = 'zarinpal', +} export interface IPaymentRequest { amount: number; callbackUrl: string; merchantId: string; description: string; + metadata: { + orderId: string; + }; } export interface IPaymentResponse { diff --git a/src/modules/payments/payments.module.ts b/src/modules/payments/payments.module.ts index b082a3f..d97fe22 100644 --- a/src/modules/payments/payments.module.ts +++ b/src/modules/payments/payments.module.ts @@ -4,9 +4,6 @@ import { MikroOrmModule } from '@mikro-orm/nestjs'; import { PaymentMethod } from './entities/payment-method.entity'; import { PaymentMethodRepository } from './repositories/payment-method.repository'; import { PaymentMethodService } from './services/payment-method.service'; -import { RestaurantPaymentMethod } from './entities/restaurant-payment-method.entity'; -import { RestaurantPaymentMethodRepository } from './repositories/restaurant-payment-method.repository'; -import { RestaurantPaymentMethodService } from './services/restaurant-payment-method.service'; // import { PaymentGatewayService } from './services/payment-gateway.service.tss'; import { Restaurant } from '../restaurants/entities/restaurant.entity'; import { PaymentsController } from './controllers/payments.controller'; @@ -14,21 +11,17 @@ import { AuthModule } from '../auth/auth.module'; import { JwtModule } from '@nestjs/jwt'; @Module({ - imports: [MikroOrmModule.forFeature([PaymentMethod, RestaurantPaymentMethod, Restaurant]), AuthModule, JwtModule], + imports: [MikroOrmModule.forFeature([PaymentMethod, Restaurant]), AuthModule, JwtModule], controllers: [PaymentsController], providers: [ PaymentsService, PaymentMethodService, PaymentMethodRepository, - RestaurantPaymentMethodService, - RestaurantPaymentMethodRepository, // PaymentGatewayService, ], exports: [ PaymentMethodRepository, PaymentMethodService, - RestaurantPaymentMethodRepository, - RestaurantPaymentMethodService, PaymentsService, // PaymentGatewayService, ], diff --git a/src/modules/payments/repositories/restaurant-payment-method.repository.ts b/src/modules/payments/repositories/restaurant-payment-method.repository.ts deleted file mode 100644 index a2198a3..0000000 --- a/src/modules/payments/repositories/restaurant-payment-method.repository.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; -import { EntityManager, EntityRepository } from '@mikro-orm/postgresql'; -import { RestaurantPaymentMethod } from '../entities/restaurant-payment-method.entity'; - -@Injectable() -export class RestaurantPaymentMethodRepository extends EntityRepository { - constructor(readonly em: EntityManager) { - super(em, RestaurantPaymentMethod); - } - async findOneWithPopulate(id: string): Promise { - const restaurantPaymentMethod = await this.findOne({ id }, { populate: ['restaurant', 'paymentMethod'] }); - if (!restaurantPaymentMethod) { - throw new NotFoundException('Restaurant payment method not found'); - } - // await restaurantPaymentMethod.restaurant.load(); - // await restaurantPaymentMethod.paymentMethod.load(); - return restaurantPaymentMethod; - } -} diff --git a/src/modules/payments/services/payment-method.service.ts b/src/modules/payments/services/payment-method.service.ts index 182bef2..0d2c550 100644 --- a/src/modules/payments/services/payment-method.service.ts +++ b/src/modules/payments/services/payment-method.service.ts @@ -1,10 +1,11 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; +import { Injectable, NotFoundException, ConflictException } from '@nestjs/common'; import { EntityManager } from '@mikro-orm/postgresql'; import { PaymentMethod } from '../entities/payment-method.entity'; import { PaymentMethodRepository } from '../repositories/payment-method.repository'; import { CreatePaymentMethodDto } from '../dto/create-payment-method.dto'; import { UpdatePaymentMethodDto } from '../dto/update-payment-method.dto'; import { RequiredEntityData } from '@mikro-orm/core'; +import { Restaurant } from '../../restaurants/entities/restaurant.entity'; @Injectable() export class PaymentMethodService { @@ -13,26 +14,40 @@ export class PaymentMethodService { private readonly em: EntityManager, ) {} - async create(createPaymentMethodDto: CreatePaymentMethodDto): Promise { - const paymentMethod = this.paymentMethodRepository.create( - createPaymentMethodDto as unknown as RequiredEntityData, - ); + async create(restId: string, createPaymentMethodDto: CreatePaymentMethodDto): Promise { + // Check if restaurant exists + const restaurant = await this.em.findOne(Restaurant, { id: restId }); + if (!restaurant) { + throw new NotFoundException(`Restaurant with ID ${restId} not found`); + } + + const paymentMethod = this.paymentMethodRepository.create({ + restaurant, + ...createPaymentMethodDto, + } as unknown as RequiredEntityData); await this.em.persistAndFlush(paymentMethod); return paymentMethod; } async findAll(): Promise { - return this.paymentMethodRepository.findAll(); + return this.paymentMethodRepository.findAll({ populate: ['restaurant'] }); } async findOne(id: string): Promise { - const paymentMethod = await this.paymentMethodRepository.findOne({ id }); + const paymentMethod = await this.paymentMethodRepository.findOne({ id }, { populate: ['restaurant'] }); if (!paymentMethod) { throw new NotFoundException(`PaymentMethod with ID ${id} not found`); } return paymentMethod; } + async findByRestaurant(restaurantId: string): Promise { + return this.paymentMethodRepository.find( + { restaurant: { id: restaurantId } }, + { populate: ['restaurant'] }, + ); + } + async update(id: string, updatePaymentMethodDto: UpdatePaymentMethodDto): Promise { const paymentMethod = await this.findOne(id); this.em.assign(paymentMethod, updatePaymentMethodDto); diff --git a/src/modules/payments/services/payments.service.ts b/src/modules/payments/services/payments.service.ts index c606d85..7b41c24 100644 --- a/src/modules/payments/services/payments.service.ts +++ b/src/modules/payments/services/payments.service.ts @@ -1,17 +1,24 @@ import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; -import { RestaurantPaymentMethod } from '../entities/restaurant-payment-method.entity'; import axios from 'axios'; -import { IPaymentRequest, IPaymentResponse, PaymentStatus } from '../interface/payment-status'; +import { + IPaymentRequest, + IPaymentResponse, + PaymentGatewayEnum, + PaymentMethodEnum, + PaymentStatusEnum, +} from '../interface/payment'; import { Payment } from '../entities/payment.entity'; import { EntityManager, RequiredEntityData } from '@mikro-orm/core'; import { Order } from '../../orders/entities/order.entity'; +import { PaymentMethod } from '../entities/payment-method.entity'; +import { CreatePaymentDto } from '../dto/create-payment.dto'; @Injectable() export class PaymentsService { constructor(private readonly em: EntityManager) {} async initializePayment( - restaurantPaymentMethodId: string, + paymentMethodId: string, amount: number, orderId: string, ): Promise<{ paymentUrl: string | null }> { @@ -27,78 +34,97 @@ export class PaymentsService { } // Load restaurant payment method with payment method relationship - const restaurantPaymentMethod = await this.em.findOne( - RestaurantPaymentMethod, - { id: restaurantPaymentMethodId }, - { populate: ['paymentMethod'] }, - ); - if (!restaurantPaymentMethod) { - throw new NotFoundException('Restaurant payment method not found'); + const paymentMethod = await this.em.findOne(PaymentMethod, { id: paymentMethodId }, { populate: ['restaurant'] }); + if (!paymentMethod) { + throw new NotFoundException('Payment method not found'); } - if (!restaurantPaymentMethod.paymentMethod?.isOnline) { - return { paymentUrl: null }; - } - - if (!restaurantPaymentMethod.callbackUrl) { - throw new BadRequestException('Callback URL is not configured'); - } - - if (!restaurantPaymentMethod.merchantId) { + if (paymentMethod.method !== PaymentMethodEnum.Online && !paymentMethod.merchantId) { throw new BadRequestException('Merchant ID is not provided'); } - if (!restaurantPaymentMethod.paymentMethod?.paymentUrl) { - throw new BadRequestException('Payment URL is not configured'); - } - - // Request to payment gateway with error handling - let gatewayResponse: IPaymentResponse; - try { - gatewayResponse = await this.requestToPaymentGateway(restaurantPaymentMethod.paymentMethod.paymentUrl, { - amount, - callbackUrl: restaurantPaymentMethod.callbackUrl, - merchantId: restaurantPaymentMethod.merchantId, - description: `Payment for order #${orderId}`, - }); - } catch (error) { - const errorMessage = error instanceof Error ? error.message : 'Unknown error'; - throw new BadRequestException(`Failed to connect to payment gateway: ${errorMessage}`); - } - - // Check gateway response code (typically 100 means success for Iranian gateways) - if (gatewayResponse.code !== 100 && gatewayResponse.code !== 0) { - throw new BadRequestException(`Payment gateway error: ${gatewayResponse.message || 'Unknown error'}`); - } - - if (!gatewayResponse.authority) { - throw new BadRequestException('Payment gateway did not return an authority token'); - } - // Create payment record and save authority - const payment = this.em.create(Payment, { + const restaurantDomain = paymentMethod.restaurant.domain; + const { authority } = await this.createPayment(restaurantDomain, { amount, - authority: gatewayResponse.authority, - order: this.em.getReference(Order, orderId), - gateway: restaurantPaymentMethod.paymentMethod.name, - status: PaymentStatus.Pending, - } as RequiredEntityData); + orderId, + paymentMethod: paymentMethod.method, + merchantId: paymentMethod.merchantId ?? null, + gateway: paymentMethod.gateway as PaymentGatewayEnum | null, + }); - await this.em.persistAndFlush(payment); + const paymentUrl = this.zarinpalPaymentUrl(paymentMethod.gateway, authority); - // Return payment URL - const paymentUrl = `${restaurantPaymentMethod.paymentMethod.paymentUrl}/${gatewayResponse.authority}`; return { paymentUrl }; } - private async requestToPaymentGateway( - gatewayPaymentUrl: string, - requestPayment: IPaymentRequest, - ): Promise { + async createPayment(domain: string, dto: CreatePaymentDto) { + const { amount, orderId, merchantId, gateway, paymentMethod } = dto; + + const { authority } = await this.requestToGateway(paymentMethod, amount, orderId, merchantId, domain, gateway); + + const payment = this.em.create(Payment, { + amount, + authority: authority, + order: this.em.getReference(Order, orderId), + gateway, + status: PaymentStatusEnum.Pending, + } as RequiredEntityData); + + await this.em.persistAndFlush(payment); + return { authority, payment }; + } + + async requestToGateway( + paymentMethod: PaymentMethodEnum, + amount: number, + orderId: string, + merchantId: string | null | undefined, + domain: string, + gateway: PaymentGatewayEnum | null | undefined, + ): Promise<{ authority: string | null }> { + if (paymentMethod === PaymentMethodEnum.Online && merchantId) { + const callbackUrl = `${domain}/payments/callback`; + if (gateway === PaymentGatewayEnum.ZarinPal) { + try { + const gatewayResponse = await this.requestToZarinPalGateway({ + amount, + merchantId, + description: `Payment for order #${orderId}`, + callbackUrl, + metadata: { + orderId, + }, + }); + // Check gateway response code (typically 100 means success for Iranian gateways) + if (gatewayResponse.code !== 100 && gatewayResponse.code !== 0) { + throw new BadRequestException(`Payment gateway error: ${gatewayResponse.message || 'Unknown error'}`); + } + + if (!gatewayResponse.authority) { + throw new BadRequestException('Payment gateway did not return an authority token'); + } + } catch (error) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error'; + throw new BadRequestException(`Failed to connect to payment gateway: ${errorMessage}`); + } + } + } + return { authority: null }; + } + + private async requestToZarinPalGateway(requestPayment: IPaymentRequest): Promise { + const gatewayPaymentUrl = 'https://payment.zarinpal.com/pg/v4/payment/request.json'; const response = await axios.post(gatewayPaymentUrl, requestPayment); return response.data; } + zarinpalPaymentUrl(gateway: PaymentGatewayEnum | null, authority: string | null) { + if (gateway === PaymentGatewayEnum.ZarinPal) { + return `https://payment.zarinpal.com/pg/StartPay/${authority}`; + } + return null; + } // eslint-disable-next-line @typescript-eslint/no-unused-vars update(_id: number, _updatePaymentDto: unknown) { return `This action updates a #${_id} payment`; diff --git a/src/modules/payments/services/restaurant-payment-method.service.ts b/src/modules/payments/services/restaurant-payment-method.service.ts deleted file mode 100644 index a599c47..0000000 --- a/src/modules/payments/services/restaurant-payment-method.service.ts +++ /dev/null @@ -1,135 +0,0 @@ -import { Injectable, NotFoundException, ConflictException } from '@nestjs/common'; -import { EntityManager } from '@mikro-orm/postgresql'; -import { RestaurantPaymentMethod } from '../entities/restaurant-payment-method.entity'; -import { RestaurantPaymentMethodRepository } from '../repositories/restaurant-payment-method.repository'; -import { Restaurant } from '../../restaurants/entities/restaurant.entity'; -import { PaymentMethod } from '../entities/payment-method.entity'; -import { CreateRestaurantPaymentMethodDto } from '../dto/create-restaurant-payment-method.dto'; -import { UpdateRestaurantPaymentMethodDto } from '../dto/update-restaurant-payment-method.dto'; -import { RequiredEntityData } from '@mikro-orm/core'; - -@Injectable() -export class RestaurantPaymentMethodService { - constructor( - private readonly restaurantPaymentMethodRepository: RestaurantPaymentMethodRepository, - private readonly em: EntityManager, - ) {} - - async create( - restId: string, - createRestaurantPaymentMethodDto: CreateRestaurantPaymentMethodDto, - ): Promise { - // Check if restaurant exists - const restaurant = await this.em.findOne(Restaurant, { id: restId }); - if (!restaurant) { - throw new NotFoundException(`Restaurant with ID ${restId} not found`); - } - - // Check if payment method exists - const paymentMethod = await this.em.findOne(PaymentMethod, { - id: createRestaurantPaymentMethodDto.paymentMethodId, - }); - if (!paymentMethod) { - throw new NotFoundException( - `PaymentMethod with ID ${createRestaurantPaymentMethodDto.paymentMethodId} not found`, - ); - } - - // Check if the relationship already exists - const existing = await this.restaurantPaymentMethodRepository.findOne({ - restaurant: { id: restId }, - paymentMethod: { id: createRestaurantPaymentMethodDto.paymentMethodId }, - }); - - if (existing) { - throw new ConflictException('This payment method is already assigned to this restaurant'); - } - - const restaurantPaymentMethod = this.restaurantPaymentMethodRepository.create({ - restaurant, - paymentMethod, - merchantId: createRestaurantPaymentMethodDto.merchantId, - isActive: createRestaurantPaymentMethodDto.isActive ?? true, - } as unknown as RequiredEntityData); - - await this.em.persistAndFlush(restaurantPaymentMethod); - return restaurantPaymentMethod; - } - - async findAll(): Promise { - return this.restaurantPaymentMethodRepository.findAll({ populate: ['restaurant', 'paymentMethod'] }); - } - - async findByRestaurant(restaurantId: string): Promise { - return this.restaurantPaymentMethodRepository.find( - { restaurant: { id: restaurantId } }, - { populate: ['restaurant', 'paymentMethod'] }, - ); - } - - async findByPaymentMethod(restPaymentMethodId: string): Promise { - return this.restaurantPaymentMethodRepository.findOneOrFail( - { id: restPaymentMethodId }, - { populate: ['restaurant', 'paymentMethod'] }, - ); - } - - async findOne(id: string): Promise { - const restaurantPaymentMethod = await this.restaurantPaymentMethodRepository.findOne( - { id }, - { populate: ['restaurant', 'paymentMethod'] }, - ); - if (!restaurantPaymentMethod) { - throw new NotFoundException(`RestaurantPaymentMethod with ID ${id} not found`); - } - return restaurantPaymentMethod; - } - - async update( - id: string, - updateRestaurantPaymentMethodDto: UpdateRestaurantPaymentMethodDto, - ): Promise { - const restaurantPaymentMethod = await this.findOne(id); - - if (updateRestaurantPaymentMethodDto.paymentMethodId) { - const paymentMethod = await this.em.findOne(PaymentMethod, { - id: updateRestaurantPaymentMethodDto.paymentMethodId, - }); - if (!paymentMethod) { - throw new NotFoundException( - `PaymentMethod with ID ${updateRestaurantPaymentMethodDto.paymentMethodId} not found`, - ); - } - - // Check if the new relationship already exists - const existing = await this.restaurantPaymentMethodRepository.findOne({ - restaurant: { id: restaurantPaymentMethod.restaurant.id }, - paymentMethod: { id: updateRestaurantPaymentMethodDto.paymentMethodId }, - }); - - if (existing && existing.id !== id) { - throw new ConflictException('This payment method is already assigned to this restaurant'); - } - - restaurantPaymentMethod.paymentMethod = paymentMethod; - } - - // Update merchantId and isActive if provided - if (updateRestaurantPaymentMethodDto.merchantId !== undefined) { - restaurantPaymentMethod.merchantId = updateRestaurantPaymentMethodDto.merchantId; - } - - if (updateRestaurantPaymentMethodDto.isActive !== undefined) { - restaurantPaymentMethod.isActive = updateRestaurantPaymentMethodDto.isActive; - } - - await this.em.flush(); - return restaurantPaymentMethod; - } - - async remove(id: string): Promise { - const restaurantPaymentMethod = await this.findOne(id); - await this.em.removeAndFlush(restaurantPaymentMethod); - return restaurantPaymentMethod; - } -} diff --git a/src/modules/restaurants/entities/restaurant.entity.ts b/src/modules/restaurants/entities/restaurant.entity.ts index 5b62f43..ab7ce09 100644 --- a/src/modules/restaurants/entities/restaurant.entity.ts +++ b/src/modules/restaurants/entities/restaurant.entity.ts @@ -77,6 +77,9 @@ export class Restaurant extends BaseEntity { @Property({ type: 'decimal', default: 0 }) vat?: number = 0; + @Property() + domain!: string; + // --- روش‌های ارسال --- @OneToMany(() => Delivery, delivery => delivery.restaurant) deliveries = new Collection(this);