utbilling - strpe integration tests
ADD
test/api/BillingStripeIntegrationTest.ts
+import chai, { assert, expect } from 'chai';
+
+import MongoDBStorage from '../../src/storage/mongodb/MongoDBStorage';
+import StripeIntegrationTestData from './BillingStripeTestData';
+import chaiSubset from 'chai-subset';
+import config from '../config';
+import global from '../../src/types/GlobalType';
+import responseHelper from '../helpers/responseHelper';
+
+chai.use(chaiSubset);
+chai.use(responseHelper);
+
+const testData: StripeIntegrationTestData = new StripeIntegrationTestData();
+
+describe('Billing Stripe Service', function() {
+ // Do not run the tests when the settings are not properly set
+ this.pending = !testData.isBillingProperlyConfigured();
+ this.timeout(1000000);
+
+ describe('With component Billing (tenant utbilling)', () => {
+ before(async () => {
+ global.database = new MongoDBStorage(config.get('storage'));
+ await global.database.start();
+ await testData.initialize();
+ });
+
+ describe('Where the admin user', () => {
+ before(async () => {
+ testData.billingImpl = await testData.setBillingSystemValidCredentials();
+ });
+
+ after(async () => {
+ // TODO - cleanup test data!
+ // Cleanup of the utbilling tenant is useless if we cannot cleanup the utbilling stripe account as well
+ });
+
+ it('Should add a payment method to BILLING-TEST user', async () => {
+ const customerID: string = testData.billingUser.billingData.customerID;
+ const stripe_test_token = 'tok_visa';
+ await testData.assignPaymentMethod(customerID, stripe_test_token);
+ });
+
+ it('should create and pay a first invoice for BILLING-TEST user', async () => {
+ await testData.checkForDraftInvoices(testData.dynamicUser.id, 0);
+ // Let's create an INvoice with a first Item
+ const dynamicInvoice = await testData.createDraftInvoice();
+ // Let's add an second item to the same invoice
+ await testData.updateDraftInvoice(dynamicInvoice);
+ // User should have a DRAFT invoice
+ const draftInvoices = await testData.getDraftInvoices(testData.dynamicUser.id);
+ assert(draftInvoices, 'User should have at least a draft invoice');
+ expect(draftInvoices.length).to.be.eql(1);
+ // Let's pay that particular DRAFT invoice
+ await testData.payDraftInvoice(draftInvoices[0]);
+ // Let's check that the user do not have any DRAFT invoice anymore
+ await testData.billingImpl.synchronizeInvoices(testData.dynamicUser);
+ await testData.checkForDraftInvoices(testData.dynamicUser.id, 0);
+ });
+
+ it('Should add a different payment method to BILLING-TEST user', async () => {
+ const customerID: string = testData.billingUser.billingData.customerID;
+ const stripe_test_token = 'tok_fr';
+ await testData.assignPaymentMethod(customerID, stripe_test_token);
+ });
+
+ it('should create and pay a second invoice for BILLING-TEST user', async () => {
+ await testData.checkForDraftInvoices(testData.dynamicUser.id, 0);
+ // Let's create an Invoice with a first Item
+ const dynamicInvoice = await testData.createDraftInvoice();
+ // Let's add an second item to the same invoice
+ await testData.updateDraftInvoice(dynamicInvoice);
+ // User should have a DRAFT invoice
+ const draftInvoices = await testData.getDraftInvoices(testData.dynamicUser.id);
+ assert(draftInvoices, 'User should have at least a draft invoice');
+ expect(draftInvoices.length).to.be.eql(1);
+ // Let's pay that particular DRAFT invoice
+ await testData.payDraftInvoice(draftInvoices[0]);
+ // Let's check that the user do not have any DRAFT invoice anymore
+ await testData.billingImpl.synchronizeInvoices(testData.dynamicUser);
+ await testData.checkForDraftInvoices(testData.dynamicUser.id, 0);
+ });
+
+
+ });
+
+ });
+
+});

ADD
test/api/BillingStripeTestData.ts
+import { BillingInvoice, BillingInvoiceItem, BillingInvoiceStatus, BillingUser } from '../../src/types/Billing';
+import { BillingSetting, BillingSettingsType, SettingDB, StripeBillingSetting } from '../../src/types/Setting';
+import chai, { assert, expect } from 'chai';
+
+import BillingIntegration from '../../src/integration/billing/BillingIntegration';
+import BillingStorage from '../../src/storage/mongodb/BillingStorage';
+import CentralServerService from './client/CentralServerService';
+import ContextDefinition from './context/ContextDefinition';
+import ContextProvider from './context/ContextProvider';
+import Cypher from '../../src/utils/Cypher';
+import Factory from '../factories/Factory';
+import { IStripeSource } from 'stripe';
+import StripeBillingIntegration from '../../src/integration/billing/stripe/StripeBillingIntegration';
+import TenantContext from './context/TenantContext';
+import TestConstants from './client/utils/TestConstants';
+import User from '../../src/types/User';
+import UserStorage from '../../src/storage/mongodb/UserStorage';
+import chaiSubset from 'chai-subset';
+import config from '../config';
+import responseHelper from '../helpers/responseHelper';
+
+chai.use(chaiSubset);
+chai.use(responseHelper);
+
+export default class StripeIntegrationTestData {
+
+ // Tenant: utbilling
+ public tenantContext: TenantContext;
+ // User Service for action requiring admin permissions (e.g.: set/reset stripe settings)
+ public adminUserContext: User;
+ public adminUserService: CentralServerService;
+ // Dynamic User for testing billing against an test STRIPE account
+ public dynamicUser: User;
+ // Billing Implementation - STRIPE
+ public billingImpl: BillingIntegration<BillingSetting>; // ==> StripeBillingIntegration
+ public billingUser: BillingUser; // DO NOT CONFUSE - BillingUser is not a User!
+
+ public async initialize(): Promise<void> {
+
+ this.tenantContext = await ContextProvider.defaultInstance.getTenantContext(ContextDefinition.TENANT_CONTEXTS.TENANT_BILLING);
+ this.adminUserContext = this.tenantContext.getUserContext(ContextDefinition.USER_CONTEXTS.DEFAULT_ADMIN);
+ this.adminUserService = new CentralServerService(
+ this.tenantContext.getTenant().subdomain,
+ this.adminUserContext
+ );
+ // Create a new user for testing stripe scenarios - BILING-TEST
+ const user = {
+ ...Factory.user.build(),
+ name: 'BILLING-TEST',
+ firstName: 'Billing Integration Tests',
+ issuer: true
+ } as User;
+ // Let's create a new user
+ const userData = await this.adminUserService.createEntity(
+ this.adminUserService.userApi,
+ user
+ );
+ assert(userData && userData.id, 'response should not be null');
+ // Let's get the newly created user
+ this.dynamicUser = await UserStorage.getUser(this.getTenantID(), userData.id);
+ assert(this.dynamicUser && this.dynamicUser?.billingData?.customerID, 'Customer ID should be set');
+ // Let's get access to the STRIPE implementation - StripeBillingIntegration instance
+ this.billingImpl = await this.setBillingSystemValidCredentials();
+ this.billingUser = await this.billingImpl.getUser(this.dynamicUser.billingData.customerID);
+ expect(this.billingUser, 'Billing user should not ber null');
+ }
+
+ public async setBillingSystemValidCredentials() : Promise<StripeBillingIntegration> {
+ const stripeSettings = this.getStripeSettings();
+ await this.saveBillingSettings(stripeSettings);
+ stripeSettings.secretKey = await Cypher.encrypt(this.getTenantID(), stripeSettings.secretKey);
+ const billingImpl = new StripeBillingIntegration(this.getTenantID(), stripeSettings);
+ expect(this.billingImpl).to.not.be.null;
+ return billingImpl;
+ }
+
+ public async setBillingSystemInvalidCredentials() : Promise<StripeBillingIntegration> {
+ const stripeSettings = this.getStripeSettings();
+ stripeSettings.secretKey = await Cypher.encrypt(this.getTenantID(), 'sk_test_' + 'invalid_credentials');
+ await this.saveBillingSettings(stripeSettings);
+ const billingImpl = new StripeBillingIntegration(this.getTenantID(), stripeSettings);
+ expect(this.billingImpl).to.not.be.null;
+ return billingImpl;
+ }
+
+ public getStripeSettings(): 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'),
+ immediateBillingAllowed: config.get('billing.immediateBillingAllowed'),
+ periodicBillingAllowed: config.get('billing.periodicBillingAllowed')
+ } as StripeBillingSetting;
+ }
+
+ public async saveBillingSettings(stripeSettings: StripeBillingSetting) : Promise<void> {
+ 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.sensitiveData = ['content.stripe.secretKey'];
+ await this.adminUserService.settingApi.update(componentSetting);
+ }
+
+ public async checkForDraftInvoices(userId: string, expectedValue: number): Promise<number> {
+ const result = await this.getDraftInvoices(userId);
+ assert(result?.length === expectedValue, 'The number of invoice is not the expected one');
+ return (result) ? result.length : -1;
+ }
+
+ public async getDraftInvoices(userId: string) : Promise<any> {
+ const params = { Status: BillingInvoiceStatus.DRAFT, UserID: [userId] };
+ const paging = TestConstants.DEFAULT_PAGING;
+ const ordering = [{ field: 'createdOn', direction: 'desc' }];
+ const response = await this.adminUserService.billingApi.readAll(params, paging, ordering, '/client/api/BillingUserInvoices');
+ return response?.data?.result;
+ }
+
+ public isBillingProperlyConfigured(): boolean {
+ const billingSettings = this.getStripeSettings();
+ for (const key of Object.keys(billingSettings)) {
+ if (!billingSettings[key] || billingSettings[key] === '') {
+ return false ;
+ }
+ }
+ return true;
+ }
+
+ public async assignPaymentMethod(customerID: string, stripe_test_token: string) : Promise<IStripeSource> {
+ const concreteImplementation : StripeBillingIntegration = this.billingImpl as StripeBillingIntegration;
+ const stripeInstance = await concreteImplementation.getStripeInstance();
+ const source = await stripeInstance.customers.createSource(customerID, {
+ source: stripe_test_token
+ });
+ expect(source).to.not.be.null;
+ return source;
+ }
+
+ public async createDraftInvoice() : Promise<BillingInvoice> {
+ assert(this.billingUser, 'Billing user cannot be null');
+ const item = { description: 'Stripe Integration - Item 777', amount: 777 };
+ const invoiceWrapper: { invoice: BillingInvoice; invoiceItem: BillingInvoiceItem } = await this.billingImpl.createInvoice(this.billingUser, item);
+ assert(invoiceWrapper?.invoice, 'Invoice should not be null');
+ return invoiceWrapper?.invoice;
+ }
+
+ public async updateDraftInvoice(billingInvoice: BillingInvoice) : Promise<void> {
+ assert(this.billingUser, 'Billing user cannot be null');
+ const item = { description: 'Stripe Integration - Item 555', amount: 555 };
+ const billingInvoiceItem: BillingInvoiceItem = await this.billingImpl.createInvoiceItem(this.billingUser, billingInvoice.invoiceID, item);
+ assert(billingInvoiceItem, 'Invoice Item should not be null');
+ }
+
+ public async payDraftInvoice(draftInvoice: { id: string }): Promise<void> {
+ const draftInvoiceId = draftInvoice.id;
+ const billingInvoice: BillingInvoice = await BillingStorage.getInvoice(this.getTenantID(), draftInvoiceId);
+ // Let's attempt a payment using the default payment method
+ const operationResult: any = await this.billingImpl.chargeInvoice(billingInvoice);
+ assert(operationResult && operationResult?.invoiceStatus === 'paid' && operationResult?.rawData?.paid, 'Invoice should have been paid');
+ }
+
+ public getTenantID(): string {
+ const tenantId = this.tenantContext?.getTenant()?.id;
+ assert(tenantId, 'Tenant ID cannot be null');
+ return tenantId;
+ }
+}

MODIFY
test/api/BillingTest.ts
test/api/BillingTest.ts
@@ -498,70 +498,6 @@ describe('Billing Service', function() {
});
});
- describe('Where again the admin user', () => {
- before(async () => {
- testData.userContext = testData.adminUserContext;
- testData.userService = testData.adminUserService;
- assert(!!testData.userService, 'User service cannot be null');
- testData.billingImpl = await testData.setBillingSystemValidCredentials();
- });
-
- after(async () => {
- // TODO - cleanup test data!
- // Cleanup of the utbilling tenant is useless if we cannot cleanup the utbilling stripe account as well
- });
-
- it('Should connect to Billing Provider', async () => {
- const response = await testData.userService.billingApi.testConnection({}, TestConstants.DEFAULT_PAGING, TestConstants.DEFAULT_ORDERING);
- expect(response.data).containSubset(Constants.REST_RESPONSE_SUCCESS);
- expect(response.data.connectionIsValid).to.be.true;
- });
-
- it('Should add a payment method to BILLING-TEST user', async () => {
- const concreteImplementation : StripeBillingIntegration = testData.billingImpl as StripeBillingIntegration;
- const stripe_test_token = 'tok_visa';
- const stripeInstance = await concreteImplementation.getStripeInstance();
- const source = await stripeInstance.customers.createSource(testData.billingUser.billingData.customerID, {
- source: stripe_test_token
- });
- expect(source).to.not.be.null;
- });
-
- it('should see no DRAFT invoices for BILLING-TEST user', async () => {
- const nbInvoices = await testData.checkForDraftInvoices(testData.dynamicUser.id);
- expect(nbInvoices === 0, 'User should not have any DRAFT invoices');
- });
-
- it('should create a first DRAFT invoice for BILLING-TEST user', async () => {
- assert(testData.billingUser, 'Dynamic user context cannot be null');
- // await testData.billingImpl.synchronizeUser(testData.tenantContext.getTenant().id, testData.dynamicUser);
- // const dynamicBillingUser = await testData.billingImpl.getUserByEmail(testData.dynamicUser.email);
- const dynamicBillingUser = testData.billingUser;
- assert(dynamicBillingUser, 'Dynamic Billing shoud not be null');
- const item777 = { description: 'Stripe Integration - Item 777', amount: 777 };
- const dynamicInvoice = await testData.billingImpl.createInvoice(dynamicBillingUser, item777);
- assert(dynamicInvoice, 'Invoice should not be null');
- const item555 = { description: 'Stripe Integration - Item 555', amount: 555 };
- const billingInvoiceItem: BillingInvoiceItem = await testData.billingImpl.createInvoiceItem(dynamicBillingUser, dynamicInvoice.invoice.invoiceID, item555);
- assert(billingInvoiceItem, 'Invoice Item should not be null');
- // TODO - Clarify why the next line is necessary?
- await testData.billingImpl.forceSynchronizeUser(testData.dynamicUser);
-
- // const nbInvoices = await testData.checkForDraftInvoices(testData.dynamicUser.id);
- // assert(nbInvoices === 1, 'Dynamic User should have a draft invoice');
-
- const draftInvoices = await testData.getDraftInvoices(testData.dynamicUser.id);
- assert(draftInvoices && draftInvoices.length === 1, 'Dynamic User should have a draft invoice');
-
- const draftInvoiceId = draftInvoices[0].id;
- const tenantId = testData.tenantContext?.getTenant()?.id;
- assert(!!tenantId, 'Tenant ID cannot be null');
- const draftInvoice: BillingInvoice = await BillingStorage.getInvoice(tenantId, draftInvoiceId);
- const operationResult: any = await testData.billingImpl.chargeInvoice(draftInvoice);
- assert(operationResult && operationResult?.invoiceStatus === 'paid' && operationResult?.rawData?.paid, 'Invoice should have been paid');
- });
- });
-
});
});

utbilling - strpe integration tests