Skip to content

Commit 1ba9010

Browse files
committed
[ubp] reset usage on chargebee cancellation
1 parent cd48d91 commit 1ba9010

File tree

11 files changed

+95
-21
lines changed

11 files changed

+95
-21
lines changed

components/ee/payment-endpoint/BUILD.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ packages:
1010
deps:
1111
- components/gitpod-db:lib
1212
- components/gitpod-protocol:lib
13+
- components/usage-api/typescript:lib
1314
config:
1415
packaging: offline-mirror
1516
yarnLock: ${coreYarnLockBase}/../yarn.lock
@@ -24,6 +25,7 @@ packages:
2425
deps:
2526
- components/gitpod-db:lib
2627
- components/gitpod-protocol:lib
28+
- components/usage-api/typescript:lib
2729
- :dbtest
2830
config:
2931
packaging: library
@@ -54,6 +56,7 @@ packages:
5456
- components/gitpod-db:dbtest-init
5557
- components/gitpod-db:lib
5658
- components/gitpod-protocol:lib
59+
- components/usage-api/typescript:lib
5760
config:
5861
packaging: library
5962
yarnLock: ${coreYarnLockBase}/../yarn.lock

components/ee/payment-endpoint/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"dependencies": {
88
"@gitpod/gitpod-db": "0.1.5",
99
"@gitpod/gitpod-protocol": "0.1.5",
10+
"@gitpod/usage-api": "0.1.5",
1011
"@octokit/rest": "18.5.6",
1112
"@octokit/webhooks": "9.17.0",
1213
"body-parser": "^1.19.2",

components/ee/payment-endpoint/src/accounting/team-subscription-service.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { SubscriptionModel } from "./subscription-model";
2727
import { SubscriptionService } from "./subscription-service";
2828
import { AccountService } from "./account-service";
2929
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
30+
import { UbpResetOnCancel } from "../chargebee/ubp-reset-on-cancel";
3031

3132
type TS = string | TeamSubscription;
3233

@@ -36,6 +37,7 @@ export class TeamSubscriptionService {
3637
@inject(AccountingDB) protected readonly accountingDb: AccountingDB;
3738
@inject(AccountService) protected readonly accountService: AccountService;
3839
@inject(SubscriptionService) protected readonly subscriptionService: SubscriptionService;
40+
@inject(UbpResetOnCancel) protected readonly ubpResetOnCancel: UbpResetOnCancel;
3941

4042
/**
4143
* Adds new, free slots to the given TeamSubscription
@@ -178,6 +180,7 @@ export class TeamSubscriptionService {
178180
// Cancel assignee's subscription (if present)
179181
if (state === "assigned") {
180182
const assignedSlot = slot as TeamSubscriptionSlotAssigned;
183+
await this.ubpResetOnCancel.resetUsage(assignedSlot.assigneeId);
181184
await this.cancelSubscription(db, assignedSlot.assigneeId, ts.planId, slot.id, now);
182185
}
183186

@@ -453,6 +456,7 @@ export class TeamSubscriptionService {
453456
throw new Error(`Cannot find subscription for Team Subscription Slot ${slotId}!`);
454457
}
455458
model.cancel(subscription, cancellationDate, cancellationDate);
459+
await this.ubpResetOnCancel.resetUsage(assigneeId);
456460
await this.subscriptionService.store(db, model);
457461
}
458462

components/ee/payment-endpoint/src/accounting/team-subscription2-service.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { AssignedTeamSubscription2, Subscription } from "@gitpod/gitpod-protocol
99
import { Plans } from "@gitpod/gitpod-protocol/lib/plans";
1010
import { TeamSubscription2 } from "@gitpod/gitpod-protocol/lib/team-subscription-protocol";
1111
import { inject, injectable } from "inversify";
12+
import { UbpResetOnCancel } from "../chargebee/ubp-reset-on-cancel";
1213
import { SubscriptionModel } from "./subscription-model";
1314
import { SubscriptionService } from "./subscription-service";
1415

@@ -17,6 +18,7 @@ export class TeamSubscription2Service {
1718
@inject(TeamDB) protected readonly teamDB: TeamDB;
1819
@inject(AccountingDB) protected readonly accountingDb: AccountingDB;
1920
@inject(SubscriptionService) protected readonly subscriptionService: SubscriptionService;
21+
@inject(UbpResetOnCancel) protected readonly ubpResetOnCancel: UbpResetOnCancel;
2022

2123
async addAllTeamMemberSubscriptions(ts2: TeamSubscription2): Promise<void> {
2224
const members = await this.teamDB.findMembersByTeam(ts2.teamId);
@@ -102,6 +104,7 @@ export class TeamSubscription2Service {
102104
teamMembershipId: string,
103105
cancellationDate: string,
104106
) {
107+
await this.ubpResetOnCancel.resetUsage(userId);
105108
const model = await this.loadSubscriptionModel(db, userId);
106109
const subscription = model.findSubscriptionByTeamMembershipId(teamMembershipId);
107110
if (!subscription) {

components/ee/payment-endpoint/src/chargebee/subscription-handler.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ import { formatDate } from "@gitpod/gitpod-protocol/lib/util/date-time";
1818
import { getUpdatedAt } from "./chargebee-subscription-helper";
1919
import { UserPaidSubscription } from "@gitpod/gitpod-protocol/lib/accounting-protocol";
2020
import { DBSubscriptionAdditionalData } from "@gitpod/gitpod-db/lib/typeorm/entity/db-subscription";
21+
import { UbpResetOnCancel } from "./ubp-reset-on-cancel";
2122

2223
@injectable()
2324
export class SubscriptionHandler implements EventHandler<chargebee.SubscriptionEventV2> {
2425
@inject(SubscriptionService) protected readonly subscriptionService: SubscriptionService;
2526
@inject(AccountingDB) protected readonly db: AccountingDB;
2627
@inject(SubscriptionMapperFactory) protected readonly mapperFactory: SubscriptionMapperFactory;
2728
@inject(UpgradeHelper) protected readonly upgradeHelper: UpgradeHelper;
29+
@inject(UbpResetOnCancel) protected readonly ubpResetOnCancel: UbpResetOnCancel;
2830

2931
canHandle(event: chargebee.Event<any>): boolean {
3032
if (event.event_type.startsWith("subscription")) {
@@ -55,6 +57,8 @@ export class SubscriptionHandler implements EventHandler<chargebee.SubscriptionE
5557

5658
if (event.event_type === "subscription_changed") {
5759
await this.checkAndChargeForUpgrade(userId, chargebeeSubscription);
60+
} else if (event.event_type === "subscription_cancelled") {
61+
await this.ubpResetOnCancel.resetUsage(userId);
5862
}
5963

6064
await this.mapToGitpodSubscription(userId, event);
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* Copyright (c) 2022 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { AttributionId } from "@gitpod/gitpod-protocol/lib/attribution";
8+
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
9+
import { UsageServiceClient, UsageServiceDefinition } from "@gitpod/usage-api/lib/usage/v1/usage.pb";
10+
import { inject, injectable } from "inversify";
11+
12+
injectable();
13+
export class UbpResetOnCancel {
14+
@inject(UsageServiceDefinition.name) protected readonly usageService: UsageServiceClient;
15+
16+
async resetUsage(userId: string) {
17+
try {
18+
const attributionId = AttributionId.render({ kind: "user", userId: userId });
19+
const balanceResponse = await this.usageService.getBalance({ attributionId });
20+
const balance = balanceResponse.credits;
21+
log.info({ userId }, "Chargbee subscription cancelled, adding credit note for remaining balance", {
22+
balance,
23+
attributionId,
24+
});
25+
if (balance > 0) {
26+
await this.usageService.addUsageCreditNote({
27+
attributionId,
28+
credits: balance,
29+
description: "Resetting balance after chargebee subscription cancellation",
30+
});
31+
} else {
32+
log.info({ userId }, "No balance to reset", { balance, attributionId });
33+
}
34+
} catch (err) {
35+
log.error({ userId }, "Failed to reset usage balance", err);
36+
}
37+
}
38+
}

components/ee/payment-endpoint/src/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ export GITPOD_GITHUB_APP_MKT_NAME=gitpod-draft-development-app
4141
readonly maxTeamSlotsOnCreation: number = !!process.env.TS_MAX_SLOTS_ON_CREATION
4242
? parseInt(process.env.TS_MAX_SLOTS_ON_CREATION)
4343
: 1000;
44+
45+
readonly usageServiceAddr: string = process.env.GITPOD_USAGE_SERVICE_ADDR || "usage.default.svc.cluster.local:9001";
4446
}
4547

4648
export interface ChargebeeWebhook {

components/ee/payment-endpoint/src/container-module.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,27 @@
66

77
import { ContainerModule } from "inversify";
88

9+
import { log } from "@gitpod/gitpod-protocol/lib/util/logging";
10+
import { UsageServiceClient, UsageServiceDefinition } from "@gitpod/usage-api/lib/usage/v1/usage.pb";
11+
import { createChannel, createClient } from "nice-grpc";
12+
import { AccountService } from "./accounting/account-service";
13+
import { AccountServiceImpl } from "./accounting/account-service-impl";
14+
import { SubscriptionService } from "./accounting/subscription-service";
15+
import { TeamSubscriptionService } from "./accounting/team-subscription-service";
16+
import { TeamSubscription2Service } from "./accounting/team-subscription2-service";
17+
import { CompositeEventHandler, EventHandler } from "./chargebee/chargebee-event-handler";
918
import { ChargebeeProvider, ChargebeeProviderOptions } from "./chargebee/chargebee-provider";
1019
import { EndpointController } from "./chargebee/endpoint-controller";
11-
import { SubscriptionService } from "./accounting/subscription-service";
12-
import { Config } from "./config";
13-
import { Server } from "./server";
1420
import { SubscriptionHandler } from "./chargebee/subscription-handler";
15-
import { SubscriptionMapperFactory, SubscriptionMapper } from "./chargebee/subscription-mapper";
21+
import { SubscriptionMapper, SubscriptionMapperFactory } from "./chargebee/subscription-mapper";
1622
import { TeamSubscriptionHandler } from "./chargebee/team-subscription-handler";
17-
import { CompositeEventHandler, EventHandler } from "./chargebee/chargebee-event-handler";
23+
import { UbpResetOnCancel } from "./chargebee/ubp-reset-on-cancel";
1824
import { UpgradeHelper } from "./chargebee/upgrade-helper";
19-
import { TeamSubscriptionService } from "./accounting/team-subscription-service";
20-
import { TeamSubscription2Service } from "./accounting/team-subscription2-service";
21-
import { AccountService } from "./accounting/account-service";
22-
import { AccountServiceImpl } from "./accounting/account-service-impl";
25+
import { Config } from "./config";
2326
import { GithubEndpointController } from "./github/endpoint-controller";
2427
import { GithubSubscriptionMapper } from "./github/subscription-mapper";
2528
import { GithubSubscriptionReconciler } from "./github/subscription-reconciler";
29+
import { Server } from "./server";
2630

2731
export const productionContainerModule = new ContainerModule((bind, unbind, isBound, rebind) => {
2832
bind(Config).toSelf().inSingletonScope();
@@ -38,7 +42,6 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo
3842
};
3943
});
4044
bind(TeamSubscriptionHandler).toSelf().inSingletonScope();
41-
4245
bind(CompositeEventHandler).toSelf().inSingletonScope();
4346
bind(EventHandler).to(SubscriptionHandler).inSingletonScope();
4447
bind(EventHandler).to(TeamSubscriptionHandler).inSingletonScope();
@@ -60,4 +63,13 @@ export const productionContainerModule = new ContainerModule((bind, unbind, isBo
6063
bind(GithubEndpointController).toSelf().inSingletonScope();
6164
bind(GithubSubscriptionMapper).toSelf().inSingletonScope();
6265
bind(GithubSubscriptionReconciler).toSelf().inSingletonScope();
66+
67+
bind<UsageServiceClient>(UsageServiceDefinition.name)
68+
.toDynamicValue((ctx) => {
69+
const config = ctx.container.get<Config>(Config);
70+
log.info("Connecting to usage service at", { addr: config.usageServiceAddr });
71+
return createClient(UsageServiceDefinition, createChannel(config.usageServiceAddr));
72+
})
73+
.inSingletonScope();
74+
bind(UbpResetOnCancel).toSelf().inSingletonScope();
6375
});

components/licensor/typescript/ee/src/api.ts

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* Licensed under the GNU Affero General Public License (AGPL).
44
* See License.AGPL.txt in the project root for license information.
55
*/
6-
76
// generated using github.com/32leaves/bel
87
// DO NOT MODIFY
98
export enum Feature {
@@ -14,23 +13,23 @@ export enum Feature {
1413
FeatureWorkspaceSharing = "workspace-sharing",
1514
}
1615
export interface LicenseData {
17-
type: LicenseType;
18-
payload: LicensePayload;
19-
plan: LicenseSubscriptionLevel;
20-
fallbackAllowed: boolean;
16+
type: LicenseType
17+
payload: LicensePayload
18+
plan: LicenseSubscriptionLevel
19+
fallbackAllowed: boolean
2120
}
2221

2322
export enum LicenseLevel {
2423
LevelTeam = 0,
2524
LevelEnterprise = 1,
2625
}
2726
export interface LicensePayload {
28-
id: string;
29-
domain: string;
30-
level: LicenseLevel;
31-
validUntil: string;
32-
seats: number;
33-
customerID?: string;
27+
id: string
28+
domain: string
29+
level: LicenseLevel
30+
validUntil: string
31+
seats: number
32+
customerID?: string
3433
}
3534

3635
export enum LicenseSubscriptionLevel {

install/installer/pkg/common/constants.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const (
4141
SlowServerComponent = "slow-server"
4242
ServerInstallationAdminPort = 9000
4343
SystemNodeCritical = "system-node-critical"
44+
PaymentEndpointComponent = "payment-endpoint"
4445
PublicApiComponent = "public-api-server"
4546
WSManagerComponent = "ws-manager"
4647
WSManagerBridgeComponent = "ws-manager-bridge"

install/installer/pkg/components/usage/networkpolicy.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ func networkpolicy(ctx *common.RenderContext) ([]runtime.Object, error) {
5050
},
5151
},
5252
},
53+
{
54+
PodSelector: &metav1.LabelSelector{
55+
MatchLabels: map[string]string{
56+
"component": common.PaymentEndpointComponent,
57+
},
58+
},
59+
},
5360
{
5461
PodSelector: &metav1.LabelSelector{
5562
MatchLabels: map[string]string{

0 commit comments

Comments
 (0)