feat: implement sysadmin subscription management and plan usage cycle recalculation tools
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import { Body, Controller, Middlewares, Post, Response, Route, SuccessResponse } from "tsoa";
|
||||
import { ApiValidationError } from "../../../Models/Server.Error.model";
|
||||
import { sysAdminAuthMiddleware } from "../../../middleware/sysadminAuth";
|
||||
import {
|
||||
SysAdminExtendUserSubscriptionParams,
|
||||
SysAdminExtendUserSubscriptionResult,
|
||||
SysAdminUserSubscriptionDetailsParams,
|
||||
SysAdminUserSubscriptionDetailsResult,
|
||||
} from "../../../Models/Users/Users.Interface";
|
||||
import { SysAdminSubscriptionsService } from "./SysAdminSubscriptions.Service";
|
||||
|
||||
@Route("sysadmin/subscriptions")
|
||||
@Middlewares(sysAdminAuthMiddleware)
|
||||
export class SysAdminSubscriptionsController extends Controller {
|
||||
@Response<ApiValidationError>(500, "Ha ocurrido un error")
|
||||
@SuccessResponse(200, "Done")
|
||||
@Post("extend-user")
|
||||
public async extendUserSubscription(
|
||||
@Body() requestBody: SysAdminExtendUserSubscriptionParams
|
||||
): Promise<SysAdminExtendUserSubscriptionResult | ApiValidationError> {
|
||||
try {
|
||||
const result = await new SysAdminSubscriptionsService().extendUserSubscription(requestBody);
|
||||
this.setStatus(200);
|
||||
return result;
|
||||
} catch (e) {
|
||||
const errorOccurred: Error = e as Error;
|
||||
this.setStatus(500);
|
||||
return new ApiValidationError(500, errorOccurred.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Response<ApiValidationError>(500, "Ha ocurrido un error")
|
||||
@SuccessResponse(200, "Done")
|
||||
@Post("user-details")
|
||||
public async getUserSubscriptionDetails(
|
||||
@Body() requestBody: SysAdminUserSubscriptionDetailsParams
|
||||
): Promise<SysAdminUserSubscriptionDetailsResult | ApiValidationError> {
|
||||
try {
|
||||
const result = await new SysAdminSubscriptionsService().getUserSubscriptionDetails(requestBody);
|
||||
this.setStatus(200);
|
||||
return result;
|
||||
} catch (e) {
|
||||
const errorOccurred: Error = e as Error;
|
||||
this.setStatus(500);
|
||||
return new ApiValidationError(500, errorOccurred.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import { connect, isValidObjectId } from "mongoose";
|
||||
import dayjs from "dayjs";
|
||||
import PlanSubscriptionsList from "../../../Models/PlanSubscriptions/PlanSubscriptons";
|
||||
import PlanPaymentsList from "../../../Models/PlanPayments/PlanPayments";
|
||||
import PlansList from "../../../Models/Plans/Plans";
|
||||
import {
|
||||
SysAdminExtendUserSubscriptionParams,
|
||||
SysAdminExtendUserSubscriptionResult,
|
||||
SysAdminUserSubscriptionDetailsParams,
|
||||
SysAdminUserSubscriptionDetailsResult,
|
||||
} from "../../../Models/Users/Users.Interface";
|
||||
|
||||
export class SysAdminSubscriptionsService {
|
||||
public async extendUserSubscription(
|
||||
params: SysAdminExtendUserSubscriptionParams
|
||||
): Promise<SysAdminExtendUserSubscriptionResult> {
|
||||
await connect(`${process.env.DATABASE_CONNECTION}`);
|
||||
|
||||
if (!params.userId || !isValidObjectId(params.userId)) {
|
||||
throw new Error("User id is required");
|
||||
}
|
||||
|
||||
const subscription = await PlanSubscriptionsList.findOne({ sessionUser: params.userId });
|
||||
if (!subscription) {
|
||||
throw new Error("No subscription found for user");
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const previousEndDate = subscription.endDate ? new Date(subscription.endDate) : now;
|
||||
const baseDate = previousEndDate.getTime() > now.getTime() ? previousEndDate : now;
|
||||
let newEndDate: Date;
|
||||
|
||||
if (params.mode === "add_months") {
|
||||
if (params.months !== 1 && params.months !== 2) {
|
||||
throw new Error("Months must be 1 or 2");
|
||||
}
|
||||
newEndDate = dayjs(baseDate).add(params.months, "month").toDate();
|
||||
} else if (params.mode === "set_end_date") {
|
||||
if (!params.endDate) {
|
||||
throw new Error("End date is required");
|
||||
}
|
||||
const parsedEndDate = dayjs(params.endDate);
|
||||
if (!parsedEndDate.isValid()) {
|
||||
throw new Error("End date is invalid");
|
||||
}
|
||||
newEndDate = parsedEndDate.endOf("day").toDate();
|
||||
if (newEndDate.getTime() <= now.getTime()) {
|
||||
throw new Error("End date must be in the future");
|
||||
}
|
||||
if (newEndDate.getTime() <= baseDate.getTime()) {
|
||||
throw new Error("End date must be after the current effective subscription end date");
|
||||
}
|
||||
} else {
|
||||
throw new Error("Invalid extension mode");
|
||||
}
|
||||
|
||||
subscription.endDate = newEndDate;
|
||||
subscription.isActive = true;
|
||||
if (subscription.mpStatus) {
|
||||
subscription.mpStatus = "authorized";
|
||||
}
|
||||
await subscription.save();
|
||||
|
||||
const transactionId = [
|
||||
"sysadmin_bonus",
|
||||
String(subscription.id),
|
||||
now.getTime(),
|
||||
].join(":");
|
||||
const payment = await PlanPaymentsList.createIfMissingByTransactionId({
|
||||
userId: params.userId,
|
||||
subscriptionId: String(subscription.id),
|
||||
amount: 0,
|
||||
paymentDate: now,
|
||||
paymentMethod: "sysadmin_bonus",
|
||||
status: "completed",
|
||||
transactionId,
|
||||
});
|
||||
|
||||
return {
|
||||
userId: params.userId,
|
||||
subscriptionId: String(subscription.id),
|
||||
previousEndDate,
|
||||
newEndDate,
|
||||
paymentId: String(payment.id),
|
||||
transactionId,
|
||||
};
|
||||
}
|
||||
|
||||
public async getUserSubscriptionDetails(
|
||||
params: SysAdminUserSubscriptionDetailsParams
|
||||
): Promise<SysAdminUserSubscriptionDetailsResult> {
|
||||
await connect(`${process.env.DATABASE_CONNECTION}`);
|
||||
|
||||
const subscription = await PlanSubscriptionsList.findOne({ sessionUser: params.userId });
|
||||
const payments = await PlanPaymentsList.find({ userId: params.userId });
|
||||
|
||||
if (!subscription) {
|
||||
return {
|
||||
userId: params.userId,
|
||||
currentSubscription: null,
|
||||
payments: payments.map((payment) => ({
|
||||
id: String(payment.id),
|
||||
subscriptionId: String(payment.subscriptionId),
|
||||
amount: payment.amount,
|
||||
paymentDate: payment.paymentDate,
|
||||
paymentMethod: payment.paymentMethod,
|
||||
status: payment.status,
|
||||
transactionId: payment.transactionId,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
const plan = await PlansList.plans.findOne({ _id: String(subscription.planId) });
|
||||
|
||||
return {
|
||||
userId: params.userId,
|
||||
currentSubscription: {
|
||||
id: String(subscription.id),
|
||||
plan: plan
|
||||
? {
|
||||
id: String(plan.id),
|
||||
name: plan.name,
|
||||
code: plan.code,
|
||||
price: plan.price,
|
||||
}
|
||||
: null,
|
||||
startDate: subscription.startDate,
|
||||
endDate: subscription.endDate,
|
||||
isActive: subscription.isActive,
|
||||
mpStatus: subscription.mpStatus,
|
||||
billingMonths: subscription.billingMonths,
|
||||
pendingPaymentType: subscription.pendingPaymentType,
|
||||
pendingPaymentPreferenceId: subscription.pendingPaymentPreferenceId,
|
||||
lastPaymentStatus: subscription.lastPaymentStatus,
|
||||
},
|
||||
payments: payments.map((payment) => ({
|
||||
id: String(payment.id),
|
||||
subscriptionId: String(payment.subscriptionId),
|
||||
amount: payment.amount,
|
||||
paymentDate: payment.paymentDate,
|
||||
paymentMethod: payment.paymentMethod,
|
||||
status: payment.status,
|
||||
transactionId: payment.transactionId,
|
||||
})),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user