wallet payment

This commit is contained in:
2025-12-07 16:57:28 +03:30
parent 8b08873bd2
commit dcd9084383
3 changed files with 80 additions and 3 deletions
+5
View File
@@ -280,9 +280,14 @@ export class OrdersService {
if (order.status !== OrderStatus.Pending) { if (order.status !== OrderStatus.Pending) {
throw new BadRequestException('Order must be pending before accepting'); 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) { if (order.paymentMethod?.method === PaymentMethodEnum.Online && order.paymentStatus !== PaymentStatusEnum.Paid) {
throw new BadRequestException('Order must be paid online before accepting'); 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; order.status = OrderStatus.Confirmed;
await this.em.persistAndFlush(order); await this.em.persistAndFlush(order);
return order; return order;
@@ -6,6 +6,7 @@ import { EntityManager, RequiredEntityData } from '@mikro-orm/core';
import { Order } from '../../orders/entities/order.entity'; import { Order } from '../../orders/entities/order.entity';
import { PaymentMethod } from '../entities/payment-method.entity'; import { PaymentMethod } from '../entities/payment-method.entity';
import { CreatePaymentDto } from '../dto/create-payment.dto'; import { CreatePaymentDto } from '../dto/create-payment.dto';
import { User } from '../../users/entities/user.entity';
@Injectable() @Injectable()
export class PaymentsService { export class PaymentsService {
@@ -57,7 +58,20 @@ export class PaymentsService {
// } // }
async startPayment(orderId: string): Promise<{ paymentUrl: string | null }> { 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, { const { authority } = await this.createPayment(restaurantDomain, {
amount, amount,
@@ -73,7 +87,7 @@ export class PaymentsService {
} }
private async validateOrder(orderId: string) { 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) { if (!order) {
throw new NotFoundException('Order not found'); throw new NotFoundException('Order not found');
} }
@@ -94,11 +108,12 @@ export class PaymentsService {
throw new BadRequestException('Merchant ID is required for online payments'); throw new BadRequestException('Merchant ID is required for online payments');
} }
const merchantId = paymentMethod.merchantId; const merchantId = paymentMethod.merchantId;
const user = order.user;
// Create payment record and save authority // Create payment record and save authority
const restaurantDomain = paymentMethod.restaurant.domain; const restaurantDomain = paymentMethod.restaurant.domain;
const gateway = paymentMethod.gateway; const gateway = paymentMethod.gateway;
const method = paymentMethod.method; const method = paymentMethod.method;
return { amount, method, restaurantDomain, gateway, merchantId }; return { amount, method, restaurantDomain, gateway, merchantId, user };
} }
async createPayment(domain: string, dto: CreatePaymentDto) { async createPayment(domain: string, dto: CreatePaymentDto) {
@@ -125,6 +140,47 @@ export class PaymentsService {
return { authority, payment }; return { authority, payment };
} }
private async processWalletPayment(orderId: string, amount: number, user: User): Promise<Payment> {
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<Payment>);
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<Payment> { async verifyPayment(authority: string, orderId: string): Promise<Payment> {
// Find payment by authority and orderId // Find payment by authority and orderId
const payment = await this.em.findOne( const payment = await this.em.findOne(
@@ -151,6 +207,14 @@ export class PaymentsService {
throw new NotFoundException('Payment method not found'); 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 // For non-online payments, mark as paid directly
// if (paymentMethod.method !== PaymentMethodEnum.Online) { // if (paymentMethod.method !== PaymentMethodEnum.Online) {
// payment.status = PaymentStatusEnum.Paid; // payment.status = PaymentStatusEnum.Paid;
+8
View File
@@ -36,4 +36,12 @@ export const paymentMethodsData: PaymentMethodData[] = [
gateway: PaymentGatewayEnum.ZarinPal, gateway: PaymentGatewayEnum.ZarinPal,
merchantId: 'b6f55bd0-6eae-4045-aeb8-07d084fa8f73', merchantId: 'b6f55bd0-6eae-4045-aeb8-07d084fa8f73',
}, },
{
title: 'کیف پول',
method: PaymentMethodEnum.Wallet,
description: 'پرداخت از کیف پول',
enabled: true,
order: 4,
gateway: null,
},
]; ];