start transaction - precheck billing prerequisites
MODIFY
src/integration/billing/BillingIntegration.ts
src/integration/billing/BillingIntegration.ts
import { BillingChargeInvoiceAction, BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceItem, BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod, BillingStatus, BillingTax, BillingUser, BillingUserSynchronizeAction } from '../../types/Billing';
import FeatureToggles, { Feature } from '../../utils/FeatureToggles';
+import Transaction, { StartTransactionCheck } from '../../types/Transaction';
import User, { UserStatus } from '../../types/User';
import BackendError from '../../exception/BackendError';
@@ -14,7 +15,6 @@ import { ServerAction } from '../../types/Server';
import SettingStorage from '../../storage/mongodb/SettingStorage';
import Tenant from '../../types/Tenant';
import TenantStorage from '../../storage/mongodb/TenantStorage';
-import Transaction from '../../types/Transaction';
import TransactionStorage from '../../storage/mongodb/TransactionStorage';
import UserStorage from '../../storage/mongodb/UserStorage';
import Utils from '../../utils/Utils';
@@ -23,7 +23,6 @@ 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;
@@ -615,4 +614,6 @@ export default abstract class BillingIntegration {
abstract getPaymentMethods(user: User): Promise<BillingPaymentMethod[]>;
abstract deletePaymentMethod(user: User, paymentMethodId: string): Promise<BillingOperationResult>;
+
+ abstract precheckStartTransactionPrerequisites(user: User): Promise<StartTransactionCheck>;
}

MODIFY
src/integration/billing/stripe/StripeBillingIntegration.ts
src/integration/billing/stripe/StripeBillingIntegration.ts
@@ -3,6 +3,7 @@ import { AsyncTaskType, AsyncTasks } from '../../../types/AsyncTask';
import { BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceItem, BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod, BillingStatus, BillingTax, BillingUser, BillingUserData } from '../../../types/Billing';
import FeatureToggles, { Feature } from '../../../utils/FeatureToggles';
import StripeHelpers, { StripeChargeOperationResult } from './StripeHelpers';
+import Transaction, { StartTransactionCheck } from '../../../types/Transaction';
import AsyncTaskManager from '../../../async-task/AsyncTaskManager';
import AxiosFactory from '../../../utils/AxiosFactory';
@@ -20,7 +21,6 @@ import { ServerAction } from '../../../types/Server';
import SettingStorage from '../../../storage/mongodb/SettingStorage';
import Stripe from 'stripe';
import Tenant from '../../../types/Tenant';
-import Transaction from '../../../types/Transaction';
import TransactionStorage from '../../../storage/mongodb/TransactionStorage';
import User from '../../../types/User';
import UserStorage from '../../../storage/mongodb/UserStorage';
@@ -761,9 +761,13 @@ export default class StripeBillingIntegration extends BillingIntegration {
}
private isTransactionUserInternal(transaction: Transaction): boolean {
+ return this.isUserInternal(transaction?.user);
+ }
+
+ private isUserInternal(user: User): boolean {
// slf
if (this.tenant.id === '5be7fb271014d90008992f06') {
- const email = transaction?.user?.email?.toLocaleLowerCase();
+ const email = user?.email?.toLocaleLowerCase();
if (email?.endsWith('@sap.com') || email?.endsWith('@vinci-facilities.com')) {
// Internal user
return true;
@@ -1438,4 +1442,34 @@ export default class StripeBillingIntegration extends BillingIntegration {
// --------------------------------------------------------------
return null;
}
+
+ public async precheckStartTransactionPrerequisites(user: User): Promise<StartTransactionCheck> {
+ let precheckStatus = StartTransactionCheck.OK;
+ if (this.settings.billing.isTransactionBillingActivated && !this.isUserInternal(user)) {
+ // Check billing prerequisites
+ try {
+ // Make sure the STRIPE connection is ok
+ await this.checkConnection();
+ // Make sure the taxID is still valid
+ await this.checkActivationPrerequisites();
+ } catch (error) {
+ precheckStatus = StartTransactionCheck.BILLING_NO_SETTINGS;
+ }
+ if (precheckStatus === StartTransactionCheck.OK) {
+ // Check user prerequisites
+ const customerID: string = user?.billingData?.customerID;
+ if (FeatureToggles.isFeatureActive(Feature.BILLING_CHECK_CUSTOMER_ID)) {
+ try {
+ // Check whether the customer exists or not
+ const customer = await this.checkStripeCustomer(customerID);
+ // Check whether the customer has a default payment method
+ this.checkStripePaymentMethod(customer);
+ } catch (error) {
+ precheckStatus = StartTransactionCheck.BILLING_NO_PAYMENT_METHOD;
+ }
+ }
+ }
+ }
+ return precheckStatus;
+ }
}

MODIFY
src/server/rest/v1/service/UserService.ts
src/server/rest/v1/service/UserService.ts
@@ -29,6 +29,7 @@ import { OCPITokenWhitelist } from '../../../../types/ocpi/OCPIToken';
import OCPIUtils from '../../../ocpi/OCPIUtils';
import { ServerAction } from '../../../../types/Server';
import SiteStorage from '../../../../storage/mongodb/SiteStorage';
+import { StartTransactionCheck } from '../../../../types/Transaction';
import TagStorage from '../../../../storage/mongodb/TagStorage';
import TenantComponents from '../../../../types/TenantComponents';
import TenantStorage from '../../../../storage/mongodb/TenantStorage';
@@ -83,7 +84,7 @@ export default class UserService {
// Check user
const user = await UserStorage.getUser(
req.user.tenantID, userID, authorizationUserFilters.filters);
- UtilsService.assertObjectExists(action, user, `User ID '${userID}' does not exist`, MODULE_NAME, 'handleDeleteUser', req.user);
+ UtilsService.assertObjectExists(action, user, `User ID '${userID}' does not exist`, MODULE_NAME, 'handleGetUserDefaultTagCar', req.user);
// Handle Tag
// Get the default Tag
let tag = await TagStorage.getDefaultUserTag(req.user.tenantID, userID, {
@@ -110,8 +111,19 @@ export default class UserService {
]);
}
}
+ let status = StartTransactionCheck.OK;
+ if (Utils.isComponentActiveFromToken(req.user, TenantComponents.BILLING)) {
+ try {
+ const billingImpl = await BillingFactory.getBillingImpl(req.tenant);
+ if (billingImpl) {
+ status = await billingImpl.precheckStartTransactionPrerequisites(user);
+ }
+ } catch (error) {
+ status = StartTransactionCheck.BILLING_INCONSISTENT_SETTINGS;
+ }
+ }
// Return
- res.json({ tag, car });
+ res.json({ tag, car, status });
next();
}

MODIFY
src/types/Transaction.ts
src/types/Transaction.ts
@@ -32,6 +32,13 @@ export enum TransactionAction {
END = 'end'
}
+export enum StartTransactionCheck {
+ OK = 'ok',
+ BILLING_NO_PAYMENT_METHOD = 'no_payment_method', // start transaction is not possible - user has no payment method
+ BILLING_NO_SETTINGS = 'billing_no_settings', // start transaction not possible - billing settings are not set (or partially set)
+ BILLING_INCONSISTENT_SETTINGS = 'billing_inconsistent_settings', // start transaction not possible - billing settings are inconsistent
+}
+
export default interface Transaction extends AbstractCurrentConsumption {
id?: number;
carID?: string;

start transaction - precheck billing prerequisites