This commit is contained in:
2026-07-03 17:18:45 +03:30
parent 5042cadb32
commit 74d5b7a768
20 changed files with 1032 additions and 19 deletions
@@ -20,6 +20,8 @@ import { AllowWithoutSubscription } from '../auth/decorators/allow-without-subsc
import { PurchaseSmsPackageDto } from './dto/purchase-sms-package.dto';
import { PurchaseSubscriptionDto } from './dto/purchase-subscription.dto';
import { SalonSubscriptionsService } from './salon-subscriptions.service';
import { PaymentsService } from '../payments/payments.service';
import { PaymentUrlResponseDto } from '../payments/dto/payment-url-response.dto';
import { SWAGGER_JWT_AUTH } from '../swagger/swagger.setup';
import {
ApiStandardController,
@@ -37,18 +39,19 @@ import { SalonSubscription } from './entities/salon-subscription.entity';
export class SalonSubscriptionsController {
constructor(
private readonly salonSubscriptionsService: SalonSubscriptionsService,
private readonly paymentsService: PaymentsService,
) {}
@Roles(UserRole.ADMIN, UserRole.SALON)
@Permissions(PermissionCode.SUBSCRIPTIONS_WRITE)
@AllowWithoutSubscription()
@ApiStandardOkResponse(SalonSubscription)
@ApiStandardOkResponse(PaymentUrlResponseDto)
@Post('purchase')
purchaseSubscription(
@Body() dto: PurchaseSubscriptionDto,
@CurrentUser() user: AuthUser,
) {
return this.salonSubscriptionsService.purchaseSubscription(dto, user);
return this.paymentsService.initiateSubscriptionPayment(dto, user);
}
@Roles(UserRole.ADMIN, UserRole.SALON)
@@ -11,7 +11,6 @@ import { SubscriptionPolicy } from '../authorization/policies/subscription.polic
import { SalonsService } from '../salons/salons.service';
import { SMS_ROLLOVER_GRACE_DAYS } from './constants/subscription.constants';
import { PurchaseSmsPackageDto } from './dto/purchase-sms-package.dto';
import { PurchaseSubscriptionDto } from './dto/purchase-subscription.dto';
import { SalonSmsPurchase } from './entities/salon-sms-purchase.entity';
import { SalonSubscription } from './entities/salon-subscription.entity';
import { SalonSubscriptionStatus } from './enums/salon-subscription-status.enum';
@@ -32,27 +31,22 @@ export class SalonSubscriptionsService {
private readonly subscriptionPolicy: SubscriptionPolicy,
) {}
async purchaseSubscription(
dto: PurchaseSubscriptionDto,
user: AuthUser,
async fulfillSubscriptionPurchase(
salonId: string,
planId: string,
): Promise<SalonSubscription> {
const salon = await this.salonsService.findOne(dto.salonId);
if (!this.subscriptionPolicy.canPurchase(user, salon)) {
throw new ForbiddenException();
}
const plan = await this.subscriptionPlansService.findActiveById(dto.planId);
const activeSubscription = await this.findActiveSubscription(dto.salonId);
const activeSubscription = await this.findActiveSubscription(salonId);
if (activeSubscription) {
throw new BadRequestException('Salon already has an active subscription');
}
const plan = await this.subscriptionPlansService.findActiveById(planId);
const now = new Date();
const rolloverSms = await this.calculateSmsRollover(dto.salonId, now);
const rolloverSms = await this.calculateSmsRollover(salonId, now);
const freeSmsGranted = plan.freeSmsCount + rolloverSms;
const subscription = this.subscriptionsRepository.create({
salonId: dto.salonId,
salonId,
planId: plan.id,
status: SalonSubscriptionStatus.ACTIVE,
startDate: now,
@@ -63,7 +57,28 @@ export class SalonSubscriptionsService {
});
const saved = await this.subscriptionsRepository.save(subscription);
return this.findOne(saved.id, user);
return this.subscriptionsRepository.findOneOrFail({
where: { id: saved.id },
relations: { plan: true, salon: true },
});
}
async validateSubscriptionPurchase(
salonId: string,
planId: string,
user: AuthUser,
): Promise<void> {
const salon = await this.salonsService.findOne(salonId);
if (!this.subscriptionPolicy.canPurchase(user, salon)) {
throw new ForbiddenException();
}
await this.subscriptionPlansService.findActiveById(planId);
const activeSubscription = await this.findActiveSubscription(salonId);
if (activeSubscription) {
throw new BadRequestException('Salon already has an active subscription');
}
}
async purchaseSmsPackage(
+4 -2
View File
@@ -1,8 +1,9 @@
import { Module } from '@nestjs/common';
import { Module, forwardRef } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthGuardsModule } from '../auth/auth-guards.module';
import { AuthorizationModule } from '../authorization/authorization.module';
import { PaymentsModule } from '../payments/payments.module';
import { SalonsModule } from '../salons/salons.module';
import { SalonSmsPurchase } from './entities/salon-sms-purchase.entity';
import { SalonSubscription } from './entities/salon-subscription.entity';
@@ -27,6 +28,7 @@ import { SubscriptionPlansService } from './subscription-plans.service';
SalonsModule,
AuthorizationModule,
AuthGuardsModule,
forwardRef(() => PaymentsModule),
],
controllers: [
SubscriptionPlansController,
@@ -42,6 +44,6 @@ import { SubscriptionPlansService } from './subscription-plans.service';
useClass: ActiveSubscriptionGuard,
},
],
exports: [SalonSubscriptionsService],
exports: [SalonSubscriptionsService, SubscriptionPlansService],
})
export class SubscriptionsModule {}