This commit is contained in:
2025-12-02 22:57:30 +03:30
parent 40442686be
commit f193266235
19 changed files with 219 additions and 404 deletions
+3 -3
View File
@@ -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;
}
+2 -2
View File
@@ -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);
@@ -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);
}
}
@@ -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;
}
+15 -4
View File
@@ -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;
}
@@ -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;
}
@@ -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) {}
@@ -8,4 +8,3 @@ export class UpdatePaymentDto extends PartialType(CreatePaymentDto) {
@IsNumber()
id: number;
}
@@ -1,5 +0,0 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateRestaurantPaymentMethodDto } from './create-restaurant-payment-method.dto';
export class UpdateRestaurantPaymentMethodDto extends PartialType(CreateRestaurantPaymentMethodDto) {}
@@ -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;
}
@@ -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<string, any>;
@@ -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;
}
@@ -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 {
+1 -8
View File
@@ -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,
],
@@ -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<RestaurantPaymentMethod> {
constructor(readonly em: EntityManager) {
super(em, RestaurantPaymentMethod);
}
async findOneWithPopulate(id: string): Promise<RestaurantPaymentMethod> {
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;
}
}
@@ -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<PaymentMethod> {
const paymentMethod = this.paymentMethodRepository.create(
createPaymentMethodDto as unknown as RequiredEntityData<PaymentMethod>,
);
async create(restId: string, createPaymentMethodDto: CreatePaymentMethodDto): Promise<PaymentMethod> {
// 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<PaymentMethod>);
await this.em.persistAndFlush(paymentMethod);
return paymentMethod;
}
async findAll(): Promise<PaymentMethod[]> {
return this.paymentMethodRepository.findAll();
return this.paymentMethodRepository.findAll({ populate: ['restaurant'] });
}
async findOne(id: string): Promise<PaymentMethod> {
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<PaymentMethod[]> {
return this.paymentMethodRepository.find(
{ restaurant: { id: restaurantId } },
{ populate: ['restaurant'] },
);
}
async update(id: string, updatePaymentMethodDto: UpdatePaymentMethodDto): Promise<PaymentMethod> {
const paymentMethod = await this.findOne(id);
this.em.assign(paymentMethod, updatePaymentMethodDto);
@@ -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<Payment>);
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<IPaymentResponse> {
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<Payment>);
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<IPaymentResponse> {
const gatewayPaymentUrl = 'https://payment.zarinpal.com/pg/v4/payment/request.json';
const response = await axios.post<IPaymentResponse>(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`;
@@ -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<RestaurantPaymentMethod> {
// 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<RestaurantPaymentMethod>);
await this.em.persistAndFlush(restaurantPaymentMethod);
return restaurantPaymentMethod;
}
async findAll(): Promise<RestaurantPaymentMethod[]> {
return this.restaurantPaymentMethodRepository.findAll({ populate: ['restaurant', 'paymentMethod'] });
}
async findByRestaurant(restaurantId: string): Promise<RestaurantPaymentMethod[]> {
return this.restaurantPaymentMethodRepository.find(
{ restaurant: { id: restaurantId } },
{ populate: ['restaurant', 'paymentMethod'] },
);
}
async findByPaymentMethod(restPaymentMethodId: string): Promise<RestaurantPaymentMethod | null> {
return this.restaurantPaymentMethodRepository.findOneOrFail(
{ id: restPaymentMethodId },
{ populate: ['restaurant', 'paymentMethod'] },
);
}
async findOne(id: string): Promise<RestaurantPaymentMethod> {
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<RestaurantPaymentMethod> {
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<RestaurantPaymentMethod> {
const restaurantPaymentMethod = await this.findOne(id);
await this.em.removeAndFlush(restaurantPaymentMethod);
return restaurantPaymentMethod;
}
}
@@ -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<Delivery>(this);