fix : extend and upgrade user subscription

This commit is contained in:
2026-02-01 11:54:51 +03:30
parent 3a80ce021d
commit 0ba027d4e3
7 changed files with 110 additions and 56 deletions
@@ -35,7 +35,7 @@ import { UpdateInvoiceDto } from "../DTO/update-invoice.dto";
import { Invoice } from "../entities/invoice.entity";
import { RecurringPeriodEnum } from "../enums/invoice-recurring-period.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 { InvoicesRepository } from "../repositories/invoices.repository";
@Injectable()
@@ -55,7 +55,7 @@ export class InvoicesService {
private readonly dataSource: DataSource,
private readonly accessLogService: AccessLogService,
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 originalPrice = plan.originalPrice || plan.price;
const finalPrice = plan.price;
@@ -500,7 +502,7 @@ export class InvoicesService {
unitPrice: originalPrice,
discount: discount ? new Decimal(originalPrice).sub(finalPrice).toNumber() : 0,
subscriptionPlan: userSub,
plan: plan,
plan,
totalPrice: finalPrice,
};
@@ -516,6 +518,7 @@ export class InvoicesService {
dueDate,
items: [invoiceItem],
discount: discount || undefined,
purpose
});
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 originalPrice = upgradeCost || (plan.originalPrice || plan.price);
const finalPrice = upgradeCost || plan.price;
const invoiceItem = {
name: `${plan.service.name} - Upgrade`,
count: 1,
unitPrice: originalPrice,
discount: discount ? new Decimal(originalPrice).sub(finalPrice).toNumber() : 0,
unitPrice: upgradeCost,
discount: 0,
subscriptionPlan: userSub,
plan: plan,
totalPrice: finalPrice,
totalPrice: upgradeCost,
};
const taxAmount = new Decimal(finalPrice).mul(0.1);
const totalPrice = new Decimal(finalPrice).add(taxAmount);
const taxAmount = new Decimal(upgradeCost).mul(0.1);
const totalPrice = new Decimal(upgradeCost).add(taxAmount);
const invoice = qryRnr.manager.create(Invoice, {
user,
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(),
status: InvoiceStatus.WAIT_PAYMENT,
dueDate,
items: [invoiceItem],
discount: discount || undefined,
purpose: InvoicePurpose.UPGRADE
});
await qryRnr.manager.save(Invoice, invoice);
@@ -798,15 +801,19 @@ export class InvoicesService {
const invoiceItem = invoice.items[0];
// Check if this is an upgrade (subscription is ACTIVE and plan in invoice differs from current plan)
const isUpgrade = userSubscription.status === SubscriptionStatus.ACTIVE &&
invoiceItem.plan &&
invoiceItem.plan.id !== userSubscription.plan.id;
const purpose = invoice.purpose
if (isUpgrade) {
if (purpose == InvoicePurpose.UPGRADE) {
// This is an upgrade - update subscription with the new plan
const upgradePlan = invoiceItem.plan!;
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}`);
await this.scheduleNextRenewalJob(userSubscription);
@@ -818,7 +825,7 @@ export class InvoicesService {
await this.restaurantService.upgradeSubscription(
userSubscription.id,
dmenuPlan,
userSubscription.endDate.toISOString()
userSubscription.endDate
);
this.logger.log(`External API call completed for dmenu upgrade: subscription ${userSubscription.id}`);
} catch (error) {
@@ -826,22 +833,46 @@ export class InvoicesService {
// Don't fail the transaction if external API call fails
}
}
} else {
// Regular subscription logic (renewal or activation)
const isRenewal = userSubscription.status === SubscriptionStatus.ACTIVE;
} else if (purpose == InvoicePurpose.RENEW) {
if (isRenewal) {
const newEndDate = dayjs(userSubscription.endDate).add(userSubscription.plan.duration, "day").toDate();
userSubscription.endDate = newEndDate;
if (userSubscription.status == SubscriptionStatus.ACTIVE) {
userSubscription.endDate = dayjs(userSubscription.endDate).add(userSubscription.plan.duration, "day").toDate();
this.logger.log(`Subscription ${userSubscription.id} renewed until ${newEndDate}`);
await this.scheduleNextRenewalJob(userSubscription);
} else {
userSubscription.status = SubscriptionStatus.ACTIVE;
await this.scheduleNextRenewalJob(userSubscription);
userSubscription.endDate = dayjs().add(userSubscription.plan.duration, "day").toDate();
}
}
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 this.walletsService.createSubscriptionTransaction(invoice.totalPrice, userWallet.id, queryRunner);
@@ -942,7 +973,9 @@ export class InvoicesService {
subscriptionPlan: {
plan: true,
},
plan: true,
plan: {
service: true
},
supportPlan: {
supportPlan: true,
},
@@ -1144,7 +1177,7 @@ export class InvoicesService {
} finally {
await queryRunner.release();
}
}
}
//*********************************** */
private async getInvoiceByIdWithQueryRunner(invoiceId: string, userId: string, queryRunner: QueryRunner) {