billing - STRIPE - productionMode
MODIFY
src/integration/billing/BillingFactory.ts
src/integration/billing/BillingFactory.ts
-import { BillingSetting, BillingSettings, BillingSettingsType } from '../../types/Setting';
+import { BillingSettings, BillingSettingsType } from '../../types/Setting';
import BillingIntegration from './BillingIntegration';
import Logging from '../../utils/Logging';
@@ -25,10 +25,7 @@ export default class BillingFactory {
let billingIntegrationImpl = null;
switch (settings.type) {
case BillingSettingsType.STRIPE:
- if (StripeBillingIntegration.checkSettingsConsistency(settings.stripe)) {
- // Only call the constructor when the prerequisites are met
- billingIntegrationImpl = new StripeBillingIntegration(tenantID, settings);
- }
+ billingIntegrationImpl = StripeBillingIntegration.getInstance(tenantID, settings);
break;
}
return billingIntegrationImpl;

MODIFY
src/integration/billing/BillingIntegration.ts
src/integration/billing/BillingIntegration.ts
import { BillingChargeInvoiceAction, BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceDocument, BillingInvoiceItem, BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod, BillingTax, BillingUser, BillingUserSynchronizeAction } from '../../types/Billing';
-import { BillingSetting, BillingSettings } from '../../types/Setting';
/* eslint-disable @typescript-eslint/member-ordering */
import User, { UserStatus } from '../../types/User';
import BackendError from '../../exception/BackendError';
+import { BillingSettings } from '../../types/Setting';
import BillingStorage from '../../storage/mongodb/BillingStorage';
import Constants from '../../utils/Constants';
import Logging from '../../utils/Logging';
@@ -20,8 +20,8 @@ const MODULE_NAME = 'BillingIntegration';
export default abstract class BillingIntegration {
- // TO BE REMOVED - flag to switch ON/OFF some STRIPE integration logic not yet finalized!
- protected readonly __liveMode: boolean = false;
+ // Production Mode is set to true when the STRIPE account is a live one!
+ protected productionMode = false;
protected readonly tenantID: string; // Assuming UUID or other string format ID
protected settings: BillingSettings;
@@ -60,7 +60,7 @@ export default abstract class BillingIntegration {
message: `${users.length} new user(s) are going to be synchronized`
});
// Check LIVE MODE
- if (!this.__liveMode) {
+ if (!this.productionMode) {
await Logging.logWarning({
tenantID: this.tenantID,
source: Constants.CENTRAL_SERVER,
@@ -292,7 +292,7 @@ export default abstract class BillingIntegration {
action: ServerAction.BILLING_TRANSACTION
});
}
- if (this.__liveMode) {
+ if (this.productionMode) {
// Check Billing Data (only in Live Mode)
if (!transaction.user.billingData) {
throw new BackendError({
@@ -325,7 +325,7 @@ export default abstract class BillingIntegration {
action: ServerAction.BILLING_TRANSACTION
});
}
- if (this.__liveMode) {
+ if (this.productionMode) {
// Check Billing Data (only in Live Mode)
const billingUser = transaction.user;
if (!billingUser.billingData || !billingUser.billingData.customerID) {

MODIFY
src/integration/billing/stripe/StripeBillingIntegration.ts
src/integration/billing/stripe/StripeBillingIntegration.ts
/* eslint-disable @typescript-eslint/member-ordering */
import { BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceDocument, BillingInvoiceItem, BillingInvoiceStatus, BillingOperationResult, BillingPaymentMethod, BillingStatus, BillingTax, BillingUser, BillingUserData } from '../../../types/Billing';
-import { BillingSettings, StripeBillingSetting } from '../../../types/Setting';
import { DocumentEncoding, DocumentType } from '../../../types/GlobalType';
import AxiosFactory from '../../../utils/AxiosFactory';
import { AxiosInstance } from 'axios';
import BackendError from '../../../exception/BackendError';
import BillingIntegration from '../BillingIntegration';
+import { BillingSettings } from '../../../types/Setting';
import BillingStorage from '../../../storage/mongodb/BillingStorage';
import Constants from '../../../utils/Constants';
import Cypher from '../../../utils/Cypher';
@@ -35,12 +35,28 @@ export default class StripeBillingIntegration extends BillingIntegration {
this.axiosInstance = AxiosFactory.getAxiosInstance(this.tenantID);
}
- public static checkSettingsConsistency(settings: StripeBillingSetting): boolean {
- if (settings.url && settings.secretKey && settings.publicKey) {
- return true;
+ public static getInstance(tenantID: string, settings: BillingSettings): StripeBillingIntegration {
+ if (settings.stripe?.url && settings.stripe?.secretKey && settings.stripe?.publicKey) {
+ return new StripeBillingIntegration(tenantID, settings);
}
// STRIPE prerequisites are not met
- return false ;
+ return null;
+ }
+
+ private static async isConnectedToALiveAccount(stripeFacade: Stripe): Promise<boolean> {
+ try {
+ // TODO - find a way to avoid that call
+ const list = await stripeFacade.customers.list({ limit: 1 });
+ return !!list.data?.[0]?.livemode;
+ } catch (error) {
+ throw new BackendError({
+ source: Constants.CENTRAL_SERVER,
+ module: MODULE_NAME, method: 'isConnectedToALiveAccount',
+ action: ServerAction.CHECK_CONNECTION,
+ message: 'Failed to connect to Stripe',
+ detailedMessages: { error: error.message, stack: error.stack }
+ });
+ }
}
public async getStripeInstance(): Promise<Stripe> {
@@ -49,26 +65,10 @@ export default class StripeBillingIntegration extends BillingIntegration {
return this.stripe;
}
- // public alterStripeSettings(someSettings: Partial<StripeBillingSetting>): void {
- // // TODO - To be removed - only used by automated tests!
- // this.settings = {
- // ...this.settings,
- // ...someSettings // overrides default settings to test different scenarios - e.g.: VAT 20%
- // };
- // }
-
+ // eslint-disable-next-line @typescript-eslint/require-await
public async checkConnection(): Promise<void> {
// Initialize Stripe
if (!this.stripe) {
- // STRIPE not yet initialized - let's do it!
- if (!this.settings.stripe.secretKey) {
- throw new BackendError({
- source: Constants.CENTRAL_SERVER,
- module: MODULE_NAME, method: 'checkConnection',
- action: ServerAction.CHECK_CONNECTION,
- message: 'No secret key provided for connection to Stripe'
- });
- }
try {
this.settings.stripe.secretKey = await Cypher.decrypt(this.tenantID, this.settings.stripe.secretKey);
} catch (error) {
@@ -93,31 +93,7 @@ export default class StripeBillingIntegration extends BillingIntegration {
message: 'Failed to connect to Stripe'
});
}
- // Validate the connection
- let isKeyValid = false;
- try {
- // TODO - Get one customer - to be clarified - Is this call necessary?
- const list = await this.stripe.customers.list({ limit: 1 });
- if (('object' in list) && (list.object === 'list')) {
- isKeyValid = true;
- }
- } catch (error) {
- throw new BackendError({
- source: Constants.CENTRAL_SERVER,
- module: MODULE_NAME, method: 'checkConnection',
- action: ServerAction.CHECK_CONNECTION,
- message: 'Failed to connect to Stripe',
- detailedMessages: { error: error.message, stack: error.stack }
- });
- }
- if (!isKeyValid) {
- throw new BackendError({
- source: Constants.CENTRAL_SERVER,
- module: MODULE_NAME, method: 'checkConnection',
- action: ServerAction.CHECK_CONNECTION,
- message: 'Failed to connect to Stripe - Invalid Key',
- });
- }
+ this.productionMode = await StripeBillingIntegration.isConnectedToALiveAccount(this.stripe);
}
}
@@ -663,7 +639,7 @@ export default class StripeBillingIntegration extends BillingIntegration {
// Check Transaction
this.checkStartTransaction(transaction);
- if (this.__liveMode) {
+ if (this.productionMode) {
// Check that the customer STRIPE exists
const customerID: string = transaction.user?.billingData?.customerID;
const customer = await this.getStripeCustomer(customerID);
@@ -775,7 +751,7 @@ export default class StripeBillingIntegration extends BillingIntegration {
if (customer) {
const billingDataTransactionStop: BillingDataTransactionStop = await this.billTransaction(transaction);
return billingDataTransactionStop;
- } else if (this.__liveMode) {
+ } else if (this.productionMode) {
// This should not happen - the startTransaction should have been rejected
throw new Error(`Unexpected situation - No STRIPE customer - Transaction ID '${transaction.id}'`);
}

MODIFY
test/api/BillingStripeTestData.ts
test/api/BillingStripeTestData.ts
@@ -72,7 +72,7 @@ export default class StripeIntegrationTestData {
const billingSettings = this.getLocalSettings(immediateBilling);
await this.saveBillingSettings(billingSettings);
billingSettings.stripe.secretKey = await Cypher.encrypt(this.getTenantID(), billingSettings.stripe.secretKey);
- const billingImpl = new StripeBillingIntegration(this.getTenantID(), billingSettings);
+ const billingImpl = StripeBillingIntegration.getInstance(this.getTenantID(), billingSettings);
expect(this.billingImpl).to.not.be.null;
return billingImpl;
}
@@ -81,7 +81,7 @@ export default class StripeIntegrationTestData {
const billingSettings = this.getLocalSettings(false);
billingSettings.stripe.secretKey = await Cypher.encrypt(this.getTenantID(), 'sk_test_' + 'invalid_credentials');
await this.saveBillingSettings(billingSettings);
- const billingImpl = new StripeBillingIntegration(this.getTenantID(), billingSettings);
+ const billingImpl = StripeBillingIntegration.getInstance(this.getTenantID(), billingSettings);
expect(this.billingImpl).to.not.be.null;
return billingImpl;
}

MODIFY
test/api/BillingTest.ts
test/api/BillingTest.ts
@@ -55,8 +55,8 @@ class TestData {
const tenantId = this.tenantContext?.getTenant()?.id;
assert(!!tenantId, 'Tenant ID cannot be null');
billingSettings.stripe.secretKey = await Cypher.encrypt(tenantId, billingSettings.stripe.secretKey);
- const billingImpl = new StripeBillingIntegration(tenantId, billingSettings);
- expect(this.billingImpl).to.not.be.null;
+ const billingImpl = StripeBillingIntegration.getInstance(tenantId, billingSettings);
+ expect(billingImpl).to.not.be.null;
return billingImpl;
}
@@ -66,8 +66,8 @@ class TestData {
assert(!!tenantId, 'Tenant ID cannot be null');
billingSettings.stripe.secretKey = await Cypher.encrypt(tenantId, 'sk_test_' + 'invalid_credentials');
await this.saveBillingSettings(billingSettings);
- const billingImpl = new StripeBillingIntegration(tenantId, billingSettings);
- expect(this.billingImpl).to.not.be.null;
+ const billingImpl = StripeBillingIntegration.getInstance(tenantId, billingSettings);
+ expect(billingImpl).to.not.be.null;
return billingImpl;
}
@@ -280,7 +280,7 @@ describe('Billing Service', function() {
});
xit('should synchronize 1 invoice after a transaction', async () => {
- // TODO - Synchronize Invoices is for now disabled - c.f.: __liveMode!
+ // Synchronize Invoices is now deprecated
await testData.userService.billingApi.synchronizeInvoices({});
const transactionID = await testData.generateTransaction(testData.userContext);
expect(transactionID).to.not.be.null;

billing - STRIPE - productionMode