order confirm after pay
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import type { OrderCouponDetail, OrderUserAddress, OrderCarAddress } from 'src/modules/orders/interface/order-status';
|
import type { OrderCouponDetail, OrderUserAddress, OrderCarAddress } from 'src/modules/orders/interface/order.interface';
|
||||||
|
|
||||||
export interface CartItem {
|
export interface CartItem {
|
||||||
foodId: string;
|
foodId: string;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { Delivery } from '../../delivery/entities/delivery.entity';
|
|||||||
import { DeliveryMethodEnum } from '../../delivery/interface/delivery';
|
import { DeliveryMethodEnum } from '../../delivery/interface/delivery';
|
||||||
import { Cart, CartItem } from '../interfaces/cart.interface';
|
import { Cart, CartItem } from '../interfaces/cart.interface';
|
||||||
import { CouponService } from 'src/modules/coupons/providers/coupon.service';
|
import { CouponService } from 'src/modules/coupons/providers/coupon.service';
|
||||||
import { OrderCouponDetail } from 'src/modules/orders/interface/order-status';
|
import { OrderCouponDetail } from 'src/modules/orders/interface/order.interface';
|
||||||
import { CouponType } from 'src/modules/coupons/interface/coupon';
|
import { CouponType } from 'src/modules/coupons/interface/coupon';
|
||||||
import { UserWallet } from 'src/modules/users/entities/user-wallet.entity';
|
import { UserWallet } from 'src/modules/users/entities/user-wallet.entity';
|
||||||
import { PaymentMethodEnum } from 'src/modules/payments/interface/payment';
|
import { PaymentMethodEnum } from 'src/modules/payments/interface/payment';
|
||||||
@@ -223,8 +223,8 @@ export class CartService {
|
|||||||
const cart = await this.findOneOrFail(userId, restaurantId);
|
const cart = await this.findOneOrFail(userId, restaurantId);
|
||||||
|
|
||||||
const paymentMethod = await this.getEnabledPaymentMethodOrFail(restaurantId, setPaymentMethodDto.paymentMethodId);
|
const paymentMethod = await this.getEnabledPaymentMethodOrFail(restaurantId, setPaymentMethodDto.paymentMethodId);
|
||||||
if (paymentMethod.method === PaymentMethodEnum.Wallet) {
|
if (paymentMethod.method === PaymentMethodEnum.Wallet) {
|
||||||
await this.assertWalletHasEnoughBalance(userId, restaurantId, cart.total);
|
await this.assertWalletHasEnoughBalance(userId, restaurantId, cart.total);
|
||||||
}
|
}
|
||||||
cart.paymentMethodId = setPaymentMethodDto.paymentMethodId;
|
cart.paymentMethodId = setPaymentMethodDto.paymentMethodId;
|
||||||
return this.recalculateAndSaveCart(cart);
|
return this.recalculateAndSaveCart(cart);
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { PaymentMethodEnum } from "src/modules/payments/interface/payment";
|
||||||
|
|
||||||
|
export const STOCK_RESERVE_MINUTES: Record<PaymentMethodEnum, number> = {
|
||||||
|
[PaymentMethodEnum.Online]: 15,
|
||||||
|
[PaymentMethodEnum.Cash]: 120,
|
||||||
|
[PaymentMethodEnum.Wallet]: 15,
|
||||||
|
};
|
||||||
@@ -15,11 +15,6 @@ export class BulkReserveFoodItemDto {
|
|||||||
@Type(() => Number)
|
@Type(() => Number)
|
||||||
quantity!: number;
|
quantity!: number;
|
||||||
|
|
||||||
@ApiProperty({ example: '2024-12-31T23:59:59Z', description: 'Reservation expiration date' })
|
|
||||||
@IsNotEmpty()
|
|
||||||
@IsDate()
|
|
||||||
@Type(() => Date)
|
|
||||||
expiresAt!: Date;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BulkReserveFoodDto {
|
export class BulkReserveFoodDto {
|
||||||
|
|||||||
@@ -2,3 +2,5 @@ export enum ReservationStatus {
|
|||||||
ACTIVE = 'active',
|
ACTIVE = 'active',
|
||||||
CONFIRMED = 'confirmed',
|
CONFIRMED = 'confirmed',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,5 +10,6 @@ import { JwtModule } from '@nestjs/jwt';
|
|||||||
imports: [MikroOrmModule.forFeature([Inventory]), AuthModule, JwtModule],
|
imports: [MikroOrmModule.forFeature([Inventory]), AuthModule, JwtModule],
|
||||||
controllers: [InventoryController],
|
controllers: [InventoryController],
|
||||||
providers: [InventoryService],
|
providers: [InventoryService],
|
||||||
|
exports: [InventoryService],
|
||||||
})
|
})
|
||||||
export class InventoryModule {}
|
export class InventoryModule {}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { Food } from '../foods/entities/food.entity';
|
|||||||
import { Restaurant } from '../restaurants/entities/restaurant.entity';
|
import { Restaurant } from '../restaurants/entities/restaurant.entity';
|
||||||
import { Order } from '../orders/entities/order.entity';
|
import { Order } from '../orders/entities/order.entity';
|
||||||
import { ReservationStatus } from './inteface/reservation';
|
import { ReservationStatus } from './inteface/reservation';
|
||||||
|
import { STOCK_RESERVE_MINUTES } from './const/reservation';
|
||||||
|
import { PaymentMethodEnum } from '../payments/interface/payment';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InventoryService {
|
export class InventoryService {
|
||||||
@@ -59,8 +61,8 @@ export class InventoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async bulkSetStockForFoods(restaurantId: string, bulkSetStockDto: BulkSetStockDto): Promise<Inventory[]> {
|
async bulkSetStockForFoods(restaurantId: string, bulkSetStockDto: BulkSetStockDto): Promise<Inventory[]> {
|
||||||
const { items } = bulkSetStockDto;
|
|
||||||
|
|
||||||
|
const { items } = bulkSetStockDto;
|
||||||
// Validate all items first
|
// Validate all items first
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (item.availableStock > item.totalStock) {
|
if (item.availableStock > item.totalStock) {
|
||||||
@@ -134,17 +136,19 @@ export class InventoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async tempBulkReserveFood(
|
async tempBulkReserveFood(
|
||||||
|
em: EntityManager,
|
||||||
restaurantId: string,
|
restaurantId: string,
|
||||||
orderId: string,
|
orderId: string,
|
||||||
|
paymentMethod: PaymentMethodEnum,
|
||||||
bulkReserveFoodDto: BulkReserveFoodDto,
|
bulkReserveFoodDto: BulkReserveFoodDto,
|
||||||
): Promise<Reservation[]> {
|
): Promise<Reservation[]> {
|
||||||
const { items } = bulkReserveFoodDto;
|
const { items } = bulkReserveFoodDto;
|
||||||
|
const expiresAt = this.getReservationExpiry(paymentMethod);
|
||||||
// Get all unique food IDs
|
// Get all unique food IDs
|
||||||
const foodIds = [...new Set(items.map(item => item.foodId))];
|
const foodIds = [...new Set(items.map(item => item.foodId))];
|
||||||
|
|
||||||
// Load all foods in one query
|
// Load all foods in one query
|
||||||
const foods = await this.em.find(Food, { id: { $in: foodIds } }, { populate: ['restaurant'] });
|
const foods = await em.find(Food, { id: { $in: foodIds } }, { populate: ['restaurant'] });
|
||||||
|
|
||||||
// Verify all foods exist and belong to the restaurant
|
// Verify all foods exist and belong to the restaurant
|
||||||
const foodMap = new Map<string, Food>();
|
const foodMap = new Map<string, Food>();
|
||||||
@@ -162,13 +166,13 @@ export class InventoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load order
|
// Load order
|
||||||
const order = await this.em.findOne(Order, { id: orderId });
|
const order = await em.findOne(Order, { id: orderId });
|
||||||
if (!order) {
|
if (!order) {
|
||||||
throw new NotFoundException(`Order with ID ${orderId} not found`);
|
throw new NotFoundException(`Order with ID ${orderId} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load all existing inventories in one query
|
// Load all existing inventories in one query
|
||||||
const existingInventories = await this.em.find(Inventory, {
|
const existingInventories = await em.find(Inventory, {
|
||||||
food: { id: { $in: foodIds }, restaurant: { id: restaurantId } },
|
food: { id: { $in: foodIds }, restaurant: { id: restaurantId } },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -196,11 +200,11 @@ export class InventoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create reservation record
|
// Create reservation record
|
||||||
const reservation = this.em.create(Reservation, {
|
const reservation = em.create(Reservation, {
|
||||||
food,
|
food,
|
||||||
order,
|
order,
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
expiresAt: item.expiresAt,
|
expiresAt,
|
||||||
status: ReservationStatus.ACTIVE,
|
status: ReservationStatus.ACTIVE,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -210,21 +214,35 @@ export class InventoryService {
|
|||||||
inventory.availableStock -= item.quantity;
|
inventory.availableStock -= item.quantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush all changes at once
|
|
||||||
await this.em.flush();
|
|
||||||
|
|
||||||
return reservations;
|
return reservations;
|
||||||
}
|
}
|
||||||
|
|
||||||
async confirmReservationByOrderId(orderId: string): Promise<Reservation[]> {
|
async confirmReservationByOrderId(em: EntityManager, orderId: string): Promise<Reservation[]> {
|
||||||
const reservations = await this.em.find(Reservation, { order: { id: orderId } });
|
const reservations = await em.find(Reservation, { order: { id: orderId } });
|
||||||
if (!reservations) {
|
if (!reservations) {
|
||||||
throw new NotFoundException(`Reservations with order ID ${orderId} not found`);
|
throw new NotFoundException(`Reservations with order ID ${orderId} not found`);
|
||||||
}
|
}
|
||||||
for (const reservation of reservations) {
|
for (const reservation of reservations) {
|
||||||
reservation.status = ReservationStatus.CONFIRMED;
|
reservation.status = ReservationStatus.CONFIRMED;
|
||||||
}
|
}
|
||||||
await this.em.flush();
|
|
||||||
return reservations;
|
return reservations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async releaseReservationByOrderId(em: EntityManager, orderId: string): Promise<Reservation[]> {
|
||||||
|
const reservations = await em.find(Reservation, { order: { id: orderId } });
|
||||||
|
if (!reservations) {
|
||||||
|
throw new NotFoundException(`Reservations with order ID ${orderId} not found`);
|
||||||
|
}
|
||||||
|
for (const reservation of reservations) {
|
||||||
|
em.remove(reservation);
|
||||||
|
}
|
||||||
|
return reservations;
|
||||||
|
}
|
||||||
|
|
||||||
|
getReservationExpiry(method: PaymentMethodEnum): Date {
|
||||||
|
const minutes = STOCK_RESERVE_MINUTES[method] ?? 15;
|
||||||
|
return new Date(Date.now() + minutes * 60 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { UserId } from '../../../common/decorators/user-id.decorator';
|
|||||||
import { RestId } from 'src/common/decorators/rest-id.decorator';
|
import { RestId } from 'src/common/decorators/rest-id.decorator';
|
||||||
import { AdminAuthGuard } from '../../auth/guards/adminAuth.guard';
|
import { AdminAuthGuard } from '../../auth/guards/adminAuth.guard';
|
||||||
import { FindOrdersDto } from '../dto/find-orders.dto';
|
import { FindOrdersDto } from '../dto/find-orders.dto';
|
||||||
import { OrderStatus } from '../interface/order-status';
|
import { OrderStatus } from '../interface/order.interface';
|
||||||
|
|
||||||
@ApiTags('orders')
|
@ApiTags('orders')
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { IsOptional, IsString, IsNumber, Min, IsIn, IsEnum, IsDateString } from 'class-validator';
|
import { IsOptional, IsString, IsNumber, Min, IsIn, IsEnum, IsDateString } from 'class-validator';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { ApiPropertyOptional } from '@nestjs/swagger';
|
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
import { OrderStatus } from '../interface/order-status';
|
import { OrderStatus } from '../interface/order.interface';
|
||||||
import { PaymentStatusEnum } from '../../payments/interface/payment';
|
import { PaymentStatusEnum } from '../../payments/interface/payment';
|
||||||
|
|
||||||
// Define the valid sort directions
|
// Define the valid sort directions
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ import {
|
|||||||
} from '@mikro-orm/core';
|
} from '@mikro-orm/core';
|
||||||
import { BaseEntity } from '../../../common/entities/base.entity';
|
import { BaseEntity } from '../../../common/entities/base.entity';
|
||||||
import { PaymentStatusEnum } from '../../payments/interface/payment';
|
import { PaymentStatusEnum } from '../../payments/interface/payment';
|
||||||
import { OrderCouponDetail, OrderStatus } from '../interface/order-status';
|
import { OrderCouponDetail, OrderStatus } from '../interface/order.interface';
|
||||||
import { User } from '../../users/entities/user.entity';
|
import { User } from '../../users/entities/user.entity';
|
||||||
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
||||||
import { PaymentMethod } from '../../payments/entities/payment-method.entity';
|
import { PaymentMethod } from '../../payments/entities/payment-method.entity';
|
||||||
import { OrderItem } from './order-item.entity';
|
import { OrderItem } from './order-item.entity';
|
||||||
import { Delivery } from '../../delivery/entities/delivery.entity';
|
import { Delivery } from '../../delivery/entities/delivery.entity';
|
||||||
import { OrderUserAddress, OrderCarAddress } from '../interface/order-status';
|
import { OrderUserAddress, OrderCarAddress } from '../interface/order.interface';
|
||||||
|
|
||||||
@Entity({ tableName: 'orders' })
|
@Entity({ tableName: 'orders' })
|
||||||
@Unique({ properties: ['restaurant', 'orderNumber'] })
|
@Unique({ properties: ['restaurant', 'orderNumber'] })
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { OrderRepository } from './repositories/order.repository';
|
|||||||
import { OrderListeners } from './listeners/order.listeners';
|
import { OrderListeners } from './listeners/order.listeners';
|
||||||
import { AdminModule } from '../admin/admin.module';
|
import { AdminModule } from '../admin/admin.module';
|
||||||
import { NotificationsModule } from '../notifications/notifications.module';
|
import { NotificationsModule } from '../notifications/notifications.module';
|
||||||
|
import { InventoryModule } from '../inventory/inventory.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -29,6 +30,7 @@ import { NotificationsModule } from '../notifications/notifications.module';
|
|||||||
JwtModule,
|
JwtModule,
|
||||||
AdminModule,
|
AdminModule,
|
||||||
NotificationsModule,
|
NotificationsModule,
|
||||||
|
InventoryModule
|
||||||
],
|
],
|
||||||
controllers: [OrdersController],
|
controllers: [OrdersController],
|
||||||
providers: [OrdersService, OrderRepository, OrderListeners],
|
providers: [OrdersService, OrderRepository, OrderListeners],
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { User } from '../../users/entities/user.entity';
|
|||||||
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
|
||||||
import { Food } from '../../foods/entities/food.entity';
|
import { Food } from '../../foods/entities/food.entity';
|
||||||
import { CartService } from '../../cart/providers/cart.service';
|
import { CartService } from '../../cart/providers/cart.service';
|
||||||
import { OrderStatus, OrderUserAddress, OrderCarAddress } from '../interface/order-status';
|
import { OrderStatus, OrderUserAddress, OrderCarAddress } from '../interface/order.interface';
|
||||||
import { PaymentMethodEnum, PaymentStatusEnum } from '../../payments/interface/payment';
|
import { PaymentMethodEnum, PaymentStatusEnum } from '../../payments/interface/payment';
|
||||||
import { Cart } from '../../cart/interfaces/cart.interface';
|
import { Cart } from '../../cart/interfaces/cart.interface';
|
||||||
import { PaymentMethod } from '../../payments/entities/payment-method.entity';
|
import { PaymentMethod } from '../../payments/entities/payment-method.entity';
|
||||||
@@ -17,6 +17,8 @@ import { OrderRepository } from '../repositories/order.repository';
|
|||||||
import { FindOrdersDto } from '../dto/find-orders.dto';
|
import { FindOrdersDto } from '../dto/find-orders.dto';
|
||||||
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
||||||
import { Payment } from 'src/modules/payments/entities/payment.entity';
|
import { Payment } from 'src/modules/payments/entities/payment.entity';
|
||||||
|
import { InventoryService } from 'src/modules/inventory/inventory.service';
|
||||||
|
import { BulkReserveFoodDto } from 'src/modules/inventory/dto/bulk-reserve-food.dto';
|
||||||
|
|
||||||
type OrderItemData = { food: Food; quantity: number; unitPrice: number; discount: number };
|
type OrderItemData = { food: Food; quantity: number; unitPrice: number; discount: number };
|
||||||
|
|
||||||
@@ -53,6 +55,7 @@ export class OrdersService {
|
|||||||
private readonly cartService: CartService,
|
private readonly cartService: CartService,
|
||||||
private readonly orderRepository: OrderRepository,
|
private readonly orderRepository: OrderRepository,
|
||||||
private readonly paymentsService: PaymentsService,
|
private readonly paymentsService: PaymentsService,
|
||||||
|
private readonly inventoryService: InventoryService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async checkout(userId: string, restaurantId: string) {
|
async checkout(userId: string, restaurantId: string) {
|
||||||
@@ -109,8 +112,16 @@ export class OrdersService {
|
|||||||
method: order.paymentMethod.method,
|
method: order.paymentMethod.method,
|
||||||
gateway: order.paymentMethod.gateway ?? null,
|
gateway: order.paymentMethod.gateway ?? null,
|
||||||
});
|
});
|
||||||
em.persist(payment);
|
|
||||||
|
|
||||||
|
em.persist(payment);
|
||||||
|
// reserve stock based on payment method.
|
||||||
|
const bulkReserveFoodDto: BulkReserveFoodDto = {
|
||||||
|
items: validated.orderItemsData.map(item => ({
|
||||||
|
foodId: item.food.id,
|
||||||
|
quantity: item.quantity,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
await this.inventoryService.tempBulkReserveFood(em, restaurantId, order.id, order.paymentMethod.method, bulkReserveFoodDto);
|
||||||
await em.flush();
|
await em.flush();
|
||||||
this.logger.debug(`Order ${order.id} created for user ${userId} (restaurant ${restaurantId})`);
|
this.logger.debug(`Order ${order.id} created for user ${userId} (restaurant ${restaurantId})`);
|
||||||
return order;
|
return order;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { EntityManager, EntityRepository } from '@mikro-orm/postgresql';
|
|||||||
import { FilterQuery } from '@mikro-orm/core';
|
import { FilterQuery } from '@mikro-orm/core';
|
||||||
import { Order } from '../entities/order.entity';
|
import { Order } from '../entities/order.entity';
|
||||||
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
import { PaginatedResult } from 'src/common/interfaces/pagination.interface';
|
||||||
import { OrderStatus } from '../interface/order-status';
|
import { OrderStatus } from '../interface/order.interface';
|
||||||
import { PaymentStatusEnum } from '../../payments/interface/payment';
|
import { PaymentStatusEnum } from '../../payments/interface/payment';
|
||||||
import { Review } from '../../review/entities/review.entity';
|
import { Review } from '../../review/entities/review.entity';
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ export class OrderRepository extends EntityRepository<Order> {
|
|||||||
limit,
|
limit,
|
||||||
offset,
|
offset,
|
||||||
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
|
orderBy: { [orderBy]: order.toLowerCase() as 'asc' | 'desc' },
|
||||||
populate: ['user', 'restaurant', 'deliveryMethod', 'paymentMethod', 'items', 'items.food'] as never,
|
populate: ['user', 'restaurant', 'deliveryMethod', 'paymentMethod', 'items', 'items.food'] as never,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Collect all (orderId, foodId) pairs for efficient review lookup
|
// Collect all (orderId, foodId) pairs for efficient review lookup
|
||||||
|
|||||||
@@ -12,11 +12,14 @@ import { Payment } from './entities/payment.entity';
|
|||||||
import { ZarinpalGateway } from './gateways/zarinpal.gateway';
|
import { ZarinpalGateway } from './gateways/zarinpal.gateway';
|
||||||
import { GatewayManager } from './services/gateway.manager';
|
import { GatewayManager } from './services/gateway.manager';
|
||||||
import { PaymentRepository } from './repositories/payment.repository';
|
import { PaymentRepository } from './repositories/payment.repository';
|
||||||
|
import { InventoryModule } from '../inventory/inventory.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [MikroOrmModule.forFeature([PaymentMethod, Payment, Restaurant]), AuthModule, JwtModule],
|
imports: [MikroOrmModule.forFeature([PaymentMethod, Payment, Restaurant]),
|
||||||
|
AuthModule, JwtModule, InventoryModule],
|
||||||
controllers: [PaymentsController],
|
controllers: [PaymentsController],
|
||||||
providers: [PaymentsService, PaymentMethodService, PaymentMethodRepository, PaymentRepository, ZarinpalGateway, GatewayManager],
|
providers: [PaymentsService, PaymentMethodService, PaymentMethodRepository,
|
||||||
|
PaymentRepository, ZarinpalGateway, GatewayManager],
|
||||||
exports: [
|
exports: [
|
||||||
PaymentMethodRepository,
|
PaymentMethodRepository,
|
||||||
PaymentRepository,
|
PaymentRepository,
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { PaymentGatewayEnum, PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment';
|
import { PaymentGatewayEnum, PaymentMethodEnum, PaymentStatusEnum } from '../interface/payment';
|
||||||
import { Payment } from '../entities/payment.entity';
|
import { Payment } from '../entities/payment.entity';
|
||||||
import { EntityManager } from '@mikro-orm/core';
|
import { EntityManager } from '@mikro-orm/postgresql';
|
||||||
import { Order } from '../../orders/entities/order.entity';
|
import { Order } from '../../orders/entities/order.entity';
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
import { GatewayManager } from './gateway.manager';
|
import { GatewayManager } from './gateway.manager';
|
||||||
import { UserWallet } from 'src/modules/users/entities/user-wallet.entity';
|
import { UserWallet } from 'src/modules/users/entities/user-wallet.entity';
|
||||||
import { OrderPaymentContext } from '../interface/payment';
|
import { OrderPaymentContext } from '../interface/payment';
|
||||||
|
import { InventoryService } from 'src/modules/inventory/inventory.service';
|
||||||
|
import { OrderStatus } from 'src/modules/orders/interface/order.interface';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PaymentsService {
|
export class PaymentsService {
|
||||||
@@ -15,6 +17,7 @@ export class PaymentsService {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly em: EntityManager,
|
private readonly em: EntityManager,
|
||||||
private readonly gatewayManager: GatewayManager,
|
private readonly gatewayManager: GatewayManager,
|
||||||
|
private readonly inventoryService: InventoryService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
async payOrder(
|
async payOrder(
|
||||||
@@ -201,16 +204,12 @@ export class PaymentsService {
|
|||||||
payment.verifyResponse = result.raw;
|
payment.verifyResponse = result.raw;
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
payment.status = PaymentStatusEnum.Failed;
|
this.failPayment(payment);
|
||||||
payment.failedAt = new Date();
|
return payment;
|
||||||
payment.order.paymentStatus = PaymentStatusEnum.Failed;
|
|
||||||
} else {
|
|
||||||
payment.status = PaymentStatusEnum.Paid;
|
|
||||||
payment.referenceId = result.referenceId;
|
|
||||||
payment.cardPan = result.cardPan;
|
|
||||||
payment.paidAt = new Date();
|
|
||||||
payment.order.paymentStatus = PaymentStatusEnum.Paid;
|
|
||||||
}
|
}
|
||||||
|
this.markPaid(payment);
|
||||||
|
this.confirmOrder(payment.order);
|
||||||
|
await this.confirmStock(orderId, em);
|
||||||
|
|
||||||
await em.flush();
|
await em.flush();
|
||||||
return payment;
|
return payment;
|
||||||
@@ -225,6 +224,28 @@ export class PaymentsService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private markPaid(payment: Payment) {
|
||||||
|
payment.status = PaymentStatusEnum.Paid;
|
||||||
|
payment.paidAt = new Date();
|
||||||
|
payment.referenceId = payment.referenceId;
|
||||||
|
payment.order.paymentStatus = PaymentStatusEnum.Paid;
|
||||||
|
}
|
||||||
|
|
||||||
|
private failPayment(payment: Payment) {
|
||||||
|
payment.status = PaymentStatusEnum.Failed;
|
||||||
|
payment.failedAt = new Date();
|
||||||
|
payment.order.paymentStatus = PaymentStatusEnum.Failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private confirmOrder(order: Order) {
|
||||||
|
order.status = OrderStatus.PAID;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async confirmStock(orderId: string, em: EntityManager) {
|
||||||
|
await this.inventoryService.confirmReservationByOrderId(em, orderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private async getOrCreateLatestPendingPayment(
|
private async getOrCreateLatestPendingPayment(
|
||||||
orderId: string,
|
orderId: string,
|
||||||
params: {
|
params: {
|
||||||
|
|||||||
Reference in New Issue
Block a user