From b9ef611a76f93695f7449b87c6f5bf928da84076 Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Mon, 29 Dec 2025 10:43:42 +0330 Subject: [PATCH] create wallet transaction after order created --- .../orders/listeners/order.listeners.ts | 38 +++++-- src/modules/orders/orders.module.ts | 6 +- src/modules/users/interface/wallet.ts | 1 + src/modules/users/providers/user.service.ts | 98 +++++++++++-------- src/modules/users/providers/wallet.service.ts | 34 +++---- 5 files changed, 107 insertions(+), 70 deletions(-) diff --git a/src/modules/orders/listeners/order.listeners.ts b/src/modules/orders/listeners/order.listeners.ts index 8b6b173..0a3a6fb 100644 --- a/src/modules/orders/listeners/order.listeners.ts +++ b/src/modules/orders/listeners/order.listeners.ts @@ -9,22 +9,25 @@ import { ConfigService } from '@nestjs/config'; import { OrderRepository } from '../repositories/order.repository'; import { OrderStatus } from '../interface/order.interface'; import { PaymentMethodEnum } from 'src/modules/payments/interface/payment'; +import { UserService } from 'src/modules/users/providers/user.service'; +import { WalletTransactionReason, WalletTransactionType } from 'src/modules/users/interface/wallet'; @Injectable() export class OrderListeners { private readonly logger = new Logger(OrderListeners.name); private orderCreatedSmsTemplateId: string; private orderStatusChangedSmsTemplateId: string; - private orderCompletedSmsTemplateId: string; + // private orderCompletedSmsTemplateId: string; constructor( private readonly adminService: AdminRepository, private readonly OrderRepository: OrderRepository, private readonly notificationService: NotificationService, private readonly configService: ConfigService, + private readonly userService: UserService, ) { this.orderCreatedSmsTemplateId = this.configService.get('SMS_PATTERN_ORDER_CREATED') ?? '123'; this.orderStatusChangedSmsTemplateId = this.configService.get('SMS_PATTERN_ORDER_STATUS_CHANGE') ?? '123'; - this.orderCompletedSmsTemplateId = this.configService.get('SMS_PATTERN_ORDER_STATUS_COMPLETED') ?? '123'; + // this.orderCompletedSmsTemplateId = this.configService.get('SMS_PATTERN_ORDER_STATUS_COMPLETED') ?? '123'; } private getStatusFarsi(status: OrderStatus): string { @@ -52,6 +55,8 @@ export class OrderListeners { if (order?.paymentMethod.method === PaymentMethodEnum.Online) { return; } + + // get admnin os restuaraant that have order permissuins const admins = await this.adminService.findAdminsWithPermission(event.restaurantId, Permission.MANAGE_ORDERS); const recipients = admins.map(admin => ({ @@ -99,20 +104,39 @@ export class OrderListeners { this.logger.log( `Order status changed event received: ${event.orderId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`, ); - + //TODO : REFACTOR to use queue or other way to handle this const recipients = [ { userId: event.userId, }, ]; - if(event.newStatus === OrderStatus.COMPLETED) { + if (event.newStatus === OrderStatus.COMPLETED) { + + if (!event?.userId) { + this.logger.log( + `User not found for order: ${event.orderId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`, + ); + } + const order = await this.OrderRepository.findOne(event.orderId); + if (!order) { + this.logger.log( + `Order not found for order: ${event.orderId} for restaurant: ${event.restaurantId} and order number: ${event.orderNumber}`, + ); + return; + } + this.userService.createWalletTransaction(event.userId, event.restaurantId, { + amount: order.subTotal, + type: WalletTransactionType.CREDIT, + reason: WalletTransactionReason.ORDER_COMPLETED_DEPOSIT, + }); + await this.notificationService.sendNotification({ restaurantId: event.restaurantId, message: { title: NotifTitleEnum.ORDER_STATUS_CHANGED, content: `لطفابرای ثبت نظر سفارش ${event.orderNumber} به اپ مراجعه کنید`, sms: { - templateId: this.orderCreatedSmsTemplateId , + templateId: this.orderCreatedSmsTemplateId, parameters: { orderNumber: event.orderNumber, }, @@ -132,7 +156,7 @@ export class OrderListeners { priority: 1, }, }); - }else{ + } else { await this.notificationService.sendNotification({ restaurantId: event.restaurantId, message: { @@ -169,4 +193,4 @@ export class OrderListeners { } } - } +} diff --git a/src/modules/orders/orders.module.ts b/src/modules/orders/orders.module.ts index 883e619..6208faf 100644 --- a/src/modules/orders/orders.module.ts +++ b/src/modules/orders/orders.module.ts @@ -20,7 +20,8 @@ import { OrdersCrone } from './crone/order.crone'; import { AdminModule } from '../admin/admin.module'; import { NotificationsModule } from '../notifications/notifications.module'; import { InventoryModule } from '../inventory/inventory.module'; - +import { UserModule } from '../users/user.module'; + @Module({ imports: [ MikroOrmModule.forFeature([Order, OrderItem, User, Restaurant, Food, UserAddress, PaymentMethod]), @@ -31,7 +32,8 @@ import { InventoryModule } from '../inventory/inventory.module'; JwtModule, AdminModule, NotificationsModule, - InventoryModule + InventoryModule, + forwardRef(() => UserModule) ], controllers: [OrdersController], providers: [OrdersService, OrderRepository, OrderListeners, OrdersCrone], diff --git a/src/modules/users/interface/wallet.ts b/src/modules/users/interface/wallet.ts index ad91490..b6383d2 100644 --- a/src/modules/users/interface/wallet.ts +++ b/src/modules/users/interface/wallet.ts @@ -10,4 +10,5 @@ export enum WalletTransactionReason { CONVERT_SCORE_TO_WALLET = 'convert_score_to_wallet', WITHDRAW = 'withdraw', DEPOSIT = 'deposit', + ORDER_COMPLETED_DEPOSIT = 'order_completed_deposit', } \ No newline at end of file diff --git a/src/modules/users/providers/user.service.ts b/src/modules/users/providers/user.service.ts index a6e3292..4662cc8 100644 --- a/src/modules/users/providers/user.service.ts +++ b/src/modules/users/providers/user.service.ts @@ -2,6 +2,7 @@ import { Injectable, NotFoundException, BadRequestException } from '@nestjs/comm import { FilterQuery, RequiredEntityData } from '@mikro-orm/core'; import { User } from '../entities/user.entity'; import { UserAddress } from '../entities/user-address.entity'; +import { Restaurant } from 'src/modules/restaurants/entities/restaurant.entity'; import { EntityManager } from '@mikro-orm/postgresql'; import { UpdateUserDto } from '../dto/update-user.dto'; import { FindUsersDto } from '../dto/find-user.dto'; @@ -16,6 +17,7 @@ import { RestRepository } from 'src/modules/restaurants/repositories/rest.reposi import { UserWallet } from '../entities/user-wallet.entity'; import { UserWalletRepository } from '../repositories/user-wallet.repository'; import { WalletService } from './wallet.service'; +import { WalletTransactionReason, WalletTransactionType } from '../interface/wallet'; @Injectable() export class UserService { @@ -298,54 +300,62 @@ export class UserService { } async convertScoreToWallet(userId: string, restId: string) { - const user = await this.userRepository.findOne({ id: userId }); - if (!user) { - throw new NotFoundException(`User with ID ${userId} not found.`); - } - const restaurant = await this.restaurantRepository.findOne({ id: restId }); - if (!restaurant) { - throw new NotFoundException(`Restaurant with ID ${restId} not found.`); - } - let userWallet = await this.userWalletRepository.findOne({ user: { id: userId }, restaurant: { id: restId } }); - if (!userWallet) { - userWallet = this.userWalletRepository.create({ user: user, restaurant, wallet: 0, points: 0 }); - await this.em.persistAndFlush(userWallet); - } - // Determine how many points to convert - const pointsToConvert = userWallet.points; + return this.em.transactional(async em => { + const user = await em.findOne(User, { id: userId }); + if (!user) { + throw new NotFoundException(`User with ID ${userId} not found.`); + } + const restaurant = await em.findOne(Restaurant, { id: restId }); + if (!restaurant) { + throw new NotFoundException(`Restaurant with ID ${restId} not found.`); + } + let userWallet = await em.findOne(UserWallet, { user: { id: userId }, restaurant: { id: restId } }); + if (!userWallet) { + userWallet = em.create(UserWallet, { user: user, restaurant, wallet: 0, points: 0 }); + await em.persistAndFlush(userWallet); + } + // Determine how many points to convert + const pointsToConvert = userWallet.points; - // Validate points to convert - if (pointsToConvert <= 0) { - throw new BadRequestException('Points to convert must be greater than 0.'); - } + // Validate points to convert + if (pointsToConvert <= 0) { + throw new BadRequestException('Points to convert must be greater than 0.'); + } - if (userWallet.points < pointsToConvert) { - throw new BadRequestException( - `Insufficient points. Available: ${userWallet.points}, Requested: ${pointsToConvert}.`, - ); - } + if (userWallet.points < pointsToConvert) { + throw new BadRequestException( + `Insufficient points. Available: ${userWallet.points}, Requested: ${pointsToConvert}.`, + ); + } - if (!restaurant.score) { - throw new BadRequestException('Restaurant score not found.'); - } - if (!restaurant.score.purchaseScore) { - throw new BadRequestException('Restaurant purchase score not found.'); - } - if (!restaurant.score.purchaseAmount) { - throw new BadRequestException('Restaurant purchase amount not found.'); - } - const walletIncreaseAmount = - (Number(pointsToConvert) * Number(restaurant.score.purchaseAmount)) / Number(restaurant.score.purchaseScore); - // Convert points to wallet (1:1 ratio - can be made configurable) + if (!restaurant.score) { + throw new BadRequestException('Restaurant score not found.'); + } + if (!restaurant.score.purchaseScore) { + throw new BadRequestException('Restaurant purchase score not found.'); + } + if (!restaurant.score.purchaseAmount) { + throw new BadRequestException('Restaurant purchase amount not found.'); + } + const walletIncreaseAmount = + (Number(pointsToConvert) * Number(restaurant.score.purchaseAmount)) / Number(restaurant.score.purchaseScore); + // Convert points to wallet (1:1 ratio - can be made configurable) - // Update user's wallet and points - userWallet.wallet = (userWallet.wallet || 0) + walletIncreaseAmount; - userWallet.points = (userWallet.points || 0) - pointsToConvert; + this.walletService.createTransaction(em, userId, restId, { + amount: walletIncreaseAmount, + type: WalletTransactionType.CREDIT, + reason: WalletTransactionReason.CONVERT_SCORE_TO_WALLET, + }); + // Update user's wallet and points + userWallet.wallet = (userWallet.wallet || 0) + walletIncreaseAmount; + userWallet.points = (userWallet.points || 0) - pointsToConvert; - await this.em.flush(); + await em.flush(); - return user; + return user; + }); } + getUserWallet(userId: string, restId: string): Promise { return this.walletService.getUserWallet(userId, restId); } @@ -354,8 +364,10 @@ export class UserService { return this.walletService.getUserWalletTransactions(userId, restId, dto); } - createWalletTransaction(userId: string, restId: string, dto: CreateWalletTransactionDto) { - return this.walletService.createTransaction(userId, restId, dto); + async createWalletTransaction(userId: string, restId: string, dto: CreateWalletTransactionDto) { + return this.em.transactional(async em => { + return this.walletService.createTransaction(em, userId, restId, dto); + }); } } diff --git a/src/modules/users/providers/wallet.service.ts b/src/modules/users/providers/wallet.service.ts index 5682812..18fd4ff 100644 --- a/src/modules/users/providers/wallet.service.ts +++ b/src/modules/users/providers/wallet.service.ts @@ -113,6 +113,7 @@ export class WalletService { } async createTransaction( + em: EntityManager, userId: string, restId: string, dto: CreateWalletTransactionDto, @@ -125,7 +126,7 @@ export class WalletService { } // Find the user's wallet for this restaurant - const userWallet = await this.userWalletRepository.findOne({ + const userWallet = await em.findOne(UserWallet, { user: { id: userId }, restaurant: { id: restId }, }); @@ -151,24 +152,21 @@ export class WalletService { throw new BadRequestException(`Invalid transaction type: ${type}`); } - // Use transaction to ensure consistency - return await this.em.transactional(async (em) => { - // Update wallet balance - userWallet.wallet = newBalance; - await em.persistAndFlush(userWallet); + // Update wallet balance + userWallet.wallet = newBalance; + await em.persistAndFlush(userWallet); - // Create transaction record - const transaction = em.create(WalletTransaction, { - userWallet, - amount, - balance: newBalance, - type, - reason, - }); - - await em.persistAndFlush(transaction); - - return transaction; + // Create transaction record + const transaction = em.create(WalletTransaction, { + userWallet, + amount, + balance: newBalance, + type, + reason, }); + + await em.persistAndFlush(transaction); + + return transaction; } }