From 7e73d675d78d5d8f703102b505370df5db0881d3 Mon Sep 17 00:00:00 2001 From: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Date: Mon, 23 Feb 2026 15:57:13 +0000 Subject: [PATCH] refactor: improve invoice.upcoming logging and add strategyName to billing strategies (#28143) - Add strategyName property to ISeatBillingStrategy interface and all implementations - Log which strategy handled the invoice.upcoming webhook (not just whether it applied) - Always log strategy result (not just when applied=true) for better observability - Rename highWaterMarkApplied to strategyApplied since applied covers multiple strategies Co-Authored-By: unknown <> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../ee/billing/api/webhook/_invoice.upcoming.ts | 11 ++++++----- .../seatBillingStrategy/ActiveUserBillingStrategy.ts | 5 +++-- .../seatBillingStrategy/HighWaterMarkStrategy.ts | 7 ++++--- .../seatBillingStrategy/ISeatBillingStrategy.ts | 2 ++ .../seatBillingStrategy/ImmediateUpdateStrategy.ts | 4 +++- .../seatBillingStrategy/MonthlyProrationStrategy.ts | 5 +++-- 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/packages/features/ee/billing/api/webhook/_invoice.upcoming.ts b/packages/features/ee/billing/api/webhook/_invoice.upcoming.ts index e6a55df369..9a6167227c 100644 --- a/packages/features/ee/billing/api/webhook/_invoice.upcoming.ts +++ b/packages/features/ee/billing/api/webhook/_invoice.upcoming.ts @@ -1,6 +1,5 @@ import { getSeatBillingStrategyFactory } from "@calcom/features/ee/billing/di/containers/Billing"; import logger from "@calcom/lib/logger"; - import type { SWHMap } from "./__handler"; type Data = SWHMap["invoice.upcoming"]["data"]; @@ -29,11 +28,13 @@ const handler = async (data: Data) => { const strategy = await factory.createBySubscriptionId(subscriptionId); const { applied } = await strategy.onInvoiceUpcoming(subscriptionId); - if (applied) { - log.info("Strategy applied invoice.upcoming handling", { subscriptionId }); - } + log.info("invoice.upcoming handled", { + subscriptionId, + strategy: strategy.strategyName, + applied, + }); - return { success: true, highWaterMarkApplied: applied }; + return { success: true, strategyApplied: applied }; } catch (error) { log.error("Failed to process invoice.upcoming", { subscriptionId, error }); return { success: false, error: String(error) }; diff --git a/packages/features/ee/billing/service/seatBillingStrategy/ActiveUserBillingStrategy.ts b/packages/features/ee/billing/service/seatBillingStrategy/ActiveUserBillingStrategy.ts index a6bbf87493..1f23729430 100644 --- a/packages/features/ee/billing/service/seatBillingStrategy/ActiveUserBillingStrategy.ts +++ b/packages/features/ee/billing/service/seatBillingStrategy/ActiveUserBillingStrategy.ts @@ -1,10 +1,9 @@ import logger from "@calcom/lib/logger"; - import type { ActiveUserBillingService } from "../../active-user/services/ActiveUserBillingService"; import type { ITeamBillingDataRepository } from "../../repository/teamBillingData/ITeamBillingDataRepository"; import type { IBillingProviderService } from "../billingProvider/IBillingProviderService"; -import { BaseSeatBillingStrategy } from "./ISeatBillingStrategy"; import type { SeatChangeContext } from "./ISeatBillingStrategy"; +import { BaseSeatBillingStrategy } from "./ISeatBillingStrategy"; const log = logger.getSubLogger({ prefix: ["ActiveUserBillingStrategy"] }); @@ -15,6 +14,8 @@ export interface IActiveUserBillingStrategyDeps { } export class ActiveUserBillingStrategy extends BaseSeatBillingStrategy { + readonly strategyName = "ActiveUserBilling"; + constructor(private readonly deps: IActiveUserBillingStrategyDeps) { super(); } diff --git a/packages/features/ee/billing/service/seatBillingStrategy/HighWaterMarkStrategy.ts b/packages/features/ee/billing/service/seatBillingStrategy/HighWaterMarkStrategy.ts index 4a40f86402..80d42099c8 100644 --- a/packages/features/ee/billing/service/seatBillingStrategy/HighWaterMarkStrategy.ts +++ b/packages/features/ee/billing/service/seatBillingStrategy/HighWaterMarkStrategy.ts @@ -1,9 +1,8 @@ import logger from "@calcom/lib/logger"; - -import type { HighWaterMarkService } from "../highWaterMark/HighWaterMarkService"; import type { HighWaterMarkRepository } from "../../repository/highWaterMark/HighWaterMarkRepository"; -import { BaseSeatBillingStrategy } from "./ISeatBillingStrategy"; +import type { HighWaterMarkService } from "../highWaterMark/HighWaterMarkService"; import type { SeatChangeContext } from "./ISeatBillingStrategy"; +import { BaseSeatBillingStrategy } from "./ISeatBillingStrategy"; const log = logger.getSubLogger({ prefix: ["HighWaterMarkStrategy"] }); @@ -13,6 +12,8 @@ export interface IHighWaterMarkStrategyDeps { } export class HighWaterMarkStrategy extends BaseSeatBillingStrategy { + readonly strategyName = "HighWaterMark"; + constructor(private readonly deps: IHighWaterMarkStrategyDeps) { super(); } diff --git a/packages/features/ee/billing/service/seatBillingStrategy/ISeatBillingStrategy.ts b/packages/features/ee/billing/service/seatBillingStrategy/ISeatBillingStrategy.ts index 09b5be5472..a9d0a4a3f2 100644 --- a/packages/features/ee/billing/service/seatBillingStrategy/ISeatBillingStrategy.ts +++ b/packages/features/ee/billing/service/seatBillingStrategy/ISeatBillingStrategy.ts @@ -17,6 +17,7 @@ export interface StripeInvoiceData { } export interface ISeatBillingStrategy { + readonly strategyName: string; onSeatChange(context: SeatChangeContext): Promise; onInvoiceUpcoming(subscriptionId: string): Promise<{ applied: boolean }>; onRenewalPaid(subscriptionId: string, periodStart: Date): Promise<{ reset: boolean }>; @@ -25,6 +26,7 @@ export interface ISeatBillingStrategy { } export abstract class BaseSeatBillingStrategy implements ISeatBillingStrategy { + abstract readonly strategyName: string; abstract onSeatChange(context: SeatChangeContext): Promise; async onInvoiceUpcoming(_subscriptionId: string): Promise<{ applied: boolean }> { diff --git a/packages/features/ee/billing/service/seatBillingStrategy/ImmediateUpdateStrategy.ts b/packages/features/ee/billing/service/seatBillingStrategy/ImmediateUpdateStrategy.ts index 126fbcf477..52e598af3f 100644 --- a/packages/features/ee/billing/service/seatBillingStrategy/ImmediateUpdateStrategy.ts +++ b/packages/features/ee/billing/service/seatBillingStrategy/ImmediateUpdateStrategy.ts @@ -1,8 +1,10 @@ import type { IBillingProviderService } from "../billingProvider/IBillingProviderService"; -import { BaseSeatBillingStrategy } from "./ISeatBillingStrategy"; import type { SeatChangeContext } from "./ISeatBillingStrategy"; +import { BaseSeatBillingStrategy } from "./ISeatBillingStrategy"; export class ImmediateUpdateStrategy extends BaseSeatBillingStrategy { + readonly strategyName = "ImmediateUpdate"; + constructor(private readonly billingProviderService: IBillingProviderService) { super(); } diff --git a/packages/features/ee/billing/service/seatBillingStrategy/MonthlyProrationStrategy.ts b/packages/features/ee/billing/service/seatBillingStrategy/MonthlyProrationStrategy.ts index 877e66d4d3..a1c5f0e37e 100644 --- a/packages/features/ee/billing/service/seatBillingStrategy/MonthlyProrationStrategy.ts +++ b/packages/features/ee/billing/service/seatBillingStrategy/MonthlyProrationStrategy.ts @@ -1,9 +1,8 @@ import logger from "@calcom/lib/logger"; - import { findMonthlyProrationLineItem } from "../../lib/proration-utils"; import type { MonthlyProrationService } from "../proration/MonthlyProrationService"; -import { BaseSeatBillingStrategy } from "./ISeatBillingStrategy"; import type { SeatChangeContext, StripeInvoiceData } from "./ISeatBillingStrategy"; +import { BaseSeatBillingStrategy } from "./ISeatBillingStrategy"; const log = logger.getSubLogger({ prefix: ["MonthlyProrationStrategy"] }); @@ -12,6 +11,8 @@ export interface IMonthlyProrationStrategyDeps { } export class MonthlyProrationStrategy extends BaseSeatBillingStrategy { + readonly strategyName = "MonthlyProration"; + constructor(private readonly deps: IMonthlyProrationStrategyDeps) { super(); }