feat: implement plan upgrade logic with prorated pricing and MercadoPago integration
This commit is contained in:
@@ -42,6 +42,17 @@ export class PlanSuscriptionsAdapterMongoose implements IPlanSuscriptionsAdapter
|
||||
pendingPaymentInitPoint: { type: String, required: false },
|
||||
pendingPaymentPreferenceId: { type: String, required: false },
|
||||
pendingPaymentBillingMonths: { type: Number, required: false },
|
||||
pendingPaymentType: { type: String, required: false, enum: ["extension", "upgrade"] },
|
||||
pendingPaymentPlanId: { type: Schema.Types.ObjectId, required: false, ref: "Plan" },
|
||||
pendingPaymentCurrentPlanPrice: { type: Number, required: false },
|
||||
pendingPaymentRequestedPlanPrice: { type: Number, required: false },
|
||||
pendingPaymentProratedAmount: { type: Number, required: false },
|
||||
pendingPaymentRemainingDays: { type: Number, required: false },
|
||||
pendingPaymentPeriodEndDate: { type: Date, required: false },
|
||||
lastPaymentStatus: { type: String, required: false, enum: ["rejected", "failed", "cancelled"] },
|
||||
lastPaymentPreferenceId: { type: String, required: false },
|
||||
lastPaymentAt: { type: Date, required: false },
|
||||
lastPaymentType: { type: String, required: false, enum: ["new", "extension", "upgrade"] },
|
||||
});
|
||||
|
||||
this.planSuscriptionList = model<IPlanSuscriptionDocument>("PlanSuscription", this.schema);
|
||||
|
||||
@@ -25,6 +25,17 @@ export type CreatePlanSuscriptionParams = {
|
||||
pendingPaymentInitPoint?: string;
|
||||
pendingPaymentPreferenceId?: string;
|
||||
pendingPaymentBillingMonths?: number;
|
||||
pendingPaymentType?: "extension" | "upgrade";
|
||||
pendingPaymentPlanId?: string;
|
||||
pendingPaymentCurrentPlanPrice?: number;
|
||||
pendingPaymentRequestedPlanPrice?: number;
|
||||
pendingPaymentProratedAmount?: number;
|
||||
pendingPaymentRemainingDays?: number;
|
||||
pendingPaymentPeriodEndDate?: Date;
|
||||
lastPaymentStatus?: LastPlanPaymentStatus;
|
||||
lastPaymentPreferenceId?: string;
|
||||
lastPaymentAt?: Date;
|
||||
lastPaymentType?: LastPlanPaymentType;
|
||||
};
|
||||
|
||||
export type CancellPlanSuscriptionParams = {
|
||||
@@ -75,6 +86,22 @@ export type GetSubscriptionInitPointParams = {
|
||||
sessionUser: string;
|
||||
};
|
||||
|
||||
export type VerifyPendingPlanPaymentParams = {
|
||||
sessionUser: string;
|
||||
};
|
||||
|
||||
export type VerifyPendingPlanPaymentResponse = {
|
||||
status: "approved" | "pending" | "not_found" | "rejected" | "failed" | "cancelled";
|
||||
approved: boolean;
|
||||
message: string;
|
||||
subscriptionId?: string;
|
||||
paymentId?: string;
|
||||
paymentType?: "new" | "extension" | "upgrade";
|
||||
};
|
||||
|
||||
export type LastPlanPaymentStatus = "rejected" | "failed" | "cancelled";
|
||||
export type LastPlanPaymentType = "new" | "extension" | "upgrade";
|
||||
|
||||
export interface GetSuscriptionInitPointResponse {
|
||||
init_point: string;
|
||||
}
|
||||
@@ -99,6 +126,17 @@ export interface IPlanSuscription {
|
||||
pendingPaymentInitPoint?: string; // URL de pago pendiente para extender el plan actual.
|
||||
pendingPaymentPreferenceId?: string; // ID de preferencia pendiente para extender el plan actual.
|
||||
pendingPaymentBillingMonths?: number; // Meses pendientes de acreditar al aprobarse la extensión.
|
||||
pendingPaymentType?: "extension" | "upgrade";
|
||||
pendingPaymentPlanId?: string;
|
||||
pendingPaymentCurrentPlanPrice?: number;
|
||||
pendingPaymentRequestedPlanPrice?: number;
|
||||
pendingPaymentProratedAmount?: number;
|
||||
pendingPaymentRemainingDays?: number;
|
||||
pendingPaymentPeriodEndDate?: Date;
|
||||
lastPaymentStatus?: LastPlanPaymentStatus;
|
||||
lastPaymentPreferenceId?: string;
|
||||
lastPaymentAt?: Date;
|
||||
lastPaymentType?: LastPlanPaymentType;
|
||||
}
|
||||
|
||||
export interface ISubscriptionInfo {
|
||||
@@ -110,6 +148,13 @@ export interface ISubscriptionInfo {
|
||||
autoRenew: boolean;
|
||||
mpStatus: string;
|
||||
mpDateCreated: Date;
|
||||
pendingPaymentInitPoint?: string;
|
||||
pendingPaymentPreferenceId?: string;
|
||||
pendingPaymentType?: "extension" | "upgrade";
|
||||
lastPaymentStatus?: LastPlanPaymentStatus;
|
||||
lastPaymentPreferenceId?: string;
|
||||
lastPaymentAt?: Date;
|
||||
lastPaymentType?: LastPlanPaymentType;
|
||||
}
|
||||
|
||||
export interface IPlanSuscriptionsAdapter {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
const DEFAULT_BILLING_PERIOD_DAYS = 30;
|
||||
|
||||
export function calculateProratedUpgradeAmount(
|
||||
currentPlanPrice: number,
|
||||
requestedPlanPrice: number,
|
||||
remainingDays: number,
|
||||
billingPeriodDays = DEFAULT_BILLING_PERIOD_DAYS
|
||||
): number {
|
||||
const dailyDifference = (requestedPlanPrice - currentPlanPrice) / billingPeriodDays;
|
||||
return Math.round(dailyDifference * remainingDays * 100) / 100;
|
||||
}
|
||||
@@ -31,10 +31,13 @@ import { NotificationsManager } from "../Notifications/Notifications";
|
||||
import WapServerList from "../WapServer/WapServer";
|
||||
import CompaniesList from "../Companies/Companies";
|
||||
import { NotificationType } from "../../Models/SystemNotifications/SystemNotification.Interface";
|
||||
import { calculateProratedUpgradeAmount } from "./PlanSubscriptions.pricing";
|
||||
|
||||
dayjs.locale("es");
|
||||
dayjs.extend(isSameOrAfter);
|
||||
|
||||
const DAY_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
class PlanSuscriptionsManager implements IPlanSuscriptionsManager {
|
||||
planSuscriptions: PlanSuscriptionsAdapterMongoose;
|
||||
|
||||
@@ -46,6 +49,20 @@ class PlanSuscriptionsManager implements IPlanSuscriptionsManager {
|
||||
return subscription.mpStatus === MP_SUBS_STATUS.AUTHORIZED || subscription.mpStatus === MP_SUBS_STATUS.CANCELLED;
|
||||
}
|
||||
|
||||
private buildMercadoPagoBackUrl(returnStatus: "success" | "failure" | "pending"): string {
|
||||
const backUrl = process.env.MP_BACK_URL || "";
|
||||
if (!backUrl) return backUrl;
|
||||
|
||||
try {
|
||||
const url = new URL(backUrl);
|
||||
url.searchParams.set("mp_return", returnStatus);
|
||||
return url.toString();
|
||||
} catch {
|
||||
const separator = backUrl.includes("?") ? "&" : "?";
|
||||
return `${backUrl}${separator}mp_return=${returnStatus}`;
|
||||
}
|
||||
}
|
||||
|
||||
public async getInitPoint(
|
||||
data: GetSubscriptionInitPointParams
|
||||
): Promise<GetSuscriptionInitPointResponse> {
|
||||
@@ -190,6 +207,23 @@ class PlanSuscriptionsManager implements IPlanSuscriptionsManager {
|
||||
months: data.months || 1,
|
||||
});
|
||||
} else {
|
||||
const isActivePaidSubscription =
|
||||
actualPlan.price > 0 &&
|
||||
(subscription.mpStatus === MP_SUBS_STATUS.AUTHORIZED ||
|
||||
subscription.mpStatus === MP_SUBS_STATUS.CANCELLED) &&
|
||||
dayjs(subscription.endDate).isAfter(new Date());
|
||||
|
||||
if (isActivePaidSubscription && actualPlan.id !== data.planId) {
|
||||
if (plan.price <= actualPlan.price) {
|
||||
throw new Error("No se puede cambiar a un plan igual o inferior desde esta ruta de actualización.");
|
||||
}
|
||||
|
||||
subscription = await this.toMP({
|
||||
planId: data.planId,
|
||||
sessionUser: data.sessionUser,
|
||||
months: data.months || 1,
|
||||
});
|
||||
} else {
|
||||
if (subscription.mpStatus != MP_SUBS_STATUS.CANCELLED && actualPlan.id !== data.planId) {
|
||||
throw new Error(
|
||||
"No se puede cambiar el plan, ya que el usuario ya tiene una suscripción activa. Para cambiarlo primero debes cancelar tu plan actual."
|
||||
@@ -201,6 +235,7 @@ class PlanSuscriptionsManager implements IPlanSuscriptionsManager {
|
||||
sessionUser: data.sessionUser,
|
||||
months: data.months || 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -249,6 +284,42 @@ class PlanSuscriptionsManager implements IPlanSuscriptionsManager {
|
||||
sessionUser: data.sessionUser,
|
||||
});
|
||||
|
||||
const currentPlan = currentSubscription
|
||||
? await PlansList.plans.findOne({ _id: String(currentSubscription.planId) })
|
||||
: null;
|
||||
|
||||
const isUpgradePayment =
|
||||
currentSubscription &&
|
||||
currentPlan &&
|
||||
String(currentSubscription.planId) !== String(data.planId) &&
|
||||
currentPlan.price > 0 &&
|
||||
plan.price > currentPlan.price &&
|
||||
(currentSubscription.mpStatus === MP_SUBS_STATUS.AUTHORIZED ||
|
||||
currentSubscription.mpStatus === MP_SUBS_STATUS.CANCELLED) &&
|
||||
dayjs(currentSubscription.endDate).isAfter(preferenceDate);
|
||||
|
||||
const remainingDays = isUpgradePayment
|
||||
? Math.max(0, Math.ceil((new Date(currentSubscription.endDate).getTime() - preferenceDate.toDate().getTime()) / DAY_MS))
|
||||
: 0;
|
||||
const proratedAmount = isUpgradePayment
|
||||
? calculateProratedUpgradeAmount(currentPlan.price, plan.price, remainingDays)
|
||||
: 0;
|
||||
|
||||
if (isUpgradePayment && remainingDays <= 0) {
|
||||
throw new Error("No se puede actualizar el plan porque la suscripción actual no tiene días restantes.");
|
||||
}
|
||||
|
||||
if (
|
||||
currentSubscription?.pendingPaymentPreferenceId &&
|
||||
currentSubscription.pendingPaymentInitPoint
|
||||
) {
|
||||
if (String(currentSubscription.pendingPaymentPlanId || currentSubscription.planId) === String(data.planId)) {
|
||||
return currentSubscription;
|
||||
}
|
||||
|
||||
throw new Error("Ya tenés un pago pendiente para tu plan. Verificalo o esperá a que se actualice antes de intentar otro cambio.");
|
||||
}
|
||||
|
||||
if (
|
||||
currentSubscription &&
|
||||
String(currentSubscription.planId) === String(data.planId) &&
|
||||
@@ -265,16 +336,18 @@ class PlanSuscriptionsManager implements IPlanSuscriptionsManager {
|
||||
{
|
||||
items: [
|
||||
{
|
||||
title: "TurnosXpress.com.ar - Plan " + plan.name + " (" + data.months + " meses)",
|
||||
title: isUpgradePayment
|
||||
? "TurnosXpress.com.ar - Upgrade al plan " + plan.name
|
||||
: "TurnosXpress.com.ar - Plan " + plan.name + " (" + data.months + " meses)",
|
||||
quantity: 1,
|
||||
currency_id: "ARS",
|
||||
unit_price: finalPrice
|
||||
unit_price: isUpgradePayment ? proratedAmount : finalPrice
|
||||
}
|
||||
],
|
||||
back_urls: {
|
||||
success: process.env.MP_BACK_URL,
|
||||
failure: process.env.MP_BACK_URL,
|
||||
pending: process.env.MP_BACK_URL
|
||||
success: this.buildMercadoPagoBackUrl("success"),
|
||||
failure: this.buildMercadoPagoBackUrl("failure"),
|
||||
pending: this.buildMercadoPagoBackUrl("pending")
|
||||
},
|
||||
auto_return: "approved",
|
||||
payer: {
|
||||
@@ -293,6 +366,35 @@ class PlanSuscriptionsManager implements IPlanSuscriptionsManager {
|
||||
|
||||
const subscriptionData = response.data;
|
||||
|
||||
if (isUpgradePayment && currentSubscription && currentPlan) {
|
||||
currentSubscription.pendingPaymentInitPoint = subscriptionData.init_point;
|
||||
currentSubscription.pendingPaymentPreferenceId = subscriptionData.id;
|
||||
currentSubscription.pendingPaymentBillingMonths = undefined;
|
||||
currentSubscription.pendingPaymentType = "upgrade";
|
||||
currentSubscription.pendingPaymentPlanId = data.planId;
|
||||
currentSubscription.pendingPaymentCurrentPlanPrice = currentPlan.price;
|
||||
currentSubscription.pendingPaymentRequestedPlanPrice = plan.price;
|
||||
currentSubscription.pendingPaymentProratedAmount = proratedAmount;
|
||||
currentSubscription.pendingPaymentRemainingDays = remainingDays;
|
||||
currentSubscription.pendingPaymentPeriodEndDate = currentSubscription.endDate;
|
||||
currentSubscription.lastPaymentStatus = undefined;
|
||||
currentSubscription.lastPaymentPreferenceId = undefined;
|
||||
currentSubscription.lastPaymentAt = undefined;
|
||||
currentSubscription.lastPaymentType = undefined;
|
||||
currentSubscription.mpInitPoint = subscriptionData.init_point;
|
||||
await currentSubscription.save();
|
||||
|
||||
await NotificationsManager.sendSystemNotification({
|
||||
userId: String(data.sessionUser),
|
||||
subject: "Se ha creado una actualización de suscripción",
|
||||
message: `Se ha creado una actualización al plan ${plan.name}.`,
|
||||
type: NotificationType.BILLING,
|
||||
code: String(currentSubscription._id)
|
||||
});
|
||||
|
||||
return currentSubscription;
|
||||
}
|
||||
|
||||
if (
|
||||
currentSubscription &&
|
||||
String(currentSubscription.planId) === String(data.planId) &&
|
||||
@@ -302,6 +404,11 @@ class PlanSuscriptionsManager implements IPlanSuscriptionsManager {
|
||||
currentSubscription.pendingPaymentInitPoint = subscriptionData.init_point;
|
||||
currentSubscription.pendingPaymentPreferenceId = subscriptionData.id;
|
||||
currentSubscription.pendingPaymentBillingMonths = data.months;
|
||||
currentSubscription.pendingPaymentType = "extension";
|
||||
currentSubscription.lastPaymentStatus = undefined;
|
||||
currentSubscription.lastPaymentPreferenceId = undefined;
|
||||
currentSubscription.lastPaymentAt = undefined;
|
||||
currentSubscription.lastPaymentType = undefined;
|
||||
currentSubscription.mpInitPoint = subscriptionData.init_point;
|
||||
await currentSubscription.save();
|
||||
|
||||
@@ -335,6 +442,11 @@ class PlanSuscriptionsManager implements IPlanSuscriptionsManager {
|
||||
pendingPaymentInitPoint: "",
|
||||
pendingPaymentPreferenceId: "",
|
||||
pendingPaymentBillingMonths: undefined,
|
||||
pendingPaymentType: undefined,
|
||||
lastPaymentStatus: undefined,
|
||||
lastPaymentPreferenceId: undefined,
|
||||
lastPaymentAt: undefined,
|
||||
lastPaymentType: undefined,
|
||||
});
|
||||
|
||||
await NotificationsManager.sendSystemNotification({
|
||||
@@ -385,7 +497,7 @@ class PlanSuscriptionsManager implements IPlanSuscriptionsManager {
|
||||
const dateEnd = dayjs(subscription.endDate);
|
||||
|
||||
if (dateNow.isSameOrAfter(dateEnd)) {
|
||||
this.toFreePlan({
|
||||
await this.toFreePlan({
|
||||
sessionUser: data.sessionUser,
|
||||
});
|
||||
} else {
|
||||
@@ -393,6 +505,16 @@ class PlanSuscriptionsManager implements IPlanSuscriptionsManager {
|
||||
subscription.mpInitPoint = "";
|
||||
subscription.mpPayerEmail = "";
|
||||
subscription.mpPayerId = -1;
|
||||
subscription.pendingPaymentInitPoint = "";
|
||||
subscription.pendingPaymentPreferenceId = "";
|
||||
subscription.pendingPaymentBillingMonths = undefined;
|
||||
subscription.pendingPaymentType = undefined;
|
||||
subscription.pendingPaymentPlanId = undefined;
|
||||
subscription.pendingPaymentCurrentPlanPrice = undefined;
|
||||
subscription.pendingPaymentRequestedPlanPrice = undefined;
|
||||
subscription.pendingPaymentProratedAmount = undefined;
|
||||
subscription.pendingPaymentRemainingDays = undefined;
|
||||
subscription.pendingPaymentPeriodEndDate = undefined;
|
||||
await subscription.save();
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -462,6 +584,13 @@ class PlanSuscriptionsManager implements IPlanSuscriptionsManager {
|
||||
subscription.pendingPaymentInitPoint = "";
|
||||
subscription.pendingPaymentPreferenceId = "";
|
||||
subscription.pendingPaymentBillingMonths = undefined;
|
||||
subscription.pendingPaymentType = undefined;
|
||||
subscription.pendingPaymentPlanId = undefined;
|
||||
subscription.pendingPaymentCurrentPlanPrice = undefined;
|
||||
subscription.pendingPaymentRequestedPlanPrice = undefined;
|
||||
subscription.pendingPaymentProratedAmount = undefined;
|
||||
subscription.pendingPaymentRemainingDays = undefined;
|
||||
subscription.pendingPaymentPeriodEndDate = undefined;
|
||||
subscription.startDate = new Date();
|
||||
subscription.endDate = dayjs(new Date()).add(10, "years").toDate();
|
||||
|
||||
@@ -488,6 +617,10 @@ class PlanSuscriptionsManager implements IPlanSuscriptionsManager {
|
||||
autoRenew: subscription.autoRenew,
|
||||
mpStatus: subscription.mpStatus,
|
||||
mpDateCreated: subscription.mpDateCreated,
|
||||
lastPaymentStatus: subscription.lastPaymentStatus,
|
||||
lastPaymentPreferenceId: subscription.lastPaymentPreferenceId,
|
||||
lastPaymentAt: subscription.lastPaymentAt,
|
||||
lastPaymentType: subscription.lastPaymentType,
|
||||
};
|
||||
|
||||
io.to(`user:${String(subscription.userId)}`).emit("suscription_status_updated", susInfo);
|
||||
@@ -539,7 +672,9 @@ class PlanSuscriptionsManager implements IPlanSuscriptionsManager {
|
||||
_id: subscription.planId,
|
||||
});
|
||||
|
||||
if (!subscription.isActive || subscription.mpStatus === MP_SUBS_STATUS.PENDING) {
|
||||
const isPendingPaidSubscription = subscription.mpStatus === MP_SUBS_STATUS.PENDING && plan.price > 0;
|
||||
|
||||
if (!isPendingPaidSubscription && (!subscription.isActive || subscription.mpStatus === MP_SUBS_STATUS.PENDING)) {
|
||||
const freePlan = await PlansList.findOne({ price: 0 });
|
||||
if (freePlan) {
|
||||
plan = freePlan;
|
||||
@@ -559,6 +694,13 @@ class PlanSuscriptionsManager implements IPlanSuscriptionsManager {
|
||||
autoRenew: subscription.autoRenew,
|
||||
mpStatus: subscription.mpStatus,
|
||||
mpDateCreated: subscription.mpDateCreated,
|
||||
pendingPaymentInitPoint: subscription.pendingPaymentInitPoint,
|
||||
pendingPaymentPreferenceId: subscription.pendingPaymentPreferenceId,
|
||||
pendingPaymentType: subscription.pendingPaymentType,
|
||||
lastPaymentStatus: subscription.lastPaymentStatus,
|
||||
lastPaymentPreferenceId: subscription.lastPaymentPreferenceId,
|
||||
lastPaymentAt: subscription.lastPaymentAt,
|
||||
lastPaymentType: subscription.lastPaymentType,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,14 @@
|
||||
import dayjs from "dayjs";
|
||||
import { calculateProratedUpgradeAmount } from "../PlanSubscriptions.pricing";
|
||||
import PlanSubscriptionsList from "../PlanSubscriptons";
|
||||
import { MP_SUBS_STATUS } from "../PlanSubscriptions.Adapter.Mongoose";
|
||||
import PlansList from "../../Plans/Plans";
|
||||
|
||||
jest.mock("../../../index", () => ({
|
||||
io: {
|
||||
to: jest.fn(() => ({ emit: jest.fn() })),
|
||||
},
|
||||
}));
|
||||
|
||||
describe("PlanSubscriptions Logic", () => {
|
||||
it("should calculate correct endDate for 1, 3, 6, 12 months", () => {
|
||||
@@ -40,4 +50,71 @@ describe("PlanSubscriptions Logic", () => {
|
||||
const final12 = total12 - (total12 * (30 / 100)); // 12000 - 3600 = 8400
|
||||
expect(final12).toBe(8400);
|
||||
});
|
||||
|
||||
it("should calculate only the prorated difference for paid plan upgrades", () => {
|
||||
const currentPlanPrice = 1000;
|
||||
const requestedPlanPrice = 2500;
|
||||
const remainingDays = 15;
|
||||
|
||||
const proratedAmount = calculateProratedUpgradeAmount(
|
||||
currentPlanPrice,
|
||||
requestedPlanPrice,
|
||||
remainingDays
|
||||
);
|
||||
|
||||
expect(proratedAmount).toBe(750);
|
||||
});
|
||||
|
||||
it("should keep pending paid subscription plan instead of masking it as free", async () => {
|
||||
const paidPlan = {
|
||||
id: "paid-plan-id",
|
||||
name: "Intermedio",
|
||||
description: "",
|
||||
features: [],
|
||||
code: "intermediate",
|
||||
price: 1000,
|
||||
annualPrice: 0,
|
||||
limitOrganizations: 1,
|
||||
limitEmployees: 1,
|
||||
limitServices: 1,
|
||||
limitAppointments: 1,
|
||||
limitClients: 1,
|
||||
limitRepeats: 1,
|
||||
mailNotifications: false,
|
||||
smsNotifications: false,
|
||||
wapNotifications: false,
|
||||
bot: false,
|
||||
active: true,
|
||||
dateLimit: false,
|
||||
payments: false,
|
||||
};
|
||||
const subscription = {
|
||||
id: "subscription-id",
|
||||
planId: paidPlan.id,
|
||||
startDate: new Date(),
|
||||
endDate: new Date(),
|
||||
isActive: false,
|
||||
autoRenew: false,
|
||||
mpStatus: MP_SUBS_STATUS.PENDING,
|
||||
mpDateCreated: new Date(),
|
||||
};
|
||||
|
||||
const originalPlanSuscriptions = PlanSubscriptionsList.planSuscriptions;
|
||||
const findOneSpy = jest.spyOn(PlansList, "findOne").mockResolvedValue(paidPlan);
|
||||
PlanSubscriptionsList.planSuscriptions = {
|
||||
findOne: jest.fn().mockResolvedValue(subscription),
|
||||
} as any;
|
||||
|
||||
try {
|
||||
const result = await PlanSubscriptionsList.getSubscriptionByUser({ sessionUser: "user-id" });
|
||||
|
||||
expect(result?.plan).toEqual(paidPlan);
|
||||
expect(result?.mpStatus).toBe(MP_SUBS_STATUS.PENDING);
|
||||
expect(findOneSpy).toHaveBeenCalledTimes(1);
|
||||
expect(findOneSpy).toHaveBeenCalledWith({ _id: subscription.planId });
|
||||
} finally {
|
||||
PlanSubscriptionsList.planSuscriptions = originalPlanSuscriptions;
|
||||
findOneSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user