upgrade plan

This commit is contained in:
2026-01-04 10:34:34 +03:30
parent 3c0b583a9d
commit 62aa24122e
13 changed files with 335 additions and 20 deletions
@@ -11,6 +11,7 @@ import { OperationType } from "../../access-logs/enums/operation-type.enum";
import { UserType } from "../../access-logs/enums/user-type.enum";
import { AccessLogService } from "../../access-logs/providers/access-log.service";
import { VerifyOtpWithUserId } from "../../auth/DTO/verify-otp.dto";
import { RestaurantService } from "../../dmenu/providers/restaurant.service";
import { Discount } from "../../discounts/entities/discount.entity";
import { DiscountType } from "../../discounts/enums/discount-type.enum";
import { NotificationQueue } from "../../notifications/queue/notification.queue";
@@ -53,6 +54,7 @@ export class InvoicesService {
private readonly smsService: SmsService,
private readonly dataSource: DataSource,
private readonly accessLogService: AccessLogService,
private readonly restaurantService: RestaurantService,
) {}
///********************************** */
@@ -498,6 +500,7 @@ export class InvoicesService {
unitPrice: originalPrice,
discount: discount ? new Decimal(originalPrice).sub(finalPrice).toNumber() : 0,
subscriptionPlan: userSub,
plan: plan,
totalPrice: finalPrice,
};
@@ -540,6 +543,63 @@ export class InvoicesService {
return invoice;
}
//********************************** */
async createInvoiceForSubscriptionUpgrade(user: User, plan: SubscriptionPlan, userSub: UserSubscription, dueDate: Date, qryRnr: QueryRunner, upgradeCost?: Decimal) {
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,
subscriptionPlan: userSub,
plan: plan,
totalPrice: finalPrice,
};
const taxAmount = new Decimal(finalPrice).mul(0.1);
const totalPrice = new Decimal(finalPrice).add(taxAmount);
const invoice = qryRnr.manager.create(Invoice, {
user,
totalPrice: totalPrice,
originalPrice: new Decimal(originalPrice).add(new Decimal(originalPrice).mul(0.1)),
tax: taxAmount.toNumber(),
status: InvoiceStatus.WAIT_PAYMENT,
dueDate,
items: [invoiceItem],
discount: discount || undefined,
});
await qryRnr.manager.save(Invoice, invoice);
await this.notificationQueue.addInvoiceCreationNotification(user.id, {
invoiceId: invoice.numericId.toString(),
dueDate: invoice.dueDate,
createDate: invoice.createdAt,
price: totalPrice.toNumber(),
userPhone: user.phone,
userEmail: user.email,
items: plan.service.name,
});
await this.invoiceQueue.add(
INVOICE.REMINDER_JOB_NAME,
{ invoiceId: invoice.id },
{
delay: dayjs(invoice.dueDate).subtract(INVOICE.DAYS_BEFORE_OVERDUE, "day").diff(dayjs()),
attempts: INVOICE.REMINDER_JOB_ATTEMPTS,
backoff: { type: "exponential", delay: INVOICE.REMINDER_JOB_BACKOFF },
priority: INVOICE.REMINDER_JOB_PRIORITY,
},
);
return invoice;
}
//********************************** */
async createInvoiceForSupportPlan(
user: User,
@@ -735,18 +795,51 @@ export class InvoicesService {
if (invoice.items[0]?.subscriptionPlan && !invoice.isExternal) {
const userSubscription = invoice.items[0].subscriptionPlan;
const invoiceItem = invoice.items[0];
const isRenewal = userSubscription.status === SubscriptionStatus.ACTIVE;
// 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;
if (isRenewal) {
const newEndDate = dayjs(userSubscription.endDate).add(userSubscription.plan.duration, "day").toDate();
userSubscription.endDate = newEndDate;
if (isUpgrade) {
// This is an upgrade - update subscription with the new plan
const upgradePlan = invoiceItem.plan!;
this.logger.log(`Subscription ${userSubscription.id} renewed until ${newEndDate}`);
userSubscription.plan = upgradePlan;
this.logger.log(`Subscription ${userSubscription.id} upgraded to plan ${upgradePlan.name}`);
await this.scheduleNextRenewalJob(userSubscription);
// Call external API for dmenu service upgrades
if (upgradePlan.service.name.toLowerCase().includes('dmenu') || upgradePlan.service.slug?.toLowerCase().includes('dmenu')) {
try {
this.logger.log(`Calling external API for dmenu upgrade: subscription ${userSubscription.id}`);
const dmenuPlan = upgradePlan.name.includes("دلیوری") ? "premium" : "base";
await this.restaurantService.upgradeSubscription(
userSubscription.id,
dmenuPlan,
userSubscription.endDate.toISOString()
);
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 {
userSubscription.status = SubscriptionStatus.ACTIVE;
await this.scheduleNextRenewalJob(userSubscription);
// Regular subscription logic (renewal or activation)
const isRenewal = userSubscription.status === SubscriptionStatus.ACTIVE;
if (isRenewal) {
const newEndDate = 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 {
userSubscription.status = SubscriptionStatus.ACTIVE;
await this.scheduleNextRenewalJob(userSubscription);
}
}
//
@@ -849,6 +942,7 @@ export class InvoicesService {
subscriptionPlan: {
plan: true,
},
plan: true,
supportPlan: {
supportPlan: true,
},