billing  facade
ADD
src/integration/billing/BillingFacade.ts
+import BackendError from '../../exception/BackendError';
+import { BillingDataTransactionStop } from '../../types/Billing';
+import BillingFactory from './BillingFactory';
+import ChargingStation from '../../types/ChargingStation';
+import Logging from '../../utils/Logging';
+import LoggingHelper from '../../utils/LoggingHelper';
+import { ServerAction } from '../../types/Server';
+import SiteArea from '../../types/SiteArea';
+import Tenant from '../../types/Tenant';
+import Transaction from '../../types/Transaction';
+
+const MODULE_NAME = 'BillingFacade';
+
+export default class BillingFacade {
+
+ public static async processStartTransaction(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation, siteArea: SiteArea): Promise<void> {
+ if (!transaction.user || !transaction.user.issuer) {
+ return;
+ }
+ const billingImpl = await BillingFactory.getBillingImpl(tenant);
+ if (billingImpl) {
+ try {
+ let withBillingActive = false;
+ // Check parameters' consistency
+ if (billingImpl.checkStartTransaction(transaction, chargingStation, siteArea)) {
+ // Delegate to the concrete implementation
+ const billingDataTransactionStart = await billingImpl.startTransaction(transaction);
+ withBillingActive = billingDataTransactionStart.withBillingActive;
+ }
+ // Update Billing Data
+ transaction.billingData = {
+ withBillingActive, // false when access control is OFF or when the user has free access
+ lastUpdate: new Date()
+ };
+ } catch (error) {
+ const message = `Billing - startTransaction failed - transaction ID '${transaction.id}'`;
+ await Logging.logError({
+ ...LoggingHelper.getTransactionProperties(transaction),
+ tenantID: tenant.id,
+ action: ServerAction.BILLING_TRANSACTION,
+ module: MODULE_NAME, method: 'processStartTransaction',
+ message, detailedMessages: { error: error.stack }
+ });
+ // Prevent from starting a transaction when Billing prerequisites are not met
+ throw new BackendError({
+ ...LoggingHelper.getTransactionProperties(transaction),
+ action: ServerAction.BILLING_TRANSACTION,
+ module: MODULE_NAME, method: 'processStartTransaction',
+ message, detailedMessages: { error: error.stack }
+ });
+ }
+ }
+ }
+
+ public static async processUpdateTransaction(tenant: Tenant, transaction: Transaction): Promise<void> {
+ if (!transaction.user || !transaction.user.issuer) {
+ return;
+ }
+ const billingImpl = await BillingFactory.getBillingImpl(tenant);
+ if (billingImpl) {
+ try {
+ await billingImpl.updateTransaction(transaction);
+ // Update Billing Data
+ if (transaction.billingData) {
+ transaction.billingData.lastUpdate = new Date();
+ }
+ } catch (error) {
+ const message = `Billing - updateTransaction failed - transaction ID '${transaction.id}'`;
+ await Logging.logError({
+ ...LoggingHelper.getTransactionProperties(transaction),
+ tenantID: tenant.id,
+ action: ServerAction.BILLING_TRANSACTION,
+ module: MODULE_NAME, method: 'processUpdateTransaction',
+ message, detailedMessages: { error: error.stack }
+ });
+ }
+ }
+ }
+
+ public static async processStopTransaction(tenant: Tenant, transaction: Transaction): Promise<void> {
+ if (!transaction.user || !transaction.user.issuer) {
+ return;
+ }
+ const billingImpl = await BillingFactory.getBillingImpl(tenant);
+ if (billingImpl) {
+ try {
+ const billingDataStop: BillingDataTransactionStop = await billingImpl.stopTransaction(transaction);
+ // Update Billing Data
+ if (transaction.billingData) {
+ transaction.billingData.stop = billingDataStop;
+ transaction.billingData.lastUpdate = new Date();
+ }
+ } catch (error) {
+ const message = `Billing - stopTransaction failed - transaction ID '${transaction.id}'`;
+ await Logging.logError({
+ ...LoggingHelper.getTransactionProperties(transaction),
+ tenantID: tenant.id,
+ action: ServerAction.BILLING_TRANSACTION,
+ module: MODULE_NAME, method: 'processStopTransaction',
+ message, detailedMessages: { error: error.stack }
+ });
+ }
+ }
+ }
+
+ public static async processEndTransaction(tenant: Tenant, transaction: Transaction): Promise<void> {
+ if (!transaction.user || !transaction.user.issuer) {
+ return;
+ }
+ const billingImpl = await BillingFactory.getBillingImpl(tenant);
+ if (billingImpl) {
+ try {
+ // Delegate
+ const billingDataStop: BillingDataTransactionStop = await billingImpl.endTransaction(transaction);
+ // Update
+ if (transaction.billingData) {
+ transaction.billingData.stop = billingDataStop;
+ transaction.billingData.lastUpdate = new Date();
+ }
+ } catch (error) {
+ const message = `Billing - endTransaction failed - transaction ID '${transaction.id}'`;
+ await Logging.logError({
+ ...LoggingHelper.getTransactionProperties(transaction),
+ tenantID: tenant.id,
+ action: ServerAction.BILLING_TRANSACTION,
+ module: MODULE_NAME, method: 'processEndTransaction',
+ message, detailedMessages: { error: error.stack }
+ });
+ }
+ }
+ }
+
+}

MODIFY
src/integration/billing/BillingIntegration.ts
src/integration/billing/BillingIntegration.ts
@@ -15,6 +15,7 @@ import NotificationHandler from '../../notification/NotificationHandler';
import { Promise } from 'bluebird';
import { Request } from 'express';
import { ServerAction } from '../../types/Server';
+import SiteArea from '../../types/SiteArea';
import TenantStorage from '../../storage/mongodb/TenantStorage';
import TransactionStorage from '../../storage/mongodb/TransactionStorage';
import UserStorage from '../../storage/mongodb/UserStorage';
@@ -22,7 +23,6 @@ import Utils from '../../utils/Utils';
import moment from 'moment';
const MODULE_NAME = 'BillingIntegration';
-
export default abstract class BillingIntegration {
// Production Mode is set to true when the target account is a live one!
protected productionMode = false;
@@ -163,9 +163,9 @@ export default abstract class BillingIntegration {
// Send link to the user using our notification framework (link to the front-end + download)
const tenant = await TenantStorage.getTenant(this.tenant.id);
// Stripe saves amount in cents
- const decimInvoiceAmount = new Decimal(billingInvoice.amount).div(100);
- // Format amunt with currency symbol depending on locale
- const invoiceAmount = new Intl.NumberFormat(Utils.convertLocaleForCurrency(billingInvoice.user.locale), { style: 'currency', currency: billingInvoice.currency.toUpperCase() }).format(decimInvoiceAmount.toNumber());
+ const invoiceAmountAsDecimal = new Decimal(billingInvoice.amount).div(100);
+ // Format amount with currency symbol depending on locale
+ const invoiceAmount = new Intl.NumberFormat(Utils.convertLocaleForCurrency(billingInvoice.user.locale), { style: 'currency', currency: billingInvoice.currency.toUpperCase() }).format(invoiceAmountAsDecimal.toNumber());
// Send async notification
void NotificationHandler.sendBillingNewInvoiceNotification(
this.tenant,
@@ -229,11 +229,14 @@ export default abstract class BillingIntegration {
}
}
- public checkStartTransaction(transaction: Transaction, chargingStation: ChargingStation): void {
+ public checkStartTransaction(transaction: Transaction, chargingStation: ChargingStation, siteArea: SiteArea): boolean {
+ if (!this.settings.billing.isTransactionBillingActivated) {
+ return false;
+ }
// Check User
if (!transaction.userID || !transaction.user) {
throw new BackendError({
- message: 'User is not provided',
+ message: 'User ID is not provided',
module: MODULE_NAME,
method: 'checkStartTransaction',
action: ServerAction.BILLING_TRANSACTION
@@ -258,7 +261,7 @@ export default abstract class BillingIntegration {
}
if (Utils.isTenantComponentActive(this.tenant, TenantComponents.ORGANIZATION)) {
// Check for the Site Area
- if (!chargingStation.siteArea) {
+ if (!siteArea) {
throw new BackendError({
message: 'The site area is mandatory to start a transaction',
module: MODULE_NAME,
@@ -266,7 +269,46 @@ export default abstract class BillingIntegration {
action: ServerAction.BILLING_TRANSACTION
});
}
+ if (!siteArea.accessControl) {
+ return false;
+ }
+ }
+ // Check Free Access
+ if (transaction.user.freeAccess) {
+ return false;
+ }
+ return true;
}
+
+ protected async updateTransactionsBillingData(billingInvoice: BillingInvoice): Promise<void> {
+ if (!billingInvoice.sessions) {
+ // This should not happen - but it happened once!
+ throw new Error(`Unexpected situation - Invoice ${billingInvoice.id} has no sessions attached to it`);
+ }
+ await Promise.all(billingInvoice.sessions.map(async (session) => {
+ const transactionID = session.transactionID;
+ try {
+ const transaction = await TransactionStorage.getTransaction(this.tenant, Number(transactionID));
+ // Update Billing Data
+ if (transaction?.billingData?.stop) {
+ transaction.billingData.stop.invoiceStatus = billingInvoice.status;
+ transaction.billingData.stop.invoiceNumber = billingInvoice.number;
+ transaction.billingData.lastUpdate = new Date();
+ // Save
+ await TransactionStorage.saveTransactionBillingData(this.tenant, transaction.id, transaction.billingData);
+ }
+ } catch (error) {
+ // Catch stripe errors and send the information back to the client
+ await Logging.logError({
+ tenantID: this.tenant.id,
+ action: ServerAction.BILLING_CHARGE_INVOICE,
+ actionOnUser: billingInvoice.user,
+ module: MODULE_NAME, method: 'updateTransactionsBillingData',
+ message: `Failed to update transaction billing data - transaction: ${transactionID}`,
+ detailedMessages: { error: error.stack }
+ });
+ }
+ }));
}
private async _synchronizeUser(user: User, forceMode = false): Promise<BillingUser> {
@@ -476,10 +518,10 @@ export default abstract class BillingIntegration {
// Filter the invoice status based on the billing settings
let invoiceStatus;
if (this.settings.billing?.periodicBillingAllowed) {
- // Let's finalize DRAFT invoices and trigger a payment attemnpt for unpaid invoices as well
+ // Let's finalize DRAFT invoices and trigger a payment attempt for unpaid invoices as well
invoiceStatus = [ BillingInvoiceStatus.DRAFT, BillingInvoiceStatus.OPEN ];
} else {
- // Let's trigger a new payment attemnpt for unpaid invoices
+ // Let's trigger a new payment attempt for unpaid invoices
invoiceStatus = [ BillingInvoiceStatus.OPEN ];
}
// Now return the query parameters
@@ -506,7 +548,7 @@ export default abstract class BillingIntegration {
abstract resetConnectionSettings() : Promise<BillingSettings>;
- abstract startTransaction(transaction: Transaction, chargingStation: ChargingStation): Promise<BillingDataTransactionStart>;
+ abstract startTransaction(transaction: Transaction): Promise<BillingDataTransactionStart>;
abstract updateTransaction(transaction: Transaction): Promise<BillingDataTransactionUpdate>;

MODIFY
src/integration/billing/stripe/StripeBillingIntegration.ts
src/integration/billing/stripe/StripeBillingIntegration.ts
@@ -4,7 +4,6 @@ import { BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTra
import { DimensionType, PricedConsumptionData, PricedDimensionData } from '../../../types/Pricing';
import FeatureToggles, { Feature } from '../../../utils/FeatureToggles';
import StripeHelpers, { StripeChargeOperationResult } from './StripeHelpers';
-import Tenant, { TenantComponents } from '../../../types/Tenant';
import Transaction, { StartTransactionErrorCode } from '../../../types/Transaction';
import AsyncTaskBuilder from '../../../async-task/AsyncTaskBuilder';
@@ -14,7 +13,6 @@ import BackendError from '../../../exception/BackendError';
import BillingIntegration from '../BillingIntegration';
import { BillingSettings } from '../../../types/Setting';
import BillingStorage from '../../../storage/mongodb/BillingStorage';
-import ChargingStation from '../../../types/ChargingStation';
import Constants from '../../../utils/Constants';
import Cypher from '../../../utils/Cypher';
import DatabaseUtils from '../../../storage/mongodb/DatabaseUtils';
@@ -28,6 +26,7 @@ import { Request } from 'express';
import { ServerAction } from '../../../types/Server';
import SettingStorage from '../../../storage/mongodb/SettingStorage';
import Stripe from 'stripe';
+import Tenant from '../../../types/Tenant';
import TransactionStorage from '../../../storage/mongodb/TransactionStorage';
import User from '../../../types/User';
import UserStorage from '../../../storage/mongodb/UserStorage';
@@ -458,38 +457,6 @@ export default class StripeBillingIntegration extends BillingIntegration {
return billingInvoice;
}
- // TODO - move this method to the billing abstraction to make it common to all billing implementation
- private async updateTransactionsBillingData(billingInvoice: BillingInvoice): Promise<void> {
- if (!billingInvoice.sessions) {
- // This should not happen - but it happened once!
- throw new Error(`Unexpected situation - Invoice ${billingInvoice.id} has no sessions attached to it`);
- }
- await Promise.all(billingInvoice.sessions.map(async (session) => {
- const transactionID = session.transactionID;
- try {
- const transaction = await TransactionStorage.getTransaction(this.tenant, Number(transactionID));
- // Update Billing Data
- if (transaction?.billingData?.stop) {
- transaction.billingData.stop.invoiceStatus = billingInvoice.status;
- transaction.billingData.stop.invoiceNumber = billingInvoice.number;
- transaction.billingData.lastUpdate = new Date();
- // Save
- await TransactionStorage.saveTransactionBillingData(this.tenant, transaction.id, transaction.billingData);
- }
- } catch (error) {
- // Catch stripe errors and send the information back to the client
- await Logging.logError({
- tenantID: this.tenant.id,
- action: ServerAction.BILLING_CHARGE_INVOICE,
- actionOnUser: billingInvoice.user,
- module: MODULE_NAME, method: 'updateTransactionsBillingData',
- message: `Failed to update transaction billing data - transaction: ${transactionID}`,
- detailedMessages: { error: error.stack }
- });
- }
- }));
- }
-
private async chargeStripeInvoice(invoiceID: string): Promise<StripeChargeOperationResult> {
try {
// Fetch the invoice from stripe (do NOT TRUST the local copy)
@@ -782,29 +749,7 @@ export default class StripeBillingIntegration extends BillingIntegration {
}
}
- public async startTransaction(transaction: Transaction, chargingStation: ChargingStation): Promise<BillingDataTransactionStart> {
- if (!this.settings.billing.isTransactionBillingActivated) {
- return {
- // Keeps track whether the billing was activated or not on start transaction
- withBillingActive: false
- };
- }
- // Check Transaction
- this.checkStartTransaction(transaction, chargingStation);
- // Check Free Access
- if (transaction.user.freeAccess) {
- return {
- withBillingActive: false
- };
- }
- if (Utils.isTenantComponentActive(this.tenant, TenantComponents.ORGANIZATION)) {
- // Check Access Control
- if (!chargingStation.siteArea.accessControl) {
- return {
- withBillingActive: false
- };
- }
- }
+ public async startTransaction(transaction: Transaction): Promise<BillingDataTransactionStart> {
// Check Stripe
await this.checkConnection();
// Check Start Transaction Prerequisites

MODIFY
src/scheduler/tasks/BillPendingTransactionTask.ts
src/scheduler/tasks/BillPendingTransactionTask.ts
import Tenant, { TenantComponents } from '../../types/Tenant';
+import BillingFacade from '../../integration/billing/BillingFacade';
import BillingFactory from '../../integration/billing/BillingFactory';
import { BillingStatus } from '../../types/Billing';
import { ChargePointStatus } from '../../types/ocpp/OCPPServer';
import LockingHelper from '../../locking/LockingHelper';
import LockingManager from '../../locking/LockingManager';
import Logging from '../../utils/Logging';
-import OCPPUtils from '../../server/ocpp/utils/OCPPUtils';
import { ServerAction } from '../../types/Server';
import TenantSchedulerTask from '../TenantSchedulerTask';
-import { TransactionAction } from '../../types/Transaction';
import TransactionStorage from '../../storage/mongodb/TransactionStorage';
import Utils from '../../utils/Utils';
import global from '../../types/GlobalType';
@@ -110,7 +109,7 @@ export default class BillPendingTransactionTask extends TenantSchedulerTask {
transaction.stop.extraInactivityComputed = true;
transaction.stop.extraInactivitySecs = 0;
// Billing - This starts the billing async task - the BillingStatus will remain PENDING for a while!
- await OCPPUtils.processTransactionBilling(tenant, transaction, transaction.chargeBox, TransactionAction.END);
+ await BillingFacade.processEndTransaction(tenant, transaction);
// Save
await TransactionStorage.saveTransaction(tenant, transaction);
await Logging.logInfo({

MODIFY
src/server/ocpp/services/OCPPService.ts
src/server/ocpp/services/OCPPService.ts
@@ -8,6 +8,7 @@ import Transaction, { InactivityStatus, TransactionAction } from '../../../types
import { Action } from '../../../types/Authorization';
import Authorizations from '../../../authorization/Authorizations';
import BackendError from '../../../exception/BackendError';
+import BillingFacade from '../../../integration/billing/BillingFacade';
import CarConnectorFactory from '../../../integration/car-connector/CarConnectorFactory';
import CarStorage from '../../../storage/mongodb/CarStorage';
import ChargingStationClientFactory from '../../../client/ocpp/ChargingStationClientFactory';
@@ -217,7 +218,7 @@ export default class OCPPService {
// Pricing
await OCPPUtils.processTransactionPricing(tenant, transaction, chargingStation, consumption, TransactionAction.UPDATE);
// Billing
- await OCPPUtils.processTransactionBilling(tenant, transaction, chargingStation, TransactionAction.UPDATE);
+ await BillingFacade.processUpdateTransaction(tenant, transaction);
}
// Save
await ConsumptionStorage.saveConsumption(tenant, consumption);
@@ -382,7 +383,7 @@ export default class OCPPService {
const firstConsumption = await OCPPUtils.createFirstConsumption(tenant, chargingStation, newTransaction);
await OCPPUtils.processTransactionPricing(tenant, newTransaction, chargingStation, firstConsumption, TransactionAction.START);
// Billing
- await OCPPUtils.processTransactionBilling(tenant, newTransaction, chargingStation, TransactionAction.START);
+ await BillingFacade.processStartTransaction(tenant, newTransaction, chargingStation, chargingStation.siteArea);
// Roaming
await OCPPUtils.processTransactionRoaming(tenant, newTransaction, chargingStation, chargingStation.siteArea, tag, TransactionAction.START);
// Save it
@@ -489,7 +490,7 @@ export default class OCPPService {
// Update Transaction with Stop Transaction and Stop MeterValues
OCPPUtils.updateTransactionWithStopTransaction(transaction, chargingStation, stopTransaction, user, alternateUser, tagID, isSoftStop);
// Bill
- await OCPPUtils.processTransactionBilling(tenant, transaction, chargingStation, TransactionAction.STOP);
+ await BillingFacade.processStopTransaction(tenant, transaction);
// Roaming
await OCPPUtils.processTransactionRoaming(tenant, transaction, chargingStation, chargingStation.siteArea, transaction.tag, TransactionAction.STOP);
// Save the transaction
@@ -796,7 +797,7 @@ export default class OCPPService {
const transactionUpdated = await this.checkAndComputeTransactionExtraInactivityFromStatusNotification(
tenant, chargingStation, lastTransaction, connector, statusNotification);
// Billing: Trigger the asynchronous billing task
- const billingDataUpdated = await this.checkAndBillTransaction(tenant, lastTransaction, chargingStation);
+ const billingDataUpdated = await this.checkAndBillTransaction(tenant, lastTransaction);
// OCPI: Post the CDR
const ocpiUpdated = await this.checkAndSendOCPITransactionCdr(
tenant, lastTransaction, chargingStation, lastTransaction.tag);
@@ -893,13 +894,13 @@ export default class OCPPService {
return extraInactivityUpdated;
}
- private async checkAndBillTransaction(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation): Promise<boolean> {
+ private async checkAndBillTransaction(tenant: Tenant, transaction: Transaction): Promise<boolean> {
let transactionUpdated = false;
// Make sure the Extra Inactivity is already known
if (transaction.stop?.extraInactivityComputed) {
transactionUpdated = true;
// Billing - Start the asynchronous billing flow
- await OCPPUtils.processTransactionBilling(tenant, transaction, chargingStation, TransactionAction.END);
+ await BillingFacade.processEndTransaction(tenant, transaction);
}
return transactionUpdated;
}

MODIFY
src/server/ocpp/utils/OCPPUtils.ts
src/server/ocpp/utils/OCPPUtils.ts
@@ -300,107 +300,6 @@ export default class OCPPUtils {
}
}
- public static async processTransactionBilling(tenant: Tenant, transaction: Transaction, chargingStation: ChargingStation, action: TransactionAction): Promise<void> {
- if (!transaction.user || !transaction.user.issuer) {
- return;
- }
- const billingImpl = await BillingFactory.getBillingImpl(tenant);
- if (billingImpl) {
- switch (action) {
- // Start Transaction
- case TransactionAction.START:
- try {
- // Delegate
- const billingDataTransactionStart: BillingDataTransactionStart = await billingImpl.startTransaction(transaction, chargingStation);
- // Update
- transaction.billingData = {
- withBillingActive: billingDataTransactionStart.withBillingActive,
- lastUpdate: new Date()
- };
- } catch (error) {
- const message = `Billing - startTransaction failed - transaction ID '${transaction.id}'`;
- await Logging.logError({
- ...LoggingHelper.getTransactionProperties(transaction),
- tenantID: tenant.id,
- action: ServerAction.BILLING_TRANSACTION,
- module: MODULE_NAME, method: 'processTransactionBilling',
- message, detailedMessages: { error: error.stack }
- });
- // Prevent from starting a transaction when Billing prerequisites are not met
- throw new BackendError({
- ...LoggingHelper.getTransactionProperties(transaction),
- action: ServerAction.BILLING_TRANSACTION,
- module: MODULE_NAME, method: 'processTransactionBilling',
- message, detailedMessages: { error: error.stack }
- });
- }
- break;
- // Meter Values
- case TransactionAction.UPDATE:
- try {
- // Delegate
- await billingImpl.updateTransaction(transaction);
- // Update
- if (transaction.billingData) {
- transaction.billingData.lastUpdate = new Date();
- }
- } catch (error) {
- const message = `Billing - updateTransaction failed - transaction ID '${transaction.id}'`;
- await Logging.logError({
- ...LoggingHelper.getTransactionProperties(transaction),
- tenantID: tenant.id,
- action: ServerAction.BILLING_TRANSACTION,
- module: MODULE_NAME, method: 'processTransactionBilling',
- message, detailedMessages: { error: error.stack }
- });
- }
- break;
- // Stop Transaction - Extra inactivity is not yet known
- case TransactionAction.STOP:
- try {
- // Delegate
- const billingDataStop: BillingDataTransactionStop = await billingImpl.stopTransaction(transaction);
- // Update
- if (transaction.billingData) {
- transaction.billingData.stop = billingDataStop;
- transaction.billingData.lastUpdate = new Date();
- }
- } catch (error) {
- const message = `Billing - stopTransaction failed - transaction ID '${transaction.id}'`;
- await Logging.logError({
- ...LoggingHelper.getTransactionProperties(transaction),
- tenantID: tenant.id,
- action: ServerAction.BILLING_TRANSACTION,
- module: MODULE_NAME, method: 'processTransactionBilling',
- message, detailedMessages: { error: error.stack }
- });
- }
- break;
- // End Transaction - Extra inactivity is now known
- case TransactionAction.END:
- try {
- // Delegate
- const billingDataStop: BillingDataTransactionStop = await billingImpl.endTransaction(transaction);
- // Update
- if (transaction.billingData) {
- transaction.billingData.stop = billingDataStop;
- transaction.billingData.lastUpdate = new Date();
- }
- } catch (error) {
- const message = `Billing - endTransaction failed - transaction ID '${transaction.id}'`;
- await Logging.logError({
- ...LoggingHelper.getTransactionProperties(transaction),
- tenantID: tenant.id,
- action: ServerAction.BILLING_TRANSACTION,
- module: MODULE_NAME, method: 'processTransactionBilling',
- message, detailedMessages: { error: error.stack }
- });
- }
- break;
- }
- }
- }
-
public static assertConsistencyInConsumption(chargingStation: ChargingStation, connectorID: number, consumption: Consumption): void {
// Check Total Power with Meter Value Power L1, L2, L3
if (consumption.instantWattsL1 > 0 || consumption.instantWattsL2 > 0 || consumption.instantWattsL3 > 0) {

MODIFY
test/api/BillingTest.ts
test/api/BillingTest.ts
@@ -743,6 +743,8 @@ describe('Billing', function() {
describe('When basic user has a free access', () => {
// eslint-disable-next-line @typescript-eslint/require-await
before(async () => {
+ // Initialize the charging station context
+ await billingTestHelper.initChargingStationContext();
billingTestHelper.billingImpl = await billingTestHelper.setBillingSystemValidCredentials();
billingTestHelper.adminUserContext = await billingTestHelper.tenantContext.getUserContext(ContextDefinition.USER_CONTEXTS.DEFAULT_ADMIN);
expect(billingTestHelper.adminUserContext).to.not.be.null;
@@ -780,6 +782,8 @@ describe('Billing', function() {
describe('When basic user does not have a free access', () => {
// eslint-disable-next-line @typescript-eslint/require-await
before(async () => {
+ // Initialize the charging station context
+ await billingTestHelper.initChargingStationContext();
billingTestHelper.billingImpl = await billingTestHelper.setBillingSystemValidCredentials();
billingTestHelper.adminUserContext = await billingTestHelper.tenantContext.getUserContext(ContextDefinition.USER_CONTEXTS.DEFAULT_ADMIN);
expect(billingTestHelper.adminUserContext).to.not.be.null;

MODIFY
test/api/BillingTestHelper.ts
test/api/BillingTestHelper.ts
@@ -8,6 +8,7 @@ import PricingDefinition, { DayOfWeek, PricingDimension, PricingDimensions, Pric
import chai, { assert, expect } from 'chai';
import AsyncTaskStorage from '../../src/storage/mongodb/AsyncTaskStorage';
+import BillingFacade from '../../src/integration/billing/BillingFacade';
import CentralServerService from './client/CentralServerService';
import ChargingStationContext from './context/ChargingStationContext';
import Constants from '../../src/utils/Constants';
@@ -17,7 +18,6 @@ import Cypher from '../../src/utils/Cypher';
import { DataResult } from '../../src/types/DataResult';
import Decimal from 'decimal.js';
import LoggingStorage from '../../src/storage/mongodb/LoggingStorage';
-import OCPPUtils from '../../src/server/ocpp/utils/OCPPUtils';
import SiteAreaContext from './context/SiteAreaContext';
import SiteContext from './context/SiteContext';
import { StatusCodes } from 'http-status-codes';
@@ -27,7 +27,6 @@ import { TenantComponents } from '../../src/types/Tenant';
import TenantContext from './context/TenantContext';
import TestConstants from './client/utils/TestConstants';
import TestUtils from './TestUtils';
-import { TransactionAction } from '../../src/types/Transaction';
import TransactionStorage from '../../src/storage/mongodb/TransactionStorage';
import User from '../../src/types/User';
import Utils from '../../src/utils/Utils';
@@ -598,7 +597,7 @@ export default class BillingTestHelper {
transaction = await TransactionStorage.getTransaction(tenant, transactionId, { withUser: true, withChargingStation: true });
transaction.stop.extraInactivityComputed = true;
transaction.stop.extraInactivitySecs = 0;
- await OCPPUtils.processTransactionBilling(tenant, transaction, transaction.chargeBox, TransactionAction.END);
+ await BillingFacade.processEndTransaction(tenant, transaction);
} else {
// #end
const stopTransactionResponse = await this.chargingStationContext.stopTransaction(transactionId, tagId, meterStop, stopDate.toDate());

billing  facade