diff --git a/components/ee/payment-endpoint/BUILD.yaml b/components/ee/payment-endpoint/BUILD.yaml index 46c529bd7e3626..76b26c4f0459ce 100644 --- a/components/ee/payment-endpoint/BUILD.yaml +++ b/components/ee/payment-endpoint/BUILD.yaml @@ -10,6 +10,7 @@ packages: deps: - components/gitpod-db:lib - components/gitpod-protocol:lib + - components/usage-api/typescript:lib config: packaging: offline-mirror yarnLock: ${coreYarnLockBase}/../yarn.lock @@ -24,6 +25,7 @@ packages: deps: - components/gitpod-db:lib - components/gitpod-protocol:lib + - components/usage-api/typescript:lib - :dbtest config: packaging: library @@ -54,6 +56,7 @@ packages: - components/gitpod-db:dbtest-init - components/gitpod-db:lib - components/gitpod-protocol:lib + - components/usage-api/typescript:lib config: packaging: library yarnLock: ${coreYarnLockBase}/../yarn.lock diff --git a/components/ee/payment-endpoint/package.json b/components/ee/payment-endpoint/package.json index e9ef2e2fda6d6d..a0c475bc66ef00 100644 --- a/components/ee/payment-endpoint/package.json +++ b/components/ee/payment-endpoint/package.json @@ -7,6 +7,7 @@ "dependencies": { "@gitpod/gitpod-db": "0.1.5", "@gitpod/gitpod-protocol": "0.1.5", + "@gitpod/usage-api": "0.1.5", "@octokit/rest": "18.5.6", "@octokit/webhooks": "9.17.0", "body-parser": "^1.19.2", diff --git a/components/ee/payment-endpoint/src/accounting/team-subscription-service.ts b/components/ee/payment-endpoint/src/accounting/team-subscription-service.ts index fc940186ef0a93..8a9552db689aaf 100644 --- a/components/ee/payment-endpoint/src/accounting/team-subscription-service.ts +++ b/components/ee/payment-endpoint/src/accounting/team-subscription-service.ts @@ -27,6 +27,7 @@ import { SubscriptionModel } from "./subscription-model"; import { SubscriptionService } from "./subscription-service"; import { AccountService } from "./account-service"; import { log } from "@gitpod/gitpod-protocol/lib/util/logging"; +import { UbpResetOnCancel } from "../chargebee/ubp-reset-on-cancel"; type TS = string | TeamSubscription; @@ -36,6 +37,7 @@ export class TeamSubscriptionService { @inject(AccountingDB) protected readonly accountingDb: AccountingDB; @inject(AccountService) protected readonly accountService: AccountService; @inject(SubscriptionService) protected readonly subscriptionService: SubscriptionService; + @inject(UbpResetOnCancel) protected readonly ubpResetOnCancel: UbpResetOnCancel; /** * Adds new, free slots to the given TeamSubscription @@ -453,6 +455,7 @@ export class TeamSubscriptionService { throw new Error(`Cannot find subscription for Team Subscription Slot ${slotId}!`); } model.cancel(subscription, cancellationDate, cancellationDate); + await this.ubpResetOnCancel.resetUsage(assigneeId); await this.subscriptionService.store(db, model); } diff --git a/components/ee/payment-endpoint/src/accounting/team-subscription2-service.ts b/components/ee/payment-endpoint/src/accounting/team-subscription2-service.ts index b6a7c99cc72e94..b40daf724add19 100644 --- a/components/ee/payment-endpoint/src/accounting/team-subscription2-service.ts +++ b/components/ee/payment-endpoint/src/accounting/team-subscription2-service.ts @@ -9,6 +9,7 @@ import { AssignedTeamSubscription2, Subscription } from "@gitpod/gitpod-protocol import { Plans } from "@gitpod/gitpod-protocol/lib/plans"; import { TeamSubscription2 } from "@gitpod/gitpod-protocol/lib/team-subscription-protocol"; import { inject, injectable } from "inversify"; +import { UbpResetOnCancel } from "../chargebee/ubp-reset-on-cancel"; import { SubscriptionModel } from "./subscription-model"; import { SubscriptionService } from "./subscription-service"; @@ -17,6 +18,7 @@ export class TeamSubscription2Service { @inject(TeamDB) protected readonly teamDB: TeamDB; @inject(AccountingDB) protected readonly accountingDb: AccountingDB; @inject(SubscriptionService) protected readonly subscriptionService: SubscriptionService; + @inject(UbpResetOnCancel) protected readonly ubpResetOnCancel: UbpResetOnCancel; async addAllTeamMemberSubscriptions(ts2: TeamSubscription2): Promise { const members = await this.teamDB.findMembersByTeam(ts2.teamId); @@ -102,6 +104,7 @@ export class TeamSubscription2Service { teamMembershipId: string, cancellationDate: string, ) { + await this.ubpResetOnCancel.resetUsage(userId); const model = await this.loadSubscriptionModel(db, userId); const subscription = model.findSubscriptionByTeamMembershipId(teamMembershipId); if (!subscription) { diff --git a/components/ee/payment-endpoint/src/chargebee/subscription-handler.ts b/components/ee/payment-endpoint/src/chargebee/subscription-handler.ts index 058058af8a5c88..fedaad361c27e8 100644 --- a/components/ee/payment-endpoint/src/chargebee/subscription-handler.ts +++ b/components/ee/payment-endpoint/src/chargebee/subscription-handler.ts @@ -18,6 +18,7 @@ import { formatDate } from "@gitpod/gitpod-protocol/lib/util/date-time"; import { getUpdatedAt } from "./chargebee-subscription-helper"; import { UserPaidSubscription } from "@gitpod/gitpod-protocol/lib/accounting-protocol"; import { DBSubscriptionAdditionalData } from "@gitpod/gitpod-db/lib/typeorm/entity/db-subscription"; +import { UbpResetOnCancel } from "./ubp-reset-on-cancel"; @injectable() export class SubscriptionHandler implements EventHandler { @@ -25,6 +26,7 @@ export class SubscriptionHandler implements EventHandler): boolean { if (event.event_type.startsWith("subscription")) { @@ -55,6 +57,8 @@ export class SubscriptionHandler implements EventHandler 0) { + await this.usageService.addUsageCreditNote({ + attributionId, + credits: balance, + description: "Resetting balance after chargebee subscription cancellation", + }); + } else { + log.info({ userId }, "No balance to reset", { balance, attributionId }); + } + } catch (err) { + log.error({ userId }, "Failed to reset usage balance", err); + } + } +} diff --git a/components/ee/payment-endpoint/src/config.ts b/components/ee/payment-endpoint/src/config.ts index a6dc658bc5564c..1b6e86da4cf50a 100644 --- a/components/ee/payment-endpoint/src/config.ts +++ b/components/ee/payment-endpoint/src/config.ts @@ -41,6 +41,8 @@ export GITPOD_GITHUB_APP_MKT_NAME=gitpod-draft-development-app readonly maxTeamSlotsOnCreation: number = !!process.env.TS_MAX_SLOTS_ON_CREATION ? parseInt(process.env.TS_MAX_SLOTS_ON_CREATION) : 1000; + + readonly usageServiceAddr: string = process.env.GITPOD_USAGE_SERVICE_ADDR || "usage.default.svc.cluster.local:9001"; } export interface ChargebeeWebhook { diff --git a/components/ee/payment-endpoint/src/container-module.ts b/components/ee/payment-endpoint/src/container-module.ts index 605fbee7217bde..15864aefa2d3b8 100644 --- a/components/ee/payment-endpoint/src/container-module.ts +++ b/components/ee/payment-endpoint/src/container-module.ts @@ -6,23 +6,27 @@ import { ContainerModule } from "inversify"; +import { log } from "@gitpod/gitpod-protocol/lib/util/logging"; +import { UsageServiceClient, UsageServiceDefinition } from "@gitpod/usage-api/lib/usage/v1/usage.pb"; +import { createChannel, createClient } from "nice-grpc"; +import { AccountService } from "./accounting/account-service"; +import { AccountServiceImpl } from "./accounting/account-service-impl"; +import { SubscriptionService } from "./accounting/subscription-service"; +import { TeamSubscriptionService } from "./accounting/team-subscription-service"; +import { TeamSubscription2Service } from "./accounting/team-subscription2-service"; +import { CompositeEventHandler, EventHandler } from "./chargebee/chargebee-event-handler"; import { ChargebeeProvider, ChargebeeProviderOptions } from "./chargebee/chargebee-provider"; import { EndpointController } from "./chargebee/endpoint-controller"; -import { SubscriptionService } from "./accounting/subscription-service"; -import { Config } from "./config"; -import { Server } from "./server"; import { SubscriptionHandler } from "./chargebee/subscription-handler"; -import { SubscriptionMapperFactory, SubscriptionMapper } from "./chargebee/subscription-mapper"; +import { SubscriptionMapper, SubscriptionMapperFactory } from "./chargebee/subscription-mapper"; import { TeamSubscriptionHandler } from "./chargebee/team-subscription-handler"; -import { CompositeEventHandler, EventHandler } from "./chargebee/chargebee-event-handler"; +import { UbpResetOnCancel } from "./chargebee/ubp-reset-on-cancel"; import { UpgradeHelper } from "./chargebee/upgrade-helper"; -import { TeamSubscriptionService } from "./accounting/team-subscription-service"; -import { TeamSubscription2Service } from "./accounting/team-subscription2-service"; -import { AccountService } from "./accounting/account-service"; -import { AccountServiceImpl } from "./accounting/account-service-impl"; +import { Config } from "./config"; import { GithubEndpointController } from "./github/endpoint-controller"; import { GithubSubscriptionMapper } from "./github/subscription-mapper"; import { GithubSubscriptionReconciler } from "./github/subscription-reconciler"; +import { Server } from "./server"; export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => { bind(Config).toSelf().inSingletonScope(); @@ -38,7 +42,6 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo }; }); bind(TeamSubscriptionHandler).toSelf().inSingletonScope(); - bind(CompositeEventHandler).toSelf().inSingletonScope(); bind(EventHandler).to(SubscriptionHandler).inSingletonScope(); bind(EventHandler).to(TeamSubscriptionHandler).inSingletonScope(); @@ -60,4 +63,13 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo bind(GithubEndpointController).toSelf().inSingletonScope(); bind(GithubSubscriptionMapper).toSelf().inSingletonScope(); bind(GithubSubscriptionReconciler).toSelf().inSingletonScope(); + + bind(UsageServiceDefinition.name) + .toDynamicValue((ctx) => { + const config = ctx.container.get(Config); + log.info("Connecting to usage service at", { addr: config.usageServiceAddr }); + return createClient(UsageServiceDefinition, createChannel(config.usageServiceAddr)); + }) + .inSingletonScope(); + bind(UbpResetOnCancel).toSelf().inSingletonScope(); }); diff --git a/components/licensor/typescript/ee/src/api.ts b/components/licensor/typescript/ee/src/api.ts index a2f7ac73f89fc2..6a06ca3a57dc64 100644 --- a/components/licensor/typescript/ee/src/api.ts +++ b/components/licensor/typescript/ee/src/api.ts @@ -3,7 +3,6 @@ * Licensed under the GNU Affero General Public License (AGPL). * See License.AGPL.txt in the project root for license information. */ - // generated using github.com/32leaves/bel // DO NOT MODIFY export enum Feature { @@ -14,10 +13,10 @@ export enum Feature { FeatureWorkspaceSharing = "workspace-sharing", } export interface LicenseData { - type: LicenseType; - payload: LicensePayload; - plan: LicenseSubscriptionLevel; - fallbackAllowed: boolean; + type: LicenseType + payload: LicensePayload + plan: LicenseSubscriptionLevel + fallbackAllowed: boolean } export enum LicenseLevel { @@ -25,12 +24,12 @@ export enum LicenseLevel { LevelEnterprise = 1, } export interface LicensePayload { - id: string; - domain: string; - level: LicenseLevel; - validUntil: string; - seats: number; - customerID?: string; + id: string + domain: string + level: LicenseLevel + validUntil: string + seats: number + customerID?: string } export enum LicenseSubscriptionLevel { diff --git a/components/server/src/container-module.ts b/components/server/src/container-module.ts index 8c620eb7ac816d..f1c3a5d851e3ea 100644 --- a/components/server/src/container-module.ts +++ b/components/server/src/container-module.ts @@ -114,6 +114,7 @@ import { prometheusClientMiddleware } from "@gitpod/gitpod-protocol/lib/util/nic import { UsageService, UsageServiceImpl } from "./user/usage-service"; import { OpenPrebuildPrefixContextParser } from "./workspace/open-prebuild-prefix-context-parser"; import { contentServiceBinder } from "./util/content-service-sugar"; +import { UbpResetOnCancel } from "@gitpod/gitpod-payment-endpoint/lib/chargebee/ubp-reset-on-cancel"; export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => { bind(Config).toConstantValue(ConfigFile.fromFile()); @@ -301,4 +302,5 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo bind(UsageServiceImpl).toSelf().inSingletonScope(); bind(UsageService).toService(UsageServiceImpl); + bind(UbpResetOnCancel).toSelf().inSingletonScope(); }); diff --git a/components/usage/pkg/apiv1/usage.go b/components/usage/pkg/apiv1/usage.go index 2a92bbf1b7a992..230635d8b140de 100644 --- a/components/usage/pkg/apiv1/usage.go +++ b/components/usage/pkg/apiv1/usage.go @@ -477,11 +477,6 @@ func (s *UsageService) AddUsageCreditNote(ctx context.Context, req *v1.AddUsageC return nil, status.Error(codes.InvalidArgument, "The description must not be empty.") } - userId, err := uuid.Parse(req.UserId) - if err != nil { - return nil, fmt.Errorf("The user id is not a valid UUID. %w", err) - } - usage := db.Usage{ ID: uuid.New(), AttributionID: attributionId, @@ -492,9 +487,15 @@ func (s *UsageService) AddUsageCreditNote(ctx context.Context, req *v1.AddUsageC Draft: false, } - err = usage.SetCreditNoteMetaData(db.CreditNoteMetaData{UserId: userId.String()}) - if err != nil { - return nil, err + if req.UserId != "" { + userId, err := uuid.Parse(req.UserId) + if err != nil { + return nil, fmt.Errorf("The user id is not a valid UUID. %w", err) + } + err = usage.SetCreditNoteMetaData(db.CreditNoteMetaData{UserId: userId.String()}) + if err != nil { + return nil, err + } } err = db.InsertUsage(ctx, s.conn, usage) diff --git a/install/installer/pkg/common/constants.go b/install/installer/pkg/common/constants.go index 435b7553fa613e..f2db81bf809e29 100644 --- a/install/installer/pkg/common/constants.go +++ b/install/installer/pkg/common/constants.go @@ -41,6 +41,7 @@ const ( SlowServerComponent = "slow-server" ServerInstallationAdminPort = 9000 SystemNodeCritical = "system-node-critical" + PaymentEndpointComponent = "payment-endpoint" PublicApiComponent = "public-api-server" WSManagerComponent = "ws-manager" WSManagerBridgeComponent = "ws-manager-bridge" diff --git a/install/installer/pkg/components/usage/networkpolicy.go b/install/installer/pkg/components/usage/networkpolicy.go index dbffeb75050792..f383ce0e886a45 100644 --- a/install/installer/pkg/components/usage/networkpolicy.go +++ b/install/installer/pkg/components/usage/networkpolicy.go @@ -50,6 +50,13 @@ func networkpolicy(ctx *common.RenderContext) ([]runtime.Object, error) { }, }, }, + { + PodSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "component": common.PaymentEndpointComponent, + }, + }, + }, { PodSelector: &metav1.LabelSelector{ MatchLabels: map[string]string{