diff --git a/src/modules/orders/orders.service.ts b/src/modules/orders/orders.service.ts index ca9db06..bf028f1 100644 --- a/src/modules/orders/orders.service.ts +++ b/src/modules/orders/orders.service.ts @@ -280,9 +280,14 @@ export class OrdersService { if (order.status !== OrderStatus.Pending) { throw new BadRequestException('Order must be pending before accepting'); } + // Wallet payments should be paid before accepting (like Online) if (order.paymentMethod?.method === PaymentMethodEnum.Online && order.paymentStatus !== PaymentStatusEnum.Paid) { throw new BadRequestException('Order must be paid online before accepting'); } + + // Cash, CardOnDelivery, and Wallet can be accepted even if payment status is pending + // (they're paid on delivery or already deducted) + order.status = OrderStatus.Confirmed; await this.em.persistAndFlush(order); return order; diff --git a/src/modules/payments/services/payments.service.ts b/src/modules/payments/services/payments.service.ts index b00e63e..905a887 100644 --- a/src/modules/payments/services/payments.service.ts +++ b/src/modules/payments/services/payments.service.ts @@ -6,6 +6,7 @@ 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'; +import { User } from '../../users/entities/user.entity'; @Injectable() export class PaymentsService { @@ -57,7 +58,20 @@ export class PaymentsService { // } async startPayment(orderId: string): Promise<{ paymentUrl: string | null }> { - const { amount, method, restaurantDomain, gateway, merchantId } = await this.validateOrder(orderId); + const { amount, method, restaurantDomain, gateway, merchantId, user } = await this.validateOrder(orderId); + + // Handle Wallet payment immediately + if (method === PaymentMethodEnum.Wallet) { + // Check wallet balance + if (user.wallet < amount) { + throw new BadRequestException('Insufficient wallet balance'); + } + + // Deduct from wallet and create payment record + const payment = await this.processWalletPayment(orderId, amount, user); + + return { paymentUrl: null }; // No redirect needed for wallet + } const { authority } = await this.createPayment(restaurantDomain, { amount, @@ -73,7 +87,7 @@ export class PaymentsService { } private async validateOrder(orderId: string) { - const order = await this.em.findOne(Order, { id: orderId }); + const order = await this.em.findOne(Order, { id: orderId }, { populate: ['user', 'paymentMethod'] }); if (!order) { throw new NotFoundException('Order not found'); } @@ -94,11 +108,12 @@ export class PaymentsService { throw new BadRequestException('Merchant ID is required for online payments'); } const merchantId = paymentMethod.merchantId; + const user = order.user; // Create payment record and save authority const restaurantDomain = paymentMethod.restaurant.domain; const gateway = paymentMethod.gateway; const method = paymentMethod.method; - return { amount, method, restaurantDomain, gateway, merchantId }; + return { amount, method, restaurantDomain, gateway, merchantId, user }; } async createPayment(domain: string, dto: CreatePaymentDto) { @@ -125,6 +140,47 @@ export class PaymentsService { return { authority, payment }; } + private async processWalletPayment(orderId: string, amount: number, user: User): Promise { + return this.em.transactional(async em => { + // Reload user to get latest wallet balance + const freshUser = await em.findOne(User, { id: user.id }); + if (!freshUser) { + throw new NotFoundException('User not found'); + } + + // Double-check balance + if (freshUser.wallet < amount) { + throw new BadRequestException('Insufficient wallet balance'); + } + + // Deduct from wallet + freshUser.wallet -= amount; + em.persist(freshUser); + + // Create payment record + const payment = em.create(Payment, { + amount, + authority: null, + order: em.getReference(Order, orderId), + gateway: null, + status: PaymentStatusEnum.Paid, + paidAt: new Date(), + } as RequiredEntityData); + + em.persist(payment); + + // Update order payment status + const order = await em.findOne(Order, { id: orderId }); + if (order) { + order.paymentStatus = PaymentStatusEnum.Paid; + em.persist(order); + } + + await em.flush(); + return payment; + }); + } + async verifyPayment(authority: string, orderId: string): Promise { // Find payment by authority and orderId const payment = await this.em.findOne( @@ -151,6 +207,14 @@ export class PaymentsService { throw new NotFoundException('Payment method not found'); } + // For Wallet payments, they're already paid during startPayment + if (paymentMethod.method === PaymentMethodEnum.Wallet) { + if (payment.status === PaymentStatusEnum.Paid) { + return payment; + } + throw new BadRequestException('Wallet payment was not processed correctly'); + } + // For non-online payments, mark as paid directly // if (paymentMethod.method !== PaymentMethodEnum.Online) { // payment.status = PaymentStatusEnum.Paid; diff --git a/src/seeders/data/payment-methods.data.ts b/src/seeders/data/payment-methods.data.ts index 47d9156..b38df13 100644 --- a/src/seeders/data/payment-methods.data.ts +++ b/src/seeders/data/payment-methods.data.ts @@ -36,4 +36,12 @@ export const paymentMethodsData: PaymentMethodData[] = [ gateway: PaymentGatewayEnum.ZarinPal, merchantId: 'b6f55bd0-6eae-4045-aeb8-07d084fa8f73', }, + { + title: 'کیف پول', + method: PaymentMethodEnum.Wallet, + description: 'پرداخت از کیف پول', + enabled: true, + order: 4, + gateway: null, + }, ];