billing settings reorg
MODIFY
src/integration/billing/BillingFactory.ts
src/integration/billing/BillingFactory.ts
-import { BillingSetting, BillingSettingsType } from '../../types/Setting';
+import { BillingSetting, BillingSettings, BillingSettingsType } from '../../types/Setting';
import BillingIntegration from './BillingIntegration';
import Logging from '../../utils/Logging';
@@ -13,21 +13,21 @@ import Utils from '../../utils/Utils';
const MODULE_NAME = 'BillingFactory';
export default class BillingFactory {
- static async getBillingImpl(tenantID: string): Promise<BillingIntegration<BillingSetting>> {
+ static async getBillingImpl(tenantID: string): Promise<BillingIntegration> {
// Get the tenant
const tenant: Tenant = await TenantStorage.getTenant(tenantID);
// Check if billing is active
if (Utils.isTenantComponentActive(tenant, TenantComponents.PRICING) &&
Utils.isTenantComponentActive(tenant, TenantComponents.BILLING)) {
// Get the billing's settings
- const settings = await SettingStorage.getBillingSettings(tenantID);
+ const settings: BillingSettings = await SettingStorage.getBillingSettings(tenantID);
if (settings) {
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.stripe);
+ billingIntegrationImpl = new StripeBillingIntegration(tenantID, settings);
}
break;
}

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 { BillingSetting } from '../../types/Setting';
import BillingStorage from '../../storage/mongodb/BillingStorage';
import Constants from '../../utils/Constants';
import Logging from '../../utils/Logging';
@@ -18,15 +18,15 @@ import Utils from '../../utils/Utils';
const MODULE_NAME = 'BillingIntegration';
-export default abstract class BillingIntegration<T extends BillingSetting> {
+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;
protected readonly tenantID: string; // Assuming UUID or other string format ID
- protected settings: T;
+ protected settings: BillingSettings;
- protected constructor(tenantID: string, settings: T) {
+ protected constructor(tenantID: string, settings: BillingSettings) {
this.tenantID = tenantID;
this.settings = settings;
}
@@ -89,8 +89,8 @@ export default abstract class BillingIntegration<T extends BillingSetting> {
);
// Update last synchronization
const billingSettings = await SettingStorage.getBillingSettings(this.tenantID);
- billingSettings.stripe.usersLastSynchronizedOn = new Date();
- await SettingStorage.saveBillingSettings(this.tenantID, billingSettings);
+ this.settings.billing.usersLastSynchronizedOn = new Date();
+ await SettingStorage.saveBillingSettings(this.tenantID, this.settings);
// Result
return actionsDone;
}
@@ -196,90 +196,15 @@ export default abstract class BillingIntegration<T extends BillingSetting> {
inSuccess: 0,
inError: 0
};
- // Check LIVE MODE
- if (!this.__liveMode) {
await Logging.logWarning({
tenantID: this.tenantID,
source: Constants.CENTRAL_SERVER,
action: ServerAction.BILLING_SYNCHRONIZE_INVOICES,
module: MODULE_NAME, method: 'synchronizeInvoices',
- message: 'Live Mode is OFF - operation has been aborted'
+ message: 'Method is deprecated - operation skipped'
});
return actionsDone;
}
- // Check billing settings consistency
- await this.checkConnection();
- // Let's make sure the billing data is consistent
- let billingUser: BillingUser = null;
- if (user) {
- billingUser = await this.checkUser(user);
- }
- // Fetch the list of updated invoice
- const invoiceIDs: string[] = await this.getUpdatedInvoiceIDsInBilling(billingUser);
- if (invoiceIDs?.length > 0) {
- await Logging.logInfo({
- tenantID: this.tenantID,
- user: user,
- source: Constants.CENTRAL_SERVER,
- action: ServerAction.BILLING_SYNCHRONIZE_INVOICES,
- module: MODULE_NAME, method: 'synchronizeInvoices',
- message: `${invoiceIDs.length} billing invoice(s) are going to be synchronized`
- });
- for (const invoiceID of invoiceIDs) {
- try {
- // Let's replicate invoice data as a Billing Invoice
- const billingInvoice = await this.synchronizeAsBillingInvoice(invoiceID, true /* checkUserExists */);
- // Make sure we get the actual user - the one the invoice refers to
- const userInInvoice = await UserStorage.getUser(this.tenantID, billingInvoice.userID);
- // Update user billing data
- const billingData = {
- ...userInInvoice.billingData,
- invoicesLastSynchronizedOn: new Date()
- };
- // Update the invoicesLastSynchronizedOn property of the user
- userInInvoice.billingData.invoicesLastSynchronizedOn = new Date();
- await UserStorage.saveUserBillingData(this.tenantID, billingInvoice.userID, billingData);
- // Ok
- actionsDone.inSuccess++;
- await Logging.logDebug({
- tenantID: this.tenantID,
- user: user,
- source: Constants.CENTRAL_SERVER,
- action: ServerAction.BILLING_SYNCHRONIZE_INVOICES,
- module: MODULE_NAME, method: 'synchronizeInvoices',
- message: `Invoice with ID '${invoiceID}' has been replicated in e-Mobility`,
- detailedMessages: { billingInvoice, userInInvoice }
- });
- } catch (error) {
- actionsDone.inError++;
- await Logging.logError({
- tenantID: this.tenantID,
- user: user,
- source: Constants.CENTRAL_SERVER,
- action: ServerAction.BILLING_SYNCHRONIZE_INVOICES,
- module: MODULE_NAME, method: 'synchronizeInvoices',
- message: `Unable to process the invoice with ID '${invoiceID}'`,
- detailedMessages: { error: error.message, stack: error.stack, invoiceID }
- });
- }
- }
- }
- // Log
- await Logging.logActionsResponse(this.tenantID, ServerAction.BILLING_SYNCHRONIZE_INVOICES,
- MODULE_NAME, 'synchronizeInvoices', actionsDone,
- '{{inSuccess}} invoice(s) were successfully synchronized',
- '{{inError}} invoice(s) failed to be synchronized',
- '{{inSuccess}} invoice(s) were successfully synchronized and {{inError}} failed to be synchronized',
- 'All the invoices are up to date'
- );
- if (!user) {
- // Update global last synchronization timestamp
- const billingSettings = await SettingStorage.getBillingSettings(this.tenantID);
- billingSettings.stripe.invoicesLastSynchronizedOn = new Date();
- await SettingStorage.saveBillingSettings(this.tenantID, billingSettings);
- }
- return actionsDone;
- }
public async chargeInvoices(): Promise<BillingChargeInvoiceAction> {
const actionsDone: BillingChargeInvoiceAction = {
@@ -490,8 +415,6 @@ export default abstract class BillingIntegration<T extends BillingSetting> {
abstract getTaxes(): Promise<BillingTax[]>;
- abstract getUpdatedInvoiceIDsInBilling(billingUser?: BillingUser): Promise<string[]>;
-
abstract synchronizeAsBillingInvoice(stripeInvoiceID: string, checkUserExists: boolean): Promise<BillingInvoice>;
abstract billInvoiceItem(user: User, billingInvoiceItems: BillingInvoiceItem, idemPotencyKey?: string): Promise<BillingInvoice>;

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';
@@ -15,7 +16,6 @@ import Logging from '../../../utils/Logging';
import { Request } from 'express';
import { ServerAction } from '../../../types/Server';
import Stripe from 'stripe';
-import { StripeBillingSetting } from '../../../types/Setting';
import StripeHelpers from './StripeHelpers';
import Transaction from '../../../types/Transaction';
import User from '../../../types/User';
@@ -24,13 +24,13 @@ import Utils from '../../../utils/Utils';
import moment from 'moment';
const MODULE_NAME = 'StripeBillingIntegration';
-export default class StripeBillingIntegration extends BillingIntegration<StripeBillingSetting> {
+export default class StripeBillingIntegration extends BillingIntegration {
private static readonly STRIPE_MAX_LIST = 100;
private axiosInstance: AxiosInstance;
private stripe: Stripe;
- constructor(tenantId: string, settings: StripeBillingSetting) {
+ constructor(tenantId: string, settings: BillingSettings) {
super(tenantId, settings);
this.axiosInstance = AxiosFactory.getAxiosInstance(this.tenantID);
}
@@ -61,7 +61,7 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
// Initialize Stripe
if (!this.stripe) {
// STRIPE not yet initialized - let's do it!
- if (!this.settings.secretKey) {
+ if (!this.settings.stripe.secretKey) {
throw new BackendError({
source: Constants.CENTRAL_SERVER,
module: MODULE_NAME, method: 'checkConnection',
@@ -70,7 +70,7 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
});
}
try {
- this.settings.secretKey = await Cypher.decrypt(this.tenantID, this.settings.secretKey);
+ this.settings.stripe.secretKey = await Cypher.decrypt(this.tenantID, this.settings.stripe.secretKey);
} catch (error) {
throw new BackendError({
source: Constants.CENTRAL_SERVER,
@@ -81,7 +81,7 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
});
}
// Try to connect
- this.stripe = new Stripe(this.settings.secretKey, {
+ this.stripe = new Stripe(this.settings.stripe.secretKey, {
apiVersion: '2020-08-27',
});
// Let's check if the connection is working properly
@@ -237,54 +237,6 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
return stripeInvoice;
}
- // ----------------------------------------------------------
- // TODO - get rid of this logic and use webhooks instead!
- // ----------------------------------------------------------
- public async getUpdatedInvoiceIDsInBilling(billingUser?: BillingUser): Promise<string[]> {
- let createdSince: number;
- // Check Stripe
- await this.checkConnection();
- if (billingUser) {
- // Start sync from last invoices sync
- createdSince = billingUser.billingData.invoicesLastSynchronizedOn ? moment(billingUser.billingData.invoicesLastSynchronizedOn).unix() : 0;
- } else {
- // Start sync from last global sync
- createdSince = this.settings.invoicesLastSynchronizedOn ? moment(this.settings.invoicesLastSynchronizedOn).unix() : 0;
- }
- const collectedInvoiceIDs: string[] = [];
- const queryRange: Stripe.RangeQueryParam = { gt: createdSince };
- const request: Stripe.EventListParams = {
- created: queryRange,
- limit: StripeBillingIntegration.STRIPE_MAX_LIST,
- type: 'invoice.*',
- };
- // Loop until all invoices are read
- do {
- const events: Stripe.ApiList<Stripe.Event> = await this.stripe.events.list(request);
- for (const evt of events.data) {
- // c.f.: https://stripe.com/docs/api/events/object
- const invoice: Stripe.Invoice = evt.data.object as Stripe.Invoice; // TODO - to be clarified how to determine the object type?
- if (invoice.object === 'invoice' && invoice.id) {
- if (!collectedInvoiceIDs.includes(invoice.id)) {
- if (billingUser) {
- // Collect specific user's invoices
- if (billingUser.billingData.customerID === invoice.customer) {
- collectedInvoiceIDs.push(invoice.id);
- }
- } else {
- // Collect every invoices
- collectedInvoiceIDs.push(invoice.id);
- }
- }
- }
- }
- if (request['has_more']) {
- request['starting_after'] = collectedInvoiceIDs[collectedInvoiceIDs.length - 1];
- }
- } while (request['has_more']);
- return collectedInvoiceIDs;
- }
-
private async _createStripeInvoice(customerID: string, userID: string, idempotencyKey?: string | number): Promise<Stripe.Invoice> {
// Let's create the STRIPE invoice
const stripeInvoice: Stripe.Invoice = await this.stripe.invoices.create({
@@ -363,8 +315,8 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
}
private getTaxRateIds(): Array<string> {
- if (this.settings.taxID) {
- return [this.settings.taxID] ;
+ if (this.settings.billing.taxID) {
+ return [this.settings.billing.taxID] ;
}
return []; // No tax rates so far!
}
@@ -942,7 +894,7 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
stripeInvoice = await this._createStripeInvoice(customerID, userID, this.buildIdemPotencyKey(idemPotencyKey));
}
let operationResult: BillingOperationResult;
- if (this.settings.immediateBillingAllowed) {
+ if (this.settings.billing.immediateBillingAllowed) {
// Let's try to bill the stripe invoice using the default payment method of the customer
operationResult = await this._chargeStripeInvoice(stripeInvoice.id);
if (!operationResult?.succeeded && operationResult?.error) {

MODIFY
src/server/rest/v1/service/security/SettingSecurity.ts
src/server/rest/v1/service/security/SettingSecurity.ts
@@ -173,16 +173,15 @@ export default class SettingSecurity {
}
break;
case BillingSettingsType.STRIPE:
+ settings.content.billing = {
+ immediateBillingAllowed: sanitize(request.content.billing.immediateBillingAllowed),
+ periodicBillingAllowed: sanitize(request.content.billing.periodicBillingAllowed),
+ taxID: sanitize(request.content.billing.taxID)
+ };
settings.content.stripe = {
url: sanitize(request.content.stripe.url),
secretKey: sanitize(request.content.stripe.secretKey),
publicKey: sanitize(request.content.stripe.publicKey),
- noCardAllowed: sanitize(request.content.stripe.noCardAllowed),
- immediateBillingAllowed: sanitize(request.content.stripe.immediateBillingAllowed),
- periodicBillingAllowed: sanitize(request.content.stripe.periodicBillingAllowed),
- advanceBillingAllowed: sanitize(request.content.stripe.advanceBillingAllowed),
- currency: sanitize(request.content.stripe.currency),
- taxID: sanitize(request.content.stripe.taxID)
};
break;
case SmartChargingSettingsType.SAP_SMART_CHARGING:

MODIFY
src/storage/mongodb/SettingStorage.ts
src/storage/mongodb/SettingStorage.ts
@@ -262,8 +262,7 @@ export default class SettingStorage {
public static async saveBillingSettings(tenantID: string, billingSettingsToSave: BillingSettings): Promise<string> {
const settings = await SettingStorage.getBillingSettings(tenantID);
if (settings.type === BillingSettingsType.STRIPE) {
- if (!billingSettingsToSave.stripe.secretKey ||
- (!billingSettingsToSave.stripe.immediateBillingAllowed && billingSettingsToSave.stripe.periodicBillingAllowed && !billingSettingsToSave.stripe.advanceBillingAllowed)) {
+ if (!billingSettingsToSave.stripe.secretKey) {
throw new BackendError({
source: Constants.CENTRAL_SERVER,
module: MODULE_NAME,
@@ -280,6 +279,7 @@ export default class SettingStorage {
backupSensitiveData: billingSettingsToSave.backupSensitiveData,
lastChangedOn: new Date(),
content: {
+ billing: billingSettingsToSave.billing,
stripe: billingSettingsToSave.stripe
},
} as SettingDB;
@@ -299,33 +299,24 @@ export default class SettingStorage {
billingSettings.id = settings.result[0].id;
billingSettings.sensitiveData = settings.result[0].sensitiveData;
billingSettings.backupSensitiveData = settings.result[0].backupSensitiveData;
- // Currency
- const pricingSettings = await SettingStorage.getPricingSettings(tenantID);
- let currency = 'EUR';
- if (pricingSettings) {
- if (pricingSettings.simple) {
- currency = pricingSettings.simple.currency;
- } else if (pricingSettings.convergentCharging) {
- if (pricingSettings.convergentCharging['currency']) {
- currency = pricingSettings.convergentCharging['currency'];
- }
- }
+ // Billing Common Properties
+ if (config.billing) {
+ const { immediateBillingAllowed, periodicBillingAllowed, taxID, usersLastSynchronizedOn } = config.billing;
+ billingSettings.billing = {
+ immediateBillingAllowed,
+ periodicBillingAllowed,
+ usersLastSynchronizedOn,
+ taxID,
+ };
}
- // Billing type
+ // Billing Concrete Implementation Properties
if (config.stripe) {
billingSettings.type = BillingSettingsType.STRIPE;
+ const { url, publicKey, secretKey } = config.stripe;
billingSettings.stripe = {
- url: config.stripe.url ? config.stripe.url : '',
- publicKey: config.stripe.publicKey ? config.stripe.publicKey : '',
- secretKey: config.stripe.secretKey ? config.stripe.secretKey : '',
- currency: currency,
- noCardAllowed: config.stripe.noCardAllowed ? config.stripe.noCardAllowed : false,
- advanceBillingAllowed: config.stripe.advanceBillingAllowed ? config.stripe.advanceBillingAllowed : false,
- immediateBillingAllowed: config.stripe.immediateBillingAllowed ? config.stripe.immediateBillingAllowed : false,
- periodicBillingAllowed: config.stripe.periodicBillingAllowed ? config.stripe.periodicBillingAllowed : false,
- usersLastSynchronizedOn: config.stripe.usersLastSynchronizedOn ? config.stripe.usersLastSynchronizedOn : new Date(0),
- invoicesLastSynchronizedOn: config.stripe.invoicesLastSynchronizedOn ? config.stripe.invoicesLastSynchronizedOn : new Date(0),
- taxID: config.stripe.taxID ? (config.stripe.taxID !== 'none' ? config.stripe.taxID : null) : null
+ url,
+ publicKey,
+ secretKey,
};
}
return billingSettings;

MODIFY
src/types/Setting.ts
src/types/Setting.ts
@@ -41,8 +41,10 @@ export interface SettingDBContent {
| CarConnectorSettingsType;
ocpi?: OcpiSetting;
oicp?: OicpSetting;
+ // pricing?: PricingSetting; //TODO - reorg pricing similar to billing
simple?: SimplePricingSetting;
convergentCharging?: ConvergentChargingPricingSetting;
+ billing?: BillingSetting;
stripe?: StripeBillingSetting;
sac?: SacAnalyticsSetting;
links?: SettingLink[];
@@ -230,24 +232,21 @@ export enum BillingSettingsType {
export interface BillingSettings extends Setting{
identifier: TenantComponents.BILLING;
type: BillingSettingsType;
+ billing: BillingSetting;
stripe?: StripeBillingSetting;
}
export interface BillingSetting {
+ immediateBillingAllowed: boolean;
+ periodicBillingAllowed: boolean;
+ taxID: string;
usersLastSynchronizedOn?: Date;
- invoicesLastSynchronizedOn?: Date
}
-export interface StripeBillingSetting extends BillingSetting {
+export interface StripeBillingSetting {
url: string;
secretKey: string;
publicKey: string;
- noCardAllowed: boolean;
- immediateBillingAllowed: boolean;
- periodicBillingAllowed: boolean;
- advanceBillingAllowed: boolean;
- currency: string;
- taxID: string;
}
export enum BillingContentType {
@@ -339,7 +338,6 @@ export interface CarConnectorMercedesConnectionType {
clientSecret: string;
}
-
export enum CryptoSettingsType {
CRYPTO = 'crypto'
}
@@ -350,12 +348,6 @@ export interface CryptoSettings extends Setting {
crypto: CryptoSetting;
}
-export interface CryptoSetting {
- key: string;
- keyProperties: CryptoKeyProperties;
- formerKey?: string;
- formerKeyProperties?: CryptoKeyProperties;
-}
export interface CryptoKeyProperties {
blockCypher: string;
blockSize: number;

MODIFY
test/api/BillingStripeTestData.ts
test/api/BillingStripeTestData.ts
import { BillingInvoice, BillingInvoiceItem, BillingInvoiceStatus, BillingOperationResult, BillingUser, BillingUserData } from '../../src/types/Billing';
-import { BillingSettingsType, SettingDB, StripeBillingSetting } from '../../src/types/Setting';
+import { BillingSettings, BillingSettingsType, SettingDB, StripeBillingSetting } from '../../src/types/Setting';
import chai, { assert, expect } from 'chai';
import BillingStorage from '../../src/storage/mongodb/BillingStorage';
@@ -10,6 +10,7 @@ import Cypher from '../../src/utils/Cypher';
import Factory from '../factories/Factory';
import Stripe from 'stripe';
import StripeBillingIntegration from '../../src/integration/billing/stripe/StripeBillingIntegration';
+import TenantComponents from '../../src/types/TenantComponents';
import TenantContext from './context/TenantContext';
import TestConstants from './client/utils/TestConstants';
import User from '../../src/types/User';
@@ -68,50 +69,57 @@ export default class StripeIntegrationTestData {
}
public async setBillingSystemValidCredentials(immediateBilling: boolean) : Promise<StripeBillingIntegration> {
- const stripeSettings = this.getLocalSettings(immediateBilling);
- await this.saveBillingSettings(stripeSettings);
- stripeSettings.secretKey = await Cypher.encrypt(this.getTenantID(), stripeSettings.secretKey);
- const billingImpl = new StripeBillingIntegration(this.getTenantID(), stripeSettings);
+ 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);
expect(this.billingImpl).to.not.be.null;
return billingImpl;
}
public async setBillingSystemInvalidCredentials() : Promise<StripeBillingIntegration> {
- const stripeSettings = this.getLocalSettings(false);
- stripeSettings.secretKey = await Cypher.encrypt(this.getTenantID(), 'sk_test_' + 'invalid_credentials');
- await this.saveBillingSettings(stripeSettings);
- const billingImpl = new StripeBillingIntegration(this.getTenantID(), stripeSettings);
+ 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);
expect(this.billingImpl).to.not.be.null;
return billingImpl;
}
- public getLocalSettings(immediateBilling: boolean): StripeBillingSetting {
- const settings: StripeBillingSetting = {
- url: config.get('billing.url'),
- publicKey: config.get('billing.publicKey'),
- secretKey: config.get('billing.secretKey'),
- noCardAllowed: config.get('billing.noCardAllowed'),
- advanceBillingAllowed: config.get('billing.advanceBillingAllowed'),
- currency: config.get('billing.currency'),
+ public getLocalSettings(immediateBilling: boolean): BillingSettings {
+ const billingProperties = {
immediateBillingAllowed: config.get('billing.immediateBillingAllowed'),
periodicBillingAllowed: config.get('billing.periodicBillingAllowed'),
taxID: config.get('billing.taxID')
};
+ const stripeProperties = {
+ url: config.get('stripe.url'),
+ publicKey: config.get('stripe.publicKey'),
+ secretKey: config.get('stripe.secretKey'),
+ };
+ const settings: BillingSettings = {
+ identifier: TenantComponents.BILLING,
+ type: BillingSettingsType.STRIPE,
+ billing: billingProperties,
+ stripe: stripeProperties,
+ };
// ---------------------------------------------------------
// Our test needs the immediate billing to be switched off!
// Because we want to check the DRAFT state of the invoice
- settings.immediateBillingAllowed = immediateBilling;
+ settings.billing.immediateBillingAllowed = immediateBilling;
// ---------------------------------------------------------
return settings;
}
- public async saveBillingSettings(stripeSettings: StripeBillingSetting) : Promise<void> {
+ public async saveBillingSettings(billingSettings: BillingSettings) : Promise<void> {
+ // TODO - rethink that part
const tenantBillingSettings = await this.adminUserService.settingApi.readAll({ 'Identifier': 'billing' });
expect(tenantBillingSettings.data.count).to.be.eq(1);
const componentSetting: SettingDB = tenantBillingSettings.data.result[0];
componentSetting.content.type = BillingSettingsType.STRIPE;
- componentSetting.content.stripe = stripeSettings;
+ componentSetting.content.billing = billingSettings.billing;
+ componentSetting.content.stripe = billingSettings.stripe;
componentSetting.sensitiveData = ['content.stripe.secretKey'];
await this.adminUserService.settingApi.update(componentSetting);
}
@@ -119,10 +127,9 @@ export default class StripeIntegrationTestData {
public isBillingProperlyConfigured(): boolean {
const billingSettings = this.getLocalSettings(false);
// Check that the mandatory settings are properly provided
- return (!!billingSettings.publicKey
- && !!billingSettings.secretKey
- && !!billingSettings.url
- && !!billingSettings.currency);
+ return (!!billingSettings.stripe.publicKey
+ && !!billingSettings.stripe.secretKey
+ && !!billingSettings.stripe.url);
}
public async assignPaymentMethod(stripe_test_token: string) : Promise<Stripe.CustomerSource> {

MODIFY
test/api/BillingTest.ts
test/api/BillingTest.ts
import { BillingInvoiceStatus, BillingUser } from '../../src/types/Billing';
-import { BillingSetting, BillingSettingsType, SettingDB, StripeBillingSetting } from '../../src/types/Setting';
+import { BillingSetting, BillingSettings, BillingSettingsType, SettingDB, StripeBillingSetting } from '../../src/types/Setting';
import chai, { assert, expect } from 'chai';
import BillingIntegration from '../../src/integration/billing/BillingIntegration';
@@ -10,12 +10,12 @@ import ContextDefinition from './context/ContextDefinition';
import ContextProvider from './context/ContextProvider';
import Cypher from '../../src/utils/Cypher';
import Factory from '../factories/Factory';
-import { HTTPAuthError } from '../../src/types/HTTPError';
import MongoDBStorage from '../../src/storage/mongodb/MongoDBStorage';
import { ObjectID } from 'mongodb';
import SiteContext from './context/SiteContext';
import { StatusCodes } from 'http-status-codes';
import StripeBillingIntegration from '../../src/integration/billing/stripe/StripeBillingIntegration';
+import TenantComponents from '../../src/types/TenantComponents';
import TenantContext from './context/TenantContext';
import TestConstants from './client/utils/TestConstants';
import User from '../../src/types/User';
@@ -46,58 +46,65 @@ class TestData {
// Dynamic User for testing billing against an empty STRIPE account
public dynamicUser: User;
// Billing Implementation - STRIPE?
- public billingImpl: BillingIntegration<BillingSetting>;
+ public billingImpl: BillingIntegration;
public billingUser: BillingUser; // DO NOT CONFUSE - BillingUser is not a User!
public async setBillingSystemValidCredentials() : Promise<StripeBillingIntegration> {
- const stripeSettings = this.getLocalSettings(false);
- await this.saveBillingSettings(stripeSettings);
+ const billingSettings = this.getLocalSettings(false);
+ await this.saveBillingSettings(billingSettings);
const tenantId = this.tenantContext?.getTenant()?.id;
assert(!!tenantId, 'Tenant ID cannot be null');
- stripeSettings.secretKey = await Cypher.encrypt(tenantId, stripeSettings.secretKey);
- const billingImpl = new StripeBillingIntegration(tenantId, stripeSettings);
+ billingSettings.stripe.secretKey = await Cypher.encrypt(tenantId, billingSettings.stripe.secretKey);
+ const billingImpl = new StripeBillingIntegration(tenantId, billingSettings);
expect(this.billingImpl).to.not.be.null;
return billingImpl;
}
public async setBillingSystemInvalidCredentials() : Promise<StripeBillingIntegration> {
- const stripeSettings = this.getLocalSettings(false);
+ const billingSettings = this.getLocalSettings(false);
const tenantId = this.tenantContext?.getTenant()?.id;
assert(!!tenantId, 'Tenant ID cannot be null');
- stripeSettings.secretKey = await Cypher.encrypt(tenantId, 'sk_test_' + 'invalid_credentials');
- await this.saveBillingSettings(stripeSettings);
- const billingImpl = new StripeBillingIntegration(tenantId, stripeSettings);
+ 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;
return billingImpl;
}
- public getLocalSettings(immediateBilling: boolean): StripeBillingSetting {
- const settings: StripeBillingSetting = {
- url: config.get('billing.url'),
- publicKey: config.get('billing.publicKey'),
- secretKey: config.get('billing.secretKey'),
- noCardAllowed: config.get('billing.noCardAllowed'),
- advanceBillingAllowed: config.get('billing.advanceBillingAllowed'),
- currency: config.get('billing.currency'),
+ public getLocalSettings(immediateBilling: boolean): BillingSettings {
+ const billingProperties = {
immediateBillingAllowed: config.get('billing.immediateBillingAllowed'),
periodicBillingAllowed: config.get('billing.periodicBillingAllowed'),
taxID: config.get('billing.taxID')
};
+ const stripeProperties = {
+ url: config.get('stripe.url'),
+ publicKey: config.get('stripe.publicKey'),
+ secretKey: config.get('stripe.secretKey'),
+ };
+ const settings: BillingSettings = {
+ identifier: TenantComponents.BILLING,
+ type: BillingSettingsType.STRIPE,
+ billing: billingProperties,
+ stripe: stripeProperties,
+ };
// ---------------------------------------------------------
// Our test needs the immediate billing to be switched off!
// Because we want to check the DRAFT state of the invoice
- settings.immediateBillingAllowed = immediateBilling;
+ settings.billing.immediateBillingAllowed = immediateBilling;
// ---------------------------------------------------------
return settings;
}
- public async saveBillingSettings(stripeSettings: StripeBillingSetting) {
+ public async saveBillingSettings(billingSettings: BillingSettings) {
+ // TODO - rethink that part
const tenantBillingSettings = await this.adminUserService.settingApi.readAll({ 'Identifier': 'billing' });
expect(tenantBillingSettings.data.count).to.be.eq(1);
const componentSetting: SettingDB = tenantBillingSettings.data.result[0];
componentSetting.content.type = BillingSettingsType.STRIPE;
- componentSetting.content.stripe = stripeSettings;
+ componentSetting.content.billing = billingSettings.billing;
+ componentSetting.content.stripe = billingSettings.stripe;
componentSetting.sensitiveData = ['content.stripe.secretKey'];
await this.adminUserService.settingApi.update(componentSetting);
}
@@ -142,10 +149,9 @@ class TestData {
public isBillingProperlyConfigured(): boolean {
const billingSettings = this.getLocalSettings(false);
// Check that the mandatory settings are properly provided
- return (!!billingSettings.publicKey
- && !!billingSettings.secretKey
- && !!billingSettings.url
- && !!billingSettings.currency);
+ return (!!billingSettings.stripe.publicKey
+ && !!billingSettings.stripe.secretKey
+ && !!billingSettings.stripe.url);
}
public async getLatestDraftInvoice(userId?: string) {

MODIFY
test/api/context/BillingContext.ts
test/api/context/BillingContext.ts
+import { BillingSettings, BillingSettingsType } from '../../../src/types/Setting';
+
import BackendError from '../../../src/exception/BackendError';
import BillingFactory from '../../../src/integration/billing/BillingFactory';
import Constants from '../../../src/utils/Constants';
import ContextDefinition from './ContextDefinition';
import Cypher from '../../../src/utils/Cypher';
import SettingStorage from '../../../src/storage/mongodb/SettingStorage';
-import { StripeBillingSetting } from '../../../src/types/Setting';
+import TenantComponents from '../../../src/types/TenantComponents';
import TenantContext from './TenantContext';
import User from '../../../src/types/User';
import config from '../../config';
@@ -17,23 +19,28 @@ export default class BillingContext {
this.tenantContext = tenantContext;
}
- private static getBillingSettings(): StripeBillingSetting {
- return {
- url: config.get('billing.url'),
- publicKey: config.get('billing.publicKey'),
- secretKey: config.get('billing.secretKey'),
- noCardAllowed: config.get('billing.noCardAllowed'),
- advanceBillingAllowed: config.get('billing.advanceBillingAllowed'),
- currency: config.get('billing.currency'),
+ private static getBillingSettings(): BillingSettings {
+ const billingProperties = {
immediateBillingAllowed: config.get('billing.immediateBillingAllowed'),
periodicBillingAllowed: config.get('billing.periodicBillingAllowed'),
taxID: config.get('billing.taxID')
};
+ const stripeProperties = {
+ url: config.get('stripe.url'),
+ publicKey: config.get('stripe.publicKey'),
+ secretKey: config.get('stripe.secretKey'),
+ };
+ return {
+ identifier: TenantComponents.BILLING,
+ type: BillingSettingsType.STRIPE,
+ billing: billingProperties,
+ stripe: stripeProperties,
+ };
}
public async createTestData(): Promise<void> {
const settings = BillingContext.getBillingSettings();
- const skip = (!settings.secretKey);
+ const skip = (!settings.stripe.secretKey);
if (skip) {
// Skip billing context generation if no settings are provided
return;
@@ -57,11 +64,13 @@ export default class BillingContext {
await billingImpl.synchronizeUser(basicUser);
}
- private async saveBillingSettings(stripeSettings) {
+ private async saveBillingSettings(billingSettings: BillingSettings) {
+ // TODO - rethink that part
const tenantBillingSettings = await SettingStorage.getBillingSettings(this.tenantContext.getTenant().id);
- tenantBillingSettings.stripe = stripeSettings;
+ tenantBillingSettings.billing = billingSettings.billing;
+ tenantBillingSettings.stripe = billingSettings.stripe;
tenantBillingSettings.sensitiveData = ['content.stripe.secretKey'];
- tenantBillingSettings.stripe.secretKey = await Cypher.encrypt(this.tenantContext.getTenant().id, stripeSettings.secretKey);
+ tenantBillingSettings.stripe.secretKey = await Cypher.encrypt(this.tenantContext.getTenant().id, billingSettings.stripe.secretKey);
await SettingStorage.saveBillingSettings(this.tenantContext.getTenant().id, tenantBillingSettings);
}
}

MODIFY
test/api/context/ContextDefinition.ts
test/api/context/ContextDefinition.ts
@@ -177,16 +177,15 @@ export default class ContextDefinition {
billing: {
content: {
type: BillingSettingsType.STRIPE,
+ billing: {
+ immediateBillingAllowed: true,
+ periodicBillingAllowed: true,
+ taxID: ''
+ },
stripe: {
- currency: 'EUR',
url: '',
secretKey: '',
publicKey: '',
- noCardAllowed: true,
- immediateBillingAllowed: true,
- periodicBillingAllowed: true,
- advanceBillingAllowed: true,
- taxID: ''
}
}
}
@@ -306,16 +305,15 @@ export default class ContextDefinition {
billing: {
content: {
type: BillingSettingsType.STRIPE,
+ billing: {
+ immediateBillingAllowed: true,
+ periodicBillingAllowed: true,
+ taxID: ''
+ },
stripe: {
- currency: 'EUR',
url: '',
secretKey: '',
publicKey: '',
- noCardAllowed: true,
- immediateBillingAllowed: true,
- periodicBillingAllowed: true,
- advanceBillingAllowed: true,
- taxID: ''
}
}
},

MODIFY
test/config-template.json
test/config-template.json
"debug": false
},
"billing": {
- "currency": "EUR",
+ "immediateBillingAllowed": true,
+ "periodicBillingAllowed": true,
+ "taxID": ""
+ },
+ "stripe": {
"url": "",
"publicKey": "",
- "secretKey": "",
- "noCardAllowed": true,
- "advanceBillingAllowed": true,
- "immediateBillingAllowed": true,
- "periodicBillingAllowed": true
+ "secretKey": ""
},
"smartCharging": {
"optimizerUrl": "",

MODIFY
test/config.js
test/config.js
@@ -252,11 +252,23 @@ const config = convict({
}
},
billing: {
- currency: {
- doc: 'billing currency',
+ immediateBillingAllowed: {
+ doc: 'Allow immediate billing',
+ format: Boolean,
+ default: ''
+ },
+ periodicBillingAllowed: {
+ doc: 'Allow periodic billing',
+ format: Boolean,
+ default: ''
+ },
+ taxID: {
+ doc: 'taxes to apply by default',
format: String,
default: ''
+ }
},
+ stripe: {
url: {
doc: 'Billing provider dashboard',
format: String,
@@ -272,31 +284,6 @@ const config = convict({
format: String,
default: ''
},
- noCardAllowed: {
- doc: 'Allow no card user',
- format: Boolean,
- default: ''
- },
- advanceBillingAllowed: {
- doc: 'Allow advance billing',
- format: Boolean,
- default: ''
- },
- immediateBillingAllowed: {
- doc: 'Allow immediate billing',
- format: Boolean,
- default: ''
- },
- periodicBillingAllowed: {
- doc: 'Allow periodic billing',
- format: Boolean,
- default: ''
- },
- taxID: {
- doc: 'taxes to apply by default',
- format: String,
- default: ''
- }
},
smartCharging: {
optimizerUrl: {

billing settings reorg