Skip to content

Commit 2cfc8fe

Browse files
authored
feat(paymaster): add paidUntil recharge logic (#416)
* feat(paymaster): add paidUntil recharge logic * test(paymaster): cover paidUntil recharge logic * imp(paymaster): isTimeToRecharge added * fix(): expect ts error * imp(): comment
1 parent dbaf3d0 commit 2cfc8fe

3 files changed

Lines changed: 109 additions & 16 deletions

File tree

lib/utils/hasValue.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/**
22
* Returns true if specified value is not undefined, null and empty string
3+
*
34
* @param v - value to check
45
*/
56
export function hasValue<T>(v: T | undefined | null): v is T {

workers/paymaster/src/index.ts

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -60,24 +60,47 @@ export default class PaymasterWorker extends Worker {
6060
/**
6161
* Check if today is a payday for passed timestamp
6262
*
63-
* Pay day is calculated by formula: last charge date + 30 days
64-
*
6563
* @param date - last charge date
6664
* @param paidUntil - paid until date
6765
* @param isDebug - flag for debug purposes
6866
*/
69-
private static isTimeToPay(date: Date, paidUntil: Date, isDebug = false): boolean {
70-
const expectedPayDay = paidUntil ? new Date(paidUntil) : new Date(date);
67+
private static isTimeToPay(date: Date, paidUntil?: Date, isDebug = false): boolean {
68+
const expectedPayDay = paidUntil ? new Date(paidUntil) : this.composeBillingPeriodEndDate(date, isDebug);
69+
const now = new Date();
70+
71+
return now >= expectedPayDay;
72+
}
73+
74+
/**
75+
* Check if today is a recharge day for passed timestamp
76+
* It equals to the isTimeToPay in all cases except prePaid workspaces
77+
*
78+
* @param date - last charge date
79+
* @param isDebug - flag for debug purposes
80+
*/
81+
private static isTimeToRecharge(date: Date, isDebug = false): boolean {
82+
const nexTimeToRecharge = this.composeBillingPeriodEndDate(date, isDebug);
83+
const now = new Date();
84+
85+
return now >= nexTimeToRecharge;
86+
}
87+
88+
/**
89+
* Returns the date - end of the billing period
90+
*
91+
* @param date - last charge date
92+
* @param isDebug - flag for debug workspaces
93+
*/
94+
private static composeBillingPeriodEndDate(date: Date, isDebug = false): Date {
95+
const endDate = new Date(date);
7196

7297
if (isDebug) {
73-
expectedPayDay.setDate(date.getDate() + 1);
74-
} else if (!paidUntil) {
75-
expectedPayDay.setMonth(date.getMonth() + 1);
98+
endDate.setDate(date.getDate() + 1);
99+
} else {
100+
endDate.setMonth(date.getMonth() + 1);
76101
}
77102

78-
const now = new Date().getTime();
79-
80-
return now >= expectedPayDay.getTime();
103+
return endDate;
81104
}
82105

83106
/**
@@ -214,6 +237,12 @@ export default class PaymasterWorker extends Worker {
214237
// @ts-expect-error debug
215238
const isTimeToPay = PaymasterWorker.isTimeToPay(workspace.lastChargeDate, workspace.paidUntil, workspace.isDebug);
216239

240+
/**
241+
* Is it time to recharge workspace limits
242+
*/
243+
// @ts-expect-error debug
244+
const isTimeToRecharge = PaymasterWorker.isTimeToRecharge(workspace.lastChargeDate, workspace.isDebug);
245+
217246
/**
218247
* How many days have passed since payments the expected day of payments
219248
*/
@@ -237,7 +266,17 @@ export default class PaymasterWorker extends Worker {
237266
*/
238267
if (!isTimeToPay) {
239268
/**
240-
* If workspace was manually unblocked (reset of billingPeriodEventsCount and lastChargeDate) in db
269+
* If it is time to recharge workspace limits, but not time to pay
270+
* Start new month - recharge billing period events count and update last charge date
271+
*/
272+
if (isTimeToRecharge) {
273+
await this.updateLastChargeDate(workspace, date);
274+
await this.clearBillingPeriodEventsCount(workspace);
275+
}
276+
277+
/**
278+
* If workspace is blocked, but it is not time to pay
279+
* This case could be reached by prepaid workspaces and manually recharged ones
241280
*/
242281
if (workspace.isBlocked) {
243282
await this.unblockWorkspace(workspace);

workers/paymaster/tests/index.test.ts

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -481,11 +481,6 @@ describe('PaymasterWorker', () => {
481481
MockDate.reset();
482482
});
483483

484-
afterAll(async () => {
485-
await connection.close();
486-
MockDate.reset();
487-
});
488-
489484
test('Should send notification if payday is coming for workspace with paidUntil value', async () => {
490485
/**
491486
* Arrange
@@ -542,4 +537,62 @@ describe('PaymasterWorker', () => {
542537

543538
MockDate.reset();
544539
});
540+
541+
test('Should recharge workspace billing period when month passes since last charge date and paidUntil is set to several months in the future', async () => {
542+
/**
543+
* Arrange
544+
*/
545+
const currentDate = new Date();
546+
const lastChargeDate = new Date(currentDate.getTime());
547+
548+
lastChargeDate.setMonth(lastChargeDate.getMonth() - 1); // Set last charge date to 1 month ago
549+
550+
const paidUntil = new Date(currentDate.getTime());
551+
552+
paidUntil.setMonth(paidUntil.getMonth() + 3); // Set paidUntil to 3 months in the future
553+
554+
const plan = createPlanMock({
555+
monthlyCharge: 100,
556+
isDefault: true,
557+
});
558+
const workspace = createWorkspaceMock({
559+
plan,
560+
subscriptionId: null,
561+
lastChargeDate,
562+
isBlocked: false,
563+
billingPeriodEventsCount: 10,
564+
paidUntil,
565+
});
566+
567+
await fillDatabaseWithMockedData({
568+
workspace,
569+
plan,
570+
});
571+
572+
MockDate.set(currentDate);
573+
574+
/**
575+
* Act
576+
*/
577+
const worker = new PaymasterWorker();
578+
579+
await worker.start();
580+
await worker.handle(WORKSPACE_SUBSCRIPTION_CHECK);
581+
await worker.finish();
582+
583+
/**
584+
* Assert
585+
*/
586+
const updatedWorkspace = await workspacesCollection.findOne({ _id: workspace._id });
587+
588+
expect(updatedWorkspace.lastChargeDate).toEqual(currentDate);
589+
expect(updatedWorkspace.billingPeriodEventsCount).toEqual(0);
590+
591+
MockDate.reset();
592+
});
593+
594+
afterAll(async () => {
595+
await connection.close();
596+
MockDate.reset();
597+
});
545598
});

0 commit comments

Comments
 (0)