utbilling createContext
MODIFY
src/integration/billing/stripe/StripeBillingIntegration.ts
src/integration/billing/stripe/StripeBillingIntegration.ts
@@ -748,15 +748,19 @@ export default class StripeBillingIntegration extends BillingIntegration {
}
}
- private isTransactionUserInternal(transaction: Transaction): boolean {
- return this.isUserInternal(transaction?.user);
+ private async isTransactionUserInternal(transaction: Transaction): Promise<boolean> {
+ return await this.isUserInternal(transaction?.user);
}
- private isUserInternal(user: User): boolean {
- // Check user's flag 'billable'
- if (Utils.isBoolean(user.billable)) {
+ private async isUserInternal(user: User): Promise<boolean> {
+ // Get billable flag from Mongo
+ const u = await UserStorage.getUser(this.tenant, user.id, { }, ['billable']);
+ if (Utils.isBoolean(u.billable)) {
return !user.billable;
}
+ if (!u.billable) {
+ return true; // Do not bill users without the billing flag.
+ }
// slf
if (this.tenant.id === '5be7fb271014d90008992f06') {
const email = user?.email?.toLocaleLowerCase();
@@ -784,7 +788,7 @@ export default class StripeBillingIntegration extends BillingIntegration {
};
}
// Temporary solution - Check for internal users
- if (this.isTransactionUserInternal(transaction)) {
+ if (await this.isTransactionUserInternal(transaction)) {
return {
// Do not bill internal users
withBillingActive: false

MODIFY
src/types/Billing.ts
src/types/Billing.ts
@@ -116,6 +116,7 @@ export enum BillingInvoiceStatus {
VOID = 'void',
UNCOLLECTIBLE = 'uncollectible',
DELETED = 'deleted',
+ NOT_BILLED = 'unbilled',
}
export interface BillingOperationResult {

MODIFY
test/api/BillingTest.ts
test/api/BillingTest.ts
@@ -27,6 +27,7 @@ import User from '../../src/types/User';
import { UserInErrorType } from '../../src/types/InError';
import chaiSubset from 'chai-subset';
import config from '../config';
+import exp from 'constants';
import global from '../../src/types/GlobalType';
import moment from 'moment';
import responseHelper from '../helpers/responseHelper';
@@ -629,7 +630,6 @@ describe('Billing Service', function() {
before(async () => {
testData.billingImpl = await testData.setBillingSystemValidCredentials();
testData.userContext = testData.tenantContext.getUserContext(ContextDefinition.USER_CONTEXTS.BASIC_USER);
- assert(!!testData.userService, 'User service cannot be null');
testData.userService = new CentralServerService(
testData.tenantContext.getTenant().subdomain,
testData.userContext
@@ -908,6 +908,32 @@ describe('Billing Service', function() {
});
});
+
+ describe('Where basic user', () => {
+ // eslint-disable-next-line @typescript-eslint/require-await
+ before(async () => {
+ testData.userContext = testData.tenantContext.getUserContext(ContextDefinition.USER_CONTEXTS.BASIC_USER);
+ assert(testData.userContext, 'User context cannot be null');
+ testData.userService = new CentralServerService(
+ testData.tenantContext.getTenant().subdomain,
+ testData.userContext
+ );
+ assert(!!testData.userService, 'User service cannot be null');
+ });
+
+ it('should NOT add an item to a DRAFT invoice after a transaction', async () => {
+ await testData.userService.billingApi.forceSynchronizeUser({ id: testData.userContext.id });
+ // const userWithBillingData = await testData.billingImpl.getUser(testData.userContext);
+ // await testData.assignPaymentMethod(userWithBillingData, 'tok_fr');
+ const itemsBefore = await testData.getNumberOfSessions(testData.userContext.id);
+ const transactionID = await testData.generateTransaction(testData.userContext);
+ assert(transactionID, 'transactionID should not be null');
+ // await testData.userService.billingApi.synchronizeInvoices({});
+ const itemsAfter = await testData.getNumberOfSessions(testData.userContext.id);
+ expect(itemsAfter).to.be.eq(itemsBefore);
+ });
+
+ });
});
@@ -950,6 +976,47 @@ describe('Billing Service', function() {
});
});
+
+ describe('Where non-billable basic user', () => {
+ // eslint-disable-next-line @typescript-eslint/require-await
+ before(async () => {
+ testData.userContext = testData.tenantContext.getUserContext(ContextDefinition.USER_CONTEXTS.BASIC_USER);
+ assert(testData.userContext, 'User context cannot be null');
+ testData.userService = new CentralServerService(
+ testData.tenantContext.getTenant().subdomain,
+ testData.userContext
+ );
+ assert(!!testData.userService, 'User service cannot be null');
+ });
+
+ it('should NOT create bill after a transaction', async () => {
+ await testData.userService.billingApi.forceSynchronizeUser({ id: testData.userContext.id });
+ const userWithBillingData = await testData.billingImpl.getUser(testData.userContext);
+ await testData.assignPaymentMethod(userWithBillingData, 'tok_fr');
+ const transactionID = await testData.generateTransaction(testData.userContext);
+ assert(transactionID, 'transactionID should not be null');
+ // Check that we have a new invoice with an invoiceID and an invoiceNumber
+ await testData.checkTransactionBillingData(transactionID, BillingInvoiceStatus.NOT_BILLED);
+ });
+ });
+
+ describe('Where billable basic user', () => {
+ // eslint-disable-next-line @typescript-eslint/require-await
+ before(async () => {
+ testData.userContext = testData.tenantContext.getUserContext(ContextDefinition.USER_CONTEXTS.BASIC_USER);
+ assert(testData.userContext, 'User context cannot be null');
+ testData.userService = new CentralServerService(
+ testData.tenantContext.getTenant().subdomain,
+ testData.userContext
+ );
+ assert(!!testData.userService, 'User service cannot be null');
+ });
+
+ it('should NOT start a transaction', async () => {
+ const transactionID = await testData.generateTransaction(testData.userContext, 'Invalid');
+ expect(transactionID).eq(0);
+ });
+ });
});
describe('with Transaction Billing + Periodic Billing ON', () => {

MODIFY
test/api/context/ContextBuilder.ts
test/api/context/ContextBuilder.ts
@@ -157,6 +157,9 @@ export default class ContextBuilder {
await TagStorage.saveTag(buildTenant, tag);
}
}
+ if (Utils.isBoolean(ContextDefinition.TENANT_USER_LIST[0].billable)) {
+ await UserStorage.saveUserAdminData(buildTenant, userId, { billable: ContextDefinition.TENANT_USER_LIST[0].billable });
+ }
const defaultAdminUser = await UserStorage.getUser(buildTenant, ContextDefinition.TENANT_USER_LIST[0].id);
// Create Central Server Service
const localCentralServiceService: CentralServerService = new CentralServerService(buildTenant.subdomain);
@@ -249,6 +252,9 @@ export default class ContextBuilder {
if (userDef.assignedToSite) {
userListToAssign.push(userModel);
}
+ if (userDef.billable) {
+ await UserStorage.saveUserAdminData(buildTenant, user.id, { billable: userDef.billable });
+ }
// Set back password to clear value for login/logout
(userModel as any).passwordClear = config.get('admin.password');
userList.push(userModel);

MODIFY
test/api/context/ContextDefinition.ts
test/api/context/ContextDefinition.ts
@@ -386,7 +386,8 @@ export default class ContextDefinition {
visualID: new ObjectId().toString(),
issuer: false,
active: true
- }] : null)
+ }] : null),
+ billable: true,
},
{ // Admin not assigned
id: '5ce249a1a39ae1c056c123ef',
@@ -406,7 +407,8 @@ export default class ContextDefinition {
visualID: new ObjectId().toString(),
issuer: false,
active: true
- }] : null)
+ }] : null),
+ billable: true,
},
{ // Basic user
id: '5ce249a1a39ae1c056c123ab',
@@ -426,6 +428,27 @@ export default class ContextDefinition {
visualID: new ObjectId().toString(),
issuer: false,
active: true
+ }] : null),
+ billable: true,
+ },
+ { // Basic user NON BILLABLE
+ id: '5ce249a1a39ae1c056c123ff',
+ name: 'DOES NOT PAY',
+ firstName: 'User',
+ locale: 'en-US',
+ phone: '66666666666',
+ mobile: '66666666666',
+ plateID: '666-FB-69',
+ issuer: ContextDefinition.USER_CONTEXTS.BASIC_USER.issuer,
+ role: ContextDefinition.USER_CONTEXTS.BASIC_USER.role,
+ status: ContextDefinition.USER_CONTEXTS.BASIC_USER.status,
+ assignedToSite: ContextDefinition.USER_CONTEXTS.BASIC_USER.assignedToSite,
+ emailPrefix: 'basic2-',
+ tags: (ContextDefinition.USER_CONTEXTS.BASIC_USER.withTags ? [{
+ id: 'A1234999',
+ visualID: new ObjectId().toString(),
+ issuer: false,
+ active: true
}] : null)
},
{ // Demo user
@@ -446,7 +469,8 @@ export default class ContextDefinition {
visualID: new ObjectId().toString(),
issuer: false,
active: true
- }] : null)
+ }] : null),
+ billable: true,
},
{ // Basic user unassigned
id: '5ce249a1a39ae1c056c456ad',
@@ -466,7 +490,8 @@ export default class ContextDefinition {
visualID: new ObjectId().toString(),
issuer: false,
active: true
- }] : null)
+ }] : null),
+ billable: true,
},
{ // Basic user pending
id: '5ce249a1a39ae1c056c456ab',
@@ -506,7 +531,8 @@ export default class ContextDefinition {
visualID: new ObjectId().toString(),
issuer: false,
active: true
- }] : null)
+ }] : null),
+ billable: true,
},
{ // Basic user No Tags
id: '5ce249a1a39ae1c056c567ab',
@@ -526,7 +552,8 @@ export default class ContextDefinition {
visualID: new ObjectId().toString(),
issuer: false,
active: true
- }] : null)
+ }] : null),
+ billable: true,
},
{ // External User
id: '5ce249a1a39ae1c056c456ae',
@@ -546,7 +573,8 @@ export default class ContextDefinition {
visualID: new ObjectId().toString(),
issuer: false,
active: true
- }] : null)
+ }] : null),
+ billable: true,
}
];

utbilling createContext