pricing/billing - 1st draft
MODIFY
src/integration/billing/stripe/StripeBillingIntegration.ts
src/integration/billing/stripe/StripeBillingIntegration.ts
@@ -856,24 +856,30 @@ export default class StripeBillingIntegration extends BillingIntegration {
};
}
- private _buildInvoiceItemParkTimeParameters(customerID: string, billingInvoiceItem: BillingInvoiceItem, invoiceID?: string): Stripe.InvoiceItemCreateParams {
- const { parkingData, taxes } = billingInvoiceItem;
- const currency = parkingData.pricingData.currency.toLowerCase();
+ private async _buildInvoiceItemParametersFor(customerID: string, pricingDimension: string, idemPotencyKey: string,
+ billingInvoiceItem: BillingInvoiceItem, invoiceID?: string): Promise<Stripe.InvoiceItemCreateParams> {
+ const { effectivePricing, taxes } = billingInvoiceItem;
+ const currency = effectivePricing.currency.toLowerCase();
+ if (!effectivePricing[pricingDimension]?.amount) {
+ // Do not bill that dimension
+ return null;
+ }
// Build stripe parameters for the parking time
const parameters: Stripe.InvoiceItemCreateParams = {
invoice: invoiceID,
customer: customerID,
currency,
- description: parkingData.description,
+ description: effectivePricing[pricingDimension].description,
tax_rates: taxes,
// quantity: 1, //Cannot be set separately
- amount: Utils.createDecimal(parkingData.pricingData.amount).times(100).round().toNumber(),
+ amount: Utils.createDecimal(effectivePricing[pricingDimension].amount).times(100).round().toNumber(),
metadata: { ...billingInvoiceItem?.metadata }
};
if (!parameters.invoice) {
// STRIPE throws an exception when invoice is set to null.
delete parameters.invoice;
}
+ await this._createStripeInvoiceItem(parameters, this.buildIdemPotencyKey(idemPotencyKey, pricingDimension));
return parameters;
}
@@ -889,8 +895,8 @@ export default class StripeBillingIntegration extends BillingIntegration {
Stripe alternative - 'unit_amount_decimal' in Cents, with 2 decimals, as a string!
unit_amount_decimal: '004.00' (in Cents, with 2 decimals, as a string)
----------------------------------------------------------------------------------- */
- const { description, pricingData, taxes } = billingInvoiceItem;
- const currency = pricingData.currency.toLowerCase();
+ const { description, effectivePricing, taxes } = billingInvoiceItem;
+ const currency = effectivePricing.currency.toLowerCase();
// Build stripe parameters for the item
const parameters: Stripe.InvoiceItemCreateParams = {
invoice: invoiceID,
@@ -899,7 +905,7 @@ export default class StripeBillingIntegration extends BillingIntegration {
description,
tax_rates: taxes,
// quantity: 1, //Cannot be set separately
- amount: Utils.createDecimal(pricingData.amount).times(100).round().toNumber(), // In cents
+ amount: Utils.createDecimal(effectivePricing.energy.amount).times(100).round().toNumber(), // In cents
metadata: { ...billingInvoiceItem?.metadata }
};
@@ -1049,12 +1055,12 @@ export default class StripeBillingIntegration extends BillingIntegration {
private shrinkInvoiceItem(fatInvoiceItem: BillingInvoiceItem): BillingInvoiceItem {
// The initial invoice item includes redundant transaction data
- const { description, transactionID, pricingData } = fatInvoiceItem;
+ const { description, transactionID, effectivePricing } = fatInvoiceItem;
// Let's return only essential information
const lightInvoiceItem: BillingInvoiceItem = {
description,
transactionID,
- pricingData
+ effectivePricing
};
return lightInvoiceItem;
}
@@ -1076,10 +1082,12 @@ export default class StripeBillingIntegration extends BillingIntegration {
const billingInvoiceItem: BillingInvoiceItem = {
description,
transactionID,
- pricingData: {
- quantity,
+ effectivePricing: {
+ currency,
+ energy: {
amount,
- currency
+ quantity
+ }
},
taxes,
metadata: {
@@ -1095,18 +1103,6 @@ export default class StripeBillingIntegration extends BillingIntegration {
end: timestamp?.valueOf()
}
};
- // Add Parking Time information
- if (FeatureToggles.isFeatureActive(Feature.BILLING_ITEM_WITH_PARKING_TIME)) {
- // TODO - draft implementation - behind a feature toggle - not yet activated
- billingInvoiceItem.parkingData = {
- description: this.buildLineItemParkingTimeDescription(transaction),
- pricingData: {
- quantity: 1,
- amount: 0,
- currency
- }
- };
- }
// Returns a item representing the complete charging session (energy + parking information)
return billingInvoiceItem ;
}
@@ -1126,33 +1122,17 @@ export default class StripeBillingIntegration extends BillingIntegration {
// immediateBillingAllowed is OFF - let's add to the latest DRAFT invoice (if any)
stripeInvoice = await this._getLatestDraftInvoiceOfTheMonth(customerID);
}
+ if (FeatureToggles.isFeatureActive(Feature.PRINCING_NEW_MODEL)) {
+ // Let's create an invoice item per dimension
+ await this._buildInvoiceItemParametersFor(customerID, 'flatFee', idemPotencyKey, billingInvoiceItem, stripeInvoice?.id);
+ await this._buildInvoiceItemParametersFor(customerID, 'chargingTime', idemPotencyKey, billingInvoiceItem, stripeInvoice?.id);
+ await this._buildInvoiceItemParametersFor(customerID, 'energy', idemPotencyKey, billingInvoiceItem, stripeInvoice?.id);
+ await this._buildInvoiceItemParametersFor(customerID, 'parkingTime', idemPotencyKey, billingInvoiceItem, stripeInvoice?.id);
+ } else {
// Let's create an invoice item
// When the stripeInvoice is null a pending item is created
- if (FeatureToggles.isFeatureActive(Feature.BILLING_ITEM_WITH_PARKING_TIME) && billingInvoiceItem.parkingData) {
- const invoiceItemParameters: Stripe.InvoiceItemCreateParams = this._buildInvoiceItemParkTimeParameters(customerID, billingInvoiceItem, stripeInvoice?.id);
- const stripeInvoiceItem = await this._createStripeInvoiceItem(invoiceItemParameters, this.buildIdemPotencyKey(idemPotencyKey, 'parkTime'));
- if (!stripeInvoiceItem) {
- await Logging.logError({
- tenantID: this.tenant.id,
- user: user.id,
- source: Constants.CENTRAL_SERVER,
- action: ServerAction.BILLING_TRANSACTION,
- module: MODULE_NAME, method: 'billInvoiceItem',
- message: `Unexpected situation - stripe invoice item is null - stripe invoice id: '${stripeInvoice?.id}'`
- });
- }
- }
const invoiceItemParameters: Stripe.InvoiceItemCreateParams = this._buildInvoiceItemParameters(customerID, billingInvoiceItem, stripeInvoice?.id);
- const stripeInvoiceItem = await this._createStripeInvoiceItem(invoiceItemParameters, this.buildIdemPotencyKey(idemPotencyKey, 'energy'));
- if (!stripeInvoiceItem) {
- await Logging.logError({
- tenantID: this.tenant.id,
- user: user.id,
- source: Constants.CENTRAL_SERVER,
- action: ServerAction.BILLING_TRANSACTION,
- module: MODULE_NAME, method: 'billInvoiceItem',
- message: `Unexpected situation - stripe invoice item is null - stripe invoice id: '${stripeInvoice?.id}'`
- });
+ await this._createStripeInvoiceItem(invoiceItemParameters, this.buildIdemPotencyKey(idemPotencyKey, 'energy'));
}
if (!stripeInvoice) {
// Let's create a new DRAFT invoice (if none has been found)

MODIFY
src/integration/billing/stripe/StripeHelpers.ts
src/integration/billing/stripe/StripeHelpers.ts
-import { BillingAdditionalData, BillingError, BillingErrorCode, BillingErrorType, BillingInvoice, BillingInvoiceItem, BillingOperationResult, BillingSessionData } from '../../../types/Billing';
+import { BillingAdditionalData, BillingError, BillingErrorCode, BillingErrorType, BillingInvoice, BillingInvoiceItem, BillingSessionData } from '../../../types/Billing';
import BillingStorage from '../../../storage/mongodb/BillingStorage';
import Constants from '../../../utils/Constants';
@@ -33,7 +33,7 @@ export default class StripeHelpers {
session = {
transactionID: billingInvoiceItem.transactionID,
description: billingInvoiceItem.description,
- pricingData: billingInvoiceItem.pricingData,
+ effectivePricing: billingInvoiceItem.effectivePricing,
};
}
// Is there anything to update?

MODIFY
src/types/Billing.ts
src/types/Billing.ts
import { ActionsResponse } from './GlobalType';
+import { EffectivePricing } from './Pricing';
import User from './User';
export interface TransactionBillingData {
@@ -83,24 +84,20 @@ export interface BillingInvoice {
export interface BillingInvoiceItem {
description: string;
transactionID: number;
- pricingData: BillingPricingData;
+ // pricingData: BillingPricingData;
+ effectivePricing: EffectivePricing
taxes?: string[];
metadata?: {
// Just a flat list of key/value pairs!
[name: string]: string | number | null;
}
- parkingData?: BillingParkingData
-}
-
-export interface BillingParkingData {
- description: string;
- pricingData: BillingPricingData;
}
export interface BillingSessionData {
transactionID: number;
description: string;
- pricingData: BillingPricingData;
+ // pricingData: BillingPricingData;
+ effectivePricing: EffectivePricing;
}
export interface BillingPricingData {

MODIFY
src/types/Pricing.ts
src/types/Pricing.ts
@@ -7,14 +7,19 @@ export interface PricingModel {
}
export interface Pricing {
- components: PricingComponent[];
+ comment?: string,
+ // components: PricingComponent[];
+ flatFee?: PricingComponent,
+ energy?: PricingComponent,
+ parkingTime?: PricingComponent,
+ chargingTime?: PricingComponent,
restrictions?: PricingRestriction;
}
export interface PricingComponent {
- type: PricingDimensionType, // Type of dimension
+ // type: PricingDimensionType, // Type of dimension
price: number, // Price per unit (excluding VAT) for this tariff dimension
- stepSize: number, // Minimum amount to be billed. This unit will be billed in this step_size blocks. For example: if type is time and step_size is 300, then time will be billed in blocks of 5 minutes, so if 6 minutes is used, 10 minutes (2 blocks of step_size) will be billed.
+ stepSize?: number, // Minimum amount to be billed. This unit will be billed in this step_size blocks. For example: if type is time and step_size is 300, then time will be billed in blocks of 5 minutes, so if 6 minutes is used, 10 minutes (2 blocks of step_size) will be billed.
}
export interface PricingRestriction {
@@ -24,11 +29,11 @@ export interface PricingRestriction {
endDate?: string, // End date, for example: 2015-12-27, valid until this day (excluding this day)
minKWh?: number, // Minimum used energy in kWh, for example 20, valid from this amount of energy is used
maxKWh?: number, // Maximum used energy in kWh, for example 50, valid until this amount of energy is used
- minPowerkW: number, // Minimum power in kW, for example 0, valid from this charging speed
- maxPowerkW: number, // Maximum power in kW, for example 20, valid up to this charging speed
- minDurationSecs: number, // Minimum duration in seconds, valid for a duration from x seconds
- maxDurationSecs: number, // Maximum duration in seconds, valid for a duration up to x seconds
- daysOfWeek: DayOfWeek[], // Which day(s) of the week this tariff is valid
+ minPowerkW?: number, // Minimum power in kW, for example 0, valid from this charging speed
+ maxPowerkW?: number, // Maximum power in kW, for example 20, valid up to this charging speed
+ minDurationSecs?: number, // Minimum duration in seconds, valid for a duration from x seconds
+ maxDurationSecs?: number, // Maximum duration in seconds, valid for a duration up to x seconds
+ daysOfWeek?: DayOfWeek[], // Which day(s) of the week this tariff is valid
}
export enum DayOfWeek {
@@ -41,13 +46,29 @@ export enum DayOfWeek {
SUNDAY = 7
}
-export enum PricingDimensionType {
- ENERGY = 'E', // Defined in kWh, step_size multiplier: 1 Wh
- FLAT = 'F', // Flat fee, no unit
- PARKING_TIME = 'PT', // Time not charging: defined in hours, step_size multiplier: 1 second
- TIME = 'T', // Time charging: defined in hours, step_size multiplier: 1 second
+// export enum PricingDimensionType {
+// ENERGY = 'E', // Defined in kWh, step_size multiplier: 1 Wh
+// FLAT = 'F', // Flat fee, no unit
+// PARKING_TIME = 'PT', // Time not charging: defined in hours, step_size multiplier: 1 second
+// TIME = 'T', // Time charging: defined in hours, step_size multiplier: 1 second
+// }
+
+export interface EffectivePricing {
+ // description: string,
+ currency: string,
+ // pricingSource: EffectivePricingData;
+ flatFee?: EffectivePricingData,
+ energy?: EffectivePricingData,
+ parkingTime?: EffectivePricingData,
+ chargingTime?: EffectivePricingData,
}
+export interface EffectivePricingData {
+ amount: number;
+ quantity: number;
+ cumulatedAmount?: number;
+ roundedAmount?: number;
+}
export interface PricedConsumption {
amount: number;
cumulatedAmount: number;

MODIFY
src/utils/FeatureToggles.ts
src/utils/FeatureToggles.ts
// Declare here the list of features
export enum Feature {
BILLING_SYNC_USERS,
- BILLING_ITEM_WITH_PARKING_TIME,
BILLING_CHECK_THRESHOLD_ON_STOP,
- BILLING_PREVENT_CUSTOMER_DELETION
+ BILLING_PREVENT_CUSTOMER_DELETION,
+ PRINCING_NEW_MODEL
}
export default class FeatureToggles {
// Comment out the features that you want to switch OFF
static activeFeatures: Feature[] = [
// Feature.BILLING_SYNC_USERS, - When switched OFF the sync of the user should be implicit (LAZY mode)
- // Feature.BILLING_ITEM_WITH_PARKING_TIME,
Feature.BILLING_CHECK_THRESHOLD_ON_STOP,
- Feature.BILLING_PREVENT_CUSTOMER_DELETION
+ Feature.BILLING_PREVENT_CUSTOMER_DELETION,
+ Feature.PRINCING_NEW_MODEL
];
// Check whether the feature is active or not!

MODIFY
test/api/BillingStripeTestData.ts
test/api/BillingStripeTestData.ts
@@ -257,10 +257,17 @@ export default class StripeIntegrationTestData {
const invoiceItem:BillingInvoiceItem = {
description: `Stripe Integration - ${quantity} kWh * ${price} Eur`,
transactionID: 777,
- pricingData: {
- quantity, // kW.h
+ // pricingData: {
+ // quantity, // kW.h
+ // amount, // total amount to bill - not yet in cents
+ // currency: 'EUR'
+ // }
+ effectivePricing: {
+ currency: 'EUR',
+ energy: {
amount, // total amount to bill - not yet in cents
- currency: 'EUR'
+ quantity, // kW.h
+ }
}
};
if (taxId) {

pricing/billing - 1st draft