feat(rest): added sub-account creation route
MODIFY
src/server/rest/v1/router/api/BillingRouter.ts
src/server/rest/v1/router/api/BillingRouter.ts
@@ -40,6 +40,10 @@ export default class BillingRouter {
// ROUTES for TAXES
// -----------------------------------
this.buildRouteBillingGetTaxes();
+ // -----------------------------------
+ // ROUTES for SUB-ACCOUNTS
+ // -----------------------------------
+ this.buildRouteBillingCreateSubAccount();
return this.router;
}
@@ -138,4 +142,10 @@ export default class BillingRouter {
void RouterUtils.handleRestServerAction(BillingService.handleGetBillingTaxes.bind(this), ServerAction.BILLING_TAXES, req, res, next);
});
}
+
+ private buildRouteBillingCreateSubAccount(): void {
+ this.router.post(`/${RESTServerRoute.REST_BILLING_SUB_ACCOUNTS}`, (req: Request, res: Response, next: NextFunction) => {
+ void RouterUtils.handleRestServerAction(BillingService.handleCreateSubAccount.bind(this), ServerAction.BILLING_CREATE_SUB_ACCOUNT, req, res, next);
+ });
+ }
}

MODIFY
src/server/rest/v1/service/BillingService.ts
src/server/rest/v1/service/BillingService.ts
@@ -16,6 +16,8 @@ import Constants from '../../../../utils/Constants';
import LockingHelper from '../../../../locking/LockingHelper';
import LockingManager from '../../../../locking/LockingManager';
import Logging from '../../../../utils/Logging';
+import NotificationHandler from '../../../../notification/NotificationHandler';
+import NotificationService from './NotificationService';
import { ServerAction } from '../../../../types/Server';
import SettingStorage from '../../../../storage/mongodb/SettingStorage';
import { StatusCodes } from 'http-status-codes';
@@ -470,6 +472,34 @@ export default class BillingService {
next();
}
+ 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_SUB_ACCOUNT, MODULE_NAME, 'handleCreateSubAccount');
+ const filteredRequest = BillingValidator.getInstance().validateBillingCreateSubAccountReq(req.body);
+ // Get the billing impl
+ const billingImpl = await BillingFactory.getBillingImpl(req.tenant);
+ if (!billingImpl) {
+ throw new AppError({
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: 'Billing service is not configured',
+ module: MODULE_NAME, method: 'handleDownloadInvoice',
+ action: action,
+ user: req.user
+ });
+ }
+ // Get the user
+ const user = await UserStorage.getUser(req.tenant, filteredRequest.userID);
+ UtilsService.assertObjectExists(action, user, `User ID '${filteredRequest.userID}' does not exist`,
+ MODULE_NAME, 'handleCreateSubAccount', req.user);
+ // Create the sub account
+ const subAccount = await billingImpl.createSubAccount();
+ // Notify the user by mail
+ void NotificationHandler.sendBillingSubAccountCreationLink(req.tenant, Utils.generateUUID(), user, { onboardingLink: subAccount.activationLink });
+ res.status(StatusCodes.CREATED).json(subAccount);
+ next();
+ }
+
private static async checkActivationPrerequisites(action: ServerAction, req: Request) : Promise<void> {
const billingImpl = await BillingFactory.getBillingImpl(req.tenant);
if (!billingImpl) {

MODIFY
src/server/rest/v1/validator/BillingValidator.ts
src/server/rest/v1/validator/BillingValidator.ts
-import { HttpBillingInvoiceRequest, HttpBillingInvoicesRequest, HttpDeletePaymentMethod, HttpPaymentMethods, HttpSetupPaymentMethod } from '../../../../types/requests/HttpBillingRequest';
+import { HttpBillingCreateSubAccount, HttpBillingInvoiceRequest, HttpBillingInvoicesRequest, HttpDeletePaymentMethod, HttpPaymentMethods, HttpSetupPaymentMethod } from '../../../../types/requests/HttpBillingRequest';
import { BillingSettings } from '../../../../types/Setting';
import Schema from '../../../../types/validator/Schema';
@@ -14,6 +14,7 @@ export default class BillingValidator extends SchemaValidator {
private billingSetupUserPaymentMethod: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/billing/billing-setup-payment-method.json`, 'utf8'));
private billingInvoicesGet: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/billing/invoices-get.json`, 'utf8'));
private billingInvoiceGet: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/billing/invoice-get.json`, 'utf8'));
+ private billingCreateSubAccount: Schema = JSON.parse(fs.readFileSync(`${global.appRoot}/assets/server/rest/v1/schemas/billing/billing-create-sub-account.json`, 'utf8'));
private constructor() {
super('BillingValidator');
@@ -49,4 +50,8 @@ export default class BillingValidator extends SchemaValidator {
public validateBillingInvoiceGetReq(data: Record<string, unknown>): HttpBillingInvoiceRequest {
return this.validate(this.billingInvoiceGet, data);
}
+
+ public validateBillingCreateSubAccountReq(data: Record<string, unknown>): HttpBillingCreateSubAccount {
+ return this.validate(this.billingCreateSubAccount, data);
+ }
}

MODIFY
src/types/Authorization.ts
src/types/Authorization.ts
@@ -76,6 +76,7 @@ export enum Entity {
PRICING = 'Pricing',
PRICING_DEFINITION = 'PricingDefinition',
BILLING = 'Billing',
+ BILLING_SUB_ACCOUNT = 'BillingSubAccount',
SETTING = 'Setting',
ASYNC_TASK = 'AsyncTask',
OCPI_ENDPOINT = 'OcpiEndpoint',

MODIFY
src/types/Server.ts
src/types/Server.ts
@@ -679,6 +679,8 @@ export enum RESTServerRoute {
REST_BILLING_TAXES = 'billing/taxes',
+ REST_BILLING_SUB_ACCOUNTS = 'billing/sub-accounts',
+
// BILLING URLs for CRUD operations on INVOICES
REST_BILLING_INVOICES = 'invoices',
REST_BILLING_INVOICE = 'invoices/:invoiceID',

MODIFY
src/types/requests/HttpBillingRequest.ts
src/types/requests/HttpBillingRequest.ts
@@ -28,3 +28,7 @@ export interface HttpDeletePaymentMethod {
userID: string;
paymentMethodId: string;
}
+
+export interface HttpBillingCreateSubAccount {
+ userID: string;
+}

feat(rest): added sub-account creation route