feat(billing-platform): save platformFeeStrategy in companies and sites
MODIFY
src/assets/schemas/billing/billing-subaccount.json
src/assets/schemas/billing/billing-subaccount.json
"definitions": {
"accountID": {
"$ref": "common#/definitions/id"
+ },
+ "platformFeeStrategy": {
+ "type": "object",
+ "properties": {
+ "flatFeePerSession": {
+ "type": "number",
+ "sanitize": "mongo"
+ },
+ "percentage": {
+ "type": "number",
+ "sanitize": "number"
+ }
+ }
}
}
}

MODIFY
src/assets/server/rest/v1/docs/e-mobility-oas.json
src/assets/server/rest/v1/docs/e-mobility-oas.json
"properties": {
"accountID": {
"type": "string"
+ },
+ "platformFeeStrategy": {
+ "type": "object",
+ "properties": {
+ "flatFeePerSession": {
+ "type": "number"
+ },
+ "percentage": {
+ "type": "number"
+ }
+ }
}
}
}
"properties": {
"accountID": {
"type": "string"
+ },
+ "platformFeeStrategy": {
+ "type": "object",
+ "properties": {
+ "flatFeePerSession": {
+ "type": "number"
+ },
+ "percentage": {
+ "type": "number"
+ }
+ }
}
}
}

MODIFY
src/assets/server/rest/v1/schemas/company/company-create.json
src/assets/server/rest/v1/schemas/company/company-create.json
"properties": {
"accountID": {
"$ref": "billing-subaccount#/definitions/accountID"
+ },
+ "platformFeeStrategy": {
+ "$ref": "billing-subaccount#/definitions/platformFeeStrategy"
}
- }
+ },
+ "required": [
+ "accountID",
+ "platformFeeStrategy"
+ ]
}
},
"required": [

MODIFY
src/assets/server/rest/v1/schemas/company/company-update.json
src/assets/server/rest/v1/schemas/company/company-update.json
"properties": {
"accountID": {
"$ref": "billing-subaccount#/definitions/accountID"
+ },
+ "platformFeeStrategy": {
+ "$ref": "billing-subaccount#/definitions/platformFeeStrategy"
}
- }
+ },
+ "required": [
+ "accountID",
+ "platformFeeStrategy"
+ ]
}
},
"required": [

MODIFY
src/assets/server/rest/v1/schemas/site/site-create.json
src/assets/server/rest/v1/schemas/site/site-create.json
"properties": {
"accountID": {
"$ref": "billing-subaccount#/definitions/accountID"
+ },
+ "platformFeeStrategy": {
+ "$ref": "billing-subaccount#/definitions/platformFeeStrategy"
}
- }
+ },
+ "required": [
+ "accountID",
+ "platformFeeStrategy"
+ ]
}
},
"required": [

MODIFY
src/assets/server/rest/v1/schemas/site/site-update.json
src/assets/server/rest/v1/schemas/site/site-update.json
"properties": {
"accountID": {
"$ref": "billing-subaccount#/definitions/accountID"
+ },
+ "platformFeeStrategy": {
+ "$ref": "billing-subaccount#/definitions/platformFeeStrategy"
}
- }
+ },
+ "required": [
+ "accountID",
+ "platformFeeStrategy"
+ ]
}
},
"required": [

MODIFY
src/authorization/AuthorizationsDefinition.ts
src/authorization/AuthorizationsDefinition.ts
@@ -175,7 +175,7 @@ export const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
'id', 'name', 'address.address1', 'address.address2', 'address.postalCode', 'address.city',
'address.department', 'address.region', 'address.country',
'address.coordinates', 'logo', 'issuer', 'distanceMeters', 'createdOn', 'lastChangedOn',
- 'createdBy.name', 'createdBy.firstName', 'lastChangedBy.name', 'lastChangedBy.firstName', 'accountData.accountID'
+ 'createdBy.name', 'createdBy.firstName', 'lastChangedBy.name', 'lastChangedBy.firstName', 'accountData'
]
},
{
@@ -218,7 +218,7 @@ export const AUTHORIZATION_DEFINITION: AuthorizationDefinition = {
attributes: [
'id', 'name', 'issuer', 'logo',
'address.address1', 'address.address2', 'address.postalCode', 'address.city',
- 'address.department', 'address.region', 'address.country', 'address.coordinates', 'accountData.accountID'
+ 'address.department', 'address.region', 'address.country', 'address.coordinates', 'accountData'
]
},
{

MODIFY
src/server/rest/v1/service/CompanyService.ts
src/server/rest/v1/service/CompanyService.ts
@@ -152,7 +152,7 @@ export default class CompanyService {
Action.CREATE, Entity.COMPANY, MODULE_NAME, 'handleCreateCompany');
const billingSubAccount = await BillingStorage.getSubAccountByID(req.tenant, filteredRequest.accountData.accountID);
UtilsService.assertObjectExists(action, billingSubAccount, `Billing Sub-Account ID '${filteredRequest.accountData.accountID}' does not exist`, MODULE_NAME, 'handleCreateCompany', req.user);
- newCompany.accountData = { accountID: billingSubAccount.id };
+ newCompany.accountData.accountID = billingSubAccount.id;
}
// Save
newCompany.id = await CompanyStorage.saveCompany(req.tenant, newCompany);
@@ -190,7 +190,7 @@ export default class CompanyService {
Action.CREATE, Entity.COMPANY, MODULE_NAME, 'handleUpdateCompany');
const billingSubAccount = await BillingStorage.getSubAccountByID(req.tenant, filteredRequest.accountData.accountID);
UtilsService.assertObjectExists(action, billingSubAccount, `Billing Sub-Account ID '${filteredRequest.accountData.accountID}' does not exist`, MODULE_NAME, 'handleUpdateCompany', req.user);
- company.accountData = { accountID: billingSubAccount.id };
+ company.accountData.accountID = billingSubAccount.id;
}
// Update Company
await CompanyStorage.saveCompany(req.tenant, company, Utils.objectHasProperty(filteredRequest, 'logo') ? true : false);

MODIFY
src/server/rest/v1/service/SiteService.ts
src/server/rest/v1/service/SiteService.ts
@@ -312,7 +312,7 @@ export default class SiteService {
Action.CREATE, Entity.SITE, MODULE_NAME, 'handleCreateSite');
const billingSubAccount = await BillingStorage.getSubAccountByID(req.tenant, filteredRequest.accountData.accountID);
UtilsService.assertObjectExists(action, billingSubAccount, `Billing Sub-Account ID '${filteredRequest.accountData.accountID}' does not exist`, MODULE_NAME, 'handleCreateSite', req.user);
- site.accountData = { accountID: billingSubAccount.id };
+ site.accountData.accountID = billingSubAccount.id;
}
// Save
site.id = await SiteStorage.saveSite(req.tenant, site, Utils.objectHasProperty(filteredRequest, 'image'));
@@ -384,7 +384,7 @@ export default class SiteService {
Action.CREATE, Entity.SITE, MODULE_NAME, 'handleUpdateSite');
const billingSubAccount = await BillingStorage.getSubAccountByID(req.tenant, filteredRequest.accountData.accountID);
UtilsService.assertObjectExists(action, billingSubAccount, `Billing Sub-Account ID '${filteredRequest.accountData.accountID}' does not exist`, MODULE_NAME, 'handleUpdateSite', req.user);
- site.accountData = { accountID: billingSubAccount.id };
+ site.accountData.accountID = billingSubAccount.id;
}
// Save
await SiteStorage.saveSite(req.tenant, site, Utils.objectHasProperty(filteredRequest, 'image'));

MODIFY
src/storage/mongodb/BillingStorage.ts
src/storage/mongodb/BillingStorage.ts
-import { BillingAccount, BillingAdditionalData, BillingInvoice, BillingInvoiceStatus, BillingSessionData, BillingTransfer, BillingTransferSession } from '../../types/Billing';
+import { BillingAccount, BillingAdditionalData, BillingInvoice, BillingInvoiceStatus, BillingSessionData, BillingTransfer } from '../../types/Billing';
import global, { DatabaseCount, FilterParams } from '../../types/GlobalType';
import Constants from '../../utils/Constants';

MODIFY
src/storage/mongodb/CompanyStorage.ts
src/storage/mongodb/CompanyStorage.ts
@@ -64,6 +64,7 @@ export default class CompanyStorage {
if (companyToSave.accountData) {
companyMDB.accountData = {
accountID: DatabaseUtils.convertToObjectID(companyToSave.accountData.accountID),
+ platformFeeStrategy: companyToSave.accountData.platformFeeStrategy,
};
}
// Add Last Changed/Created props

MODIFY
src/storage/mongodb/SiteStorage.ts
src/storage/mongodb/SiteStorage.ts
@@ -273,7 +273,8 @@ export default class SiteStorage {
}
if (siteToSave.accountData) {
siteMDB.accountData = {
- accountID: DatabaseUtils.convertToObjectID(siteToSave.accountData.accountID)
+ accountID: DatabaseUtils.convertToObjectID(siteToSave.accountData.accountID),
+ platformFeeStrategy: siteToSave.accountData.platformFeeStrategy,
};
}
// Add Last Changed/Created props

MODIFY
src/types/Billing.ts
src/types/Billing.ts
@@ -175,7 +175,7 @@ export interface BillingAccount extends AuthorizationActions {
export interface BillingAccountData {
accountID: string;
- platformFee?: BillingPlatformFeeStrategy;
+ platformFeeStrategy?: BillingPlatformFeeStrategy;
}
export interface BillingTransferData extends BillingAccountData {

MODIFY
test/api/BillingTest.ts
test/api/BillingTest.ts
/* eslint-disable max-len */
-import { BillingAccount, BillingAccountStatus, BillingChargeInvoiceAction, BillingInvoiceStatus, BillingTransfer } from '../../src/types/Billing';
+import { BillingAccount, BillingAccountStatus, BillingChargeInvoiceAction, BillingInvoiceStatus } from '../../src/types/Billing';
+import { BillingPlatformFeeStrategyFactory, BillingTransferFactory } from '../factories/BillingFactory';
import { BillingSettings, BillingSettingsType } from '../../src/types/Setting';
-import { BillingTransferFactory, BillingTransferSessionFactory } from '../factories/BillingFactory';
import chai, { expect } from 'chai';
import { BillingPeriodicOperationTaskConfig } from '../../src/types/TaskConfig';
@@ -1035,15 +1035,18 @@ describeif(isBillingProperlyConfigured)('Billing', () => {
});
expect(subAccountResponse.status).to.be.eq(StatusCodes.CREATED);
+ const platformFeeStrategy = BillingPlatformFeeStrategyFactory.build();
let companyResponse = await billingTestHelper.userService.companyApi.create({
...CompanyFactory.build(),
accountData: {
- accountID: subAccountResponse.data.id
+ accountID: subAccountResponse.data.id,
+ platformFeeStrategy
}
});
expect(companyResponse.status).to.be.eq(StatusCodes.OK);
companyResponse = await billingTestHelper.userService.companyApi.readById(companyResponse.data.id);
expect(companyResponse.data.accountData.accountID).to.eq(subAccountResponse.data.id);
+ expect(companyResponse.data.accountData.platformFeeStrategy).to.deep.eq(platformFeeStrategy);
});
it('should update a company to assign a sub-account', async () => {
@@ -1055,15 +1058,20 @@ describeif(isBillingProperlyConfigured)('Billing', () => {
let companyResponse = await billingTestHelper.userService.companyApi.create(CompanyFactory.build());
expect(companyResponse.status).to.be.eq(StatusCodes.OK);
const companyID = companyResponse.data.id;
+ const platformFeeStrategy = BillingPlatformFeeStrategyFactory.build();
companyResponse = await billingTestHelper.userService.companyApi.update({
id: companyID,
...CompanyFactory.build(),
- accountData: { accountID: subAccountResponse.data.id }
+ accountData: {
+ accountID: subAccountResponse.data.id,
+ platformFeeStrategy
+ }
});
expect(companyResponse.status).to.be.eq(StatusCodes.OK);
companyResponse = await billingTestHelper.userService.companyApi.readById(companyID);
expect(companyResponse.data.accountData.accountID).to.eq(subAccountResponse.data.id);
+ expect(companyResponse.data.accountData.platformFeeStrategy).to.eq(platformFeeStrategy);
});
it('should create a site assigned to a sub-account', async () => {
@@ -1076,11 +1084,13 @@ describeif(isBillingProperlyConfigured)('Billing', () => {
const companyResponse = await billingTestHelper.userService.companyApi.create(CompanyFactory.build());
expect(companyResponse.status).to.be.eq(StatusCodes.OK);
// Create a site
+ const platformFeeStrategy = BillingPlatformFeeStrategyFactory.build();
let siteResponse = await billingTestHelper.userService.siteApi.create({
...SiteFactory.build(),
companyID: companyResponse.data.id,
accountData: {
- accountID: subAccountResponse.data.id
+ accountID: subAccountResponse.data.id,
+ platformFeeStrategy
}
});
expect(siteResponse.status).to.be.eq(StatusCodes.OK);
@@ -1105,11 +1115,15 @@ describeif(isBillingProperlyConfigured)('Billing', () => {
expect(siteResponse.status).to.be.eq(StatusCodes.OK);
const siteID = siteResponse.data.id;
+ const platformFeeStrategy = BillingPlatformFeeStrategyFactory.build();
siteResponse = await billingTestHelper.userService.siteApi.update({
id: siteID,
...SiteFactory.build(),
companyID: companyResponse.data.id,
- accountData: { accountID: subAccountResponse.data.id }
+ accountData: {
+ accountID: subAccountResponse.data.id,
+ platformFeeStrategy
+ }
});
expect(siteResponse.status).to.be.eq(StatusCodes.OK);
@@ -1224,7 +1238,10 @@ describeif(isBillingProperlyConfigured)('Billing', () => {
});
it('should not activate an inexistent sub account', async () => {
- const activationResponse = await billingTestHelper.userService.billingApi.activateSubAccount({ accountID: '5ce249a1a39ae1c056c389bd', TenantID: billingTestHelper.tenantContext.getTenant().id });
+ const activationResponse = await billingTestHelper.userService.billingApi.activateSubAccount({
+ accountID: '5ce249a1a39ae1c056c389bd', // inexistent sub account
+ TenantID: billingTestHelper.tenantContext.getTenant().id
+ });
expect(activationResponse.status).to.be.eq(StatusCodes.NOT_FOUND);
});
@@ -1235,13 +1252,13 @@ describeif(isBillingProperlyConfigured)('Billing', () => {
it('should not be able to read sub-account', async () => {
// List sub-accounts
- const subAccountResponse = await billingTestHelper.userService.billingApi.readSubAccount('62978713f146ea8cb3bf8a95');
+ const subAccountResponse = await billingTestHelper.userService.billingApi.readSubAccount('62978713f146ea8cb3bf8a95'); // inexistent sub account
expect(subAccountResponse.status).to.be.eq(StatusCodes.FORBIDDEN);
});
it('should not be able to send sub-account onboarding', async () => {
// List sub-accounts
- const subAccountResponse = await billingTestHelper.userService.billingApi.sendSubAccountOnboarding('62978713f146ea8cb3bf8a95');
+ const subAccountResponse = await billingTestHelper.userService.billingApi.sendSubAccountOnboarding('62978713f146ea8cb3bf8a95'); // inexistent sub account
expect(subAccountResponse.status).to.be.eq(StatusCodes.FORBIDDEN);
});

MODIFY
test/factories/BillingFactory.ts
test/factories/BillingFactory.ts
-import { BillingTransfer, BillingTransferSession, BillingTransferStatus } from '../../src/types/Billing';
+import { BillingPlatformFeeStrategy, BillingTransfer, BillingTransferSession, BillingTransferStatus } from '../../src/types/Billing';
import { Factory } from 'rosie';
import faker from 'faker';
@@ -25,3 +25,7 @@ export const BillingTransferSessionFactory = Factory.define<BillingTransferSessi
flatFeePerSession: faker.datatype.number(),
percentage: faker.datatype.number(),
}));
+
+export const BillingPlatformFeeStrategyFactory = Factory.define<BillingPlatformFeeStrategy>('billingplatformfeestrategy')
+ .attr('flatFeePerSession', () => faker.datatype.number())
+ .attr('percentage', () => faker.datatype.number());

feat(billing-platform): save platformFeeStrategy in companies and sites