diff --git a/lib/utils/hasValue.ts b/lib/utils/hasValue.ts index 6bbc44ab..5a74a93f 100644 --- a/lib/utils/hasValue.ts +++ b/lib/utils/hasValue.ts @@ -1,5 +1,6 @@ /** * Returns true if specified value is not undefined, null and empty string + * * @param v - value to check */ export function hasValue(v: T | undefined | null): v is T { diff --git a/workers/paymaster/src/index.ts b/workers/paymaster/src/index.ts index 10009ce6..0ce4d681 100644 --- a/workers/paymaster/src/index.ts +++ b/workers/paymaster/src/index.ts @@ -60,24 +60,47 @@ export default class PaymasterWorker extends Worker { /** * Check if today is a payday for passed timestamp * - * Pay day is calculated by formula: last charge date + 30 days - * * @param date - last charge date * @param paidUntil - paid until date * @param isDebug - flag for debug purposes */ - private static isTimeToPay(date: Date, paidUntil: Date, isDebug = false): boolean { - const expectedPayDay = paidUntil ? new Date(paidUntil) : new Date(date); + private static isTimeToPay(date: Date, paidUntil?: Date, isDebug = false): boolean { + const expectedPayDay = paidUntil ? new Date(paidUntil) : this.composeBillingPeriodEndDate(date, isDebug); + const now = new Date(); + + return now >= expectedPayDay; + } + + /** + * Check if today is a recharge day for passed timestamp + * It equals to the isTimeToPay in all cases except prePaid workspaces + * + * @param date - last charge date + * @param isDebug - flag for debug purposes + */ + private static isTimeToRecharge(date: Date, isDebug = false): boolean { + const nexTimeToRecharge = this.composeBillingPeriodEndDate(date, isDebug); + const now = new Date(); + + return now >= nexTimeToRecharge; + } + + /** + * Returns the date - end of the billing period + * + * @param date - last charge date + * @param isDebug - flag for debug workspaces + */ + private static composeBillingPeriodEndDate(date: Date, isDebug = false): Date { + const endDate = new Date(date); if (isDebug) { - expectedPayDay.setDate(date.getDate() + 1); - } else if (!paidUntil) { - expectedPayDay.setMonth(date.getMonth() + 1); + endDate.setDate(date.getDate() + 1); + } else { + endDate.setMonth(date.getMonth() + 1); } - const now = new Date().getTime(); - - return now >= expectedPayDay.getTime(); + return endDate; } /** @@ -214,6 +237,12 @@ export default class PaymasterWorker extends Worker { // @ts-expect-error debug const isTimeToPay = PaymasterWorker.isTimeToPay(workspace.lastChargeDate, workspace.paidUntil, workspace.isDebug); + /** + * Is it time to recharge workspace limits + */ + // @ts-expect-error debug + const isTimeToRecharge = PaymasterWorker.isTimeToRecharge(workspace.lastChargeDate, workspace.isDebug); + /** * How many days have passed since payments the expected day of payments */ @@ -237,7 +266,17 @@ export default class PaymasterWorker extends Worker { */ if (!isTimeToPay) { /** - * If workspace was manually unblocked (reset of billingPeriodEventsCount and lastChargeDate) in db + * If it is time to recharge workspace limits, but not time to pay + * Start new month - recharge billing period events count and update last charge date + */ + if (isTimeToRecharge) { + await this.updateLastChargeDate(workspace, date); + await this.clearBillingPeriodEventsCount(workspace); + } + + /** + * If workspace is blocked, but it is not time to pay + * This case could be reached by prepaid workspaces and manually recharged ones */ if (workspace.isBlocked) { await this.unblockWorkspace(workspace); diff --git a/workers/paymaster/tests/index.test.ts b/workers/paymaster/tests/index.test.ts index a4cc33f5..74f52e1f 100644 --- a/workers/paymaster/tests/index.test.ts +++ b/workers/paymaster/tests/index.test.ts @@ -481,11 +481,6 @@ describe('PaymasterWorker', () => { MockDate.reset(); }); - afterAll(async () => { - await connection.close(); - MockDate.reset(); - }); - test('Should send notification if payday is coming for workspace with paidUntil value', async () => { /** * Arrange @@ -542,4 +537,62 @@ describe('PaymasterWorker', () => { MockDate.reset(); }); + + test('Should recharge workspace billing period when month passes since last charge date and paidUntil is set to several months in the future', async () => { + /** + * Arrange + */ + const currentDate = new Date(); + const lastChargeDate = new Date(currentDate.getTime()); + + lastChargeDate.setMonth(lastChargeDate.getMonth() - 1); // Set last charge date to 1 month ago + + const paidUntil = new Date(currentDate.getTime()); + + paidUntil.setMonth(paidUntil.getMonth() + 3); // Set paidUntil to 3 months in the future + + const plan = createPlanMock({ + monthlyCharge: 100, + isDefault: true, + }); + const workspace = createWorkspaceMock({ + plan, + subscriptionId: null, + lastChargeDate, + isBlocked: false, + billingPeriodEventsCount: 10, + paidUntil, + }); + + await fillDatabaseWithMockedData({ + workspace, + plan, + }); + + MockDate.set(currentDate); + + /** + * Act + */ + const worker = new PaymasterWorker(); + + await worker.start(); + await worker.handle(WORKSPACE_SUBSCRIPTION_CHECK); + await worker.finish(); + + /** + * Assert + */ + const updatedWorkspace = await workspacesCollection.findOne({ _id: workspace._id }); + + expect(updatedWorkspace.lastChargeDate).toEqual(currentDate); + expect(updatedWorkspace.billingPeriodEventsCount).toEqual(0); + + MockDate.reset(); + }); + + afterAll(async () => { + await connection.close(); + MockDate.reset(); + }); });