From d653f9199cf00f452c68fdea1504d380683a3741 Mon Sep 17 00:00:00 2001 From: Gero Posmyk-Leinemann Date: Wed, 9 Nov 2022 12:57:12 +0000 Subject: [PATCH] [server] Fix UBP free tier edge case + tests --- .../ee/src/billing/billing-mode.spec.db.ts | 104 ++++-------------- .../server/ee/src/billing/billing-mode.ts | 4 +- 2 files changed, 24 insertions(+), 84 deletions(-) diff --git a/components/server/ee/src/billing/billing-mode.spec.db.ts b/components/server/ee/src/billing/billing-mode.spec.db.ts index 082253fbbaaefa..8bcf05283a1c7f 100644 --- a/components/server/ee/src/billing/billing-mode.spec.db.ts +++ b/components/server/ee/src/billing/billing-mode.spec.db.ts @@ -40,95 +40,25 @@ import Stripe from "stripe"; import { Config } from "../../../src/config"; import { BillingModes, BillingModesImpl } from "./billing-mode"; import * as deepEqualInAnyOrder from "deep-equal-in-any-order"; -import { - UsageServiceDefinition, - UsageServiceClient, - GetCostCenterResponse, - CostCenter_BillingStrategy, - GetBalanceResponse, - SetCostCenterResponse, - ReconcileUsageResponse, - ListUsageRequest_Ordering, - ListUsageResponse, - ResetUsageResponse, - ResetUsageRequest, -} from "@gitpod/usage-api/lib/usage/v1/usage.pb"; -import { CallOptions } from "nice-grpc-common"; +import { CostCenter_BillingStrategy } from "@gitpod/usage-api/lib/usage/v1/usage.pb"; +import { UsageService } from "../../../src/user/usage-service"; chai.use(deepEqualInAnyOrder); const expect = chai.expect; type StripeSubscription = Pick & { customer: string }; -class UsageServiceClientMock implements UsageServiceClient { +class UsageServiceMock implements UsageService { constructor(protected readonly subscription?: StripeSubscription) {} - async getCostCenter( - request: { attributionId?: string | undefined }, - options?: CallOptions | undefined, - ): Promise { - if (!request.attributionId) { - return { costCenter: undefined }; - } + async getCurrentBalance(attributionId: AttributionId): Promise<{ usedCredits: number; usageLimit: number }> { + throw new Error("Mock: not implemented"); + } + async getCurrentBillingStategy(attributionId: AttributionId): Promise { let billingStrategy = CostCenter_BillingStrategy.BILLING_STRATEGY_OTHER; if (this.subscription !== undefined) { billingStrategy = CostCenter_BillingStrategy.BILLING_STRATEGY_STRIPE; } - - return { - costCenter: { - attributionId: request.attributionId, - billingStrategy, - nextBillingTime: new Date(), // does not matter here. - spendingLimit: 1234, - }, - }; - } - - async getBalance( - request: { attributionId?: string | undefined }, - options?: CallOptions | undefined, - ): Promise { - throw new Error("Mock: not implemented"); - } - - async setCostCenter( - request: { - costCenter?: - | { - attributionId?: string | undefined; - spendingLimit?: number | undefined; - billingStrategy?: CostCenter_BillingStrategy | undefined; - nextBillingTime?: Date | undefined; - } - | undefined; - }, - options?: CallOptions | undefined, - ): Promise { - throw new Error("Mock: not implemented"); - } - - async reconcileUsage( - request: { from?: Date | undefined; to?: Date | undefined }, - options?: CallOptions | undefined, - ): Promise { - throw new Error("Mock: not implemented"); - } - - async listUsage( - request: { - attributionId?: string | undefined; - from?: Date | undefined; - to?: Date | undefined; - order?: ListUsageRequest_Ordering | undefined; - pagination?: { perPage?: number | undefined; page?: number | undefined } | undefined; - }, - options?: CallOptions | undefined, - ): Promise { - throw new Error("Mock: not implemented"); - } - - async resetUsage(request: ResetUsageRequest, options?: CallOptions | undefined): Promise { - throw new Error("Mock: not implemented"); + return billingStrategy; } } @@ -366,6 +296,18 @@ class BillingModeSpec { mode: "usage-based", }, }, + { + name: "user: UBP free tier, chargebee paid personal (inactive)", + subject: user(), + config: { + enablePayment: true, + usageBasedPricingEnabled: true, + subscriptions: [subscription(Plans.PERSONAL_EUR, cancellationDate, cancellationDate)], + }, + expectation: { + mode: "usage-based", + }, + }, { name: "user: stripe paid, chargebee paid personal (inactive) + team seat (inactive)", subject: user(), @@ -451,6 +393,7 @@ class BillingModeSpec { expectation: { mode: "chargebee", paid: true, + teamNames: ["team-123"], }, }, { @@ -479,7 +422,6 @@ class BillingModeSpec { expectation: { mode: "chargebee", canUpgradeToUBB: true, - teamNames: ["team-123"], }, }, { @@ -558,9 +500,7 @@ class BillingModeSpec { bind(SubscriptionService).toSelf().inSingletonScope(); bind(BillingModes).to(BillingModesImpl).inSingletonScope(); - bind(UsageServiceDefinition.name).toConstantValue( - new UsageServiceClientMock(test.config.stripeSubscription), - ); + bind(UsageService).toConstantValue(new UsageServiceMock(test.config.stripeSubscription)); bind(ConfigCatClientFactory).toConstantValue( () => new ConfigCatClientMock({ diff --git a/components/server/ee/src/billing/billing-mode.ts b/components/server/ee/src/billing/billing-mode.ts index dd020dae4763cd..2aadd4fb382211 100644 --- a/components/server/ee/src/billing/billing-mode.ts +++ b/components/server/ee/src/billing/billing-mode.ts @@ -125,7 +125,7 @@ export class BillingModesImpl implements BillingModes { await this.teamSubscriptionDb.findTeamSubscriptions({ userId: user.id }) ).filter((ts) => TeamSubscription.isActive(ts, now.toISOString())); - let canUpgradeToUBB = false; + let canUpgradeToUBB: boolean | undefined = undefined; if (cbPersonalSubscriptions.length > 0) { if (cbPersonalSubscriptions.every((s) => Subscription.isCancelled(s, now.toISOString()))) { // The user has one or more paid subscriptions, but all of them have already been cancelled @@ -168,7 +168,7 @@ export class BillingModesImpl implements BillingModes { // UBB is greedy: once a user has at least a paid team membership, they should benefit from it! return usageBased(); } - if (hasCbPaidTeamMembership || hasCbPaidTeamSeat || canUpgradeToUBB) { + if (hasCbPaidTeamMembership || hasCbPaidTeamSeat) { // Q: How to test the free-tier, then? A: Make sure you have no CB paid seats anymore // For that we lists all Team Subscriptions/Team Memberships that are "blocking" you, and display them in the UI somewhere. const result: BillingMode = { mode: "chargebee", canUpgradeToUBB }; // UBB is enabled, but no seat nor subscription yet.