Refactor metered plan handling and add new telemetry plan

This commit is contained in:
Simon Larsen
2023-12-22 17:00:39 +00:00
parent 8f8c443a72
commit e08d2bb8eb
7 changed files with 83 additions and 68 deletions

View File

@@ -115,7 +115,7 @@ export class BillingService extends BaseService {
public async subscribeToMeteredPlan(data: {
projectId: ObjectID;
customerId: string;
serverMeteredPlans: Array<typeof ServerMeteredPlan> ;
serverMeteredPlans: Array<ServerMeteredPlan> ;
trialDate: Date | null;
defaultPaymentMethodId?: string | undefined;
promoCode?: string | undefined;
@@ -127,9 +127,9 @@ export class BillingService extends BaseService {
customer: data.customerId,
items: data.serverMeteredPlans.map(
(item: typeof ServerMeteredPlan) => {
(item: ServerMeteredPlan) => {
return {
price: item.getMeteredPlan()?.getPriceId(),
price: item.getPriceId(),
};
}
),
@@ -195,7 +195,7 @@ export class BillingService extends BaseService {
public async subscribeToPlan(data: {
projectId: ObjectID;
customerId: string;
serverMeteredPlans: Array<typeof ServerMeteredPlan>;
serverMeteredPlans: Array<ServerMeteredPlan>;
plan: SubscriptionPlan;
quantity: number;
isYearly: boolean;
@@ -313,7 +313,7 @@ export class BillingService extends BaseService {
public async addOrUpdateMeteredPricingOnSubscription(
subscriptionId: string,
meteredPlan: MeteredPlan,
serverMeteredPlan: ServerMeteredPlan,
quantity: number
): Promise<void> {
if (!this.isBillingEnabled()) {
@@ -337,7 +337,7 @@ export class BillingService extends BaseService {
const pricingExists: boolean = subscription.items.data.some(
(item: SubscriptionItem) => {
return item.price?.id === meteredPlan.getPriceId();
return item.price?.id === serverMeteredPlan.getPriceId();
}
);
@@ -345,7 +345,7 @@ export class BillingService extends BaseService {
// update the quantity.
const subscriptionItemId: string | undefined =
subscription.items.data.find((item: SubscriptionItem) => {
return item.price?.id === meteredPlan.getPriceId();
return item.price?.id === serverMeteredPlan.getPriceId();
})?.id;
if (!subscriptionItemId) {
@@ -366,7 +366,7 @@ export class BillingService extends BaseService {
const subscriptionItem: SubscriptionItem =
await this.stripe.subscriptionItems.create({
subscription: subscriptionId,
price: meteredPlan.getPriceId(),
price: serverMeteredPlan.getPriceId(),
});
// use stripe usage based api to update the quantity.
@@ -470,7 +470,7 @@ export class BillingService extends BaseService {
projectId: ObjectID;
subscriptionId: string;
meteredSubscriptionId: string;
serverMeteredPlans: Array<typeof ServerMeteredPlan>;
serverMeteredPlans: Array<ServerMeteredPlan>;
newPlan: SubscriptionPlan;
quantity: number;
isYearly: boolean;

View File

@@ -641,11 +641,11 @@ describe('BillingService', () => {
it('should throw if billing is not enabled', async () => {
const subscriptionItem: SubscriptionItem | undefined =
mockSubscription.items.data[0];
const meteredPlan: MeteredPlan = new MeteredPlan(
subscriptionItem?.price?.id || '',
100,
'unit'
);
const meteredPlan: MeteredPlan = new MeteredPlan({
priceId: subscriptionItem?.price?.id || '',
pricePerUnitInUSD: 100,
unitName: 'unit'
});
billingService = mockIsBillingEnabled(false);
@@ -661,11 +661,11 @@ describe('BillingService', () => {
it('should successfully add metered pricing to a subscription', async () => {
const subscriptionItem: SubscriptionItem | undefined =
mockSubscription.items.data[0];
const meteredPlan: MeteredPlan = new MeteredPlan(
subscriptionItem?.price?.id || '',
100,
'unit'
);
const meteredPlan: MeteredPlan = new MeteredPlan({
priceId: subscriptionItem?.price?.id || '',
pricePerUnitInUSD: 100,
unitName: 'unit'
});
mockSubscription.items.data = [];
@@ -702,11 +702,11 @@ describe('BillingService', () => {
it('should successfully update existing metered pricing on a subscription', async () => {
const subscriptionItem: SubscriptionItem | undefined =
mockSubscription.items.data[0];
const meteredPlan: MeteredPlan = new MeteredPlan(
subscriptionItem?.price?.id || '',
100,
'unit'
);
const meteredPlan: MeteredPlan = new MeteredPlan({
priceId: subscriptionItem?.price?.id || '',
pricePerUnitInUSD: 100,
unitName: 'unit'
});
mockStripe.subscriptions.retrieve = jest
.fn()
@@ -734,11 +734,11 @@ describe('BillingService', () => {
it('should handle non-existent subscription', async () => {
const subscriptionItem: SubscriptionItem | undefined =
mockSubscription.items.data[0];
const meteredPlan: MeteredPlan = new MeteredPlan(
subscriptionItem?.price?.id || '',
100,
'unit'
);
const meteredPlan: MeteredPlan = new MeteredPlan({
priceId: subscriptionItem?.price?.id || '',
pricePerUnitInUSD: 100,
unitName: 'unit'
});
const subscriptionId: string = 'sub_nonexistent';
mockStripe.subscriptions.retrieve = jest

View File

@@ -22,7 +22,7 @@ export type CouponData = {
export type Subscription = {
projectId: ObjectID;
customerId: string;
serverMeteredPlans: Array<typeof ServerMeteredPlan>;
serverMeteredPlans: Array<ServerMeteredPlan>;
promoCode?: string;
defaultPaymentMethodId?: string;
trialDate: Date;
@@ -31,7 +31,7 @@ export type Subscription = {
export type MeteredSubscription = {
projectId: ObjectID;
customerId: string;
serverMeteredPlans: Array<typeof ServerMeteredPlan>;
serverMeteredPlans: Array<ServerMeteredPlan>;
plan: SubscriptionPlan;
quantity: number;
isYearly: boolean;
@@ -44,7 +44,7 @@ export type ChangePlan = {
projectId: ObjectID;
subscriptionId: string;
meteredSubscriptionId: string;
serverMeteredPlans: Array<typeof ServerMeteredPlan>;
serverMeteredPlans: Array<ServerMeteredPlan>;
newPlan: SubscriptionPlan;
quantity: number;
isYearly: boolean;

View File

@@ -1,5 +1,3 @@
import MeteredPlan from 'Common/Types/Billing/MeteredPlan';
import MeteredPlanUtil from './MeteredPlanUtil';
import ServerMeteredPlan from './ServerMeteredPlan';
import ObjectID from 'Common/Types/ObjectID';
import MonitorService from '../../../Services/MonitorService';
@@ -7,18 +5,16 @@ import QueryHelper from '../../Database/QueryHelper';
import MonitorType from 'Common/Types/Monitor/MonitorType';
import PositiveNumber from 'Common/Types/PositiveNumber';
import ProjectService from '../../../Services/ProjectService';
import BillingService from '../../../Services/BillingService';
import BillingService, { MeteredPlanName } from '../../../Services/BillingService';
import Project from 'Model/Models/Project';
export default class ActiveMonitoringMeteredPlan extends ServerMeteredPlan {
public static override getMeteredPlan(): MeteredPlan {
const meteredPlan: MeteredPlan =
MeteredPlanUtil.getMeteredPlan('ACTIVE_MONITORING');
this.meteredPlan = meteredPlan;
return meteredPlan;
public override getMeteredPlanName(): MeteredPlanName {
return MeteredPlanName.ActiveMonitoring;
}
public static override async reportQuantityToBillingProvider(
public override async reportQuantityToBillingProvider(
projectId: ObjectID,
options?: {
meteredPlanSubscriptionId?: string | undefined;
@@ -66,7 +62,7 @@ export default class ActiveMonitoringMeteredPlan extends ServerMeteredPlan {
await BillingService.addOrUpdateMeteredPricingOnSubscription(
(options?.meteredPlanSubscriptionId as string) ||
(project.paymentProviderMeteredSubscriptionId as string),
ActiveMonitoringMeteredPlan.getMeteredPlan(),
this,
count.toNumber()
);
}

View File

@@ -1,10 +1,13 @@
import ActiveMonitoringMeteredPlan from './ActiveMonitoringMeteredPlan';
import LogsDataIngestMeteredPlan from './LogsDataIngestMeteredPlan';
import ServerMeteredPlan from './ServerMeteredPlan';
import TelemetryMeteredPlan from './TelemetryMeteredPlan';
import { ProductType } from 'Model/Models/UsageBilling';
const AllMeteredPlans: Array<typeof ServerMeteredPlan> = [
ActiveMonitoringMeteredPlan,
LogsDataIngestMeteredPlan,
const AllMeteredPlans: Array<ServerMeteredPlan> = [
new ActiveMonitoringMeteredPlan(),
new TelemetryMeteredPlan(ProductType.Logs),
new TelemetryMeteredPlan(ProductType.Metrics),
new TelemetryMeteredPlan(ProductType.Traces),
];
export default AllMeteredPlans;

View File

@@ -1,20 +1,19 @@
import MeteredPlan from 'Common/Types/Billing/MeteredPlan';
import BadDataException from 'Common/Types/Exception/BadDataException';
import NotImplementedException from 'Common/Types/Exception/NotImplementedException';
import ObjectID from 'Common/Types/ObjectID';
import BillingService, { MeteredPlanName } from '../../../Services/BillingService';
import MeteredPlan from 'Common/Types/Billing/MeteredPlan';
export default class ServerMeteredPlan {
public static meteredPlan: MeteredPlan | undefined = undefined;
public static getMeteredPlan(): MeteredPlan {
if (!this.meteredPlan) {
throw new BadDataException('Metered plan not found');
}
return this.meteredPlan;
public getMeteredPlanName(): MeteredPlanName {
throw new NotImplementedException();
}
public static async reportQuantityToBillingProvider(
public getMeteredPlan(projectId: ObjectID): MeteredPlan {
throw new NotImplementedException();
}
public async reportQuantityToBillingProvider(
_projectId: ObjectID,
_options: {
meteredPlanSubscriptionId?: string | undefined;
@@ -22,4 +21,8 @@ export default class ServerMeteredPlan {
): Promise<void> {
throw new NotImplementedException();
}
public getPriceId(): string {
return BillingService.getMeteredPlanPriceId(this.getMeteredPlanName());
}
}

View File

@@ -1,23 +1,36 @@
import MeteredPlan from 'Common/Types/Billing/MeteredPlan';
import MeteredPlanUtil from './MeteredPlanUtil';
import ServerMeteredPlan from './ServerMeteredPlan';
import ObjectID from 'Common/Types/ObjectID';
import ProjectService from '../../../Services/ProjectService';
import BillingService from '../../../Services/BillingService';
import BillingService, { MeteredPlanName } from '../../../Services/BillingService';
import Project from 'Model/Models/Project';
import UsageBilling, { ProductType } from 'Model/Models/UsageBilling';
import UsageBillingService from '../../../Services/UsageBillingService';
import OneUptimeDate from 'Common/Types/Date';
export default class LogsDataIngestMeteredPlan extends ServerMeteredPlan {
public static override getMeteredPlan(): MeteredPlan {
const meteredPlan: MeteredPlan =
MeteredPlanUtil.getMeteredPlan('LOGS_DATA_INGEST');
this.meteredPlan = meteredPlan;
return meteredPlan;
export default class TelemetryMeteredPlan extends ServerMeteredPlan {
private _prodyctType!: ProductType;
public get prodyctType() : ProductType {
return this._prodyctType;
}
public set prodyctType(v : ProductType) {
this._prodyctType = v;
}
public constructor(productType: ProductType) {
super();
this.prodyctType = productType;
}
public static override async reportQuantityToBillingProvider(
public override getMeteredPlanName(): MeteredPlanName {
return MeteredPlanName.LogsDataIngestion;
}
public override async reportQuantityToBillingProvider(
projectId: ObjectID,
options?: {
meteredPlanSubscriptionId?: string | undefined;
@@ -62,7 +75,7 @@ export default class LogsDataIngestMeteredPlan extends ServerMeteredPlan {
await BillingService.addOrUpdateMeteredPricingOnSubscription(
(options?.meteredPlanSubscriptionId as string) ||
(project.paymentProviderMeteredSubscriptionId as string),
LogsDataIngestMeteredPlan.getMeteredPlan(),
this,
usageBilling.usageCount
);