Files
sean-brydonGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
f4abbb2de1 feat: refactor billing to strategy implemention (#27828)
* factory and statergie

* chore: use correct method of DI

* feat: add onchagne

* add logic to HWM stat

* add webhook resolver methods to each statergy

* move seat tracking + webhooks over to own statergy

* Move to factory base approach

* move logic to correct class

* rename create -> createByTeamId

* fix: remove debug `true ||` overrides from IS_STRIPE_ENABLED and IS_TEAM_BILLING_ENABLED

Remove accidentally committed debug overrides that short-circuited
IS_STRIPE_ENABLED and IS_TEAM_BILLING_ENABLED to always be true,
bypassing Stripe credential checks. This would break self-hosted
instances without Stripe configured.

Identified by cubic (https://cubic.dev)

Co-Authored-By: unknown <>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-02-10 13:11:36 -03:00

57 lines
2.0 KiB
TypeScript

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 { SeatChangeContext } from "./ISeatBillingStrategy";
const log = logger.getSubLogger({ prefix: ["HighWaterMarkStrategy"] });
export interface IHighWaterMarkStrategyDeps {
highWaterMarkRepository: HighWaterMarkRepository;
highWaterMarkService: HighWaterMarkService;
}
export class HighWaterMarkStrategy extends BaseSeatBillingStrategy {
constructor(private readonly deps: IHighWaterMarkStrategyDeps) {
super();
}
async onSeatChange(context: SeatChangeContext): Promise<void> {
if (context.changeType !== "addition") return;
const billing = await this.deps.highWaterMarkRepository.getByTeamId(context.teamId);
if (!billing) return;
const periodStart = billing.highWaterMarkPeriodStart || billing.subscriptionStart;
if (!periodStart) return;
const result = await this.deps.highWaterMarkRepository.updateIfHigher({
teamId: context.teamId,
isOrganization: billing.isOrganization,
newSeatCount: context.membershipCount,
periodStart,
});
if (result.updated) {
log.info(`High water mark updated for team ${context.teamId}`, {
previousHighWaterMark: result.previousHighWaterMark,
newHighWaterMark: context.membershipCount,
});
}
}
override async onInvoiceUpcoming(subscriptionId: string): Promise<{ applied: boolean }> {
const applied = await this.deps.highWaterMarkService.applyHighWaterMarkToSubscription(subscriptionId);
return { applied };
}
override async onRenewalPaid(subscriptionId: string, periodStart: Date): Promise<{ reset: boolean }> {
const reset = await this.deps.highWaterMarkService.resetSubscriptionAfterRenewal({
subscriptionId,
newPeriodStart: periodStart,
});
return { reset };
}
}