billing  - check prerequisites before a Go Live
MODIFY
src/integration/billing/BillingIntegration.ts
src/integration/billing/BillingIntegration.ts
@@ -328,7 +328,7 @@ export default abstract class BillingIntegration {
return billingUser;
}
- abstract checkConnection(): Promise<void>;
+ abstract checkConnection(checkPrerequisite?: boolean): Promise<void>;
abstract startTransaction(transaction: Transaction): Promise<BillingDataTransactionStart>;

MODIFY
src/integration/billing/stripe/StripeBillingIntegration.ts
src/integration/billing/stripe/StripeBillingIntegration.ts
@@ -68,7 +68,7 @@ export default class StripeBillingIntegration extends BillingIntegration {
}
// eslint-disable-next-line @typescript-eslint/require-await
- public async checkConnection(): Promise<void> {
+ public async checkConnection(checkPrerequisite = false): Promise<void> {
// Initialize Stripe
if (!this.stripe) {
try {
@@ -77,7 +77,7 @@ export default class StripeBillingIntegration extends BillingIntegration {
throw new BackendError({
source: Constants.CENTRAL_SERVER,
module: MODULE_NAME, method: 'checkConnection',
- action: ServerAction.CHECK_CONNECTION,
+ action: ServerAction.CHECK_BILLING_CONNECTION,
message: 'Failed to connect to Stripe - Key is inconsistent',
detailedMessages: { error: error.message, stack: error.stack }
});
@@ -91,12 +91,36 @@ export default class StripeBillingIntegration extends BillingIntegration {
throw new BackendError({
source: Constants.CENTRAL_SERVER,
module: MODULE_NAME, method: 'checkConnection',
- action: ServerAction.CHECK_CONNECTION,
+ action: ServerAction.CHECK_BILLING_CONNECTION,
message: 'Failed to get a Stripe instance'
});
}
// Let's make sure the connection works as expected
this.productionMode = await StripeBillingIntegration.isConnectedToALiveAccount(this.stripe);
+
+ // Check Taxes Prerequisites
+ if (checkPrerequisite) {
+ // Check whether the taxID is set and still active
+ const taxID = this.settings.billing?.taxID;
+ if (taxID) {
+ const billingTax: BillingTax = await this._getTaxRate(taxID);
+ if (!billingTax) {
+ throw new BackendError({
+ source: Constants.CENTRAL_SERVER,
+ module: MODULE_NAME, method: 'checkConnection',
+ action: ServerAction.CHECK_BILLING_CONNECTION,
+ message: `Billing prerequisites are not consistent - taxID is not found or inactive - taxID: '${taxID}'`
+ });
+ }
+ } else {
+ throw new BackendError({
+ source: Constants.CENTRAL_SERVER,
+ module: MODULE_NAME, method: 'checkConnection',
+ action: ServerAction.CHECK_BILLING_CONNECTION,
+ message: 'Billing prerequisites are not consistent - taxID is mandatory'
+ });
+ }
+ }
}
}
@@ -212,6 +236,31 @@ export default class StripeBillingIntegration extends BillingIntegration {
return taxes;
}
+ public async getTaxRate(taxID: string): Promise<BillingTax> {
+ await this.checkConnection();
+ return this._getTaxRate(taxID);
+ }
+
+ public async _getTaxRate(taxID: string): Promise<BillingTax> {
+ let taxRate : BillingTax = null;
+ try {
+ const stripeTaxRate: Stripe.TaxRate = await this.stripe.taxRates.retrieve(taxID);
+ if (stripeTaxRate && stripeTaxRate.active) {
+ const { id, description, display_name: displayName, percentage } = stripeTaxRate;
+ taxRate = { id, description, displayName, percentage };
+ }
+ } catch (e) {
+ // catch stripe errors and send the information back to the client
+ await Logging.logError({
+ tenantID: this.tenantID,
+ action: ServerAction.BILLING_TAXES,
+ module: MODULE_NAME, method: 'getTaxRate',
+ message: `Stripe operation failed - ${e?.message as string}`
+ });
+ }
+ return taxRate;
+ }
+
public async getStripeInvoice(id: string): Promise<Stripe.Invoice> {
// Get Invoice
const stripeInvoice = await this.stripe.invoices.retrieve(id);

MODIFY
src/server/rest/v1/router/api/BillingRouter.ts
src/server/rest/v1/router/api/BillingRouter.ts
@@ -16,11 +16,12 @@ export default class BillingRouter {
// ROUTES for BILLING SETTINGS
// -----------------------------------
this.buildRouteBillingSettings();
+ this.buildRouteCheckBillingSettingsPrerequisites();
this.buildRouteBillingSetting();
// this.buildRouteCreateBillingSetting();
this.buildRouteUpdateBillingSetting();
// this.buildRouteDeleteBillingSetting();
- this.buildRouteCheckBillingSetting();
+ // this.buildRouteCheckBillingSetting();
this.buildRouteActivateBillingSetting();
this.buildRouteCheckBillingSettingConnection();
// -----------------------------------
@@ -43,6 +44,12 @@ export default class BillingRouter {
});
}
+ protected buildRouteCheckBillingSettingsPrerequisites(): void {
+ this.router.post(`/${ServerRoute.REST_BILLING_SETTINGS_CHECK_PREREQUISITES}`, (req: Request, res: Response, next: NextFunction) => {
+ void RouterUtils.handleServerAction(BillingService.handleCheckBillingSettingsPrerequisites.bind(this), ServerAction.SETTINGS, req, res, next);
+ });
+ }
+
protected buildRouteBillingSetting(): void {
this.router.get(`/${ServerRoute.REST_BILLING_SETTING}`, (req: Request, res: Response, next: NextFunction) => {
void RouterUtils.handleServerAction(BillingService.handleGetBillingSetting.bind(this), ServerAction.SETTINGS, req, res, next);
@@ -55,11 +62,11 @@ export default class BillingRouter {
});
}
- protected buildRouteCheckBillingSetting(): void {
- this.router.post(`/${ServerRoute.REST_BILLING_SETTING_CHECK}`, (req: Request, res: Response, next: NextFunction) => {
- void RouterUtils.handleServerAction(BillingService.handleCheckBillingSetting.bind(this), ServerAction.SETTINGS, req, res, next);
- });
- }
+ // protected buildRouteCheckBillingSetting(): void {
+ // this.router.post(`/${ServerRoute.REST_BILLING_SETTING_CHECK}`, (req: Request, res: Response, next: NextFunction) => {
+ // void RouterUtils.handleServerAction(BillingService.handleCheckBillingSetting.bind(this), ServerAction.SETTINGS, req, res, next);
+ // });
+ // }
protected buildRouteActivateBillingSetting(): void {
this.router.post(`/${ServerRoute.REST_BILLING_SETTING_ACTIVATE}`, (req: Request, res: Response, next: NextFunction) => {

MODIFY
src/server/rest/v1/service/BillingService.ts
src/server/rest/v1/service/BillingService.ts
@@ -764,6 +764,32 @@ export default class BillingService {
next();
}
+ public static async handleCheckBillingSettingsPrerequisites(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ // Check if component is active
+ UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING,
+ Action.CHECK_CONNECTION, Entity.BILLING, MODULE_NAME, 'handleCheckBillingSettingsPrerequisites');
+ // -----------------------------------------------------
+ // GOAL: Check the setting consistency before a Go Live
+ // -----------------------------------------------------
+ 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: 'handleCheckBillingSettingsPrerequisites',
+ action: action,
+ user: req.user
+ });
+ }
+ // Check the connection settings + the prerequisites (such as the taxID)
+ const checkPrerequisites = true;
+ await billingImpl.checkConnection(checkPrerequisites);
+ // Ok
+ res.json(Constants.REST_RESPONSE_SUCCESS);
+ next();
+ }
+
public static async handleGetBillingSetting(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
if (!await Authorizations.canReadSetting(req.user)) {
throw new AppAuthError({
@@ -845,7 +871,6 @@ export default class BillingService {
next();
}
- // eslint-disable-next-line @typescript-eslint/require-await
public static async handleCheckBillingSettingConnection(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
// Check if component is active
UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING,
@@ -889,11 +914,31 @@ export default class BillingService {
}
// eslint-disable-next-line @typescript-eslint/require-await
- public static async handleCheckBillingSetting(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
- // TODO - check the overall consistency - such as the taxID
- res.sendStatus(StatusCodes.NOT_IMPLEMENTED);
- next();
- }
+ // public static async handleCheckBillingSetting(action: ServerAction, req: Request, res: Response, next: NextFunction): Promise<void> {
+ // // Check if component is active
+ // UtilsService.assertComponentIsActiveFromToken(req.user, TenantComponents.BILLING,
+ // Action.CHECK_CONNECTION, Entity.BILLING, MODULE_NAME, 'handleCheckBillingSettingConnection');
+ // // -----------------------------------------------------------------------
+ // // GOAL: Check the setting consistency before an activation
+ // // -----------------------------------------------------------------------
+ // 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: 'handleCheckBillingConnection',
+ // action: action,
+ // user: req.user
+ // });
+ // }
+ // // Check the connection settings + the prerequisites (such as the taxID)
+ // const checkPrerequisites = true;
+ // await billingImpl.checkConnection(checkPrerequisites);
+ // // Ok
+ // res.json(Constants.REST_RESPONSE_SUCCESS);
+ // next();
+ // }
private static async alterSensitiveData(tenantID: string, billingSettings: BillingSettings, newBillingProperties: Partial<BillingSettings>) {
// Process the sensitive data (if any)

MODIFY
src/types/Server.ts
src/types/Server.ts
@@ -508,8 +508,9 @@ export enum ServerRoute {
// BILLING SETTING URLs
REST_BILLING_SETTINGS = 'billing-settings',
+ REST_BILLING_SETTINGS_CHECK_PREREQUISITES = 'billing-settings/CheckPrerequisites',
REST_BILLING_SETTING = 'billing-settings/:id',
- REST_BILLING_SETTING_CHECK = 'billing-settings/:id/check',
+ // REST_BILLING_SETTING_CHECK = 'billing-settings/:id/check',
REST_BILLING_SETTING_ACTIVATE = 'billing-settings/:id/activate',
REST_BILLING_SETTING_CHECK_CONNECTION = 'billing-settings/:id/checkConnection',
}

billing  - check prerequisites before a Go Live