added REST routes for invoices :
/invoices works & implemented in dashboard
/invoices/:invoiceid works & not implemented in dashboard
/invoices/:invoiceid/download needs to be implemented  and tested in dahsboard
MODIFY
src/authorization/Authorizations.ts
src/authorization/Authorizations.ts
@@ -713,6 +713,11 @@ export default class Authorizations {
return Authorizations.canPerformAction(loggedUser, Entity.INVOICES, Action.LIST);
}
+ public static async canReadInvoiceBilling(loggedUser: UserToken, userID: string): Promise<boolean> {
+ return Authorizations.canPerformAction(loggedUser, Entity.INVOICE, Action.READ,
+ { user: userID, owner: loggedUser.id });
+ }
+
public static async canSynchronizeInvoicesBilling(loggedUser: UserToken): Promise<boolean> {
return Authorizations.canPerformAction(loggedUser, Entity.INVOICES, Action.SYNCHRONIZE);
}

MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
@@ -149,7 +149,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
// { resource: Entity.INVOICES, action: [Action.LIST, Action.SYNCHRONIZE], attributes: ['*'] },
// { resource: Entity.INVOICE, action: [Action.DOWNLOAD, Action.CREATE], attributes: ['*'] },
{ resource: Entity.INVOICES, action: [Action.LIST], attributes: ['*'] },
- { resource: Entity.INVOICE, action: [Action.DOWNLOAD], attributes: ['*'] },
+ { resource: Entity.INVOICE, action: [Action.DOWNLOAD, Action.READ], attributes: ['*'] },
{
resource: Entity.ASSET, action: [Action.CREATE, Action.READ, Action.UPDATE, Action.DELETE,
Action.CHECK_CONNECTION, Action.RETRIEVE_CONSUMPTION], attributes: ['*']
@@ -253,7 +253,8 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
attributes: ['id', 'name', 'issuer', 'logo', 'address']
},
{ resource: Entity.INVOICES, action: [Action.LIST], attributes: ['*'] },
- { resource: Entity.INVOICE, action: [Action.DOWNLOAD], attributes: ['*'] },
+ { resource: Entity.INVOICE, action: [Action.DOWNLOAD, Action.READ], attributes: ['*'],
+ condition: { Fn: 'EQUALS', args: { 'user': '$.owner' } } },
{ resource: Entity.PAYMENT_METHODS, action: Action.LIST, attributes: ['*'] },
{ resource: Entity.PAYMENT_METHOD, action: [Action.READ, Action.CREATE, Action.DELETE], attributes: ['*'] },
{

MODIFY
src/server/rest/v1/router/api/BillingRouter.ts
src/server/rest/v1/router/api/BillingRouter.ts
@@ -29,6 +29,14 @@ export default class BillingRouter {
this.buildRouteBillingPaymentMethodSetup();
this.buildRouteBillingPaymentMethodAttach();
this.buildRouteBillingPaymentMethodDetach();
+ // -----------------------------------
+ // ROUTES for INVOICES
+ // -----------------------------------
+ this.buildRouteBillingInvoices();
+ // not yet implemented in dashboard - working
+ // this.buildRouteBillingInvoice();
+ // not yet implemented in dashboard - needs to be tested
+ // this.buildRouteBillingInvoiceDownload();
return this.router;
}
@@ -93,4 +101,28 @@ export default class BillingRouter {
void RouterUtils.handleServerAction(BillingService.handleBillingDeletePaymentMethod.bind(this), ServerAction.BILLING_DELETE_PAYMENT_METHOD, req, res, next);
});
}
+
+ protected buildRouteBillingInvoices(): void {
+ this.router.get(`/${ServerRoute.REST_BILLING_INVOICES}`, (req: Request, res: Response, next: NextFunction) => {
+ void RouterUtils.handleServerAction(BillingService.handleGetInvoices.bind(this), ServerAction.BILLING_INVOICES, req, res, next);
+ });
+ }
+
+ // not yet implemented in dashboard - working
+ protected buildRouteBillingInvoice(): void {
+ this.router.get(`/${ServerRoute.REST_BILLING_INVOICE}`, (req: Request, res: Response, next: NextFunction) => {
+ // GET {{base_url}}/v1/api/billing/invoices/606193168f22ac7f02223c8c
+ req.query.ID = req.params.invoiceID;
+ void RouterUtils.handleServerAction(BillingService.handleGetInvoice.bind(this), ServerAction.BILLING_INVOICE, req, res, next);
+ });
+ }
+
+ // not yet implemented in dashboard - needs to be tested
+ protected buildRouteBillingInvoiceDownload(): void {
+ this.router.get(`/${ServerRoute.REST_BILLING_DOWNLOAD_INVOICE}`, (req: Request, res: Response, next: NextFunction) => {
+ // GET {{base_url}}/v1/api/billing/invoices/606193168f22ac7f02223c8c
+ req.query.ID = req.params.invoiceID;
+ void RouterUtils.handleServerAction(BillingService.handleDownloadInvoice.bind(this), ServerAction.BILLING_DOWNLOAD_INVOICE, req, res, next);
+ });
+ }
}

MODIFY
src/server/rest/v1/service/BillingService.ts
src/server/rest/v1/service/BillingService.ts
@@ -277,7 +277,7 @@ export default class BillingService {
userProject = [ 'userID', 'user.id', 'user.name', 'user.firstName', 'user.email' ];
}
// Filter
- const filteredRequest = BillingSecurity.filterGetUserInvoicesRequest(req.query);
+ const filteredRequest = BillingSecurity.filterGetInvoicesRequest(req.query);
// Get invoices
const invoices = await BillingStorage.getInvoices(req.user.tenantID,
{
@@ -302,6 +302,39 @@ export default class BillingService {
next();
}
+ public static async handleGetInvoice(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ // Check if component is active
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING,
+ Action.LIST, Entity.INVOICES, MODULE_NAME, 'handleGetInvoice');
+ // Filter
+ const filteredRequest = BillingSecurity.filterGetInvoicesRequest(req.query);
+ UtilsService.assertIdIsProvided(action, filteredRequest.ID, MODULE_NAME, 'handleGetInvoice', req.user);
+ // Check Users
+ let userProject: string[] = [];
+ if (await Authorizations.canListUsers(req.user)) {
+ userProject = [ 'userID', 'user.id', 'user.name', 'user.firstName', 'user.email' ];
+ }
+ // Get invoice
+ const invoice = await BillingStorage.getInvoice(req.user.tenantID, filteredRequest.ID,
+ [
+ 'id', 'number', 'status', 'amount', 'createdOn', 'currency', 'downloadable', 'sessions',
+ ...userProject
+ ]);
+ UtilsService.assertObjectExists(action, invoice, `Invoice ID '${filteredRequest.ID}' does not exist`, MODULE_NAME, 'handleGetInvoice', req.user);
+ // Check auth
+ if (!await Authorizations.canReadInvoiceBilling(req.user, invoice.userID)) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.FORBIDDEN,
+ user: req.user,
+ entity: Entity.INVOICE, action: Action.READ,
+ module: MODULE_NAME, method: 'handleGetInvoice',
+ });
+ }
+ // Return
+ res.json(invoice);
+ next();
+ }
+
public static async handleSynchronizeInvoices(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
if (!await Authorizations.canSynchronizeInvoicesBilling(req.user)) {
throw new AppAuthError({

MODIFY
src/server/rest/v1/service/security/BillingSecurity.ts
src/server/rest/v1/service/security/BillingSecurity.ts
@@ -18,7 +18,7 @@ export default class BillingSecurity {
return filteredUser;
}
- static filterGetUserInvoicesRequest(requestQuery: any): HttpBillingInvoiceRequest {
+ static filterGetInvoicesRequest(requestQuery: any): HttpBillingInvoiceRequest {
const filteredRequest = {} as HttpBillingInvoiceRequest;
if (Utils.objectHasProperty(requestQuery, 'UserID')) {
filteredRequest.UserID = sanitize(requestQuery.UserID);
@@ -35,6 +35,9 @@ export default class BillingSecurity {
if (Utils.objectHasProperty(requestQuery, 'Search')) {
filteredRequest.Search = sanitize(requestQuery.Search);
}
+ if (Utils.objectHasProperty(requestQuery, 'ID')) {
+ filteredRequest.ID = sanitize(requestQuery.ID);
+ }
UtilsSecurity.filterSkipAndLimit(requestQuery, filteredRequest);
UtilsSecurity.filterSort(requestQuery, filteredRequest);
return filteredRequest;

MODIFY
src/storage/mongodb/BillingStorage.ts
src/storage/mongodb/BillingStorage.ts
@@ -12,10 +12,10 @@ import Utils from '../../utils/Utils';
const MODULE_NAME = 'BillingStorage';
export default class BillingStorage {
- public static async getInvoice(tenantID: string, id: string = Constants.UNKNOWN_OBJECT_ID): Promise<BillingInvoice> {
+ public static async getInvoice(tenantID: string, id: string = Constants.UNKNOWN_OBJECT_ID, projectFields?: string[]): Promise<BillingInvoice> {
const invoicesMDB = await BillingStorage.getInvoices(tenantID, {
invoiceIDs: [id]
- }, Constants.DB_PARAMS_SINGLE_RECORD);
+ }, Constants.DB_PARAMS_SINGLE_RECORD, projectFields);
return invoicesMDB.count === 1 ? invoicesMDB.result[0] : null;
}

MODIFY
src/types/Server.ts
src/types/Server.ts
@@ -508,7 +508,10 @@ export enum ServerRoute {
REST_BILLING_PAYMENT_METHOD_DETACH = 'users/:userID/payment-methods/:paymentMethodID/detach',
REST_BILLING_SETTING = 'billing-setting', // GET and PUT
- REST_BILLING_CHECK = 'billing/check'
+ REST_BILLING_CHECK = 'billing/check',
+ REST_BILLING_INVOICES = 'invoices',
+ REST_BILLING_INVOICE = 'invoices/:invoiceID',
+ REST_BILLING_DOWNLOAD_INVOICE = 'invoices/:invoiceID/download',
}
export enum ServerProtocol {

MODIFY
src/types/requests/HttpBillingRequest.ts
src/types/requests/HttpBillingRequest.ts
@@ -10,6 +10,7 @@ export interface HttpBillingInvoiceRequest extends HttpDatabaseRequest {
StartDateTime?: Date;
EndDateTime?: Date;
Search?: string;
+ ID?: string;
}
export interface HttpBillingWebHookRequest {

added REST routes for invoices :
- /invoices works & implemented in dashboard
- /invoices/:invoiceid works & not implemented in dashboard
- /invoices/:invoiceid/download needs to be implemented  and tested in dahsboard