periodic billing - ajust logic based on first results
MODIFY
src/integration/billing/BillingIntegration.ts
src/integration/billing/BillingIntegration.ts
@@ -195,7 +195,7 @@ export default abstract class BillingIntegration {
for (const invoice of invoices.result) {
try {
// Skip invoices that are already PAID or not relevant for the current billing process
- if (this.invoiceMustBeSkipped(invoice)) {
+ if (this.isInvoiceOutOfPeriodicOperationScope(invoice)) {
continue;
}
// Make sure to avoid trying to charge it again too soon
@@ -211,7 +211,11 @@ export default abstract class BillingIntegration {
});
continue;
}
- await this.chargeInvoice(invoice);
+ const newInvoice = await this.chargeInvoice(invoice);
+ if (this.isInvoiceOutOfPeriodicOperationScope(newInvoice)) {
+ // The new invoice may now have a different status - and this impacts the pagination
+ skip--; // This is very important!
+ }
await Logging.logInfo({
tenantID: this.tenant.id,
source: Constants.CENTRAL_SERVER,
@@ -542,7 +546,7 @@ export default abstract class BillingIntegration {
await UserStorage.saveUserBillingData(this.tenant.id, user.id, null);
}
- private invoiceMustBeSkipped(invoice: BillingInvoice): boolean {
+ private isInvoiceOutOfPeriodicOperationScope(invoice: BillingInvoice): boolean {
if (invoice.status === BillingInvoiceStatus.DRAFT && this.settings.billing?.periodicBillingAllowed) {
return false;
}
@@ -566,15 +570,25 @@ export default abstract class BillingIntegration {
startDateTime = moment().date(0).date(1).startOf('day').toDate(); // 1st day of the previous month 00:00:00 (AM)
endDateTime = moment().date(1).startOf('day').toDate(); // 1st day of this month 00:00:00 (AM)
}
+ // Filter the invoice status based on the billing settings
+ let invoiceStatus;
+ if (this.settings.billing?.periodicBillingAllowed) {
+ // Let's finalize DRAFT invoices and trigger a payment attemnpt for unpaid invoices as well
+ invoiceStatus = [ BillingInvoiceStatus.DRAFT, BillingInvoiceStatus.OPEN ];
+ } else {
+ // Let's trigger a new payment attemnpt for unpaid invoices
+ invoiceStatus = [ BillingInvoiceStatus.OPEN ];
+ }
// Now return the query parameters
return {
- // ------------------------------------------------------------------------------
- // ACHTUNG!!! Make sure not to filter on data which is changed while paginating!
- // Filtering on the invoice status is not possible here
- // ------------------------------------------------------------------------------
+ // --------------------------------------------------------------------------------
+ // ACHTUNG!!! Make sure to adapt the paging logic when the data used for filtering
+ // is also updated by the periodic operation
+ // --------------------------------------------------------------------------------
filter: {
startDateTime,
- endDateTime
+ endDateTime,
+ invoiceStatus
},
limit,
sort: { createdOn: 1 } // Sort by creation date - process the eldest first!

MODIFY
test/api/BillingStripeTestData.ts
test/api/BillingStripeTestData.ts
@@ -51,7 +51,8 @@ export default class StripeIntegrationTestData {
...Factory.user.build(),
name: 'BILLING-TEST',
firstName: 'Billing Integration Tests',
- issuer: true
+ issuer: true,
+ locale: 'fr_FR'
} as User;
// Let's create a new user
const userData = await this.adminUserService.createEntity(

MODIFY
test/api/BillingTest.ts
test/api/BillingTest.ts
@@ -11,7 +11,7 @@ import Constants from '../../src/utils/Constants';
import ContextDefinition from './context/ContextDefinition';
import ContextProvider from './context/ContextProvider';
import Cypher from '../../src/utils/Cypher';
-import { DataResult } from '../types/DataResult';
+import { DataResult } from '../../src/types/DataResult';
import Factory from '../factories/Factory';
import MongoDBStorage from '../../src/storage/mongodb/MongoDBStorage';
import { ObjectID } from 'mongodb';
@@ -425,6 +425,29 @@ describe('Billing Service', function() {
await testData.billingImpl.forceSynchronizeUser(basicUser);
});
+ describe('Tune user profiles', () => {
+ // eslint-disable-next-line @typescript-eslint/require-await
+ before(async () => {
+ testData.userContext = testData.adminUserContext;
+ assert(testData.userContext, 'User context cannot be null');
+ testData.userService = testData.adminUserService;
+ assert(!!testData.userService, 'User service cannot be null');
+ // await testData.setBillingSystemValidCredentials();
+ });
+
+ it('Should change admin user locale to fr_FR', async () => {
+ const user: User = testData.tenantContext.getUserContext(ContextDefinition.USER_CONTEXTS.DEFAULT_ADMIN);
+ const { id, email, name, firstName } = user;
+ await testData.userService.updateEntity(testData.userService.userApi, { id, email, name, firstName, locale: 'fr_FR' }, true);
+ });
+
+ it('Should change basic user locale to es_ES', async () => {
+ const user: User = testData.tenantContext.getUserContext(ContextDefinition.USER_CONTEXTS.BASIC_USER);
+ const { id, email, name, firstName } = user;
+ await testData.userService.updateEntity(testData.userService.userApi, { id, email, name, firstName, locale: 'es_ES' }, true);
+ });
+ });
+
describe('Where admin user (essential)', () => {
// eslint-disable-next-line @typescript-eslint/require-await
before(async () => {

periodic billing - ajust logic based on first results