fix: adjust free monthly credits for orgs (#23798)

* add org monthly credits env variable

* use fallback for env variable

* remove comment

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
This commit is contained in:
Carina Wollendorfer
2025-09-12 17:47:13 +00:00
committed by GitHub
co-authored by CarinaWolli
parent a62c74f79e
commit 2be388373f
4 changed files with 38 additions and 20 deletions
+1
View File
@@ -203,6 +203,7 @@ NEXT_PUBLIC_IS_PREMIUM_NEW_PLAN=0
NEXT_PUBLIC_STRIPE_PREMIUM_NEW_PLAN_PRICE=
STRIPE_TEAM_MONTHLY_PRICE_ID=
NEXT_PUBLIC_STRIPE_CREDITS_PRICE_ID=
ORG_MONTHLY_CREDITS=
STRIPE_TEAM_PRODUCT_ID=
# It is a price ID in the product with id STRIPE_ORG_PRODUCT_ID
STRIPE_ORG_MONTHLY_PRICE_ID=
@@ -435,8 +435,8 @@ describe("CreditService", () => {
expect(result).toBe(1500); // (3 members * 1000 price) / 2
});
it("should calculate credits with 20% multiplier for organizations", async () => {
vi.stubEnv("STRIPE_ORG_MONTHLY_PRICE_ID", "price_org_monthly");
it("should calculate credits for organizations using ORG_MONTHLY_CREDITS", async () => {
vi.stubEnv("ORG_MONTHLY_CREDITS", "1500");
const mockTeamRepo = {
findTeamWithMembers: vi.fn().mockResolvedValue({
id: 1,
@@ -453,15 +453,31 @@ describe("CreditService", () => {
mockTeamBillingService.getSubscriptionStatus
);
const mockStripeBillingService = {
getPrice: vi.fn().mockResolvedValue({ unit_amount: 3700 }),
const result = await creditService.getMonthlyCredits(1);
expect(result).toBe(3000); // 2 members * 1500 credits per seat
});
it("should calculate credits for organizations with default 1000 credits per seat", async () => {
// Clear ORG_MONTHLY_CREDITS to test default behavior
vi.stubEnv("ORG_MONTHLY_CREDITS", undefined);
const mockTeamRepo = {
findTeamWithMembers: vi.fn().mockResolvedValue({
id: 1,
isOrganization: true,
members: [{ accepted: true }, { accepted: true }, { accepted: true }],
}),
};
vi.spyOn(StripeBillingService.prototype, "getPrice").mockImplementation(
mockStripeBillingService.getPrice
vi.mocked(TeamRepository).mockImplementation(() => mockTeamRepo as any);
const mockTeamBillingService = {
getSubscriptionStatus: vi.fn().mockResolvedValue("active"),
};
vi.spyOn(InternalTeamBilling.prototype, "getSubscriptionStatus").mockImplementation(
mockTeamBillingService.getSubscriptionStatus
);
const result = await creditService.getMonthlyCredits(1);
expect(result).toBe(1480); // (2 members * 3700 price) * 0.2
expect(result).toBe(3000); // 3 members * 1000 credits per seat (default)
});
});
+11 -13
View File
@@ -588,8 +588,6 @@ export class CreditService {
if (!team) return 0;
let totalMonthlyCredits = 0;
const teamBillingService = new InternalTeamBilling(team);
const subscriptionStatus = await teamBillingService.getSubscriptionStatus();
@@ -599,25 +597,25 @@ export class CreditService {
const activeMembers = team.members.filter((member) => member.accepted).length;
const billingService = new StripeBillingService();
if (team.isOrganization) {
const orgMonthlyCredits = process.env.ORG_MONTHLY_CREDITS;
const creditsPerSeat = orgMonthlyCredits ? parseInt(orgMonthlyCredits) : 1000;
return activeMembers * creditsPerSeat;
}
const priceId = team.isOrganization
? process.env.STRIPE_ORG_MONTHLY_PRICE_ID
: process.env.STRIPE_TEAM_MONTHLY_PRICE_ID;
const billingService = new StripeBillingService();
const priceId = process.env.STRIPE_TEAM_MONTHLY_PRICE_ID;
if (!priceId) {
log.warn("Monthly price ID not configured", { teamId, isOrganization: team.isOrganization });
log.warn("Monthly price ID not configured", { teamId });
return 0;
}
const monthlyPrice = await billingService.getPrice(priceId || "");
const monthlyPrice = await billingService.getPrice(priceId);
const pricePerSeat = monthlyPrice.unit_amount ?? 0;
const creditsPerSeat = pricePerSeat * 0.5;
// Teams get 50% of the price as credits, organizations get 20%
const creditMultiplier = team.isOrganization ? 0.2 : 0.5;
totalMonthlyCredits = activeMembers * pricePerSeat * creditMultiplier;
return totalMonthlyCredits;
return activeMembers * creditsPerSeat;
}
calculateCreditsFromPrice(price: number) {
+3
View File
@@ -120,6 +120,7 @@
"NEXT_PUBLIC_STRIPE_PREMIUM_PLAN_PRICE_MONTHLY",
"NEXT_PUBLIC_STRIPE_PRICING_TABLE_PUBLISHABLE_KEY",
"NEXT_PUBLIC_STRIPE_CREDITS_PRICE_ID",
"ORG_MONTHLY_CREDITS",
"NEXT_PUBLIC_PLAIN_CHAT_ID",
"NEXT_PUBLIC_PLAIN_CHAT_EXCLUDED_PATHS",
"NEXT_PUBLIC_BOOKER_NUMBER_OF_DAYS_TO_LOAD",
@@ -187,6 +188,7 @@
"STRIPE_TEAM_MONTHLY_PRICE_ID",
"STRIPE_TEAM_PRODUCT_ID",
"STRIPE_ORG_MONTHLY_PRICE_ID",
"ORG_MONTHLY_CREDITS",
"TANDEM_BASE_URL",
"TANDEM_CLIENT_ID",
"TANDEM_CLIENT_SECRET",
@@ -331,6 +333,7 @@
"STRIPE_TEAM_MONTHLY_PRICE_ID",
"NEXT_PUBLIC_STRIPE_CREDITS_PRICE_ID",
"STRIPE_TEAM_PRODUCT_ID",
"ORG_MONTHLY_CREDITS",
"STRIPE_ORG_MONTHLY_PRICE_ID",
"STRIPE_ORG_PRODUCT_ID",
"NEXT_PUBLIC_API_V2_URL",