refactor(subaccounts): pending bool to status enum
MODIFY
src/integration/billing/stripe/StripeBillingIntegration.ts
src/integration/billing/stripe/StripeBillingIntegration.ts
import { AsyncTaskType, AsyncTasks } from '../../../types/AsyncTask';
/* eslint-disable @typescript-eslint/member-ordering */
-import { BillingAccount, BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceItem, BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod, BillingStatus, BillingTax, BillingUser, BillingUserData } from '../../../types/Billing';
+import { BillingAccount, BillingAccountStatus, BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceItem, BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod, BillingStatus, BillingTax, BillingUser, BillingUserData } from '../../../types/Billing';
import { DimensionType, PricedConsumptionData, PricedDimensionData } from '../../../types/Pricing';
import FeatureToggles, { Feature } from '../../../utils/FeatureToggles';
import StripeHelpers, { StripeChargeOperationResult } from './StripeHelpers';
@@ -1722,7 +1722,7 @@ export default class StripeBillingIntegration extends BillingIntegration {
return {
accountID: subAccount.id,
activationLink: activationLink.url,
- pending: true
+ status: BillingAccountStatus.IDLE
};
}
}

MODIFY
src/server/rest/v1/service/BillingService.ts
src/server/rest/v1/service/BillingService.ts
import { Action, Entity } from '../../../../types/Authorization';
+import { BillingAccountStatus, BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod } from '../../../../types/Billing';
import { BillingInvoiceDataResult, BillingPaymentMethodDataResult, BillingSubaccountsDataResult } from '../../../../types/DataResult';
-import { BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod } from '../../../../types/Billing';
import { NextFunction, Request, Response } from 'express';
import AppError from '../../../../exception/AppError';
@@ -474,10 +474,10 @@ export default class BillingService {
public static async handleCreateSubAccount(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING_PLATFORM,
- Action.BILLING_CREATE_SUB_ACCOUNT, Entity.BILLING, MODULE_NAME, 'handleCreateSubAccount');
+ Action.BILLING_CREATE_SUB_ACCOUNT, Entity.BILLING_PLATFORM, MODULE_NAME, 'handleCreateSubAccount');
const filteredRequest = BillingValidatorRest.getInstance().validateBillingCreateSubAccountReq(req.body);
// Check authorization
- await AuthorizationService.checkAndGetBillingAuthorizations(req.tenant, req.user, Action.BILLING_CREATE_SUB_ACCOUNT);
+ await AuthorizationService.checkAndGetBillingPlatformAuthorizations(req.tenant, req.user, Action.BILLING_CREATE_SUB_ACCOUNT);
// Get the billing impl
const billingImpl = await BillingFactory.getBillingImpl(req.tenant);
if (!billingImpl) {
@@ -523,7 +523,17 @@ export default class BillingService {
const subAccount = await BillingStorage.getSubAccountByID(tenant, filteredRequest.ID);
UtilsService.assertObjectExists(action, subAccount, `Sub account ID '${filteredRequest.ID}' does not exist`, MODULE_NAME, 'handleActivateSubAccount', req.user);
// Check if the sub account is already activated
- if (!subAccount.pending) {
+ if (subAccount.status === BillingAccountStatus.IDLE) {
+ throw new AppError({
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: 'Sub account cannot be activated yet',
+ detailedMessages: 'Activation link has not been sent',
+ module: MODULE_NAME, method: 'handleActivateSubAccount',
+ action: action,
+ user: req.user
+ });
+ }
+ if (subAccount.status === BillingAccountStatus.ACTIVE) {
throw new AppError({
errorCode: HTTPError.GENERAL_ERROR,
message: 'Sub account is already activated',
@@ -533,7 +543,7 @@ export default class BillingService {
});
}
// Activate and save the sub account
- subAccount.pending = false;
+ subAccount.status = BillingAccountStatus.ACTIVE;
await BillingStorage.saveSubAccount(tenant, subAccount);
// Get the sub account owner
const user = await UserStorage.getUser(tenant, subAccount.userID);

MODIFY
src/storage/mongodb/BillingStorage.ts
src/storage/mongodb/BillingStorage.ts
@@ -224,7 +224,7 @@ export default class BillingStorage {
const subAccountMDB: any = {
_id: subAccount.id ? DatabaseUtils.convertToObjectID(subAccount.id) : new ObjectId(),
accountID: subAccount.accountID,
- pending: subAccount.pending,
+ status: subAccount.status,
userID: DatabaseUtils.convertToObjectID(subAccount.userID)
};
// Modify and return the modified document

MODIFY
src/types/Billing.ts
src/types/Billing.ts
@@ -153,12 +153,18 @@ export interface BillingAdditionalData {
lastError?: BillingError,
}
+export enum BillingAccountStatus {
+ IDLE = 'idle',
+ PENDING = 'pending',
+ ACTIVE = 'active'
+}
+
export interface BillingAccount {
id?: string;
accountID: string;
activationLink?: string;
userID?: string;
- pending: boolean;
+ status: BillingAccountStatus;
}
export interface BillingAccountSetting {

MODIFY
test/api/BillingTest.ts
test/api/BillingTest.ts
/* eslint-disable max-len */
-import { BillingChargeInvoiceAction, BillingInvoiceStatus } from '../../src/types/Billing';
+import { BillingAccount, BillingAccountStatus, BillingChargeInvoiceAction, BillingInvoiceStatus } from '../../src/types/Billing';
import { BillingSettings, BillingSettingsType } from '../../src/types/Setting';
import chai, { expect } from 'chai';
@@ -92,7 +92,7 @@ describeif(isBillingProperlyConfigured)('Billing', () => {
const subAccount = await stripeTestHelper.createSubAccount();
expect(subAccount.accountID).to.exist;
expect(subAccount.activationLink).to.include('https://connect.stripe.com/setup/s/');
- expect(subAccount.pending).to.be.true;
+ expect(subAccount.status).to.be.eq(BillingAccountStatus.IDLE);
});
});
@@ -1017,6 +1017,16 @@ describeif(isBillingProperlyConfigured)('Billing', () => {
expect(activationResponse.status).to.be.eq(StatusCodes.INTERNAL_SERVER_ERROR);
});
+ it('should not activate an idled sub-account', async () => {
+ const response = await billingTestHelper.userService.billingApi.createSubAccount({
+ userID: billingTestHelper.userContext.id
+ });
+ expect(response.status).to.be.eq(StatusCodes.CREATED);
+
+ const activationResponse = await billingTestHelper.userService.billingApi.activateSubAccount({ accountID: response.data.id, TenantID: billingTestHelper.tenantContext.getTenant().id });
+ expect(activationResponse.status).to.be.eq(StatusCodes.INTERNAL_SERVER_ERROR);
+ });
+
it('should create a company assigned to a sub-account', async () => {
const subAccountResponse = await billingTestHelper.userService.billingApi.createSubAccount({
userID: billingTestHelper.userContext.id
@@ -1104,6 +1114,18 @@ describeif(isBillingProperlyConfigured)('Billing', () => {
siteResponse = await billingTestHelper.userService.siteApi.readById(siteID);
expect(siteResponse.data.billingData.accountID).to.eq(subAccountResponse.data.id);
});
+
+ it('should list sub-accounts', async () => {
+ const subAccountResponse = await billingTestHelper.userService.billingApi.createSubAccount({
+ userID: billingTestHelper.userContext.id
+ });
+ expect(subAccountResponse.status).to.be.eq(StatusCodes.CREATED);
+
+ // List sub-accounts
+ const subAccountsResponse = await billingTestHelper.userService.billingApi.readAllSubAccounts({});
+ expect(subAccountsResponse.status).to.be.eq(StatusCodes.OK);
+ expect(subAccountsResponse.data.result.map((subAccount: BillingAccount) => subAccount.id)).to.include(subAccountResponse.data.id);
+ });
});
});
@@ -1130,6 +1152,11 @@ describeif(isBillingProperlyConfigured)('Billing', () => {
const activationResponse = await billingTestHelper.userService.billingApi.activateSubAccount({ accountID: '5ce249a1a39ae1c056c389bd', TenantID: billingTestHelper.tenantContext.getTenant().id });
expect(activationResponse.status).to.be.eq(StatusCodes.NOT_FOUND);
});
+
+ it('should not be able to list sub-accounts', async () => {
+ const subAccountsResponse = await billingTestHelper.userService.billingApi.readAllSubAccounts({});
+ expect(subAccountsResponse.status).to.be.eq(StatusCodes.FORBIDDEN);
+ });
});
});

MODIFY
test/api/client/BillingApi.ts
test/api/client/BillingApi.ts
@@ -59,4 +59,8 @@ export default class BillingApi extends CrudApi {
public async activateSubAccount(params) {
return super.read(params, super.buildUtilRestEndpointUrl(RESTServerRoute.REST_BILLING_SUB_ACCOUNT_ACTIVATE, { id: params.accountID }));
}
+
+ public async readAllSubAccounts(params, paging = TestConstants.DEFAULT_PAGING, ordering = TestConstants.DEFAULT_ORDERING) {
+ return super.readAll(params, paging, ordering, super.buildRestEndpointUrl(RESTServerRoute.REST_BILLING_SUB_ACCOUNTS));
+ }
}

refactor(subaccounts): pending bool to status enum