billing - method persistBillingInvoice to make sure local data is uptodate
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, BillingInvoiceRawData, BillingInvoiceStatus, BillingStatus, BillingTax, BillingUser } from '../../../types/Billing';
+import { BillingDataTransactionStart, BillingDataTransactionStop, BillingDataTransactionUpdate, BillingInvoice, BillingInvoiceDocument, BillingInvoiceItem, BillingInvoiceStatus, BillingStatus, BillingTax, BillingUser } from '../../../types/Billing';
import { DocumentEncoding, DocumentType } from '../../../types/GlobalType';
import AxiosFactory from '../../../utils/AxiosFactory';
@@ -332,12 +332,13 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
idempotencyKey: idempotencyKey?.toString(), // STRIPE version 8.137.0 - property as been renamed!!!
});
// Let's update the data which is replicated on our side
- return this.updateBillingInvoice(user, stripeInvoice);
+ return this.persistBillingInvoice(user, stripeInvoice);
}
- private async updateBillingInvoice(billingUser: BillingUser, stripeInvoice: Stripe.Invoice): Promise<BillingInvoice> {
+ private async persistBillingInvoice(billingUser: BillingUser, stripeInvoice: Stripe.Invoice, billingInvoiceID?: string): Promise<BillingInvoice> {
const nbrOfItems: number = this.getNumberOfItems(stripeInvoice);
- const invoiceData: BillingInvoiceRawData = {
+ const invoiceToSave: Partial<BillingInvoice> = {
+ id: billingInvoiceID, // null when the billing invoice does not yet exist
userID: billingUser.userID,
invoiceID: stripeInvoice.id,
customerID: stripeInvoice.customer as string, // TODO - clarify is this is always correct - customer might be expanded
@@ -354,9 +355,9 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
// --------------------------------------------------------
// Save Invoice
const billingInvoice: BillingInvoice = {
- id: await BillingStorage.saveInvoice(this.tenantID, invoiceData),
- ...invoiceData,
- };
+ id: await BillingStorage.saveInvoice(this.tenantID, invoiceToSave),
+ ...invoiceToSave,
+ } as BillingInvoice;
return billingInvoice;
}
@@ -641,7 +642,7 @@ export default class StripeBillingIntegration extends BillingIntegration<StripeB
// Let's get the raw data from stripe!
const stripeInvoice = await this.getStripeInvoice(draftInvoice.invoiceID);
// Well ... we need to update the billing invoice to reflect the latest changes
- await this.updateBillingInvoice(billingUser, stripeInvoice);
+ await this.persistBillingInvoice(billingUser, stripeInvoice, draftInvoice.id);
}
// Return the operation result as a BillingDataTransactionStop
return {

MODIFY
src/storage/mongodb/BillingStorage.ts
src/storage/mongodb/BillingStorage.ts
-import { BillingInvoice, BillingInvoiceDocument, BillingInvoiceRawData, BillingInvoiceStatus } from '../../types/Billing';
+import { BillingInvoice, BillingInvoiceDocument, BillingInvoiceStatus } from '../../types/Billing';
import global, { FilterParams } from '../../types/GlobalType';
import Constants from '../../utils/Constants';
@@ -157,28 +157,24 @@ export default class BillingStorage {
};
}
- public static async saveInvoice(tenantID: string, invoiceRawData: BillingInvoice | BillingInvoiceRawData): Promise<string> {
+ public static async saveInvoice(tenantID: string, invoiceToSave: Partial<BillingInvoice>): Promise<string> {
// Debug
const uniqueTimerID = Logging.traceStart(tenantID, MODULE_NAME, 'saveInvoice');
- // Is it an a create or an update
- const _id = (invoiceRawData['id']) ? Utils.convertToObjectID(invoiceRawData['id']) : new ObjectID();
- const userID = Utils.convertToObjectID(invoiceRawData.userID);
-
// Build Request
// Properties to save
const invoiceMDB = {
- _id,
- invoiceID: invoiceRawData.invoiceID,
- number: invoiceRawData.number,
- userID,
- customerID: invoiceRawData.customerID,
- amount: Utils.convertToFloat(invoiceRawData.amount),
- status: invoiceRawData.status,
- currency: invoiceRawData.currency,
- createdOn: Utils.convertToDate(invoiceRawData.createdOn),
- nbrOfItems: Utils.convertToInt(invoiceRawData.nbrOfItems),
- downloadable: Utils.convertToBoolean(invoiceRawData.downloadable),
- downloadUrl: invoiceRawData.downloadUrl
+ _id: invoiceToSave.id ? Utils.convertToObjectID(invoiceToSave.id) : new ObjectID(),
+ invoiceID: invoiceToSave.invoiceID,
+ number: invoiceToSave.number,
+ userID: invoiceToSave.userID ? Utils.convertToObjectID(invoiceToSave.userID) : null,
+ customerID: invoiceToSave.customerID,
+ amount: Utils.convertToFloat(invoiceToSave.amount),
+ status: invoiceToSave.status,
+ currency: invoiceToSave.currency,
+ createdOn: Utils.convertToDate(invoiceToSave.createdOn),
+ nbrOfItems: Utils.convertToInt(invoiceToSave.nbrOfItems),
+ downloadable: Utils.convertToBoolean(invoiceToSave.downloadable),
+ downloadUrl: invoiceToSave.downloadUrl
};
// Modify and return the modified document
await global.database.getCollection<BillingInvoice>(tenantID, 'invoices').findOneAndReplace(

MODIFY
src/types/Billing.ts
src/types/Billing.ts
@@ -66,11 +66,8 @@ export interface BillingTax {
percentage: number;
}
-export interface BillingInvoice extends BillingInvoiceRawData {
+export interface BillingInvoice {
id: string;
-}
-
-export interface BillingInvoiceRawData {
invoiceID: string;
userID?: string;
user?: User;
@@ -85,7 +82,6 @@ export interface BillingInvoiceRawData {
downloadUrl?: string;
}
-
export interface BillingInvoiceItem {
description: string;
amount: number;

MODIFY
test/api/BillingTest.ts
test/api/BillingTest.ts
@@ -116,8 +116,14 @@ class TestData {
return (result?.length) ? result.length : -1;
}
- public async getDraftInvoices(userId: string) {
- const params = { Status: BillingInvoiceStatus.DRAFT, UserID: [userId] };
+ public async getDraftInvoices(userId?: string) {
+ let params;
+ if (userId) {
+ params = { Status: BillingInvoiceStatus.DRAFT, UserID: [this.userContext.id] };
+ } else {
+ params = { Status: BillingInvoiceStatus.DRAFT };
+ }
+
const paging = TestConstants.DEFAULT_PAGING;
const ordering = [{ field: 'createdOn', direction: 'desc' }];
const response = await testData.adminUserService.billingApi.readAll(params, paging, ordering, '/client/api/BillingUserInvoices');
@@ -134,18 +140,18 @@ class TestData {
return true;
}
- public async getNumberOfItems(forUser?: User): Promise<number> {
+ public async getLatestDraftInvoice(userId?: string) {
// ACHTUNG: There is no data after running: npm run mochatest:createContext
// In that situation we return 0!
- let params;
- if (forUser) {
- params = { Status: BillingInvoiceStatus.DRAFT, UserID: [this.userContext.id] };
- } else {
- params = { Status: BillingInvoiceStatus.DRAFT };
+ const draftInvoices = await this.getDraftInvoices(userId);
+ return (draftInvoices && draftInvoices.length) ? draftInvoices[draftInvoices.length - 1] : null;
}
- const response = await this.userService.billingApi.readAll(params, TestConstants.DEFAULT_PAGING, [{ field: 'createdOn', direction: 'desc' }], '/client/api/BillingUserInvoices');
- return (response.data?.result?.[0]) ? response.data?.result?.[0]?.nbrOfItems : 0;
+ public async getNumberOfItems(userId?: string): Promise<number> {
+ // ACHTUNG: There is no data after running: npm run mochatest:createContext
+ // In that situation we return 0!
+ const draftInvoice = await this.getLatestDraftInvoice(userId);
+ return (draftInvoice) ? draftInvoice.nbrOfItems : 0;
}
}
@@ -186,12 +192,12 @@ describe('Billing Service', function() {
it('should add an item to a DRAFT invoice after a transaction', async () => {
// await testData.userService.billingApi.forceSynchronizeUser({ id: testData.userContext.id });
- const itemsBefore = await testData.getNumberOfItems(testData.userContext);
+ const itemsBefore = await testData.getNumberOfItems(testData.userContext.id);
const transactionID = await testData.generateTransaction(testData.userContext);
expect(transactionID).to.not.be.null;
// await testData.userService.billingApi.synchronizeInvoices({});
- const itemsAfter = await testData.getNumberOfItems(testData.userContext);
- expect(itemsAfter).to.be.eq(itemsBefore + 1);
+ const itemsAfter = await testData.getNumberOfItems(testData.userContext.id);
+ expect(itemsAfter).to.be.gt(itemsBefore);
});
});
@@ -273,11 +279,11 @@ describe('Billing Service', function() {
it('should add an item to the existing invoice after a transaction', async () => {
await testData.userService.billingApi.forceSynchronizeUser({ id: testData.userContext.id });
- const itemsBefore = await testData.getNumberOfItems(testData.userContext);
+ const itemsBefore = await testData.getNumberOfItems(testData.userContext.id);
const transactionID = await testData.generateTransaction(testData.userContext);
expect(transactionID).to.not.be.null;
await testData.userService.billingApi.synchronizeInvoices({});
- const itemsAfter = await testData.getNumberOfItems(testData.userContext);
+ const itemsAfter = await testData.getNumberOfItems(testData.userContext.id);
expect(itemsAfter).to.be.eq(itemsBefore + 1);
});
@@ -432,11 +438,11 @@ describe('Billing Service', function() {
testData.userContext
);
await testData.userService.billingApi.synchronizeInvoices({});
- const itemsBefore = await testData.getNumberOfItems();
+ const itemsBefore = await testData.getNumberOfItems(basicUser.id);
const transactionID = await testData.generateTransaction(testData.userContext);
expect(transactionID).to.not.be.null;
await testData.userService.billingApi.synchronizeInvoices({});
- const itemsAfter = await testData.getNumberOfItems();
+ const itemsAfter = await testData.getNumberOfItems(basicUser.id);
expect(itemsAfter).to.be.eq(itemsBefore + 1);
});
});

billing - method persistBillingInvoice to make sure local data is uptodate