Auth renaming Link to Create Invoice
MODIFY
src/authorization/Authorizations.ts
src/authorization/Authorizations.ts
@@ -602,7 +602,7 @@ export default class Authorizations {
}
public static canLinkTransactionToInvoice(loggedUser: UserToken): boolean {
- return Authorizations.canPerformAction(loggedUser, Entity.INVOICES, Action.LINK_INVOICE);
+ return Authorizations.canPerformAction(loggedUser, Entity.INVOICE, Action.LINK);
}
public static canDownloadInvoiceBilling(loggedUser: UserToken, userId: string): boolean {

MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
@@ -61,8 +61,8 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
{ resource: Entity.PRICING, action: [Action.READ, Action.UPDATE], attributes: ['*'] },
{ resource: Entity.BILLING, action: [Action.CHECK_CONNECTION] },
{ resource: Entity.TAXES, action: [Action.LIST], attributes: ['*'] },
- { resource: Entity.INVOICES, action: [Action.LIST, Action.SYNCHRONIZE, Action.LINK_INVOICE], attributes: ['*'] },
- { resource: Entity.INVOICE, action: [Action.DOWNLOAD], attributes: ['*'] },
+ { resource: Entity.INVOICES, action: [Action.LIST, Action.SYNCHRONIZE], attributes: ['*'] },
+ { resource: Entity.INVOICE, action: [Action.DOWNLOAD, Action.LINK], attributes: ['*'] },
{ resource: Entity.ASSET, action: [Action.CREATE, Action.READ, Action.UPDATE, Action.DELETE, Action.CHECK_CONNECTION, Action.RETRIEVE_CONSUMPTION], attributes: ['*'] },
{ resource: Entity.ASSETS, action: Action.LIST, attributes: ['*'] },
{ resource: Entity.SETTINGS, action: Action.LIST, attributes: ['*'] },

MODIFY
src/integration/billing/BillingIntegration.ts
src/integration/billing/BillingIntegration.ts
@@ -2,7 +2,7 @@ import BackendError from '../../exception/BackendError';
import BillingStorage from '../../storage/mongodb/BillingStorage';
import SettingStorage from '../../storage/mongodb/SettingStorage';
import UserStorage from '../../storage/mongodb/UserStorage';
-import { BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceDocument, BillingInvoiceItem, BillingTax, BillingUser, BillingUserSynchronizeAction } from '../../types/Billing';
+import { BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceDocument, BillingInvoiceItem, BillingMethod, BillingTax, BillingUser, BillingUserSynchronizeAction } from '../../types/Billing';
import { UserInErrorType } from '../../types/InError';
import { ServerAction } from '../../types/Server';
import { BillingSetting } from '../../types/Setting';
@@ -402,6 +402,92 @@ export default abstract class BillingIntegration<T extends BillingSetting> {
return invoice;
}
+ public checkStopTransaction(transaction: Transaction): void {
+ // Check User
+ if (!transaction.userID || !transaction.user) {
+ throw new BackendError({
+ message: 'User is not provided',
+ source: Constants.CENTRAL_SERVER,
+ module: MODULE_NAME,
+ method: 'checkStopTransaction',
+ action: ServerAction.BILLING_TRANSACTION
+ });
+ }
+ // Check Charging Station
+ if (!transaction.chargeBox) {
+ throw new BackendError({
+ message: 'Charging Station is not provided',
+ source: Constants.CENTRAL_SERVER,
+ module: MODULE_NAME,
+ method: 'checkStopTransaction',
+ action: ServerAction.BILLING_TRANSACTION
+ });
+ }
+ // Check Charging Station
+ if (transaction.billingData) {
+ throw new BackendError({
+ message: 'Transaction has already billing data',
+ source: Constants.CENTRAL_SERVER,
+ module: MODULE_NAME,
+ method: 'checkStopTransaction',
+ action: ServerAction.BILLING_TRANSACTION
+ });
+ }
+ if (!transaction.user.billingData) {
+ throw new BackendError({
+ message: 'User has no Billing Data',
+ source: Constants.CENTRAL_SERVER,
+ module: MODULE_NAME,
+ method: 'checkStopTransaction',
+ action: ServerAction.BILLING_TRANSACTION
+ });
+ }
+ }
+
+ public checkStartTransaction(transaction: Transaction): void {
+ // Check User
+ if (!transaction.userID || !transaction.user) {
+ throw new BackendError({
+ message: 'User is not provided',
+ source: Constants.CENTRAL_SERVER,
+ module: MODULE_NAME,
+ method: 'startTransaction',
+ action: ServerAction.BILLING_TRANSACTION
+ });
+ }
+ // Get User
+ const billingUser = transaction.user;
+ if (!billingUser.billingData || !billingUser.billingData.customerID || !billingUser.billingData.method) {
+ throw new BackendError({
+ message: 'Transaction user has no billing method or no customer in Stripe',
+ source: Constants.CENTRAL_SERVER,
+ module: MODULE_NAME,
+ method: 'startTransaction',
+ action: ServerAction.BILLING_TRANSACTION
+ });
+ }
+ if (billingUser.billingData.method !== BillingMethod.IMMEDIATE &&
+ billingUser.billingData.method !== BillingMethod.PERIODIC) {
+ throw new BackendError({
+ message: 'Transaction user is assigned to unknown billing method',
+ source: Constants.CENTRAL_SERVER,
+ module: MODULE_NAME,
+ method: 'startTransaction',
+ action: ServerAction.BILLING_TRANSACTION
+ });
+ }
+ if (!billingUser.billingData.subscriptionID &&
+ billingUser.billingData.method !== BillingMethod.IMMEDIATE) {
+ throw new BackendError({
+ message: 'Transaction user is not subscribed to Stripe billing plan',
+ source: Constants.CENTRAL_SERVER,
+ module: MODULE_NAME,
+ method: 'startTransaction',
+ action: ServerAction.BILLING_TRANSACTION
+ });
+ }
+ }
+
private async checkAndGetBillingUser(user: User): Promise<BillingUser> {
const billingUser = await this.getUserByEmail(user.email);
if (!billingUser) {

MODIFY
src/integration/billing/stripe/StripeBillingIntegration.ts
src/integration/billing/stripe/StripeBillingIntegration.ts
@@ -408,47 +408,10 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
try {
// Check Stripe
await this.checkConnection();
- // Check User
- if (!transaction.userID || !transaction.user) {
- throw new BackendError({
- message: 'User is not provided',
- source: Constants.CENTRAL_SERVER,
- module: MODULE_NAME,
- method: 'startTransaction',
- action: ServerAction.BILLING_TRANSACTION
- });
- }
- // Get User
+ // Check Transaction
+ this.checkStartTransaction(transaction);
+ // Checks
const billingUser = transaction.user;
- if (!billingUser.billingData || !billingUser.billingData.customerID || !billingUser.billingData.method) {
- throw new BackendError({
- message: 'Transaction user has no billing method or no customer in Stripe',
- source: Constants.CENTRAL_SERVER,
- module: MODULE_NAME,
- method: 'startTransaction',
- action: ServerAction.BILLING_TRANSACTION
- });
- }
- if (billingUser.billingData.method !== BillingMethod.IMMEDIATE &&
- billingUser.billingData.method !== BillingMethod.PERIODIC) {
- throw new BackendError({
- message: 'Transaction user is assigned to unknown billing method',
- source: Constants.CENTRAL_SERVER,
- module: MODULE_NAME,
- method: 'startTransaction',
- action: ServerAction.BILLING_TRANSACTION
- });
- }
- if (!billingUser.billingData.subscriptionID &&
- billingUser.billingData.method !== BillingMethod.IMMEDIATE) {
- throw new BackendError({
- message: 'Transaction user is not subscribed to Stripe billing plan',
- source: Constants.CENTRAL_SERVER,
- module: MODULE_NAME,
- method: 'startTransaction',
- action: ServerAction.BILLING_TRANSACTION
- });
- }
if (billingUser.billingData.subscriptionID &&
billingUser.billingData.method !== BillingMethod.IMMEDIATE) {
const subscription = await this.getSubscription(billingUser.billingData.subscriptionID);
@@ -520,42 +483,15 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
try {
// Check Stripe
await this.checkConnection();
- // Check User
- if (!transaction.userID || !transaction.user) {
- throw new BackendError({
- message: 'User is not provided',
- source: Constants.CENTRAL_SERVER,
- module: MODULE_NAME,
- method: 'stopTransaction',
- action: ServerAction.BILLING_TRANSACTION
- });
- }
- // Check Charging Station
- if (!transaction.chargeBox) {
- throw new BackendError({
- message: 'Charging Station is not provided',
- source: Constants.CENTRAL_SERVER,
- module: MODULE_NAME,
- method: 'stopTransaction',
- action: ServerAction.BILLING_TRANSACTION
- });
- }
- if (!transaction.user.billingData) {
- throw new BackendError({
- message: 'User has no Billing Data',
- source: Constants.CENTRAL_SERVER,
- module: MODULE_NAME,
- method: 'stopTransaction',
- action: ServerAction.BILLING_TRANSACTION
- });
- }
+ // Check object
+ this.checkStopTransaction(transaction);
+ // Get the user
const billingUser = await this.getUser(transaction.user.billingData.customerID);
if (!billingUser) {
throw new BackendError({
message: 'User does not exists in Stripe',
source: Constants.CENTRAL_SERVER,
- module: MODULE_NAME,
- method: 'stopTransaction',
+ module: MODULE_NAME, method: 'stopTransaction',
action: ServerAction.BILLING_TRANSACTION
});
}

MODIFY
src/server/rest/CentralRestServerService.ts
src/server/rest/CentralRestServerService.ts
@@ -81,7 +81,7 @@ class RequestMapper {
[ServerAction.BILLING_FORCE_SYNCHRONIZE_USER]: BillingService.handleForceSynchronizeUser.bind(this),
[ServerAction.BILLING_SYNCHRONIZE_INVOICES]: BillingService.handleSynchronizeInvoices.bind(this),
[ServerAction.BILLING_FORCE_SYNCHRONIZE_USER_INVOICES]: BillingService.handleForceSynchronizeUserInvoices.bind(this),
- [ServerAction.BILLING_LINK_TRANSACTION_TO_INVOICE]: BillingService.handleLinkTransactionToInvoice.bind(this),
+ [ServerAction.BILLING_CREATE_TRANSACTION_INVOICE]: BillingService.handleCreateTransactionInvoice.bind(this),
[ServerAction.OCPI_ENPOINT_CREATE]: OCPIEndpointService.handleCreateOcpiEndpoint.bind(this),
[ServerAction.OCPI_ENPOINT_PING]: OCPIEndpointService.handlePingOcpiEndpoint.bind(this),
[ServerAction.OCPI_ENPOINT_TRIGGER_JOBS]: OCPIEndpointService.handleTriggerJobsEndpoint.bind(this),

MODIFY
src/server/rest/service/BillingService.ts
src/server/rest/service/BillingService.ts
@@ -3,12 +3,10 @@ import fs from 'fs';
import Authorizations from '../../../authorization/Authorizations';
import AppAuthError from '../../../exception/AppAuthError';
import AppError from '../../../exception/AppError';
-import BackendError from '../../../exception/BackendError';
import BillingFactory from '../../../integration/billing/BillingFactory';
import LockingHelper from '../../../locking/LockingHelper';
import LockingManager from '../../../locking/LockingManager';
import BillingStorage from '../../../storage/mongodb/BillingStorage';
-import TenantStorage from '../../../storage/mongodb/TenantStorage';
import TransactionStorage from '../../../storage/mongodb/TransactionStorage';
import UserStorage from '../../../storage/mongodb/UserStorage';
import { Action, Entity } from '../../../types/Authorization';
@@ -434,35 +432,37 @@ export default class BillingService {
next();
}
- public static async handleLinkTransactionToInvoice(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ public static async handleCreateTransactionInvoice(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ // Check if component is active
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING,
+ Action.LINK, Entity.INVOICE, MODULE_NAME, 'handleCreateTransactionInvoice');
+ // Check Auth
if (!Authorizations.canLinkTransactionToInvoice(req.user)) {
throw new AppAuthError({
errorCode: HTTPAuthError.ERROR,
user: req.user,
- entity: Entity.USER, action: Action.LINK_INVOICE,
- module: MODULE_NAME, method: 'handleLinkInvoiceToTransaction',
+ entity: Entity.INVOICE, action: Action.LINK,
+ module: MODULE_NAME, method: 'handleCreateTransactionInvoice',
});
}
- // Check if component is active
- UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING,
- Action.LINK_INVOICE, Entity.BILLING, MODULE_NAME, 'handleLinkInvoiceToTransaction');
- const tenant = await TenantStorage.getTenant(req.user.tenantID);
- const billingImpl = await BillingFactory.getBillingImpl(tenant.id);
+ const filteredRequest = BillingSecurity.filterLinkTransactionToInvoiceRequest(req.body);
+ // Get Billing impl
+ const billingImpl = await BillingFactory.getBillingImpl(req.user.tenantID);
if (!billingImpl) {
throw new AppError({
source: Constants.CENTRAL_SERVER,
errorCode: HTTPError.GENERAL_ERROR,
message: 'Billing service is not configured',
- module: MODULE_NAME, method: 'handleLinkInvoiceToTransaction',
+ module: MODULE_NAME, method: 'handleCreateTransactionInvoice',
action: action,
user: req.user
});
}
- const filteredRequest = BillingSecurity.filterLinkTransactionToInvoiceRequest(req.body);
// Get the Transaction
- const transaction = await TransactionStorage.getTransaction(req.user.tenantID, Utils.convertToInt(filteredRequest.transactionID));
+ const transaction = await TransactionStorage.getTransaction(req.user.tenantID,
+ Utils.convertToInt(filteredRequest.transactionID));
UtilsService.assertObjectExists(action, transaction, `Transaction '${filteredRequest.transactionID}' does not exist`,
- MODULE_NAME, 'handleLinkInvoiceToTransaction', req.user);
+ MODULE_NAME, 'handleCreateTransactionInvoice', req.user);
try {
// Create an invoice for the transaction
const billingDataStop = await billingImpl.stopTransaction(transaction);
@@ -475,11 +475,21 @@ export default class BillingService {
lastUpdate: new Date()
};
await TransactionStorage.saveTransaction(req.user.tenantID, transaction);
+ // Ok
+ Logging.logInfo({
+ tenantID: req.user.tenantID,
+ user: req.user, actionOnUser: transaction.userID,
+ module: MODULE_NAME, method: 'handleCreateTransactionInvoice',
+ message: `Transaction ID '${transaction.id}' has been billed successfully`,
+ action: action,
+ });
} catch (error) {
- throw new BackendError({
+ throw new AppError({
source: Constants.CENTRAL_SERVER,
module: MODULE_NAME,
- method: 'handleLinkTransactionToInvoice',
+ errorCode: HTTPError.GENERAL_ERROR,
+ user: req.user, actionOnUser: transaction.userID,
+ method: 'handleCreateTransactionInvoice',
message: `Failed to link transaction ${transaction.id} to invoice`,
detailedMessages: { error: error.message, stack: error.stack }
});

MODIFY
src/types/Authorization.ts
src/types/Authorization.ts
@@ -91,6 +91,7 @@ export enum Action {
RESET = 'Reset',
ASSIGN = 'Assign',
CLEAR_CACHE = 'ClearCache',
+ LINK = 'Link',
SYNCHRONIZE = 'Synchronize',
GET_CONFIGURATION = 'GetConfiguration',
CHANGE_CONFIGURATION = 'ChangeConfiguration',
@@ -109,7 +110,6 @@ export enum Action {
REFUND_TRANSACTION = 'RefundTransaction',
SYNCHRONIZE_BILLING_USERS = 'SynchronizeBillingUsers',
SYNCHRONIZE_BILLING_USER = 'SynchronizeBillingUser',
- LINK_INVOICE = 'LinkInvoice',
CHECK_CONNECTION = 'CheckConnection',
RETRIEVE_CONSUMPTION = 'RetrieveConsumption',
PING = 'Ping',

MODIFY
src/types/Server.ts
src/types/Server.ts
@@ -344,7 +344,7 @@ export enum ServerAction {
BILLING_SYNCHRONIZE_INVOICES = 'BillingSynchronizeInvoices',
BILLING_FORCE_SYNCHRONIZE_USER_INVOICES = 'BillingForceSynchronizeUserInvoices',
BILLING_DOWNLOAD_INVOICE = 'BillingDownloadInvoice',
- BILLING_LINK_TRANSACTION_TO_INVOICE = 'BillingLinkTransactionToInvoice',
+ BILLING_CREATE_TRANSACTION_INVOICE = 'BillingCreateTransactionInvoice',
MONGO_DB = 'MongoDB',

Auth renaming Link to Create Invoice