billing - clear test data endpoint
MODIFY
src/authorization/Authorizations.ts
src/authorization/Authorizations.ts
@@ -693,6 +693,10 @@ export default class Authorizations {
return Authorizations.canPerformAction(loggedUser, Entity.PRICING, Action.UPDATE);
}
+ public static async canClearBillingTestData(loggedUser: UserToken): Promise<boolean> {
+ return Authorizations.canPerformAction(loggedUser, Entity.BILLING, Action.CLEAR_BILLING_TEST_DATA);
+ }
+
public static async canCheckBillingConnection(loggedUser: UserToken): Promise<boolean> {
return Authorizations.canPerformAction(loggedUser, Entity.BILLING, Action.CHECK_CONNECTION);
}

MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
@@ -158,7 +158,7 @@ const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
{ resource: Entity.LOGGINGS, action: Action.LIST, attributes: ['*'] },
{ resource: Entity.LOGGING, action: Action.READ, attributes: ['*'] },
{ resource: Entity.PRICING, action: [Action.READ, Action.UPDATE], attributes: ['*'] },
- { resource: Entity.BILLING, action: [Action.CHECK_CONNECTION] },
+ { resource: Entity.BILLING, action: [Action.CHECK_CONNECTION, Action.CLEAR_BILLING_TEST_DATA] },
{ resource: Entity.TAXES, action: [Action.LIST], attributes: ['*'] },
// ---------------------------------------------------------------------------------------------------
// TODO - no use-case so far - clarify whether a SYNC INVOICES and CREATE INVOICE makes sense or not!

MODIFY
src/integration/billing/BillingIntegration.ts
src/integration/billing/BillingIntegration.ts
@@ -371,7 +371,7 @@ export default abstract class BillingIntegration {
// eslint-disable-next-line @typescript-eslint/member-ordering
public async clearTestData(): Promise<void> {
- await this.checkConnection();
+ // await this.checkConnection(); - stripe connection is useless to cleanup test data
await Logging.logInfo({
tenantID: this.tenant.id,
source: Constants.CENTRAL_SERVER,
@@ -568,6 +568,10 @@ export default abstract class BillingIntegration {
abstract checkActivationPrerequisites(): Promise<void>;
+ abstract checkTestDataCleanupPrerequisites() : Promise<void>;
+
+ abstract resetConnectionSettings() : Promise<void>;
+
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
@@ -17,6 +17,7 @@ import I18nManager from '../../../utils/I18nManager';
import Logging from '../../../utils/Logging';
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 Transaction from '../../../types/Transaction';
@@ -109,6 +110,46 @@ export default class StripeBillingIntegration extends BillingIntegration {
}
}
+ public async checkTestDataCleanupPrerequisites(): Promise<void> {
+ // Make sure the STRIPE account is not live
+ let secretKey: string, publicKey: string;
+ try {
+ secretKey = await Cypher.decrypt(this.tenant.id, this.settings.stripe.secretKey);
+ publicKey = this.settings.stripe.publicKey;
+ } catch (error) {
+ // Ignore error
+ }
+ if (secretKey?.startsWith('sk_live_') || publicKey?.startsWith('pk_live_')) {
+ throw new BackendError({
+ source: Constants.CENTRAL_SERVER,
+ module: MODULE_NAME, method: 'checkTestDataCleanupPrerequisites',
+ action: ServerAction.BILLING_TEST_DATA_CLEANUP,
+ message: 'Stripe Account is live - Test data cleanup has been aborted'
+ });
+ }
+ }
+
+ public async resetConnectionSettings(): Promise<void> {
+ // reset connection settings
+ const { identifier, type } = this.settings;
+ const newBillingsSettings: BillingSettings = {
+ identifier,
+ type,
+ billing: {
+ isTransactionBillingActivated: false,
+ immediateBillingAllowed: false,
+ periodicBillingAllowed: false,
+ taxID: null
+ },
+ stripe: {
+ url: null,
+ secretKey: null,
+ publicKey: null
+ }
+ };
+ await SettingStorage.saveBillingSetting(this.tenant.id, newBillingsSettings);
+ }
+
public async getUsers(): Promise<BillingUser[]> {
const users = [];
let request;

MODIFY
src/server/rest/v1/router/api/BillingRouter.ts
src/server/rest/v1/router/api/BillingRouter.ts
@@ -18,6 +18,7 @@ export default class BillingRouter {
this.buildRouteBillingSetting();
this.buildRouteUpdateBillingSetting();
this.buildRouteCheckBillingConnection();
+ this.buildRouteClearTestData();
// -----------------------------------
// ROUTES for PAYMENT METHODS
// -----------------------------------
@@ -56,6 +57,12 @@ export default class BillingRouter {
});
}
+ protected buildRouteClearTestData(): void {
+ this.router.post(`/${ServerRoute.REST_BILLING_CLEAR_TEST_DATA}`, (req: Request, res: Response, next: NextFunction) => {
+ void RouterUtils.handleServerAction(BillingService.handleClearBillingTestData.bind(this), ServerAction.SETTINGS, req, res, next);
+ });
+ }
+
protected buildRouteBillingPaymentMethods(): void {
this.router.get(`/${ServerRoute.REST_BILLING_PAYMENT_METHODS}`, (req: Request, res: Response, next: NextFunction) => {
// GET {{base_url}}/v1/api/billing/users/5be451dad0685c19bff48856/payment-methods?Limit=100&SortFields=id

MODIFY
src/server/rest/v1/service/BillingService.ts
src/server/rest/v1/service/BillingService.ts
@@ -29,6 +29,53 @@ const MODULE_NAME = 'BillingService';
export default class BillingService {
+ public static async handleClearBillingTestData(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ // Check if component is active
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING,
+ Action.CLEAR_BILLING_TEST_DATA, Entity.BILLING, MODULE_NAME, 'handleClearBillingTestData');
+ if (!await Authorizations.canClearBillingTestData(req.user)) {
+ throw new AppAuthError({
+ errorCode: HTTPAuthError.FORBIDDEN,
+ user: req.user,
+ entity: Entity.BILLING, action: Action.CLEAR_BILLING_TEST_DATA,
+ module: MODULE_NAME, method: 'handleClearBillingTestData',
+ });
+ }
+ const billingImpl = await BillingFactory.getBillingImpl(req.tenant);
+ if (!billingImpl) {
+ throw new AppError({
+ source: Constants.CENTRAL_SERVER,
+ errorCode: HTTPError.GENERAL_ERROR,
+ message: 'Billing service is not configured',
+ module: MODULE_NAME, method: 'handleClearBillingTestData',
+ action: action,
+ user: req.user
+ });
+ }
+ try {
+ // Check Prerequisites
+ await billingImpl.checkTestDataCleanupPrerequisites();
+ // Clear the test data
+ await billingImpl.clearTestData();
+ // Reset billing settings
+ await billingImpl.resetConnectionSettings();
+ // Ok
+ res.json(Object.assign({ done: true }, Constants.REST_RESPONSE_SUCCESS));
+ } catch (error) {
+ // Ko
+ await Logging.logError({
+ tenantID: req.user.tenantID,
+ user: req.user,
+ module: MODULE_NAME, method: 'handleClearBillingTestData',
+ message: 'Billing connection failed',
+ action: action,
+ detailedMessages: { error: error.message, stack: error.stack }
+ });
+ res.json(Object.assign({ done: false }, Constants.REST_RESPONSE_SUCCESS));
+ }
+ next();
+ }
+
public static async handleCheckBillingConnection(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING,

MODIFY
src/types/Authorization.ts
src/types/Authorization.ts
@@ -142,6 +142,7 @@ export enum Action {
BILLING_DELETE_PAYMENT_METHOD = 'BillingDeletePaymentMethod',
BILLING_CHARGE_INVOICE = 'BillingChargeInvoice',
CHECK_CONNECTION = 'CheckConnection',
+ CLEAR_BILLING_TEST_DATA = 'ClearBillingTestData',
RETRIEVE_CONSUMPTION = 'RetrieveConsumption',
PING = 'Ping',
GENERATE_LOCAL_TOKEN = 'GenerateLocalToken',

MODIFY
src/types/Server.ts
src/types/Server.ts
@@ -517,6 +517,7 @@ export enum ServerRoute {
REST_BILLING_SETTING = 'billing-setting', // GET and PUT
REST_BILLING_CHECK = 'billing/check',
+ REST_BILLING_CLEAR_TEST_DATA = 'billing/clearTestData',
// BILLING URLs for CRUD operations on INVOICES
REST_BILLING_INVOICES = 'invoices',

billing - clear test data endpoint