wallet charge request
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
import { Migration } from '@mikro-orm/migrations';
|
||||||
|
|
||||||
|
export class Migration20260704130000_removeRestaurantCreditColumn extends Migration {
|
||||||
|
|
||||||
|
override async up(): Promise<void> {
|
||||||
|
this.addSql(`alter table "restaurants" drop column if exists "credit";`);
|
||||||
|
}
|
||||||
|
|
||||||
|
override async down(): Promise<void> {
|
||||||
|
this.addSql(`alter table "restaurants" add column "credit" numeric(10,0) not null default 0;`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ import { CacheResponse } from 'src/common/decorators/cache-response.decorator';
|
|||||||
import { CacheKeyPrefixes } from 'src/common/constants/cache-keys.constant';
|
import { CacheKeyPrefixes } from 'src/common/constants/cache-keys.constant';
|
||||||
import { UpdateRestaurantBgDto } from '../dto/update-restaurant-bg.dto';
|
import { UpdateRestaurantBgDto } from '../dto/update-restaurant-bg.dto';
|
||||||
import { ChargeRequestDto } from '../dto/charge-request.dto';
|
import { ChargeRequestDto } from '../dto/charge-request.dto';
|
||||||
|
import { FinishChargeRequestDto } from '../dto/finish-charge-request.dto';
|
||||||
|
|
||||||
@ApiTags('restaurants')
|
@ApiTags('restaurants')
|
||||||
@Controller()
|
@Controller()
|
||||||
@@ -167,6 +168,15 @@ export class RestaurantsController {
|
|||||||
return this.restaurantsService.upgradeSubscription(subscriptionId, upgradeSubscriptionDto);
|
return this.restaurantsService.upgradeSubscription(subscriptionId, upgradeSubscriptionDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(SuperAdminAuthGuard)
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@ApiOperation({ summary: 'Finalize wallet charge after invoice payment' })
|
||||||
|
@ApiBody({ type: FinishChargeRequestDto })
|
||||||
|
@Post('super-admin/restaurants/charge-request/finish')
|
||||||
|
finishChargeRequest(@Body() dto: FinishChargeRequestDto) {
|
||||||
|
return this.restaurantsService.finishChargeRequest(dto);
|
||||||
|
}
|
||||||
|
|
||||||
@UseGuards(SuperAdminAuthGuard)
|
@UseGuards(SuperAdminAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@ApiOperation({ summary: 'Hard delete a restaurant and all related data' })
|
@ApiOperation({ summary: 'Hard delete a restaurant and all related data' })
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class FinishChargeRequestDto {
|
||||||
|
@ApiProperty({ description: 'External invoice ID returned after payment' })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
invoiceId!: string;
|
||||||
|
}
|
||||||
@@ -77,9 +77,6 @@ export class Restaurant extends BaseEntity {
|
|||||||
@Property({ type: 'decimal', default: 0 })
|
@Property({ type: 'decimal', default: 0 })
|
||||||
vat?: number = 0;
|
vat?: number = 0;
|
||||||
|
|
||||||
@Property({ type: 'decimal', precision: 10, scale: 0, default: 0 })
|
|
||||||
credit?: number = 0;
|
|
||||||
|
|
||||||
@Property()
|
@Property()
|
||||||
domain!: string;
|
domain!: string;
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import { RestaurantCreditRequestStatus } from '../interface/restaurant-credit-request.interface';
|
import { RestaurantCreditRequestStatus } from '../interface/restaurant-credit-request.interface';
|
||||||
import { CreateExternalInvoice } from '../interface/external-invoice.interface';
|
import { CreateExternalInvoice } from '../interface/external-invoice.interface';
|
||||||
import { ChargeRequestDto } from '../dto/charge-request.dto';
|
import { ChargeRequestDto } from '../dto/charge-request.dto';
|
||||||
|
import { FinishChargeRequestDto } from '../dto/finish-charge-request.dto';
|
||||||
import { RestMessage } from 'src/common/enums/message.enum';
|
import { RestMessage } from 'src/common/enums/message.enum';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -26,6 +27,16 @@ export class RestaurantWalletService {
|
|||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
async getCurrentBalance(restaurantId: string, em: EntityManager = this.defaultEm): Promise<number> {
|
||||||
|
const latestTransaction = await em.findOne(
|
||||||
|
RestaurantCreditTransaction,
|
||||||
|
{ restaurant: { id: restaurantId } },
|
||||||
|
{ orderBy: { createdAt: 'desc' } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return Number(latestTransaction?.balance ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
async createTransaction(params: {
|
async createTransaction(params: {
|
||||||
restaurant: Restaurant;
|
restaurant: Restaurant;
|
||||||
amount: number;
|
amount: number;
|
||||||
@@ -40,15 +51,13 @@ export class RestaurantWalletService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentCredit = Number(restaurant.credit ?? 0);
|
const currentBalance = await this.getCurrentBalance(restaurant.id, em);
|
||||||
if (type === RestaurantCreditTransactionType.DEBIT && currentCredit < amount) {
|
if (type === RestaurantCreditTransactionType.DEBIT && currentBalance < amount) {
|
||||||
throw new BadRequestException(insufficientBalanceMessage ?? 'اعتبار کیف پول رستوران کافی نیست');
|
throw new BadRequestException(insufficientBalanceMessage ?? 'اعتبار کیف پول رستوران کافی نیست');
|
||||||
}
|
}
|
||||||
|
|
||||||
const newBalance =
|
const newBalance =
|
||||||
type === RestaurantCreditTransactionType.DEBIT ? currentCredit - amount : currentCredit + amount;
|
type === RestaurantCreditTransactionType.DEBIT ? currentBalance - amount : currentBalance + amount;
|
||||||
|
|
||||||
restaurant.credit = newBalance;
|
|
||||||
|
|
||||||
const creditTransaction = em.create(RestaurantCreditTransaction, {
|
const creditTransaction = em.create(RestaurantCreditTransaction, {
|
||||||
restaurant,
|
restaurant,
|
||||||
@@ -58,7 +67,7 @@ export class RestaurantWalletService {
|
|||||||
reason,
|
reason,
|
||||||
});
|
});
|
||||||
|
|
||||||
await em.persistAndFlush([restaurant, creditTransaction]);
|
await em.persistAndFlush(creditTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
async chargeRequest(restId: string, dto: ChargeRequestDto): Promise<RestaurantCreditRequest> {
|
async chargeRequest(restId: string, dto: ChargeRequestDto): Promise<RestaurantCreditRequest> {
|
||||||
@@ -124,4 +133,39 @@ export class RestaurantWalletService {
|
|||||||
|
|
||||||
return creditRequest;
|
return creditRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async finishChargeRequest(dto: FinishChargeRequestDto): Promise<RestaurantCreditRequest> {
|
||||||
|
return this.defaultEm.transactional(async (em) => {
|
||||||
|
const creditRequest = await em.findOne(
|
||||||
|
RestaurantCreditRequest,
|
||||||
|
{ invoiceId: dto.invoiceId },
|
||||||
|
{ populate: ['restaurant'] },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!creditRequest) {
|
||||||
|
throw new NotFoundException(RestMessage.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (creditRequest.status === RestaurantCreditRequestStatus.PAID) {
|
||||||
|
return creditRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (creditRequest.status !== RestaurantCreditRequestStatus.PENDING) {
|
||||||
|
throw new BadRequestException('درخواست شارژ کیف پول قابل پردازش نیست');
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.createTransaction({
|
||||||
|
restaurant: creditRequest.restaurant,
|
||||||
|
amount: Number(creditRequest.amount),
|
||||||
|
type: RestaurantCreditTransactionType.CREDIT,
|
||||||
|
reason: RestaurantCreditTransactionReason.DEPOSIT,
|
||||||
|
em,
|
||||||
|
});
|
||||||
|
|
||||||
|
creditRequest.status = RestaurantCreditRequestStatus.PAID;
|
||||||
|
await em.flush();
|
||||||
|
|
||||||
|
return creditRequest;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { BackgroundRepository } from '../repositories/background.repository';
|
|||||||
import { ActiveRestaurantListItemDto } from '../dto/active-restaurant-list-item.dto';
|
import { ActiveRestaurantListItemDto } from '../dto/active-restaurant-list-item.dto';
|
||||||
import { RestaurantWalletService } from './restaurant-wallet.service';
|
import { RestaurantWalletService } from './restaurant-wallet.service';
|
||||||
import { ChargeRequestDto } from '../dto/charge-request.dto';
|
import { ChargeRequestDto } from '../dto/charge-request.dto';
|
||||||
|
import { FinishChargeRequestDto } from '../dto/finish-charge-request.dto';
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -333,6 +334,10 @@ export class RestaurantsService {
|
|||||||
return this.restaurantWalletService.chargeRequest(restId, dto);
|
return this.restaurantWalletService.chargeRequest(restId, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
finishChargeRequest(dto: FinishChargeRequestDto) {
|
||||||
|
return this.restaurantWalletService.finishChargeRequest(dto);
|
||||||
|
}
|
||||||
|
|
||||||
async updateBackground(id: string, dto: UpdateRestaurantBgDto): Promise<Restaurant> {
|
async updateBackground(id: string, dto: UpdateRestaurantBgDto): Promise<Restaurant> {
|
||||||
const restaurant = await this.restRepository.findOne({ id });
|
const restaurant = await this.restRepository.findOne({ id });
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user