diff --git a/components/dashboard/src/teams/TeamUsageBasedBilling.tsx b/components/dashboard/src/teams/TeamUsageBasedBilling.tsx index 7042ed59e835af..11fbcf84152539 100644 --- a/components/dashboard/src/teams/TeamUsageBasedBilling.tsx +++ b/components/dashboard/src/teams/TeamUsageBasedBilling.tsx @@ -21,7 +21,7 @@ export default function TeamUsageBasedBilling() { const { teams } = useContext(TeamsContext); const location = useLocation(); const team = getCurrentTeam(location, teams); - const { showUsageBasedUI } = useContext(PaymentContext); + const { showUsageBasedUI, currency } = useContext(PaymentContext); const [stripeCustomerId, setStripeCustomerId] = useState(); const [isLoading, setIsLoading] = useState(true); const [showBillingSetupModal, setShowBillingSetupModal] = useState(false); @@ -68,7 +68,7 @@ export default function TeamUsageBasedBilling() { (async () => { const setupIntentId = params.get("setup_intent")!; window.history.replaceState({}, "", window.location.pathname); - await getGitpodService().server.subscribeTeamToStripe(team.id, setupIntentId); + await getGitpodService().server.subscribeTeamToStripe(team.id, setupIntentId, currency); const pendingCustomer = { pendingSince: Date.now() }; setPendingStripeCustomer(pendingCustomer); window.localStorage.setItem(`pendingStripeCustomerForTeam${team.id}`, JSON.stringify(pendingCustomer)); diff --git a/components/gitpod-protocol/src/gitpod-service.ts b/components/gitpod-protocol/src/gitpod-service.ts index 702000319e6063..6fbda1972936a1 100644 --- a/components/gitpod-protocol/src/gitpod-service.ts +++ b/components/gitpod-protocol/src/gitpod-service.ts @@ -56,6 +56,7 @@ import { import { RemotePageMessage, RemoteTrackMessage, RemoteIdentifyMessage } from "./analytics"; import { IDEServer } from "./ide-protocol"; import { InstallationAdminSettings, TelemetryData } from "./installation-admin-protocol"; +import { Currency } from "./plans"; export interface GitpodClient { onInstanceUpdate(instance: WorkspaceInstance): void; @@ -273,7 +274,7 @@ export interface GitpodServer extends JsonRpcServer, AdminServer, getStripePublishableKey(): Promise; getStripeSetupIntentClientSecret(): Promise; findStripeCustomerIdForTeam(teamId: string): Promise; - subscribeTeamToStripe(teamId: string, setupIntentId: string): Promise; + subscribeTeamToStripe(teamId: string, setupIntentId: string, currency: Currency): Promise; getStripePortalUrlForTeam(teamId: string): Promise; /** diff --git a/components/server/ee/src/user/stripe-service.ts b/components/server/ee/src/user/stripe-service.ts index d172ae15736901..a95780e6131c97 100644 --- a/components/server/ee/src/user/stripe-service.ts +++ b/components/server/ee/src/user/stripe-service.ts @@ -7,6 +7,7 @@ import { inject, injectable } from "inversify"; import Stripe from "stripe"; import { Team, User } from "@gitpod/gitpod-protocol"; +import { Currency } from "@gitpod/gitpod-protocol/lib/plans"; import { Config } from "../../../src/config"; @injectable() @@ -113,4 +114,19 @@ export class StripeService { }); return session.url; } + + async createSubscriptionForCustomer(customerId: string, currency: Currency): Promise { + // FIXME(janx): Use configmap. + const prices = { + EUR: "price_1LAE0AGadRXm50o3xjegX0Kd", + USD: "price_1LAE0AGadRXm50o3rKoktPiJ", + }; + const startOfNextMonth = new Date(new Date().toISOString().slice(0, 7) + "-01"); // First day of this month (YYYY-MM-01) + startOfNextMonth.setMonth(startOfNextMonth.getMonth() + 1); // Add one month + await this.getStripe().subscriptions.create({ + customer: customerId, + items: [{ price: prices[currency] }], + billing_cycle_anchor: Math.round(startOfNextMonth.getTime() / 1000), + }); + } } diff --git a/components/server/ee/src/workspace/gitpod-server-impl.ts b/components/server/ee/src/workspace/gitpod-server-impl.ts index 905328e977d1d9..dd5e438a04fb0e 100644 --- a/components/server/ee/src/workspace/gitpod-server-impl.ts +++ b/components/server/ee/src/workspace/gitpod-server-impl.ts @@ -76,7 +76,7 @@ import { TeamSubscriptionSlot, TeamSubscriptionSlotResolved, } from "@gitpod/gitpod-protocol/lib/team-subscription-protocol"; -import { Plans } from "@gitpod/gitpod-protocol/lib/plans"; +import { Currency, Plans } from "@gitpod/gitpod-protocol/lib/plans"; import * as pThrottle from "p-throttle"; import { formatDate } from "@gitpod/gitpod-protocol/lib/util/date-time"; import { FindUserByIdentityStrResult, UserService } from "../../../src/user/user-service"; @@ -1892,14 +1892,19 @@ export class GitpodServerEEImpl extends GitpodServerImpl { } } - async subscribeTeamToStripe(ctx: TraceContext, teamId: string, setupIntentId: string): Promise { + async subscribeTeamToStripe( + ctx: TraceContext, + teamId: string, + setupIntentId: string, + currency: Currency, + ): Promise { const user = this.checkAndBlockUser("subscribeUserToStripe"); await this.ensureIsUsageBasedFeatureFlagEnabled(user); await this.guardTeamOperation(teamId, "update"); const team = await this.teamDB.findTeamById(teamId); try { - await this.stripeService.createCustomerForTeam(user, team!, setupIntentId); - // TODO(janx): Create a Stripe usage-based Subscription for the customer + const customer = await this.stripeService.createCustomerForTeam(user, team!, setupIntentId); + await this.stripeService.createSubscriptionForCustomer(customer.id, currency); } catch (error) { log.error(`Failed to subscribe team '${teamId}' to Stripe`, error); throw new ResponseError(ErrorCodes.INTERNAL_SERVER_ERROR, `Failed to subscribe team '${teamId}' to Stripe`); diff --git a/components/server/src/workspace/gitpod-server-impl.ts b/components/server/src/workspace/gitpod-server-impl.ts index f91725bc1c1b03..7bcccf9c28194e 100644 --- a/components/server/src/workspace/gitpod-server-impl.ts +++ b/components/server/src/workspace/gitpod-server-impl.ts @@ -162,6 +162,7 @@ import { InstallationAdminTelemetryDataProvider } from "../installation-admin/te import { LicenseEvaluator } from "@gitpod/licensor/lib"; import { Feature } from "@gitpod/licensor/lib/api"; import { getExperimentsClient } from "../experiments"; +import { Currency } from "@gitpod/gitpod-protocol/lib/plans"; // shortcut export const traceWI = (ctx: TraceContext, wi: Omit) => TraceContext.setOWI(ctx, wi); // userId is already taken care of in WebsocketConnectionManager @@ -3042,7 +3043,12 @@ export class GitpodServerImpl implements GitpodServerWithTracing, Disposable { async findStripeCustomerIdForTeam(ctx: TraceContext, teamId: string): Promise { throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`); } - async subscribeTeamToStripe(ctx: TraceContext, teamId: string, setupIntentId: string): Promise { + async subscribeTeamToStripe( + ctx: TraceContext, + teamId: string, + setupIntentId: string, + currency: Currency, + ): Promise { throw new ResponseError(ErrorCodes.SAAS_FEATURE, `Not implemented in this version`); } async getStripePortalUrlForTeam(ctx: TraceContext, teamId: string): Promise {