refactor(subaccount): get tenant from query param
MODIFY
src/assets/server/rest/v1/schemas/billing/billing-activate-sub-account.json
src/assets/server/rest/v1/schemas/billing/billing-activate-sub-account.json
"properties": {
"ID": {
"$ref": "common#/definitions/id"
+ },
+ "TenantID": {
+ "$ref": "common#/definitions/id"
}
},
"required": [
- "ID"
+ "ID",
+ "TenantID"
]
}

MODIFY
src/integration/billing/stripe/StripeBillingIntegration.ts
src/integration/billing/stripe/StripeBillingIntegration.ts
@@ -1706,7 +1706,7 @@ export default class StripeBillingIntegration extends BillingIntegration {
try {
activationLink = await this.stripe.accountLinks.create({
account: subAccount.id,
- return_url: Utils.buildEvseBillingSubAccountActivationURL(this.tenant.subdomain, subAccount.id),
+ return_url: Utils.buildEvseBillingSubAccountActivationURL(this.tenant, subAccount.id),
refresh_url: Utils.buildEvseURL(),
type: 'account_onboarding',
});

MODIFY
src/server/rest/v1/router/util/UtilRouter.ts
src/server/rest/v1/router/util/UtilRouter.ts
@@ -88,7 +88,7 @@ export default class UtilRouter {
}
private buildRouteBillingActivateSubAccount(): void {
- this.router.patch(`/${RESTServerRoute.REST_BILLING_SUB_ACCOUNT_ACTIVATE}`, (req: Request, res: Response, next: NextFunction) => {
+ this.router.get(`/${RESTServerRoute.REST_BILLING_SUB_ACCOUNT_ACTIVATE}`, (req: Request, res: Response, next: NextFunction) => {
req.params.ID = req.params.id;
void RouterUtils.handleRestServerAction(BillingService.handleActivateSubAccount.bind(this), ServerAction.BILLING_SUB_ACCOUNT_ACTIVATE, req, res, next);
});

MODIFY
src/server/rest/v1/service/BillingService.ts
src/server/rest/v1/service/BillingService.ts
@@ -20,6 +20,7 @@ import { ServerAction } from '../../../../types/Server';
import SettingStorage from '../../../../storage/mongodb/SettingStorage';
import { StatusCodes } from 'http-status-codes';
import { TenantComponents } from '../../../../types/Tenant';
+import TenantStorage from '../../../../storage/mongodb/TenantStorage';
import User from '../../../../types/User';
import UserStorage from '../../../../storage/mongodb/UserStorage';
import Utils from '../../../../utils/Utils';
@@ -473,7 +474,7 @@ 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,
- Action.CREATE, Entity.BILLING, MODULE_NAME, 'handleCreateSubAccount');
+ Action.BILLING_CREATE_SUB_ACCOUNT, Entity.BILLING, MODULE_NAME, 'handleCreateSubAccount');
const filteredRequest = BillingValidator.getInstance().validateBillingCreateSubAccountReq(req.body);
// Check authorization
await AuthorizationService.checkAndGetBillingAuthorizations(req.tenant, req.user, Action.BILLING_CREATE_SUB_ACCOUNT);
@@ -505,23 +506,22 @@ export default class BillingService {
}
public static async handleActivateSubAccount(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
- // Check if component is active
- UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING,
- Action.CREATE, Entity.BILLING, MODULE_NAME, 'handleActivateSubAccount');
- const filteredRequest = BillingValidator.getInstance().validateBillingActivateSubAccountReq(req.params);
+ const filteredRequest = BillingValidator.getInstance().validateBillingActivateSubAccountReq({ ...req.params, ...req.query });
+ const tenant = await TenantStorage.getTenant(filteredRequest.TenantID);
+ UtilsService.assertObjectExists(action, tenant, `Tenant ID '${filteredRequest.TenantID}' does not exist`, MODULE_NAME, 'handleActivateSubAccount');
// Get the billing impl
- const billingImpl = await BillingFactory.getBillingImpl(req.tenant);
+ const billingImpl = await BillingFactory.getBillingImpl(tenant);
if (!billingImpl) {
throw new AppError({
errorCode: HTTPError.GENERAL_ERROR,
message: 'Billing service is not configured',
- module: MODULE_NAME, method: 'handleCreateSubAccount',
+ module: MODULE_NAME, method: 'handleActivateSubAccount',
action: action,
user: req.user
});
}
- const subAccount = await BillingStorage.getSubAccountByID(req.tenant, filteredRequest.subAccountID);
- UtilsService.assertObjectExists(action, subAccount, `Sub account ID '${filteredRequest.subAccountID}' does not exist`, MODULE_NAME, 'handleActivateSubAccount', req.user);
+ 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) {
throw new AppError({
@@ -534,13 +534,13 @@ export default class BillingService {
}
// Activate and save the sub account
subAccount.pending = false;
- await BillingStorage.saveSubAccount(req.tenant, subAccount);
+ await BillingStorage.saveSubAccount(tenant, subAccount);
// Get the sub account owner
- const user = await UserStorage.getUser(req.tenant, subAccount.userID);
+ const user = await UserStorage.getUser(tenant, subAccount.userID);
UtilsService.assertObjectExists(action, user, `User ID '${subAccount.userID}' does not exist`, MODULE_NAME, 'handleActivateSubAccount', req.user);
// Notify the user
void NotificationHandler.sendBillingSubAccountActivationNotification(
- req.tenant, Utils.generateUUID(), user, { evseDashboardURL: Utils.buildEvseURL(req.tenant.subdomain), user });
+ tenant, Utils.generateUUID(), user, { evseDashboardURL: Utils.buildEvseURL(tenant.subdomain), user });
res.status(StatusCodes.OK).json(subAccount);
next();
}

MODIFY
src/types/requests/HttpBillingRequest.ts
src/types/requests/HttpBillingRequest.ts
@@ -34,5 +34,6 @@ export interface HttpBillingCreateSubAccount {
}
export interface HttpBillingActivateSubAccount {
- subAccountID: string;
+ ID: string;
+ TenantID: string;
}

MODIFY
src/utils/Utils.ts
src/utils/Utils.ts
@@ -976,8 +976,8 @@ export default class Utils {
return `${Utils.buildEvseURL(tenantSubdomain)}/invoices?InvoiceID=${invoiceID}#all`;
}
- public static buildEvseBillingSubAccountActivationURL(tenantSubdomain: string, subAccountID: string): string {
- return `${Utils.buildEvseURL(tenantSubdomain)}/billing/sub-accounts/${subAccountID}`;
+ public static buildEvseBillingSubAccountActivationURL(tenant: Tenant, subAccountID: string): string {
+ return `${Utils.buildEvseURL(tenant.subdomain)}/billing/sub-accounts/${subAccountID}?TenantID=${tenant.id}`;
}
public static buildEvseUserToVerifyURL(tenantSubdomain: string, userId: string): string {

MODIFY
test/api/BillingTest.ts
test/api/BillingTest.ts
@@ -412,13 +412,13 @@ describeif(isBillingProperlyConfigured)('Billing', () => {
});
expect(response.status).to.be.eq(StatusCodes.CREATED);
- const activationResponse = await billingTestHelper.userService.billingApi.activateSubAccount({ accountID: response.data.id });
+ const activationResponse = await billingTestHelper.userService.billingApi.activateSubAccount({ accountID: response.data.id, TenantID: billingTestHelper.tenantContext.getTenant().id });
expect(activationResponse.status).to.be.eq(StatusCodes.OK);
expect(activationResponse.data.pending).to.be.false;
});
it('should not activate an inexistent sub account', async () => {
- const activationResponse = await billingTestHelper.userService.billingApi.activateSubAccount({ accountID: '5ce249a1a39ae1c056c389bd' });
+ const activationResponse = await billingTestHelper.userService.billingApi.activateSubAccount({ accountID: '5ce249a1a39ae1c056c389bd', TenantID: billingTestHelper.tenantContext.getTenant().id });
expect(activationResponse.status).to.be.eq(HTTPError.OBJECT_DOES_NOT_EXIST_ERROR);
});
@@ -428,9 +428,9 @@ describeif(isBillingProperlyConfigured)('Billing', () => {
});
expect(response.status).to.be.eq(StatusCodes.CREATED);
- let activationResponse = await billingTestHelper.userService.billingApi.activateSubAccount({ accountID: response.data.id });
+ let activationResponse = await billingTestHelper.userService.billingApi.activateSubAccount({ accountID: response.data.id, TenantID: billingTestHelper.tenantContext.getTenant().id });
expect(activationResponse.status).to.be.eq(StatusCodes.OK);
- activationResponse = await billingTestHelper.userService.billingApi.activateSubAccount({ accountID: response.data.id });
+ 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);
});
});
@@ -501,7 +501,7 @@ describeif(isBillingProperlyConfigured)('Billing', () => {
});
it('should not activate an inexistent sub account', async () => {
- const activationResponse = await billingTestHelper.userService.billingApi.activateSubAccount({ accountID: '5ce249a1a39ae1c056c389bd' });
+ const activationResponse = await billingTestHelper.userService.billingApi.activateSubAccount({ accountID: '5ce249a1a39ae1c056c389bd', TenantID: billingTestHelper.tenantContext.getTenant().id });
expect(activationResponse.status).to.be.eq(HTTPError.OBJECT_DOES_NOT_EXIST_ERROR);
});
});

MODIFY
test/api/client/BillingApi.ts
test/api/client/BillingApi.ts
@@ -57,6 +57,6 @@ export default class BillingApi extends CrudApi {
}
public async activateSubAccount(params) {
- return super.patch(params, super.buildRestEndpointUrl(RESTServerRoute.REST_BILLING_SUB_ACCOUNT_ACTIVATE, { id: params.accountID }));
+ return super.read(params, super.buildUtilRestEndpointUrl(RESTServerRoute.REST_BILLING_SUB_ACCOUNT_ACTIVATE, { id: params.accountID }));
}
}

MODIFY
test/api/client/utils/CrudApi.ts
test/api/client/utils/CrudApi.ts
@@ -65,12 +65,8 @@ export default class CrudApi {
});
}
- public async patch(data, path): Promise<any> {
- return this._authenticatedApi.send({
- method: 'PATCH',
- url: path,
- data: data,
- });
+ protected buildUtilRestEndpointUrl(urlPatternAsString: RESTServerRoute, params: { [name: string]: string | number | null } = {}): string {
+ return this.buildRestEndpointUrl(urlPatternAsString, params, 'util');
}
protected buildRestEndpointUrl(urlPatternAsString: RESTServerRoute, params: { [name: string]: string | number | null } = {}, urlPrefix = 'api'): string {

refactor(subaccount): get tenant from query param