Skip to content

Commit 516ed61

Browse files
jankeromnesroboquat
authored andcommitted
[payment] Fix race condition on multiple new Team Plan joins
1 parent 3536f91 commit 516ed61

File tree

2 files changed

+32
-29
lines changed

2 files changed

+32
-29
lines changed

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ import { TeamSubscription2DB } from "@gitpod/gitpod-db/lib/team-subscription-2-d
1111
import { log, LogContext } from "@gitpod/gitpod-protocol/lib/util/logging";
1212
import { Plans } from "@gitpod/gitpod-protocol/lib/plans";
1313
import { TeamSubscription, TeamSubscription2 } from "@gitpod/gitpod-protocol/lib/team-subscription-protocol";
14+
import { formatDate } from "@gitpod/gitpod-protocol/lib/util/date-time";
1415
import { getCancelledAt, getStartDate } from "./chargebee-subscription-helper";
1516
import { Chargebee as chargebee } from "./chargebee-types";
1617
import { EventHandler } from "./chargebee-event-handler";
18+
import { UpgradeHelper } from "./upgrade-helper";
1719
import { TeamSubscriptionService } from "../accounting/team-subscription-service";
1820
import { TeamSubscription2Service } from "../accounting/team-subscription2-service";
1921
import { Config } from "../config";
@@ -25,6 +27,7 @@ export class TeamSubscriptionHandler implements EventHandler<chargebee.Subscript
2527
@inject(TeamSubscription2DB) protected readonly db2: TeamSubscription2DB;
2628
@inject(TeamSubscriptionService) protected readonly service: TeamSubscriptionService;
2729
@inject(TeamSubscription2Service) protected readonly service2: TeamSubscription2Service;
30+
@inject(UpgradeHelper) protected readonly upgradeHelper: UpgradeHelper;
2831

2932
canHandle(event: chargebee.Event<any>): boolean {
3033
if (event.event_type.startsWith("subscription")) {
@@ -133,6 +136,34 @@ export class TeamSubscriptionHandler implements EventHandler<chargebee.Subscript
133136
const cancelledAt = getCancelledAt(chargebeeSubscription);
134137
sub.endDate = cancelledAt;
135138
await this.service2.cancelAllTeamMemberSubscriptions(sub, new Date(cancelledAt));
139+
} else if (chargebeeSubscription.plan_quantity > sub.quantity) {
140+
// Upgrade: Charge for it!
141+
const oldQuantity = sub.quantity;
142+
const newQuantity = chargebeeSubscription.plan_quantity;
143+
let pricePerUnitInCents = chargebeeSubscription.plan_unit_price;
144+
if (pricePerUnitInCents === undefined) {
145+
const plan = Plans.getById(sub.planId)!;
146+
pricePerUnitInCents = plan.pricePerMonth * 100;
147+
}
148+
const currentTermRemainingRatio =
149+
this.upgradeHelper.getCurrentTermRemainingRatio(chargebeeSubscription);
150+
const diffInCents = Math.round(
151+
pricePerUnitInCents * (newQuantity - oldQuantity) * currentTermRemainingRatio,
152+
);
153+
const upgradeTimestamp = new Date().toISOString();
154+
const dateString = formatDate(upgradeTimestamp);
155+
const description = `Pro-rated upgrade from ${oldQuantity} to ${newQuantity} team members (${dateString})`;
156+
this.upgradeHelper
157+
.chargeForUpgrade("", sub.paymentReference, diffInCents, description, upgradeTimestamp)
158+
.catch((error) => {
159+
log.error(`Could not charge for upgrade on TeamSubscription2 quantity increase!`, {
160+
error,
161+
ts2Id: sub.id,
162+
chargebeeId: sub.paymentReference,
163+
oldQuantity,
164+
newQuantity,
165+
});
166+
});
136167
}
137168
sub.quantity = chargebeeSubscription.plan_quantity;
138169
await db2.storeEntry(sub);

components/server/ee/src/workspace/gitpod-server-impl.ts

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1436,37 +1436,9 @@ export class GitpodServerEEImpl extends GitpodServerImpl {
14361436

14371437
protected async updateTeamSubscriptionQuantity(teamSubscription: TeamSubscription2): Promise<void> {
14381438
const members = await this.teamDB.findMembersByTeam(teamSubscription.teamId);
1439-
const oldQuantity = teamSubscription.quantity;
14401439
const newQuantity = members.length;
14411440
try {
1442-
if (oldQuantity < newQuantity) {
1443-
// Upgrade: Charge for it!
1444-
const chargebeeSubscription = await this.getChargebeeSubscription(
1445-
{},
1446-
teamSubscription.paymentReference,
1447-
);
1448-
let pricePerUnitInCents = chargebeeSubscription.plan_unit_price;
1449-
if (pricePerUnitInCents === undefined) {
1450-
const plan = Plans.getById(teamSubscription.planId)!;
1451-
pricePerUnitInCents = plan.pricePerMonth * 100;
1452-
}
1453-
const currentTermRemainingRatio =
1454-
this.upgradeHelper.getCurrentTermRemainingRatio(chargebeeSubscription);
1455-
const diffInCents = Math.round(
1456-
pricePerUnitInCents * (newQuantity - oldQuantity) * currentTermRemainingRatio,
1457-
);
1458-
const upgradeTimestamp = new Date().toISOString();
1459-
const description = `Pro-rated upgrade from '${oldQuantity}' to '${newQuantity}' team members (${formatDate(
1460-
upgradeTimestamp,
1461-
)})`;
1462-
await this.upgradeHelper.chargeForUpgrade(
1463-
"",
1464-
teamSubscription.paymentReference,
1465-
diffInCents,
1466-
description,
1467-
upgradeTimestamp,
1468-
);
1469-
}
1441+
// We only charge for upgrades in the Chargebee callback, to avoid race conditions.
14701442
await this.doUpdateSubscription("", teamSubscription.paymentReference, {
14711443
plan_quantity: newQuantity,
14721444
end_of_term: false,

0 commit comments

Comments
 (0)