Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/utils/hasValue.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/**
* Returns true if specified value is not undefined, null and empty string
*
* @param v - value to check
*/
export function hasValue<T>(v: T | undefined | null): v is T {
Expand Down
61 changes: 50 additions & 11 deletions workers/paymaster/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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
*/
Expand All @@ -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);
Expand Down
63 changes: 58 additions & 5 deletions workers/paymaster/tests/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();
});
});
Loading