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) {
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;
@@ -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<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> {
// 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;
+8
View File
@@ -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,
},
];