fix: use personal credits if no more team credits (#27518)

* use personal credits if team has no more credits

* clean up

* adjust comment

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
This commit is contained in:
Carina Wollendorfer
2026-02-12 20:02:12 +05:30
committed by GitHub
co-authored by CarinaWolli
parent 98ec38aeaa
commit 397bf3cc0c
2 changed files with 75 additions and 7 deletions
@@ -842,6 +842,60 @@ describe("CreditService", () => {
creditType: CreditType.ADDITIONAL,
});
});
it("should fall back to user credits when team has limitReached", async () => {
vi.spyOn(CreditService.prototype, "_getTeamWithAvailableCredits").mockResolvedValue({
teamId: 1,
availableCredits: 0,
creditType: CreditType.ADDITIONAL,
limitReached: true,
});
vi.spyOn(CreditService.prototype, "_getAllCredits").mockResolvedValue({
totalMonthlyCredits: 0,
totalRemainingMonthlyCredits: 0,
additionalCredits: 50,
});
const result = await creditService.getUserOrTeamToCharge({
credits: 10,
userId: 1,
});
expect(result).toEqual({
userId: 1,
remainingCredits: 40,
creditType: CreditType.ADDITIONAL,
});
});
it("should return team when both team and user have no credits", async () => {
vi.spyOn(CreditService.prototype, "_getTeamWithAvailableCredits").mockResolvedValue({
teamId: 1,
availableCredits: 0,
creditType: CreditType.ADDITIONAL,
limitReached: true,
});
vi.spyOn(CreditService.prototype, "_getAllCredits").mockResolvedValue({
totalMonthlyCredits: 0,
totalRemainingMonthlyCredits: 0,
additionalCredits: 0,
});
const result = await creditService.getUserOrTeamToCharge({
credits: 10,
userId: 1,
});
expect(result).toEqual({
teamId: 1,
availableCredits: 0,
creditType: CreditType.ADDITIONAL,
limitReached: true,
remainingCredits: -10,
});
});
});
it("should skip unpublished platform organizations and return regular team with credits", async () => {
+21 -7
View File
@@ -1,5 +1,3 @@
import type { TFunction } from "i18next";
import dayjs from "@calcom/dayjs";
import { CreditsRepository } from "@calcom/features/credits/repositories/CreditsRepository";
import { TeamRepository } from "@calcom/features/ee/teams/repositories/TeamRepository";
@@ -7,9 +5,9 @@ import { MembershipRepository } from "@calcom/features/membership/repositories/M
import { IS_SMS_CREDITS_ENABLED } from "@calcom/lib/constants";
import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId";
import logger from "@calcom/lib/logger";
import { prisma, type PrismaTransaction } from "@calcom/prisma";
import { CreditUsageType, CreditType } from "@calcom/prisma/enums";
import { type PrismaTransaction, prisma } from "@calcom/prisma";
import { CreditType, CreditUsageType } from "@calcom/prisma/enums";
import type { TFunction } from "i18next";
import { getBillingProviderService, getTeamBillingServiceFactory } from "./di/containers/Billing";
import { SubscriptionStatus } from "./repository/billing/IBillingRepository";
@@ -317,7 +315,7 @@ export class CreditService {
}
/*
always returns a team, even if all teams are out of credits
always returns a team or user, even if out of credits
*/
async getUserOrTeamToCharge({
credits,
@@ -357,11 +355,27 @@ export class CreditService {
if (userId) {
const team = await this._getTeamWithAvailableCredits({ userId, tx });
if (team) {
const teamHasCredits = team && !team.limitReached;
if (teamHasCredits) {
return { ...team, remainingCredits: team.availableCredits - credits };
}
const userCredits = await this._getAllCredits({ userId, tx });
const userHasCredits = userCredits.additionalCredits > 0;
if (userHasCredits) {
return {
userId,
remainingCredits: userCredits.additionalCredits - credits,
creditType: CreditType.ADDITIONAL,
};
}
if (team) {
return { ...team, remainingCredits: team.availableCredits - credits };
}
return {
userId,