fix : extend and upgrade user subscription
This commit is contained in:
@@ -200,6 +200,7 @@ export class RestaurantService {
|
|||||||
|
|
||||||
async getRestaurantSubscription(subscriptionId: string) {
|
async getRestaurantSubscription(subscriptionId: string) {
|
||||||
try {
|
try {
|
||||||
|
console.log('subscriptionId', subscriptionId)
|
||||||
const { data } = await firstValueFrom(
|
const { data } = await firstValueFrom(
|
||||||
this.httpService
|
this.httpService
|
||||||
.get(`${this.config.baseUrl}/super-admin/restaurants/subscription/${subscriptionId}`, {
|
.get(`${this.config.baseUrl}/super-admin/restaurants/subscription/${subscriptionId}`, {
|
||||||
@@ -212,6 +213,7 @@ export class RestaurantService {
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
console.log('data', data)
|
||||||
return data;
|
return data;
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof AxiosError && error.response) {
|
if (error instanceof AxiosError && error.response) {
|
||||||
@@ -378,13 +380,13 @@ export class RestaurantService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async upgradeSubscription(subscriptionId: string, newPlan: string, subscriptionEndDate: string) {
|
async upgradeSubscription(subscriptionId: string, newPlan: 'base' | 'premium', subscriptionEndDate: Date) {
|
||||||
try {
|
try {
|
||||||
const { data } = await firstValueFrom(
|
const { data } = await firstValueFrom(
|
||||||
this.httpService
|
this.httpService
|
||||||
.patch(`${this.config.baseUrl}/super-admin/restaurants/subscription/${subscriptionId}/upgrade`, {
|
.patch(`${this.config.baseUrl}/super-admin/restaurants/subscription/${subscriptionId}/upgrade`, {
|
||||||
plan: newPlan,
|
newPlan,
|
||||||
subscriptionEndDate,
|
subscriptionEndDate
|
||||||
}, {
|
}, {
|
||||||
headers: this.getHeaders(),
|
headers: this.getHeaders(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { Discount } from "../../discounts/entities/discount.entity";
|
|||||||
import { User } from "../../users/entities/user.entity";
|
import { User } from "../../users/entities/user.entity";
|
||||||
import { RecurringPeriodEnum } from "../enums/invoice-recurring-period.enum";
|
import { RecurringPeriodEnum } from "../enums/invoice-recurring-period.enum";
|
||||||
import { InvoiceStatus } from "../enums/invoice-status.enum";
|
import { InvoiceStatus } from "../enums/invoice-status.enum";
|
||||||
|
import { InvoicePurpose } from "../interfaces/external-invoice.interface";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Invoice extends BaseEntity {
|
export class Invoice extends BaseEntity {
|
||||||
@@ -66,7 +67,15 @@ export class Invoice extends BaseEntity {
|
|||||||
@ManyToOne(() => Discount, (discount) => discount.invoices, { nullable: true, onDelete: "RESTRICT" })
|
@ManyToOne(() => Discount, (discount) => discount.invoices, { nullable: true, onDelete: "RESTRICT" })
|
||||||
discount: Discount | null;
|
discount: Discount | null;
|
||||||
|
|
||||||
|
@Column({
|
||||||
|
type: 'enum',
|
||||||
|
enum: InvoicePurpose,
|
||||||
|
nullable: false,
|
||||||
|
})
|
||||||
|
purpose: InvoicePurpose;
|
||||||
|
|
||||||
get isOverdue(): boolean {
|
get isOverdue(): boolean {
|
||||||
return this.status === InvoiceStatus.PENDING && new Date() > this.dueDate;
|
return this.status === InvoiceStatus.PENDING && new Date() > this.dueDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,3 +9,10 @@ export interface IExternalInvoiceJob {
|
|||||||
timestamp: string;
|
timestamp: string;
|
||||||
// callbackData?: Record<string, unknown>;
|
// callbackData?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export enum InvoicePurpose {
|
||||||
|
NEW = 'new',
|
||||||
|
RENEW = 'renew',
|
||||||
|
UPGRADE = 'upgrade',
|
||||||
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import { UpdateInvoiceDto } from "../DTO/update-invoice.dto";
|
|||||||
import { Invoice } from "../entities/invoice.entity";
|
import { Invoice } from "../entities/invoice.entity";
|
||||||
import { RecurringPeriodEnum } from "../enums/invoice-recurring-period.enum";
|
import { RecurringPeriodEnum } from "../enums/invoice-recurring-period.enum";
|
||||||
import { InvoiceStatus } from "../enums/invoice-status.enum";
|
import { InvoiceStatus } from "../enums/invoice-status.enum";
|
||||||
import { IExternalInvoiceJob } from "../interfaces/external-invoice.interface";
|
import { IExternalInvoiceJob, InvoicePurpose } from "../interfaces/external-invoice.interface";
|
||||||
import { InvoiceItemsRepository } from "../repositories/invoice-items.repository";
|
import { InvoiceItemsRepository } from "../repositories/invoice-items.repository";
|
||||||
import { InvoicesRepository } from "../repositories/invoices.repository";
|
import { InvoicesRepository } from "../repositories/invoices.repository";
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -55,7 +55,7 @@ export class InvoicesService {
|
|||||||
private readonly dataSource: DataSource,
|
private readonly dataSource: DataSource,
|
||||||
private readonly accessLogService: AccessLogService,
|
private readonly accessLogService: AccessLogService,
|
||||||
private readonly restaurantService: RestaurantService,
|
private readonly restaurantService: RestaurantService,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
///********************************** */
|
///********************************** */
|
||||||
|
|
||||||
@@ -489,7 +489,9 @@ export class InvoicesService {
|
|||||||
|
|
||||||
///********************************** */
|
///********************************** */
|
||||||
|
|
||||||
async createInvoiceForSubscription(user: User, plan: SubscriptionPlan, userSub: UserSubscription, dueDate: Date, qryRnr: QueryRunner) {
|
async createInvoiceForSubscription(user: User, plan: SubscriptionPlan, userSub: UserSubscription,
|
||||||
|
dueDate: Date, qryRnr: QueryRunner, purpose: InvoicePurpose) {
|
||||||
|
|
||||||
const discount = plan.directDiscount;
|
const discount = plan.directDiscount;
|
||||||
const originalPrice = plan.originalPrice || plan.price;
|
const originalPrice = plan.originalPrice || plan.price;
|
||||||
const finalPrice = plan.price;
|
const finalPrice = plan.price;
|
||||||
@@ -500,7 +502,7 @@ export class InvoicesService {
|
|||||||
unitPrice: originalPrice,
|
unitPrice: originalPrice,
|
||||||
discount: discount ? new Decimal(originalPrice).sub(finalPrice).toNumber() : 0,
|
discount: discount ? new Decimal(originalPrice).sub(finalPrice).toNumber() : 0,
|
||||||
subscriptionPlan: userSub,
|
subscriptionPlan: userSub,
|
||||||
plan: plan,
|
plan,
|
||||||
totalPrice: finalPrice,
|
totalPrice: finalPrice,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -516,6 +518,7 @@ export class InvoicesService {
|
|||||||
dueDate,
|
dueDate,
|
||||||
items: [invoiceItem],
|
items: [invoiceItem],
|
||||||
discount: discount || undefined,
|
discount: discount || undefined,
|
||||||
|
purpose
|
||||||
});
|
});
|
||||||
|
|
||||||
await qryRnr.manager.save(Invoice, invoice);
|
await qryRnr.manager.save(Invoice, invoice);
|
||||||
@@ -545,33 +548,33 @@ export class InvoicesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//********************************** */
|
//********************************** */
|
||||||
async createInvoiceForSubscriptionUpgrade(user: User, plan: SubscriptionPlan, userSub: UserSubscription, dueDate: Date, qryRnr: QueryRunner, upgradeCost?: Decimal) {
|
async createInvoiceForSubscriptionUpgrade(user: User, plan: SubscriptionPlan, userSub: UserSubscription, dueDate: Date, qryRnr: QueryRunner, upgradeCost: number) {
|
||||||
const discount = plan.directDiscount;
|
const discount = plan.directDiscount;
|
||||||
const originalPrice = upgradeCost || (plan.originalPrice || plan.price);
|
|
||||||
const finalPrice = upgradeCost || plan.price;
|
|
||||||
|
|
||||||
const invoiceItem = {
|
const invoiceItem = {
|
||||||
name: `${plan.service.name} - Upgrade`,
|
name: `${plan.service.name} - Upgrade`,
|
||||||
count: 1,
|
count: 1,
|
||||||
unitPrice: originalPrice,
|
unitPrice: upgradeCost,
|
||||||
discount: discount ? new Decimal(originalPrice).sub(finalPrice).toNumber() : 0,
|
discount: 0,
|
||||||
subscriptionPlan: userSub,
|
subscriptionPlan: userSub,
|
||||||
plan: plan,
|
plan: plan,
|
||||||
totalPrice: finalPrice,
|
totalPrice: upgradeCost,
|
||||||
};
|
};
|
||||||
|
|
||||||
const taxAmount = new Decimal(finalPrice).mul(0.1);
|
const taxAmount = new Decimal(upgradeCost).mul(0.1);
|
||||||
const totalPrice = new Decimal(finalPrice).add(taxAmount);
|
const totalPrice = new Decimal(upgradeCost).add(taxAmount);
|
||||||
|
|
||||||
const invoice = qryRnr.manager.create(Invoice, {
|
const invoice = qryRnr.manager.create(Invoice, {
|
||||||
user,
|
user,
|
||||||
totalPrice: totalPrice,
|
totalPrice: totalPrice,
|
||||||
originalPrice: new Decimal(originalPrice).add(new Decimal(originalPrice).mul(0.1)),
|
originalPrice: new Decimal(upgradeCost).add(new Decimal(upgradeCost).mul(0.1)),
|
||||||
tax: taxAmount.toNumber(),
|
tax: taxAmount.toNumber(),
|
||||||
status: InvoiceStatus.WAIT_PAYMENT,
|
status: InvoiceStatus.WAIT_PAYMENT,
|
||||||
dueDate,
|
dueDate,
|
||||||
items: [invoiceItem],
|
items: [invoiceItem],
|
||||||
discount: discount || undefined,
|
discount: discount || undefined,
|
||||||
|
purpose: InvoicePurpose.UPGRADE
|
||||||
});
|
});
|
||||||
|
|
||||||
await qryRnr.manager.save(Invoice, invoice);
|
await qryRnr.manager.save(Invoice, invoice);
|
||||||
@@ -798,15 +801,19 @@ export class InvoicesService {
|
|||||||
const invoiceItem = invoice.items[0];
|
const invoiceItem = invoice.items[0];
|
||||||
|
|
||||||
// Check if this is an upgrade (subscription is ACTIVE and plan in invoice differs from current plan)
|
// Check if this is an upgrade (subscription is ACTIVE and plan in invoice differs from current plan)
|
||||||
const isUpgrade = userSubscription.status === SubscriptionStatus.ACTIVE &&
|
const purpose = invoice.purpose
|
||||||
invoiceItem.plan &&
|
|
||||||
invoiceItem.plan.id !== userSubscription.plan.id;
|
|
||||||
|
|
||||||
if (isUpgrade) {
|
|
||||||
|
if (purpose == InvoicePurpose.UPGRADE) {
|
||||||
// This is an upgrade - update subscription with the new plan
|
// This is an upgrade - update subscription with the new plan
|
||||||
const upgradePlan = invoiceItem.plan!;
|
const upgradePlan = invoiceItem.plan!;
|
||||||
|
|
||||||
userSubscription.plan = upgradePlan;
|
userSubscription.plan = upgradePlan;
|
||||||
|
const diffrence = upgradePlan.duration - userSubscription.plan.duration
|
||||||
|
|
||||||
|
if (diffrence > 0) {
|
||||||
|
userSubscription.endDate = dayjs(userSubscription.endDate).add(diffrence, "day").toDate();
|
||||||
|
}
|
||||||
this.logger.log(`Subscription ${userSubscription.id} upgraded to plan ${upgradePlan.name}`);
|
this.logger.log(`Subscription ${userSubscription.id} upgraded to plan ${upgradePlan.name}`);
|
||||||
await this.scheduleNextRenewalJob(userSubscription);
|
await this.scheduleNextRenewalJob(userSubscription);
|
||||||
|
|
||||||
@@ -818,7 +825,7 @@ export class InvoicesService {
|
|||||||
await this.restaurantService.upgradeSubscription(
|
await this.restaurantService.upgradeSubscription(
|
||||||
userSubscription.id,
|
userSubscription.id,
|
||||||
dmenuPlan,
|
dmenuPlan,
|
||||||
userSubscription.endDate.toISOString()
|
userSubscription.endDate
|
||||||
);
|
);
|
||||||
this.logger.log(`External API call completed for dmenu upgrade: subscription ${userSubscription.id}`);
|
this.logger.log(`External API call completed for dmenu upgrade: subscription ${userSubscription.id}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -826,22 +833,46 @@ export class InvoicesService {
|
|||||||
// Don't fail the transaction if external API call fails
|
// Don't fail the transaction if external API call fails
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if (purpose == InvoicePurpose.RENEW) {
|
||||||
// Regular subscription logic (renewal or activation)
|
|
||||||
const isRenewal = userSubscription.status === SubscriptionStatus.ACTIVE;
|
|
||||||
|
|
||||||
if (isRenewal) {
|
if (userSubscription.status == SubscriptionStatus.ACTIVE) {
|
||||||
const newEndDate = dayjs(userSubscription.endDate).add(userSubscription.plan.duration, "day").toDate();
|
userSubscription.endDate = dayjs(userSubscription.endDate).add(userSubscription.plan.duration, "day").toDate();
|
||||||
userSubscription.endDate = newEndDate;
|
|
||||||
|
|
||||||
this.logger.log(`Subscription ${userSubscription.id} renewed until ${newEndDate}`);
|
|
||||||
await this.scheduleNextRenewalJob(userSubscription);
|
|
||||||
} else {
|
} else {
|
||||||
userSubscription.status = SubscriptionStatus.ACTIVE;
|
userSubscription.endDate = dayjs().add(userSubscription.plan.duration, "day").toDate();
|
||||||
await this.scheduleNextRenewalJob(userSubscription);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
if (invoiceItem.plan) {
|
||||||
|
userSubscription.plan = invoiceItem.plan
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log(`Subscription ${userSubscription.id} renewed until ${userSubscription.endDate}`);
|
||||||
|
await this.scheduleNextRenewalJob(userSubscription);
|
||||||
|
|
||||||
|
// Call external API for dmenu service upgrades
|
||||||
|
if (userSubscription.plan.service.name.toLowerCase().includes('dmenu') || userSubscription.plan.service.slug?.toLowerCase().includes('dmenu')) {
|
||||||
|
try {
|
||||||
|
this.logger.log(`Calling external API for dmenu upgrade: subscription ${userSubscription.id}`);
|
||||||
|
const dmenuPlan = userSubscription.plan.name.includes("دلیوری") ? "premium" : "base";
|
||||||
|
await this.restaurantService.upgradeSubscription(
|
||||||
|
userSubscription.id,
|
||||||
|
dmenuPlan,
|
||||||
|
userSubscription.endDate
|
||||||
|
);
|
||||||
|
this.logger.log(`External API call completed for dmenu upgrade: subscription ${userSubscription.id}`);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Failed to call external API for dmenu upgrade: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||||
|
// Don't fail the transaction if external API call fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (purpose == InvoicePurpose.NEW) {
|
||||||
|
userSubscription.status = SubscriptionStatus.ACTIVE;
|
||||||
|
await this.scheduleNextRenewalJob(userSubscription);
|
||||||
|
this.logger.log(`Subscription ${userSubscription.id} activated `);
|
||||||
|
|
||||||
|
}
|
||||||
|
// if serivce is dmenu for renew or upgrade it must call dmenu api
|
||||||
//
|
//
|
||||||
await queryRunner.manager.save(UserSubscription, userSubscription);
|
await queryRunner.manager.save(UserSubscription, userSubscription);
|
||||||
await this.walletsService.createSubscriptionTransaction(invoice.totalPrice, userWallet.id, queryRunner);
|
await this.walletsService.createSubscriptionTransaction(invoice.totalPrice, userWallet.id, queryRunner);
|
||||||
@@ -942,7 +973,9 @@ export class InvoicesService {
|
|||||||
subscriptionPlan: {
|
subscriptionPlan: {
|
||||||
plan: true,
|
plan: true,
|
||||||
},
|
},
|
||||||
plan: true,
|
plan: {
|
||||||
|
service: true
|
||||||
|
},
|
||||||
supportPlan: {
|
supportPlan: {
|
||||||
supportPlan: true,
|
supportPlan: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { INVOICE } from "../constants";
|
|||||||
import { Invoice } from "../entities/invoice.entity";
|
import { Invoice } from "../entities/invoice.entity";
|
||||||
import { InvoiceStatus } from "../enums/invoice-status.enum";
|
import { InvoiceStatus } from "../enums/invoice-status.enum";
|
||||||
import { InvoicesService } from "../providers/invoices.service";
|
import { InvoicesService } from "../providers/invoices.service";
|
||||||
|
import { InvoicePurpose } from "../interfaces/external-invoice.interface";
|
||||||
@Processor(INVOICE.QUEUE_NAME, { concurrency: 2 })
|
@Processor(INVOICE.QUEUE_NAME, { concurrency: 2 })
|
||||||
export class InvoiceProcessor extends WorkerProcessor {
|
export class InvoiceProcessor extends WorkerProcessor {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -136,7 +137,7 @@ export class InvoiceProcessor extends WorkerProcessor {
|
|||||||
invoice.currentRecurringCycle += 1;
|
invoice.currentRecurringCycle += 1;
|
||||||
await queryRunner.manager.save(Invoice, invoice);
|
await queryRunner.manager.save(Invoice, invoice);
|
||||||
|
|
||||||
return this.invoicesService.createInvoiceForSubscription(invoice.user, plan, userSubscriptionPlan, dueDate, queryRunner);
|
return this.invoicesService.createInvoiceForSubscription(invoice.user, plan, userSubscriptionPlan, dueDate, queryRunner,InvoicePurpose.NEW);
|
||||||
}
|
}
|
||||||
//********************************** */
|
//********************************** */
|
||||||
|
|
||||||
@@ -199,6 +200,7 @@ export class InvoiceProcessor extends WorkerProcessor {
|
|||||||
userSubscription,
|
userSubscription,
|
||||||
renewalDueDate,
|
renewalDueDate,
|
||||||
queryRunner,
|
queryRunner,
|
||||||
|
InvoicePurpose.RENEW
|
||||||
);
|
);
|
||||||
|
|
||||||
this.logger.log(`Renewal invoice created successfully for subscription ${userSubscriptionId} with id ${renewalInvoice.id}`);
|
this.logger.log(`Renewal invoice created successfully for subscription ${userSubscriptionId} with id ${renewalInvoice.id}`);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { SubscriptionStatus } from "../enums/subscription-status.enum";
|
|||||||
import { IBusinessProvisioningJob } from "../interfaces/IProvisioning-job-data";
|
import { IBusinessProvisioningJob } from "../interfaces/IProvisioning-job-data";
|
||||||
import { SubscriptionsPlanRepository } from "../repositories/subscriptions.repository";
|
import { SubscriptionsPlanRepository } from "../repositories/subscriptions.repository";
|
||||||
import { UserSubscriptionsRepository } from "../repositories/user-subscriptions.repository";
|
import { UserSubscriptionsRepository } from "../repositories/user-subscriptions.repository";
|
||||||
|
import { InvoicePurpose } from "../../invoices/interfaces/external-invoice.interface";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SubscriptionsService {
|
export class SubscriptionsService {
|
||||||
@@ -308,7 +309,7 @@ export class SubscriptionsService {
|
|||||||
await queryRunner.manager.save(UserSubscription, userSubscription);
|
await queryRunner.manager.save(UserSubscription, userSubscription);
|
||||||
const invoiceDueDate = dayjs(userSubscription.startDate).add(INVOICE.DUEDATE, "day").toDate();
|
const invoiceDueDate = dayjs(userSubscription.startDate).add(INVOICE.DUEDATE, "day").toDate();
|
||||||
|
|
||||||
const invoice = await this.invoicesService.createInvoiceForSubscription(user, plan, userSubscription, invoiceDueDate, queryRunner);
|
const invoice = await this.invoicesService.createInvoiceForSubscription(user, plan, userSubscription, invoiceDueDate, queryRunner,InvoicePurpose.NEW);
|
||||||
userSubscription.status = SubscriptionStatus.INACTIVE;
|
userSubscription.status = SubscriptionStatus.INACTIVE;
|
||||||
|
|
||||||
await queryRunner.manager.save(UserSubscription, userSubscription);
|
await queryRunner.manager.save(UserSubscription, userSubscription);
|
||||||
@@ -334,7 +335,7 @@ export class SubscriptionsService {
|
|||||||
}
|
}
|
||||||
//************************************ */
|
//************************************ */
|
||||||
|
|
||||||
async extendSubscribeToPlan(userSubscriptionId: string, dto: ExtendUSerSubscribeServiceDto, userId: string) {
|
async renewUserSubscription(userSubscriptionId: string, dto: ExtendUSerSubscribeServiceDto, userId: string) {
|
||||||
const queryRunner = this.dataSource.createQueryRunner();
|
const queryRunner = this.dataSource.createQueryRunner();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -366,11 +367,11 @@ export class SubscriptionsService {
|
|||||||
|
|
||||||
const invoiceDueDate = dayjs().add(INVOICE.DUEDATE, "day").toDate();
|
const invoiceDueDate = dayjs().add(INVOICE.DUEDATE, "day").toDate();
|
||||||
|
|
||||||
const invoice = await this.invoicesService.createInvoiceForSubscription(user, plan, userSubscription, invoiceDueDate, queryRunner);
|
const invoice = await this.invoicesService.createInvoiceForSubscription(user, plan, userSubscription, invoiceDueDate, queryRunner,InvoicePurpose.RENEW);
|
||||||
|
|
||||||
userSubscription.plan = plan;
|
// userSubscription.plan = plan;
|
||||||
|
|
||||||
await queryRunner.manager.save(userSubscription);
|
// await queryRunner.manager.save(userSubscription);
|
||||||
|
|
||||||
await this.addProvisioningJob(userSubscription, plan, user);
|
await this.addProvisioningJob(userSubscription, plan, user);
|
||||||
|
|
||||||
@@ -415,28 +416,28 @@ export class SubscriptionsService {
|
|||||||
const newPlan = await this.subscriptionsPlanRepository.findById(planId);
|
const newPlan = await this.subscriptionsPlanRepository.findById(planId);
|
||||||
if (!newPlan) throw new BadRequestException(SubscriptionMessage.PLAN_NOT_FOUND);
|
if (!newPlan) throw new BadRequestException(SubscriptionMessage.PLAN_NOT_FOUND);
|
||||||
|
|
||||||
console.log('new plan', newPlan.service.id)
|
|
||||||
console.log('userSubscription.plan.service.id',userSubscription.plan.service.id)
|
|
||||||
if (userSubscription.plan.service.id !== newPlan.service.id) {
|
if (userSubscription.plan.service.id !== newPlan.service.id) {
|
||||||
throw new BadRequestException(SubscriptionMessage.SERVICE_MISMATCH);
|
throw new BadRequestException(SubscriptionMessage.SERVICE_MISMATCH);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate that new plan is more expensive than current plan
|
// Validate that new plan is more expensive than current plan
|
||||||
if (new Decimal(newPlan.price).lessThanOrEqualTo(userSubscription.plan.price)) {
|
if (new Decimal(newPlan.price).lessThan(userSubscription.plan.price)) {
|
||||||
throw new BadRequestException(SubscriptionMessage.UPGRADE_TO_LOWER_OR_EQUAL_PLAN_NOT_ALLOWED);
|
throw new BadRequestException(SubscriptionMessage.UPGRADE_TO_LOWER_OR_EQUAL_PLAN_NOT_ALLOWED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate remaining days in current subscription
|
// Calculate remaining days in current subscription
|
||||||
const today = dayjs();
|
const today = dayjs();
|
||||||
const endDate = dayjs(userSubscription.endDate);
|
const endDate = dayjs(userSubscription.endDate);
|
||||||
const remainingDays = Math.max(0, endDate.diff(today, 'day'));
|
const currentPlanRemainingDays = Math.max(0, endDate.diff(today, 'day'));
|
||||||
console.log('🔢 CALCULATION LOGS:');
|
console.log('🔢 CALCULATION LOGS:');
|
||||||
console.log(`📅 Remaining days: ${remainingDays} (endDate: ${endDate.format('YYYY-MM-DD')}, today: ${today.format('YYYY-MM-DD')})`);
|
console.log(`📅 Remaining days: ${currentPlanRemainingDays} (endDate: ${endDate.format('YYYY-MM-DD')}, today: ${today.format('YYYY-MM-DD')})`);
|
||||||
|
|
||||||
if (remainingDays <= 0) {
|
if (currentPlanRemainingDays <= 0) {
|
||||||
throw new BadRequestException(SubscriptionMessage.SUBSCRIPTION_EXPIRED_CANNOT_UPGRADE);
|
throw new BadRequestException(SubscriptionMessage.SUBSCRIPTION_EXPIRED_CANNOT_UPGRADE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const remainingDays = newPlan.duration - currentPlanRemainingDays
|
||||||
// Calculate daily rates
|
// Calculate daily rates
|
||||||
const currentPlanDuration = userSubscription.plan.duration;
|
const currentPlanDuration = userSubscription.plan.duration;
|
||||||
const newPlanDuration = newPlan.duration;
|
const newPlanDuration = newPlan.duration;
|
||||||
@@ -451,13 +452,13 @@ export class SubscriptionsService {
|
|||||||
console.log(`📈 Daily price difference: ${dailyPriceDifference.toNumber()} IRR/day`);
|
console.log(`📈 Daily price difference: ${dailyPriceDifference.toNumber()} IRR/day`);
|
||||||
|
|
||||||
// Calculate prorated upgrade cost for remaining days
|
// Calculate prorated upgrade cost for remaining days
|
||||||
const proratedUpgradeCost = dailyPriceDifference.mul(remainingDays);
|
const proratedUpgradeCost = dailyPriceDifference.mul(currentPlanRemainingDays).add(newDailyRate.mul(remainingDays))
|
||||||
console.log(`🧮 Prorated upgrade cost: ${dailyPriceDifference.toNumber()} IRR/day × ${remainingDays} days = ${proratedUpgradeCost.toNumber()} IRR`);
|
console.log(`🧮 Prorated upgrade cost: ${dailyPriceDifference.toNumber()} IRR/day × ${currentPlanRemainingDays} days = ${proratedUpgradeCost.toNumber()} IRR`);
|
||||||
|
|
||||||
// Ensure minimum charge amount
|
// Ensure minimum charge amount
|
||||||
const minimumCharge = new Decimal(1000); // 1000 IRR minimum
|
const minimumCharge = new Decimal(1000); // 1000 IRR minimum
|
||||||
const finalUpgradeCost = Decimal.max(proratedUpgradeCost, minimumCharge);
|
const finalUpgradeCost = + Decimal.max(proratedUpgradeCost, minimumCharge).toNumber().toFixed(0)
|
||||||
console.log(`✅ Final upgrade cost: max(${proratedUpgradeCost.toNumber()} IRR, ${minimumCharge.toNumber()} IRR minimum) = ${finalUpgradeCost.toNumber()} IRR`);
|
console.log(`✅ Final upgrade cost: max(${proratedUpgradeCost.toNumber()} IRR, ${minimumCharge.toNumber()} IRR minimum) = ${finalUpgradeCost} IRR`);
|
||||||
|
|
||||||
const user = await this.usersService.findOneByIdWithQueryRunner(userSubscription.user.id, queryRunner);
|
const user = await this.usersService.findOneByIdWithQueryRunner(userSubscription.user.id, queryRunner);
|
||||||
|
|
||||||
@@ -473,8 +474,8 @@ export class SubscriptionsService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Update the subscription to the new plan
|
// Update the subscription to the new plan
|
||||||
userSubscription.plan = newPlan;
|
// userSubscription.plan = newPlan;
|
||||||
await queryRunner.manager.save(UserSubscription, userSubscription);
|
// await queryRunner.manager.save(UserSubscription, userSubscription);
|
||||||
|
|
||||||
// Add provisioning job for the new plan
|
// Add provisioning job for the new plan
|
||||||
await this.addProvisioningJob(userSubscription, newPlan, user);
|
await this.addProvisioningJob(userSubscription, newPlan, user);
|
||||||
@@ -485,10 +486,10 @@ export class SubscriptionsService {
|
|||||||
userSubscription,
|
userSubscription,
|
||||||
invoice,
|
invoice,
|
||||||
upgradeDetails: {
|
upgradeDetails: {
|
||||||
remainingDays,
|
remainingDays: currentPlanRemainingDays,
|
||||||
currentPlanPrice: userSubscription.plan.price,
|
currentPlanPrice: userSubscription.plan.price,
|
||||||
newPlanPrice: newPlan.price,
|
newPlanPrice: newPlan.price,
|
||||||
proratedUpgradeCost: finalUpgradeCost.toNumber(),
|
proratedUpgradeCost: finalUpgradeCost,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ export class SubscriptionsController {
|
|||||||
@Body() subscribeDto: ExtendUSerSubscribeServiceDto,
|
@Body() subscribeDto: ExtendUSerSubscribeServiceDto,
|
||||||
@UserDec("id") userId: string,
|
@UserDec("id") userId: string,
|
||||||
) {
|
) {
|
||||||
return this.subscriptionService.extendSubscribeToPlan(paramDto.userSubscriptionId, subscribeDto, userId);
|
return this.subscriptionService.renewUserSubscription(paramDto.userSubscriptionId, subscribeDto, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation({ summary: "Upgrade subscription plan ==> user route" })
|
@ApiOperation({ summary: "Upgrade subscription plan ==> user route" })
|
||||||
|
|||||||
Reference in New Issue
Block a user