User Test Billing Data cleanup
MODIFY
src/integration/billing/BillingIntegration.ts
src/integration/billing/BillingIntegration.ts
@@ -319,8 +319,10 @@ export default abstract class BillingIntegration {
private async _getUsersWithNoBillingData(): Promise<User[]> {
const newUsers = await UserStorage.getUsers(this.tenantID,
- { 'statuses': [UserStatus.ACTIVE], 'notSynchronizedBillingData': true },
- { ...Constants.DB_PARAMS_MAX_LIMIT, sort: { 'userID': 1 } });
+ {
+ statuses: [UserStatus.ACTIVE],
+ notSynchronizedBillingData: true
+ }, Constants.DB_PARAMS_MAX_LIMIT);
if (newUsers.count > 0) {
return newUsers.result;
}
@@ -376,7 +378,15 @@ export default abstract class BillingIntegration {
source: Constants.CENTRAL_SERVER,
action: ServerAction.BILLING_TEST_DATA_CLEANUP,
module: MODULE_NAME, method: '_clearAllInvoiceTestData',
- message: 'Test data cleanup has been completed'
+ message: 'Invoice Test data cleanup has been completed'
+ });
+ await this._clearAllUsersTestData();
+ await Logging.logInfo({
+ tenantID: this.tenantID,
+ source: Constants.CENTRAL_SERVER,
+ action: ServerAction.BILLING_TEST_DATA_CLEANUP,
+ module: MODULE_NAME, method: '_clearAllInvoiceTestData',
+ message: 'User Test data cleanup has been completed'
});
}
@@ -401,7 +411,7 @@ export default abstract class BillingIntegration {
action: ServerAction.BILLING_TEST_DATA_CLEANUP,
actionOnUser: invoice.user,
module: MODULE_NAME, method: '_clearAllInvoiceTestData',
- message: `Failed to clear invoice test data '${invoice.id}'`,
+ message: `Failed to clear invoice test data - Invoice: '${invoice.id}'`,
detailedMessages: { error: error.message, stack: error.stack }
});
}
@@ -411,11 +421,11 @@ export default abstract class BillingIntegration {
private async _clearInvoiceTestData(billingInvoice: BillingInvoice): Promise<void> {
if (billingInvoice.liveMode) {
throw new BackendError({
- message: 'User is not provided',
+ message: 'Unexpected situation - attempt to clear an invoice with live billing data',
source: Constants.CENTRAL_SERVER,
module: MODULE_NAME,
method: '_clearInvoiceTestData',
- action: ServerAction.BILLING_PERFORM_OPERATIONS
+ action: ServerAction.BILLING_TEST_DATA_CLEANUP
});
}
await this._clearTransactionsTestData(billingInvoice);
@@ -454,6 +464,61 @@ export default abstract class BillingIntegration {
}));
}
+ private async _clearAllUsersTestData(): Promise<void> {
+ const users: User[] = await this._getUsersWithTestBillingData();
+ // Let's now finalize all invoices and attempt to get it paid
+ for (const user of users) {
+ try {
+ await this._clearUserTestBillingData(user);
+ await Logging.logInfo({
+ tenantID: this.tenantID,
+ source: Constants.CENTRAL_SERVER,
+ action: ServerAction.BILLING_TEST_DATA_CLEANUP,
+ actionOnUser: user,
+ module: MODULE_NAME, method: '_clearAllUsersTestData',
+ message: `Successfully cleared user test data for invoice '${user.id}'`
+ });
+ } catch (error) {
+ await Logging.logError({
+ tenantID: this.tenantID,
+ source: Constants.CENTRAL_SERVER,
+ action: ServerAction.BILLING_TEST_DATA_CLEANUP,
+ actionOnUser: user,
+ module: MODULE_NAME, method: '_clearAllUsersTestData',
+ message: `Failed to clear user test data - User: '${user.id}'`,
+ detailedMessages: { error: error.message, stack: error.stack }
+ });
+ }
+ }
+ }
+
+ private async _getUsersWithTestBillingData(): Promise<User[]> {
+ // Get the users where billingData.liveMode is set to false
+ const users = await UserStorage.getUsers(this.tenantID,
+ {
+ statuses: [UserStatus.ACTIVE],
+ withTestBillingData: true
+ }, Constants.DB_PARAMS_MAX_LIMIT);
+ if (users.count > 0) {
+ return users.result;
+ }
+ return [];
+ }
+
+ private async _clearUserTestBillingData(user: User): Promise<void> {
+ if (user?.billingData?.liveMode) {
+ throw new BackendError({
+ message: 'Unexpected situation - attempt to clear a user with live billing data',
+ source: Constants.CENTRAL_SERVER,
+ module: MODULE_NAME,
+ method: '_clearUserTestBillingData',
+ action: ServerAction.BILLING_TEST_DATA_CLEANUP
+ });
+ }
+ // Let's remove the billingData field
+ await UserStorage.saveUserBillingData(this.tenantID, user.id, null);
+ }
+
abstract checkConnection(): Promise<void>;
abstract checkActivationPrerequisites(): Promise<void>;

MODIFY
src/storage/mongodb/UserStorage.ts
src/storage/mongodb/UserStorage.ts
@@ -485,6 +485,7 @@ export default class UserStorage {
const uniqueTimerID = Logging.traceStart(tenantID, MODULE_NAME, 'saveUserBillingData');
// Check Tenant
await DatabaseUtils.checkTenant(tenantID);
+ if (billingData) {
// Set data
const updatedUserMDB: any = {
billingData: {
@@ -499,6 +500,11 @@ export default class UserStorage {
await global.database.getCollection(tenantID, 'users').findOneAndUpdate(
{ '_id': Utils.convertToObjectID(userID) },
{ $set: updatedUserMDB });
+ } else {
+ await global.database.getCollection(tenantID, 'users').findOneAndUpdate(
+ { '_id': Utils.convertToObjectID(userID) },
+ { $unset: { billingData: '' } }); // This removes the field from the document
+ }
// Debug
await Logging.traceEnd(tenantID, MODULE_NAME, 'saveUserBillingData', uniqueTimerID, billingData);
}
@@ -532,7 +538,8 @@ export default class UserStorage {
notificationsActive?: boolean; siteIDs?: string[]; excludeSiteID?: string; search?: string;
includeCarUserIDs?: string[]; excludeUserIDs?: string[]; notAssignedToCarID?: string;
userIDs?: string[]; email?: string; issuer?: boolean; passwordResetHash?: string; roles?: string[];
- statuses?: string[]; withImage?: boolean; billingUserID?: string; notSynchronizedBillingData?: boolean;
+ statuses?: string[]; withImage?: boolean; billingUserID?: string;
+ notSynchronizedBillingData?: boolean; withTestBillingData?: boolean;
notifications?: any; noLoginSince?: Date; tagIDs?: string[];
},
dbParams: DbParams, projectFields?: string[]): Promise<DataResult<User>> {
@@ -613,6 +620,14 @@ export default class UserStorage {
{ $expr: { $gt: ['$lastChangedOn', '$billingData.lastChangedOn'] } }
];
}
+ // Select users with test billing data
+ if (params.withTestBillingData) {
+ const expectedLiveMode = !params.withTestBillingData;
+ filters.$and = [
+ { 'billingData': { '$exists': true } },
+ { 'billingData.liveMode': { $eq: expectedLiveMode } }
+ ];
+ }
// Add filters
aggregation.push({
$match: filters

MODIFY
test/api/BillingStripeTestData.ts
test/api/BillingStripeTestData.ts
@@ -290,10 +290,20 @@ export default class StripeIntegrationTestData {
}
public async checkNoInvoices() : Promise<void> {
- const paging = TestConstants.DEFAULT_PAGING;
- const ordering = [{ field: '-createdOn' }];
- const response = await this.adminUserService.billingApi.readInvoices({}, paging, ordering);
- assert(response?.data?.result.length === 0, 'There should be no invoices anymore');
+ const response = await this.adminUserService.billingApi.readInvoices({}, { limit: 1, skip: 0 });
+ assert(response?.data?.result.length === 0, 'There should be no invoices with test billing data anymore');
+ }
+
+ public async checkNoUsersWithTestData() : Promise<void> {
+ // const response = await this.adminUserService.userApi.readAll({ withTestBillingData: true }, { limit: 1, skip: 0 });
+ // assert(response?.data?.result.length === 0, 'There should be no users with test billing data anymore');
+ const response = await UserStorage.getUsers(this.getTenantID(), {
+ withTestBillingData: true
+ }, {
+ limit: 1,
+ skip: 0
+ }, ['id']);
+ assert(response?.result.length === 0, 'There should be no invoices with test billing data anymore');
}
public async checkForDraftInvoices(userId: string, expectedValue: number): Promise<number> {
@@ -354,5 +364,6 @@ export default class StripeIntegrationTestData {
public async checkTestDataCleanup(): Promise<void> {
await this.billingImpl.clearTestData();
await this.checkNoInvoices();
+ await this.checkNoUsersWithTestData();
}
}

User Test Billing Data cleanup